musubi-sdd 6.2.1 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.ja.md +3 -3
  2. package/README.md +3 -3
  3. package/bin/musubi-dashboard.js +23 -14
  4. package/bin/musubi-design.js +3 -3
  5. package/bin/musubi-gaps.js +9 -9
  6. package/bin/musubi-init.js +14 -1310
  7. package/bin/musubi-requirements.js +1 -1
  8. package/bin/musubi-tasks.js +5 -5
  9. package/bin/musubi-trace.js +23 -23
  10. package/bin/musubi-upgrade.js +400 -0
  11. package/bin/musubi.js +16 -1
  12. package/package.json +4 -3
  13. package/src/analyzers/gap-detector.js +3 -3
  14. package/src/analyzers/traceability.js +17 -17
  15. package/src/cli/dashboard-cli.js +54 -60
  16. package/src/cli/init-generators.js +464 -0
  17. package/src/cli/init-helpers.js +884 -0
  18. package/src/constitutional/checker.js +67 -65
  19. package/src/constitutional/ci-reporter.js +58 -49
  20. package/src/constitutional/index.js +2 -2
  21. package/src/constitutional/phase-minus-one.js +24 -27
  22. package/src/constitutional/steering-sync.js +29 -40
  23. package/src/dashboard/index.js +2 -2
  24. package/src/dashboard/sprint-planner.js +17 -19
  25. package/src/dashboard/sprint-reporter.js +47 -38
  26. package/src/dashboard/transition-recorder.js +12 -18
  27. package/src/dashboard/workflow-dashboard.js +28 -39
  28. package/src/enterprise/error-recovery.js +109 -49
  29. package/src/enterprise/experiment-report.js +63 -37
  30. package/src/enterprise/index.js +5 -5
  31. package/src/enterprise/rollback-manager.js +28 -29
  32. package/src/enterprise/tech-article.js +41 -35
  33. package/src/generators/design.js +3 -3
  34. package/src/generators/requirements.js +5 -3
  35. package/src/generators/tasks.js +2 -2
  36. package/src/integrations/platforms.js +1 -1
  37. package/src/templates/agents/claude-code/CLAUDE.md +1 -1
  38. package/src/templates/agents/claude-code/skills/design-reviewer/SKILL.md +132 -113
  39. package/src/templates/agents/claude-code/skills/requirements-reviewer/SKILL.md +85 -56
  40. package/src/templates/agents/codex/AGENTS.md +2 -2
  41. package/src/templates/agents/cursor/AGENTS.md +2 -2
  42. package/src/templates/agents/gemini-cli/GEMINI.md +2 -2
  43. package/src/templates/agents/github-copilot/AGENTS.md +2 -2
  44. package/src/templates/agents/github-copilot/commands/sdd-requirements.prompt.md +23 -4
  45. package/src/templates/agents/qwen-code/QWEN.md +2 -2
  46. package/src/templates/agents/shared/AGENTS.md +1 -1
  47. package/src/templates/agents/windsurf/AGENTS.md +2 -2
  48. package/src/templates/skills/browser-agent.md +1 -1
  49. package/src/traceability/extractor.js +22 -21
  50. package/src/traceability/gap-detector.js +19 -17
  51. package/src/traceability/index.js +2 -2
  52. package/src/traceability/matrix-storage.js +20 -22
  53. package/src/validators/constitution.js +5 -2
  54. package/src/validators/critic-system.js +6 -6
  55. package/src/validators/traceability-validator.js +3 -3
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Constitutional Checker
3
- *
3
+ *
4
4
  * Validates compliance with Constitutional Articles.
5
- *
5
+ *
6
6
  * Requirement: IMP-6.2-005-01
7
7
  * Design: Section 5.1
8
8
  */
@@ -18,37 +18,37 @@ const ARTICLES = {
18
18
  id: 'I',
19
19
  name: 'Specification First',
20
20
  description: 'All changes must be traceable to specifications',
21
- keywords: ['REQ-', 'IMP-', 'FEAT-', 'specification', 'requirement']
21
+ keywords: ['REQ-', 'IMP-', 'FEAT-', 'specification', 'requirement'],
22
22
  },
