@@ -125,9 +125,10 @@ function rotateLogIfNeeded(): void {
125125 // Keep last 2MB, discard the rest
126126 const content = fs . readFileSync ( LOG_FILE , 'utf-8' ) ;
127127 const keepFrom = content . length - 2 * 1024 * 1024 ;
128- const newContent = keepFrom > 0
129- ? '... (log rotated)\n' + content . slice ( keepFrom )
130- : content ;
128+ const newContent =
129+ keepFrom > 0
130+ ? '... (log rotated)\n' + content . slice ( keepFrom )
131+ : content ;
131132 fs . writeFileSync ( LOG_FILE , newContent ) ;
132133 logger . info (
133134 { oldSize : stats . size , newSize : newContent . length } ,
@@ -930,7 +931,10 @@ function verifyFindingScript(
930931 { cmd : cmd . slice ( 0 , 50 ) , title : finding . title } ,
931932 'Validation command not in allowlist, skipping verification' ,
932933 ) ;
933- return { verified : true , output : 'Command not verifiable (not in allowlist)' } ;
934+ return {
935+ verified : true ,
936+ output : 'Command not verifiable (not in allowlist)' ,
937+ } ;
934938 }
935939
936940 try {
@@ -954,7 +958,10 @@ function verifyFindingScript(
954958 { title : finding . title , cmd : cmd . slice ( 0 , 80 ) , err } ,
955959 'Verification script failed — finding likely hallucinated' ,
956960 ) ;
957- return { verified : false , output : `Command failed: ${ String ( err ) . slice ( 0 , 200 ) } ` } ;
961+ return {
962+ verified : false ,
963+ output : `Command failed: ${ String ( err ) . slice ( 0 , 200 ) } ` ,
964+ } ;
958965 }
959966}
960967
@@ -1055,7 +1062,37 @@ RULES:
10551062 jsonStr = jsonMatch [ 0 ] ;
10561063 }
10571064
1058- const validation : ValidationResult = JSON . parse ( jsonStr ) ;
1065+ let validation : ValidationResult ;
1066+ try {
1067+ validation = JSON . parse ( jsonStr ) ;
1068+ } catch ( parseErr ) {
1069+ // Log full output on parse failure so we don't lose Claude's response
1070+ logger . error (
1071+ {
1072+ title : finding . title ,
1073+ output : output . slice ( 0 , 500 ) ,
1074+ parseErr,
1075+ } ,
1076+ 'Claude validation JSON parse failed — response logged for debugging' ,
1077+ ) ;
1078+ return null ;
1079+ }
1080+
1081+ // Schema validation: ensure required fields exist
1082+ if (
1083+ typeof validation . confirmed !== 'boolean' ||
1084+ ! validation . confidence ||
1085+ ! validation . analysis
1086+ ) {
1087+ logger . error (
1088+ {
1089+ title : finding . title ,
1090+ parsed : JSON . stringify ( validation ) . slice ( 0 , 200 ) ,
1091+ } ,
1092+ 'Claude validation missing required fields (confirmed/confidence/analysis)' ,
1093+ ) ;
1094+ return null ;
1095+ }
10591096
10601097 logger . info (
10611098 {
@@ -1134,6 +1171,10 @@ function extractWords(s: string): string[] {
11341171 * Pull latest cc-skills for up-to-date issue templates and patterns.
11351172 * Fire-and-forget — failure is non-fatal.
11361173 */
1174+ /** Cached cc-skills reference content for issue creation quality */
1175+ let ccSkillsLabelStrategy = '' ;
1176+ let ccSkillsContentTypes = '' ;
1177+
11371178function syncCcSkills ( ccSkillsPath : string ) : void {
11381179 if ( ! ccSkillsPath || ! fs . existsSync ( ccSkillsPath ) ) return ;
11391180 try {
@@ -1142,7 +1183,32 @@ function syncCcSkills(ccSkillsPath: string): void {
11421183 timeout : 15_000 ,
11431184 encoding : 'utf-8' ,
11441185 } ) ;
1145- logger . info ( { path : ccSkillsPath } , 'cc-skills synced' ) ;
1186+
1187+ // Read label strategy and content type references for issue creation
1188+ const labelStrategyPath = path . join (
1189+ ccSkillsPath ,
1190+ 'plugins/gh-tools/skills/issue-create/references/label-strategy.md' ,
1191+ ) ;
1192+ const contentTypesPath = path . join (
1193+ ccSkillsPath ,
1194+ 'plugins/gh-tools/skills/issue-create/references/content-types.md' ,
1195+ ) ;
1196+
1197+ if ( fs . existsSync ( labelStrategyPath ) ) {
1198+ ccSkillsLabelStrategy = fs . readFileSync ( labelStrategyPath , 'utf-8' ) . slice ( 0 , 2000 ) ;
1199+ }
1200+ if ( fs . existsSync ( contentTypesPath ) ) {
1201+ ccSkillsContentTypes = fs . readFileSync ( contentTypesPath , 'utf-8' ) . slice ( 0 , 2000 ) ;
1202+ }
1203+
1204+ logger . info (
1205+ {
1206+ path : ccSkillsPath ,
1207+ hasLabelStrategy : ! ! ccSkillsLabelStrategy ,
1208+ hasContentTypes : ! ! ccSkillsContentTypes ,
1209+ } ,
1210+ 'cc-skills synced and references loaded' ,
1211+ ) ;
11461212 } catch ( err ) {
11471213 logger . warn ( { err } , 'cc-skills sync failed (non-fatal)' ) ;
11481214 }
@@ -1209,10 +1275,15 @@ async function suggestLabels(
12091275 } ;
12101276
12111277 try {
1278+ // Inject cc-skills label strategy if available (read from synced repo)
1279+ const strategyContext = ccSkillsLabelStrategy
1280+ ? `\nLABEL STRATEGY GUIDE:\n${ ccSkillsLabelStrategy . slice ( 0 , 800 ) } \n`
1281+ : '' ;
1282+
12121283 const prompt = `Suggest 2-4 labels from the EXISTING taxonomy only for this code finding.
12131284Never suggest labels that don't exist in the list below.
12141285Return ONLY a JSON array of label names, nothing else.
1215-
1286+ ${ strategyContext }
12161287AVAILABLE LABELS:
12171288${ repoLabels . map ( ( l ) => `- ${ l } ` ) . join ( '\n' ) }
12181289
0 commit comments