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.
- package/README.ja.md +3 -3
- package/README.md +3 -3
- package/bin/musubi-dashboard.js +23 -14
- package/bin/musubi-design.js +3 -3
- package/bin/musubi-gaps.js +9 -9
- package/bin/musubi-init.js +14 -1310
- package/bin/musubi-requirements.js +1 -1
- package/bin/musubi-tasks.js +5 -5
- package/bin/musubi-trace.js +23 -23
- package/bin/musubi-upgrade.js +400 -0
- package/bin/musubi.js +16 -1
- package/package.json +4 -3
- package/src/analyzers/gap-detector.js +3 -3
- package/src/analyzers/traceability.js +17 -17
- package/src/cli/dashboard-cli.js +54 -60
- package/src/cli/init-generators.js +464 -0
- package/src/cli/init-helpers.js +884 -0
- package/src/constitutional/checker.js +67 -65
- package/src/constitutional/ci-reporter.js +58 -49
- package/src/constitutional/index.js +2 -2
- package/src/constitutional/phase-minus-one.js +24 -27
- package/src/constitutional/steering-sync.js +29 -40
- package/src/dashboard/index.js +2 -2
- package/src/dashboard/sprint-planner.js +17 -19
- package/src/dashboard/sprint-reporter.js +47 -38
- package/src/dashboard/transition-recorder.js +12 -18
- package/src/dashboard/workflow-dashboard.js +28 -39
- package/src/enterprise/error-recovery.js +109 -49
- package/src/enterprise/experiment-report.js +63 -37
- package/src/enterprise/index.js +5 -5
- package/src/enterprise/rollback-manager.js +28 -29
- package/src/enterprise/tech-article.js +41 -35
- package/src/generators/design.js +3 -3
- package/src/generators/requirements.js +5 -3
- package/src/generators/tasks.js +2 -2
- package/src/integrations/platforms.js +1 -1
- package/src/templates/agents/claude-code/CLAUDE.md +1 -1
- package/src/templates/agents/claude-code/skills/design-reviewer/SKILL.md +132 -113
- package/src/templates/agents/claude-code/skills/requirements-reviewer/SKILL.md +85 -56
- package/src/templates/agents/codex/AGENTS.md +2 -2
- package/src/templates/agents/cursor/AGENTS.md +2 -2
- package/src/templates/agents/gemini-cli/GEMINI.md +2 -2
- package/src/templates/agents/github-copilot/AGENTS.md +2 -2
- package/src/templates/agents/github-copilot/commands/sdd-requirements.prompt.md +23 -4
- package/src/templates/agents/qwen-code/QWEN.md +2 -2
- package/src/templates/agents/shared/AGENTS.md +1 -1
- package/src/templates/agents/windsurf/AGENTS.md +2 -2
- package/src/templates/skills/browser-agent.md +1 -1
- package/src/traceability/extractor.js +22 -21
- package/src/traceability/gap-detector.js +19 -17
- package/src/traceability/index.js +2 -2
- package/src/traceability/matrix-storage.js +20 -22
- package/src/validators/constitution.js +5 -2
- package/src/validators/critic-system.js +6 -6
- 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(
|
|
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 =
|
|
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(
|
|
500
|
-
|
|
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:
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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(
|
|
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 =
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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 =
|
|
209
|
-
? 'error'
|
|
210
|
-
|
|
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(
|
|
224
|
-
lines.push(
|
|
225
|
-
lines.push(
|
|
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}
|
|
238
|
+
* @param {Object} _blockDecision - Block decision (unused, for interface compatibility)
|
|
234
239
|
* @returns {string} JUnit XML
|
|
235
240
|
*/
|
|
236
|
-
formatJUnit(results,
|
|
241
|
+
formatJUnit(results, _blockDecision) {
|
|
237
242
|
const lines = [];
|
|
238
243
|
|
|
239
244
|
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
240
|
-
lines.push(
|
|
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(
|
|
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
|
};
|