23
23
  II: {
24
24
  id: 'II',
25
25
  name: 'Quality Gate',
26
26
  description: 'Code must pass quality gates before merge',
27
- keywords: ['test', 'coverage', 'lint', 'quality']
27
+ keywords: ['test', 'coverage', 'lint', 'quality'],
28
28
  },
29
29
  III: {
30
30
  id: 'III',
31
31
  name: 'Test-First',
32
32
  description: 'Tests should be written before or alongside implementation',
33
- keywords: ['test', 'spec', 'describe', 'it(']
33
+ keywords: ['test', 'spec', 'describe', 'it('],
34
34
  },
35
35
  IV: {
36
36
  id: 'IV',
37
37
  name: 'Incremental Delivery',
38
38
  description: 'Features should be delivered incrementally',
39
- keywords: ['sprint', 'iteration', 'milestone']
39
+ keywords: ['sprint', 'iteration', 'milestone'],
40
40
  },
41
41
  V: {
42
42
  id: 'V',
43
43
  name: 'Consistency',
44
44
  description: 'Code style and patterns must be consistent',
45
- keywords: ['eslint', 'prettier', 'style']
45
+ keywords: ['eslint', 'prettier', 'style'],
46
46
  },
47
47
  VI: {
48
48
  id: 'VI',
49
49
  name: 'Change Tracking',
50
50
  description: 'All changes must be tracked and documented',
51
- keywords: ['changelog', 'commit', 'version']
51
+ keywords: ['changelog', 'commit', 'version'],
52
52
  },
53
53
  VII: {
54
54
  id: 'VII',
@@ -58,25 +58,21 @@ const ARTICLES = {
58
58
  maxFileLines: 500,
59
59
  maxFunctionLines: 50,
60
60
  maxCyclomaticComplexity: 10,
61
- maxDependencies: 10
62
- }
61
+ maxDependencies: 10,
62
+ },
63
63
  },
64
64
  VIII: {
65
65
  id: 'VIII',
66
66
  name: 'Anti-Abstraction',
67
67
  description: 'Avoid premature abstraction',
68
- patterns: [
69
- /abstract\s+class/i,
70
- /implements\s+\w+Factory/i,
71
- /extends\s+Base\w+/i
72
- ]
68
+ patterns: [/abstract\s+class/i, /implements\s+\w+Factory/i, /extends\s+Base\w+/i],
73
69
  },
74
70
  IX: {
75
71
  id: 'IX',
76
72
  name: 'Documentation',
77
73
  description: 'Code must be documented',
78
- keywords: ['jsdoc', '@param', '@returns', '@description']
79
- }
74
+ keywords: ['jsdoc', '@param', '@returns', '@description'],
75
+ },
80
76
  };
81
77
 
82
78
  /**
@@ -86,12 +82,12 @@ const SEVERITY = {
86
82
  CRITICAL: 'critical',
87
83
  HIGH: 'high',
88
84
  MEDIUM: 'medium',
89
- LOW: 'low'
85
+ LOW: 'low',
90
86
  };
91
87
 
92
88
  /**
93
89
  * ConstitutionalChecker
94
- *
90
+ *
95
91
  * Validates code against Constitutional Articles.
96
92
  */
