@@ -5,6 +5,7 @@ import { GroupService } from "./GroupService";
55import { UserService } from "./UserService" ;
66import { PlatformEVaultService } from "./PlatformEVaultService" ;
77import { VotingContextService } from "./VotingContextService" ;
8+ import { ReferenceWriterService } from "./ReferenceWriterService" ;
89
910interface CharterViolation {
1011 violation : string ;
@@ -28,6 +29,7 @@ export class CerberusTriggerService {
2829 private userService : UserService ;
2930 private platformService : PlatformEVaultService ;
3031 private votingContextService : VotingContextService ;
32+ private referenceWriterService : ReferenceWriterService ;
3133 private openaiApiKey : string ;
3234
3335 constructor ( ) {
@@ -36,6 +38,7 @@ export class CerberusTriggerService {
3638 this . userService = new UserService ( ) ;
3739 this . platformService = PlatformEVaultService . getInstance ( ) ;
3840 this . votingContextService = new VotingContextService ( ) ;
41+ this . referenceWriterService = new ReferenceWriterService ( ) ;
3942 this . openaiApiKey = process . env . OPENAI_API_KEY || "" ;
4043 }
4144
@@ -319,6 +322,14 @@ export class CerberusTriggerService {
319322 */
320323 async analyzeCharterViolations ( messages : Message [ ] , group : Group , lastVoteMessage ?: Message ) : Promise < {
321324 violations : string [ ] ;
325+ userViolations : Array < {
326+ userId : string ;
327+ userName : string ;
328+ userEname : string | null ;
329+ violation : string ;
330+ severity : "low" | "medium" | "high" ;
331+ score : number ;
332+ } > ;
322333 summary : string ;
323334 hasVotingIssues : boolean ;
324335 votingStatus : string ;
@@ -327,6 +338,7 @@ export class CerberusTriggerService {
327338 if ( ! this . openaiApiKey ) {
328339 return {
329340 violations : [ ] ,
341+ userViolations : [ ] ,
330342 summary : "⚠️ OpenAI API key not configured. Cannot analyze charter violations." ,
331343 hasVotingIssues : false ,
332344 votingStatus : "Not analyzed" ,
@@ -385,10 +397,13 @@ VOTING CONTEXT:
385397 console . log ( votingContext ) ;
386398 }
387399
388- // Format messages for analysis
389- const messagesText = messages . map ( msg =>
390- `[${ msg . createdAt . toLocaleString ( ) } ] ${ msg . sender ? msg . sender . name : 'System' } : ${ msg . text } `
391- ) . join ( '\n' ) ;
400+ // Format messages for analysis — include sender ID and ename for structured violation reporting
401+ const messagesText = messages . map ( msg => {
402+ const senderInfo = msg . sender
403+ ? `${ msg . sender . name || 'Unknown' } (id:${ msg . sender . id } ${ msg . sender . ename ? ', ename:' + msg . sender . ename : '' } )`
404+ : 'System' ;
405+ return `[${ msg . createdAt . toLocaleString ( ) } ] ${ senderInfo } : ${ msg . text } ` ;
406+ } ) . join ( '\n' ) ;
392407
393408 const charterText = group . charter || "No charter defined for this group." ;
394409
@@ -430,6 +445,16 @@ CHARTER ENFORCEMENT:
430445RESPOND WITH ONLY PURE JSON - NO MARKDOWN, NO CODE BLOCKS:
431446{
432447 "violations": ["array of detailed violation descriptions with justifications"],
448+ "userViolations": [
449+ {
450+ "userId": "the user's id from the message (id:xxx)",
451+ "userName": "the user's display name",
452+ "userEname": "the user's ename if available, or null",
453+ "violation": "one-sentence description of what rule was violated",
454+ "severity": "low" | "medium" | "high",
455+ "score": 1-5 (1 = severe, 5 = minor/warning)
456+ }
457+ ],
433458 "summary": "comprehensive summary with specific examples and actionable recommendations",
434459 "hasVotingIssues": boolean,
435460 "votingStatus": "string describing current voting situation with reasoning",
@@ -486,8 +511,14 @@ Be thorough and justify your reasoning. Provide clear, actionable recommendation
486511
487512 // Parse the JSON response
488513 try {
489- const analysis = JSON . parse ( content ) ;
490-
514+ // Strip markdown code fences if present
515+ let jsonContent = content . trim ( ) ;
516+ if ( jsonContent . startsWith ( "```" ) ) {
517+ jsonContent = jsonContent . replace ( / ^ ` ` ` (?: j s o n ) ? \s * / , "" ) . replace ( / ` ` ` \s * $ / , "" ) . trim ( ) ;
518+ }
519+
520+ const analysis = JSON . parse ( jsonContent ) ;
521+
491522 // Validate required fields
492523 if ( ! Array . isArray ( analysis . violations ) ||
493524 typeof analysis . summary !== 'string' ||
@@ -497,6 +528,11 @@ Be thorough and justify your reasoning. Provide clear, actionable recommendation
497528 throw new Error ( "Invalid JSON structure from OpenAI" ) ;
498529 }
499530
531+ // Ensure userViolations exists and is an array
532+ if ( ! Array . isArray ( analysis . userViolations ) ) {
533+ analysis . userViolations = [ ] ;
534+ }
535+
500536 return analysis ;
501537
502538 } catch ( parseError ) {
@@ -505,6 +541,7 @@ Be thorough and justify your reasoning. Provide clear, actionable recommendation
505541
506542 return {
507543 violations : [ ] ,
544+ userViolations : [ ] ,
508545 summary : "❌ Error analyzing messages. Please check the logs." ,
509546 hasVotingIssues : false ,
510547 votingStatus : "Error occurred" ,
@@ -516,6 +553,7 @@ Be thorough and justify your reasoning. Provide clear, actionable recommendation
516553 console . error ( "Error analyzing with AI:" , error ) ;
517554 return {
518555 violations : [ ] ,
556+ userViolations : [ ] ,
519557 summary : "❌ Error analyzing charter violations. Please check the logs." ,
520558 hasVotingIssues : false ,
521559 votingStatus : "Error occurred" ,
@@ -589,11 +627,50 @@ Be thorough and justify your reasoning. Provide clear, actionable recommendation
589627 } ) ;
590628 }
591629
630+ // Write violation references for users who violated the charter
631+ if ( analysis . userViolations && analysis . userViolations . length > 0 ) {
632+ try {
633+ // Resolve a Cerberus system user as the author of the reference
634+ // Use the first group participant as a fallback author, or try "cerberus" ename
635+ let authorId : string | null = null ;
636+ const cerberusUser = await this . referenceWriterService . resolveUserId ( "cerberus" ) ;
637+ if ( cerberusUser ) {
638+ authorId = cerberusUser . id ;
639+ } else {
640+ // Use the trigger message sender or first available user
641+ const allUsers = await this . userService . getAllUsers ( ) ;
642+ if ( allUsers . length > 0 ) authorId = allUsers [ 0 ] . id ;
643+ }
644+
645+ if ( authorId ) {
646+ const violationRefs = analysis . userViolations . map ( ( uv : any ) => ( {
647+ targetId : uv . userId ,
648+ targetName : uv . userName ,
649+ targetEname : uv . userEname || undefined ,
650+ content : `[Cerberus - Charter Violation in ${ groupWithCharter . name } ] ${ uv . violation } ` ,
651+ numericScore : Math . max ( 1 , Math . min ( 5 , uv . score || 2 ) )
652+ } ) ) ;
653+
654+ await this . referenceWriterService . writeViolationReferences (
655+ violationRefs ,
656+ triggerMessage . group . id ,
657+ groupWithCharter . name ,
658+ authorId
659+ ) ;
660+ console . log ( `📝 Wrote ${ violationRefs . length } violation references` ) ;
661+ } else {
662+ console . warn ( "⚠️ No author user found for violation references" ) ;
663+ }
664+ } catch ( refError ) {
665+ console . error ( "❌ Error writing violation references:" , refError ) ;
666+ }
667+ }
668+
592669 // Build the final analysis text
593670 let analysisText : string ;
594671 if ( analysis . violations . length > 0 ) {
595672 analysisText = `🚨 CHARTER VIOLATIONS DETECTED!\n\n${ analysis . summary } ` ;
596-
673+
597674 // Add enforcement information if available
598675 if ( analysis . enforcement && analysis . enforcement !== "No enforcement possible - API not configured" ) {
599676 analysisText += `\n\n⚖️ ENFORCEMENT:\n${ analysis . enforcement } ` ;
0 commit comments