97
93
  class ConstitutionalChecker {
@@ -101,7 +97,7 @@ class ConstitutionalChecker {
101
97
  constructor(config = {}) {
102
98
  this.config = {
103
99
  articleVII: ARTICLES.VII.thresholds,
104
- ...config
100
+ ...config,
105
101
  };
106
102
  }
107
103
 
@@ -140,7 +136,7 @@ class ConstitutionalChecker {
140
136
  filePath,
141
137
  violations,
142
138
  passed: violations.length === 0,
143
- checkedAt: new Date().toISOString()
139
+ checkedAt: new Date().toISOString(),
144
140
  };
145
141
  }
146
142
 
@@ -152,18 +148,10 @@ class ConstitutionalChecker {
152
148
  */
153
149
  checkArticleI(content, filePath) {
154
150
  // Check if file has requirement reference
155
- const hasReqRef = ARTICLES.I.keywords.some(kw =>
156
- content.includes(kw)
157
- );
151
+ const hasReqRef = ARTICLES.I.keywords.some(kw => content.includes(kw));
158
152
 
159
153
  // Skip check for certain file types
160
- const skipPatterns = [
161
- /\.test\./,
162
- /\.spec\./,
163
- /\.config\./,
164
- /index\./,
165
- /package\.json/
166
- ];
154
+ const skipPatterns = [/\.test\./, /\.spec\./, /\.config\./, /index\./, /package\.json/];
167
155
 
168
156
  if (skipPatterns.some(p => p.test(filePath))) {
169
157
  return null;
@@ -176,7 +164,7 @@ class ConstitutionalChecker {
176
164
  severity: SEVERITY.MEDIUM,
177
165
  message: 'ファイルに要件参照(REQ-XXX、IMP-XXX等)がありません',
178
166
  filePath,
179
- suggestion: 'コードコメントまたはJSDocに関連する要件IDを追加してください'
167
+ suggestion: 'コードコメントまたはJSDocに関連する要件IDを追加してください',
180
168
  };
181
169
  }
182
170
 
@@ -193,12 +181,12 @@ class ConstitutionalChecker {
193
181
  const dir = path.dirname(filePath);
194
182
  const ext = path.extname(filePath);
195
183
  const base = path.basename(filePath, ext);
196
-
184
+
197
185
  const testPaths = [
198
186
  path.join(dir, `${base}.test${ext}`),
199
187
  path.join(dir, `${base}.spec${ext}`),
200
188
  path.join(dir, '__tests__', `${base}.test${ext}`),
201
- filePath.replace('/src/', '/tests/').replace(ext, `.test${ext}`)
189
+ filePath.replace('/src/', '/tests/').replace(ext, `.test${ext}`),
202
190
  ];
203
191
 
204
192
  for (const testPath of testPaths) {
@@ -216,7 +204,7 @@ class ConstitutionalChecker {
216
204
  severity: SEVERITY.HIGH,
217
205
  message: '対応するテストファイルがありません',
218
206
  filePath,
219
- suggestion: `テストファイル(例: ${base}.test${ext})を作成してください`
207
+ suggestion: `テストファイル(例: ${base}.test${ext})を作成してください`,
220
208
  };
221
209
  }
222
210
 
@@ -239,12 +227,14 @@ class ConstitutionalChecker {
239
227
  severity: SEVERITY.HIGH,
240
228
  message: `ファイルが長すぎます(${lines.length}行 > ${thresholds.maxFileLines}行)`,
241
229
  filePath,
242
- suggestion: 'ファイルを複数のモジュールに分割してください'
230
+ suggestion: 'ファイルを複数のモジュールに分割してください',
243
231
  });
244
232
  }
245
233
 
246
234
  // Check function length (simple heuristic)
247
- const functionMatches = content.match(/(?:function\s+\w+|(?:async\s+)?(?:\w+\s*=\s*)?(?:async\s+)?(?:function|\([^)]*\)\s*=>|\w+\s*\([^)]*\)\s*{))/g);
235
+ const functionMatches = content.match(
236
+ /(?:function\s+\w+|(?:async\s+)?(?:\w+\s*=\s*)?(?:async\s+)?(?:function|\([^)]*\)\s*=>|\w+\s*\([^)]*\)\s*{))/g
237
+ );
248
238
  if (functionMatches && functionMatches.length > 0) {
249
239
  // Count functions with many lines (rough estimate)
250
240
  const longFunctions = this.findLongFunctions(content, thresholds.maxFunctionLines);
@@ -256,7 +246,7 @@ class ConstitutionalChecker {
256
246
  message: `関数 "${fn.name}" が長すぎます(約${fn.lines}行 > ${thresholds.maxFunctionLines}行)`,
257
247
  filePath,
258
248
  line: fn.startLine,
259
- suggestion: '関数をより小さな関数に分割してください'
249
+ suggestion: '関数をより小さな関数に分割してください',
260
250
  });
261
251
  }
262
252
  }
@@ -270,7 +260,7 @@ class ConstitutionalChecker {
270
260
  severity: SEVERITY.MEDIUM,
271
261
  message: `依存関係が多すぎます(${imports.length}個 > ${thresholds.maxDependencies}個)`,
272
262
  filePath,
273
- suggestion: '依存関係を見直し、必要に応じてモジュールを再構成してください'
263
+ suggestion: '依存関係を見直し、必要に応じてモジュールを再構成してください',
274
264
  });
275
265
  }
276
266
 
@@ -286,21 +276,22 @@ class ConstitutionalChecker {
286
276
  findLongFunctions(content, maxLines) {
287
277
  const longFunctions = [];
288
278
  const lines = content.split('\n');
289
-
279
+
290
280
  // Simple bracket matching for function detection
291
- const functionPattern = /(?:async\s+)?(?:function\s+(\w+)|(\w+)\s*(?:=|:)\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>))/g;
281
+ const functionPattern =
282
+ /(?:async\s+)?(?:function\s+(\w+)|(\w+)\s*(?:=|:)\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>))/g;
292
283
  let match;
293
284
 
294
285
  while ((match = functionPattern.exec(content)) !== null) {
295
286
  const fnName = match[1] || match[2] || 'anonymous';
296
287
  const startIndex = match.index;
297
288
  const startLine = content.substring(0, startIndex).split('\n').length;
298
-
289
+
299
290
  // Find function end (simple brace counting)
300
291
  let braceCount = 0;
301
292
  let started = false;
302
293
  let endLine = startLine;
303
-
294
+
304
295
  for (let i = startLine - 1; i < lines.length; i++) {
305
296
  const line = lines[i];
306
297
  for (const char of line) {
@@ -322,7 +313,7 @@ class ConstitutionalChecker {
322
313
  longFunctions.push({
323
314
  name: fnName,
324
315
  startLine,
325
- lines: lineCount
316
+ lines: lineCount,
326
317
  });
327
318
  }
328
319
  }
@@ -348,7 +339,7 @@ class ConstitutionalChecker {
348
339
  severity: SEVERITY.HIGH,
349
340
  message: `早すぎる抽象化の可能性: "${match[0]}"`,
350
341
  filePath,
351
- suggestion: '具体的な実装から始め、必要に応じて後から抽象化してください'
342
+ suggestion: '具体的な実装から始め、必要に応じて後から抽象化してください',
352
343
  });
353
344
  }
354
345
  }
@@ -379,7 +370,7 @@ class ConstitutionalChecker {
379
370
  severity: SEVERITY.LOW,
380
371
  message: 'ドキュメンテーションが不足しています',
381
372
  filePath,
382
- suggestion: 'JSDocコメントを追加してください'
373
+ suggestion: 'JSDocコメントを追加してください',
383
374
  };
384
375
  }
385
376
 
@@ -413,7 +404,7 @@ class ConstitutionalChecker {
413
404
  filePath,
414
405
  error: error.message,
415
406
  violations: [],
416
- passed: false
407
+ passed: false,
417
408
  });
418
409
  }
419
410
  }
@@ -425,9 +416,9 @@ class ConstitutionalChecker {
425
416
  filesPassed: results.filter(r => r.passed).length,
426
417
  filesFailed: results.filter(r => !r.passed).length,
427
418
  totalViolations,
428
- violationsByArticle
419
+ violationsByArticle,
429
420
  },
430
- checkedAt: new Date().toISOString()
421
+ checkedAt: new Date().toISOString(),
431
422
  };
432
423
  }
433
424
 
@@ -496,19 +487,23 @@ class ConstitutionalChecker {
496
487
  // Block on Article VII or VIII high violations
497
488
  const phaseMinusOneViolations = results.results
498
489
  .flatMap(r => r.violations)
499
- .filter(v => (v.article === 'VII' || v.article === 'VIII') &&
500
- (v.severity === SEVERITY.HIGH || v.severity === SEVERITY.CRITICAL));
490
+ .filter(
491
+ v =>
492
+ (v.article === 'VII' || v.article === 'VIII') &&
493
+ (v.severity === SEVERITY.HIGH || v.severity === SEVERITY.CRITICAL)
494
+ );
501
495
 
502
496
  return {
503
497
  shouldBlock: criticalViolations.length > 0 || phaseMinusOneViolations.length > 0,
504
- reason: criticalViolations.length > 0
505
- ? 'クリティカルな違反があります'
506
- : phaseMinusOneViolations.length > 0
507
- ? 'Article VII/VIII違反によりPhase -1 Gateレビューが必要です'
508
- : null,
498
+ reason:
499
+ criticalViolations.length > 0
500
+ ? 'クリティカルな違反があります'
501
+ : phaseMinusOneViolations.length > 0
502
+ ? 'Article VII/VIII違反によりPhase -1 Gateレビューが必要です'
503
+ : null,
509
504
  criticalCount: criticalViolations.length,
510
505
  highCount: highViolations.length,
511
- requiresPhaseMinusOne: phaseMinusOneViolations.length > 0
506
+ requiresPhaseMinusOne: phaseMinusOneViolations.length > 0,
512
507
  };
513
508
  }
514
509
 
@@ -557,7 +552,9 @@ class ConstitutionalChecker {
557
552
  lines.push('');
558
553
  for (const [article, count] of Object.entries(results.summary.violationsByArticle)) {
559
554
  const articleInfo = ARTICLES[article];
560
- lines.push(`- **Article ${article}** (${articleInfo?.name || 'Unknown'}): ${count} violations`);
555
+ lines.push(
556
+ `- **Article ${article}** (${articleInfo?.name || 'Unknown'}): ${count} violations`
557
+ );
561
558
  }
562
559
  lines.push('');
563
560
 
@@ -571,9 +568,14 @@ class ConstitutionalChecker {
571
568
  lines.push(`### ${result.filePath}`);
572
569
  lines.push('');
573
570
  for (const v of result.violations) {
574
- const emoji = v.severity === SEVERITY.CRITICAL ? '🔴' :
575
- v.severity === SEVERITY.HIGH ? '🟠' :
576
- v.severity === SEVERITY.MEDIUM ? '🟡' : '🟢';
571
+ const emoji =
572
+ v.severity === SEVERITY.CRITICAL
573
+ ? '🔴'
574
+ : v.severity === SEVERITY.HIGH
575
+ ? '🟠'
576
+ : v.severity === SEVERITY.MEDIUM
577
+ ? '🟡'
578
+ : '🟢';
577
579
  lines.push(`${emoji} **Article ${v.article}** (${v.severity}): ${v.message}`);
578
580
  if (v.line) {
579
581
  lines.push(` - Line: ${v.line}`);
@@ -626,8 +628,8 @@ class ConstitutionalChecker {
626
628
  }
627
629
  }
628
630
 
629
- module.exports = {
630
- ConstitutionalChecker,
631
- ARTICLES,
632
- SEVERITY
631
+ module.exports = {
632
+ ConstitutionalChecker,
633
+ ARTICLES,
634
+ SEVERITY,
633
635
  };
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * CI Reporter
3
- *
3
+ *
4
4
  * Generates CI-friendly reports for Constitutional checks.
5
- *
5
+ *
6
6
  * Requirement: IMP-6.2-005-03
7
7
  * Design: Section 5.4
8
8
  */
@@ -16,7 +16,7 @@ const OUTPUT_FORMAT = {
16
16
  TEXT: 'text',
17
17
  JSON: 'json',
18
18
  GITHUB: 'github',
19
- JUNIT: 'junit'
19
+ JUNIT: 'junit',
20
20
  };
21
21
 
22
22
  /**
@@ -26,12 +26,12 @@ const EXIT_CODE = {
26
26
  SUCCESS: 0,
27
27
  WARNINGS: 0,
28
28
  FAILURES: 1,
29
- ERROR: 2
29
+ ERROR: 2,
30
30
  };
31
31
 
32
32
  /**
33
33
  * CIReporter
34
- *
34
+ *
35
35
  * Reports Constitutional check results for CI/CD systems.
36
36
  */
37
37
  class CIReporter {
@@ -42,7 +42,7 @@ class CIReporter {
42
42
  this.config = {
43
43
  format: OUTPUT_FORMAT.TEXT,
44
44
  failOnWarning: false,
45
- ...config
45
+ ...config,
46
46
  };
47
47
  this.checker = new ConstitutionalChecker(config.checkerConfig);
48
48
  }
@@ -55,7 +55,7 @@ class CIReporter {
55
55
  */
56
56
  async runAndReport(filePaths, options = {}) {
57
57
  const format = options.format || this.config.format;
58
-
58
+
59
59
  // Run checks
60
60
  const results = await this.checker.checkFiles(filePaths);
61
61
  const blockDecision = this.checker.shouldBlockMerge(results);
@@ -84,7 +84,7 @@ class CIReporter {
84
84
  exitCode,
85
85
  summary: results.summary,
86
86
  blockDecision,
87
- format
87
+ format,
88
88
  };
89
89
  }
90
90
 
@@ -156,29 +156,33 @@ class CIReporter {
156
156
  * @returns {string} JSON report
157
157
  */
158
158
  formatJSON(results, blockDecision) {
159
- return JSON.stringify({
160
- version: '1.0.0',
161
- timestamp: new Date().toISOString(),
162
- summary: {
163
- filesChecked: results.summary.filesChecked,
164
- filesPassed: results.summary.filesPassed,
165
- filesFailed: results.summary.filesFailed,
166
- totalViolations: results.summary.totalViolations,
167
- violationsByArticle: results.summary.violationsByArticle
168
- },
169
- status: {
170
- blocked: blockDecision.shouldBlock,
171
- requiresPhaseMinusOne: blockDecision.requiresPhaseMinusOne,
172
- reason: blockDecision.reason
159
+ return JSON.stringify(
160
+ {
161
+ version: '1.0.0',
162
+ timestamp: new Date().toISOString(),
163
+ summary: {
164
+ filesChecked: results.summary.filesChecked,
165
+ filesPassed: results.summary.filesPassed,
166
+ filesFailed: results.summary.filesFailed,
167
+ totalViolations: results.summary.totalViolations,
168
+ violationsByArticle: results.summary.violationsByArticle,
169
+ },
170
+ status: {
171
+ blocked: blockDecision.shouldBlock,
172
+ requiresPhaseMinusOne: blockDecision.requiresPhaseMinusOne,
173
+ reason: blockDecision.reason,
174
+ },
175
+ violations: results.results.flatMap(r =>
176
+ r.violations.map(v => ({
177
+ file: r.filePath,
178
+ ...v,
179
+ }))
180
+ ),
181
+ exitCode: this.determineExitCode(results, blockDecision),
173
182
  },
174
- violations: results.results.flatMap(r =>
175
- r.violations.map(v => ({
176
- file: r.filePath,
177
- ...v
178
- }))
179
- ),
180
- exitCode: this.determineExitCode(results, blockDecision)
181
- }, null, 2);
183
+ null,
184
+ 2
185
+ );
182
186
  }
183
187
 
184
188
  /**
@@ -194,7 +198,7 @@ class CIReporter {
194
198
  lines.push(`::group::Constitutional Compliance Summary`);
195
199
  lines.push(`Files Checked: ${results.summary.filesChecked}`);
196
200
  lines.push(`Violations: ${results.summary.totalViolations}`);
197
-
201
+
198
202
  if (blockDecision.shouldBlock) {
199
203
  lines.push(`Status: BLOCKED`);
200
204
  } else {
@@ -205,10 +209,9 @@ class CIReporter {
205
209
  // Violations as annotations
206
210
  for (const result of results.results) {
207
211
  for (const v of result.violations) {
208
- const command = v.severity === SEVERITY.CRITICAL || v.severity === SEVERITY.HIGH
209
- ? 'error'
210
- : 'warning';
211
-
212
+ const command =
213
+ v.severity === SEVERITY.CRITICAL || v.severity === SEVERITY.HIGH ? 'error' : 'warning';
214
+
212
215
  const line = v.line || 1;
213
216
  const file = result.filePath;
214
217
  const title = `Article ${v.article}: ${v.articleName}`;
@@ -218,11 +221,13 @@ class CIReporter {
218
221
  }
219
222
  }
220
223
 
221
- // Set output
224
+ // Set output using Environment Files (GITHUB_OUTPUT)
225
+ // Note: In GitHub Actions, these would be written to $GITHUB_OUTPUT file
226
+ // Format: echo "name=value" >> $GITHUB_OUTPUT
222
227
  lines.push('');
223
- lines.push(`::set-output name=violations::${results.summary.totalViolations}`);
224
- lines.push(`::set-output name=blocked::${blockDecision.shouldBlock}`);
225
- lines.push(`::set-output name=phase_minus_one::${blockDecision.requiresPhaseMinusOne}`);
228
+ lines.push(`violations=${results.summary.totalViolations}`);
229
+ lines.push(`blocked=${blockDecision.shouldBlock}`);
230
+ lines.push(`phase_minus_one=${blockDecision.requiresPhaseMinusOne}`);
226
231
 
227
232
  return lines.join('\n');
228
233
  }
@@ -230,19 +235,23 @@ class CIReporter {
230
235
  /**
231
236
  * Format as JUnit XML
232
237
  * @param {Object} results - Check results
233
- * @param {Object} blockDecision - Block decision
238
+ * @param {Object} _blockDecision - Block decision (unused, for interface compatibility)
234
239
  * @returns {string} JUnit XML
235
240
  */
236
- formatJUnit(results, blockDecision) {
241
+ formatJUnit(results, _blockDecision) {
237
242
  const lines = [];
238
243
 
239
244
  lines.push('<?xml version="1.0" encoding="UTF-8"?>');
240
- lines.push(`<testsuites name="Constitutional Compliance" tests="${results.summary.filesChecked}" failures="${results.summary.filesFailed}" errors="0">`);
245
+ lines.push(
246
+ `<testsuites name="Constitutional Compliance" tests="${results.summary.filesChecked}" failures="${results.summary.filesFailed}" errors="0">`
247
+ );
241
248
 
242
249
  for (const result of results.results) {
243
250
  const testName = result.filePath.replace(/[<>&'"]/g, '_');
244
-
245
- lines.push(` <testsuite name="${testName}" tests="1" failures="${result.violations.length > 0 ? 1 : 0}" errors="0">`);
251
+
252
+ lines.push(
253
+ ` <testsuite name="${testName}" tests="1" failures="${result.violations.length > 0 ? 1 : 0}" errors="0">`
254
+ );
246
255
  lines.push(` <testcase name="constitutional-check" classname="${testName}">`);
247
256
 
248
257
  if (result.violations.length > 0) {
@@ -250,7 +259,7 @@ class CIReporter {
250
259
  const type = `Article${v.article}Violation`;
251
260
  const message = this.escapeXml(v.message);
252
261
  const details = this.escapeXml(`${v.articleName}: ${v.suggestion}`);
253
-
262
+
254
263
  lines.push(` <failure type="${type}" message="${message}">`);
255
264
  lines.push(` ${details}`);
256
265
  if (v.line) {
@@ -329,8 +338,8 @@ class CIReporter {
329
338
  }
330
339
  }
331
340
 
332
- module.exports = {
333
- CIReporter,
334
- OUTPUT_FORMAT,
335
- EXIT_CODE
341
+ module.exports = {
342
+ CIReporter,
343
+ OUTPUT_FORMAT,
344
+ EXIT_CODE,
336
345
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Constitutional Module
3
- *
3
+ *
4
4
  * Requirement: IMP-6.2-005, IMP-6.2-007
5
5
  */
6
6
 
@@ -18,5 +18,5 @@ module.exports = {
18
18
  SEVERITY,
19
19
  GATE_STATUS,
20
20
  OUTPUT_FORMAT,
21
- EXIT_CODE
21
+ EXIT_CODE,
22
22
  };