musubi-sdd 6.1.2 → 6.2.1

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.
@@ -0,0 +1,336 @@
1
+ /**
2
+ * CI Reporter
3
+ *
4
+ * Generates CI-friendly reports for Constitutional checks.
5
+ *
6
+ * Requirement: IMP-6.2-005-03
7
+ * Design: Section 5.4
8
+ */
9
+
10
+ const { ConstitutionalChecker, SEVERITY } = require('./checker');
11
+
12
+ /**
13
+ * Output formats
14
+ */
15
+ const OUTPUT_FORMAT = {
16
+ TEXT: 'text',
17
+ JSON: 'json',
18
+ GITHUB: 'github',
19
+ JUNIT: 'junit'
20
+ };
21
+
22
+ /**
23
+ * Exit codes
24
+ */
25
+ const EXIT_CODE = {
26
+ SUCCESS: 0,
27
+ WARNINGS: 0,
28
+ FAILURES: 1,
29
+ ERROR: 2
30
+ };
31
+
32
+ /**
33
+ * CIReporter
34
+ *
35
+ * Reports Constitutional check results for CI/CD systems.
36
+ */
37
+ class CIReporter {
38
+ /**
39
+ * @param {Object} config - Configuration
40
+ */
41
+ constructor(config = {}) {
42
+ this.config = {
43
+ format: OUTPUT_FORMAT.TEXT,
44
+ failOnWarning: false,
45
+ ...config
46
+ };
47
+ this.checker = new ConstitutionalChecker(config.checkerConfig);
48
+ }
49
+
50
+ /**
51
+ * Run check and report
52
+ * @param {Array} filePaths - Files to check
53
+ * @param {Object} options - Options
54
+ * @returns {Promise<Object>} Report result
55
+ */
56
+ async runAndReport(filePaths, options = {}) {
57
+ const format = options.format || this.config.format;
58
+
59
+ // Run checks
60
+ const results = await this.checker.checkFiles(filePaths);
61
+ const blockDecision = this.checker.shouldBlockMerge(results);
62
+
63
+ // Generate report
64
+ let report;
65
+ switch (format) {
66
+ case OUTPUT_FORMAT.JSON:
67
+ report = this.formatJSON(results, blockDecision);
68
+ break;
69
+ case OUTPUT_FORMAT.GITHUB:
70
+ report = this.formatGitHub(results, blockDecision);
71
+ break;
72
+ case OUTPUT_FORMAT.JUNIT:
73
+ report = this.formatJUnit(results, blockDecision);
74
+ break;
75
+ default:
76
+ report = this.formatText(results, blockDecision);
77
+ }
78
+
79
+ // Determine exit code
80
+ const exitCode = this.determineExitCode(results, blockDecision);
81
+
82
+ return {
83
+ report,
84
+ exitCode,
85
+ summary: results.summary,
86
+ blockDecision,
87
+ format
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Format as plain text
93
+ * @param {Object} results - Check results
94
+ * @param {Object} blockDecision - Block decision
95
+ * @returns {string} Text report
96
+ */
97
+ formatText(results, blockDecision) {
98
+ const lines = [];
99
+
100
+ lines.push('═══════════════════════════════════════════════════════════');
101
+ lines.push(' MUSUBI Constitutional Compliance Report ');
102
+ lines.push('═══════════════════════════════════════════════════════════');
103
+ lines.push('');
104
+
105
+ // Summary
106
+ lines.push(`Files Checked: ${results.summary.filesChecked}`);
107
+ lines.push(`Files Passed: ${results.summary.filesPassed}`);
108
+ lines.push(`Files Failed: ${results.summary.filesFailed}`);
109
+ lines.push(`Violations: ${results.summary.totalViolations}`);
110
+ lines.push('');
111
+
112
+ // Status
113
+ if (blockDecision.shouldBlock) {
114
+ lines.push('❌ BLOCKED - Constitutional violations detected');
115
+ if (blockDecision.requiresPhaseMinusOne) {
116
+ lines.push(' Phase -1 Gate review required');
117
+ }
118
+ } else if (results.summary.totalViolations > 0) {
119
+ lines.push('⚠️ PASSED WITH WARNINGS');
120
+ } else {
121
+ lines.push('✅ PASSED - No violations');
122
+ }
123
+ lines.push('');
124
+
125
+ // Violations
126
+ if (results.summary.totalViolations > 0) {
127
+ lines.push('───────────────────────────────────────────────────────────');
128
+ lines.push('Violations:');
129
+ lines.push('───────────────────────────────────────────────────────────');
130
+
131
+ for (const result of results.results) {
132
+ if (result.violations.length > 0) {
133
+ lines.push('');
134
+ lines.push(`📁 ${result.filePath}`);
135
+ for (const v of result.violations) {
136
+ const icon = this.getSeverityIcon(v.severity);
137
+ lines.push(` ${icon} [Article ${v.article}] ${v.message}`);
138
+ if (v.line) {
139
+ lines.push(` Line: ${v.line}`);
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ lines.push('');
147
+ lines.push('═══════════════════════════════════════════════════════════');
148
+
149
+ return lines.join('\n');
150
+ }
151
+
152
+ /**
153
+ * Format as JSON
154
+ * @param {Object} results - Check results
155
+ * @param {Object} blockDecision - Block decision
156
+ * @returns {string} JSON report
157
+ */
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
173
+ },
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);
182
+ }
183
+
184
+ /**
185
+ * Format for GitHub Actions
186
+ * @param {Object} results - Check results
187
+ * @param {Object} blockDecision - Block decision
188
+ * @returns {string} GitHub Actions output
189
+ */
190
+ formatGitHub(results, blockDecision) {
191
+ const lines = [];
192
+
193
+ // Summary as workflow command
194
+ lines.push(`::group::Constitutional Compliance Summary`);
195
+ lines.push(`Files Checked: ${results.summary.filesChecked}`);
196
+ lines.push(`Violations: ${results.summary.totalViolations}`);
197
+
198
+ if (blockDecision.shouldBlock) {
199
+ lines.push(`Status: BLOCKED`);
200
+ } else {
201
+ lines.push(`Status: PASSED`);
202
+ }
203
+ lines.push('::endgroup::');
204
+
205
+ // Violations as annotations
206
+ for (const result of results.results) {
207
+ for (const v of result.violations) {
208
+ const command = v.severity === SEVERITY.CRITICAL || v.severity === SEVERITY.HIGH
209
+ ? 'error'
210
+ : 'warning';
211
+
212
+ const line = v.line || 1;
213
+ const file = result.filePath;
214
+ const title = `Article ${v.article}: ${v.articleName}`;
215
+ const message = v.message;
216
+
217
+ lines.push(`::${command} file=${file},line=${line},title=${title}::${message}`);
218
+ }
219
+ }
220
+
221
+ // Set output
222
+ 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}`);
226
+
227
+ return lines.join('\n');
228
+ }
229
+
230
+ /**
231
+ * Format as JUnit XML
232
+ * @param {Object} results - Check results
233
+ * @param {Object} blockDecision - Block decision
234
+ * @returns {string} JUnit XML
235
+ */
236
+ formatJUnit(results, blockDecision) {
237
+ const lines = [];
238
+
239
+ 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">`);
241
+
242
+ for (const result of results.results) {
243
+ const testName = result.filePath.replace(/[<>&'"]/g, '_');
244
+
245
+ lines.push(` <testsuite name="${testName}" tests="1" failures="${result.violations.length > 0 ? 1 : 0}" errors="0">`);
246
+ lines.push(` <testcase name="constitutional-check" classname="${testName}">`);
247
+
248
+ if (result.violations.length > 0) {
249
+ for (const v of result.violations) {
250
+ const type = `Article${v.article}Violation`;
251
+ const message = this.escapeXml(v.message);
252
+ const details = this.escapeXml(`${v.articleName}: ${v.suggestion}`);
253
+
254
+ lines.push(` <failure type="${type}" message="${message}">`);
255
+ lines.push(` ${details}`);
256
+ if (v.line) {
257
+ lines.push(` Line: ${v.line}`);
258
+ }
259
+ lines.push(` </failure>`);
260
+ }
261
+ }
262
+
263
+ lines.push(` </testcase>`);
264
+ lines.push(` </testsuite>`);
265
+ }
266
+
267
+ lines.push('</testsuites>');
268
+
269
+ return lines.join('\n');
270
+ }
271
+
272
+ /**
273
+ * Determine exit code
274
+ * @param {Object} results - Results
275
+ * @param {Object} blockDecision - Block decision
276
+ * @returns {number} Exit code
277
+ */
278
+ determineExitCode(results, blockDecision) {
279
+ if (blockDecision.shouldBlock) {
280
+ return EXIT_CODE.FAILURES;
281
+ }
282
+ if (this.config.failOnWarning && results.summary.totalViolations > 0) {
283
+ return EXIT_CODE.WARNINGS;
284
+ }
285
+ return EXIT_CODE.SUCCESS;
286
+ }
287
+
288
+ /**
289
+ * Get severity icon
290
+ * @param {string} severity - Severity
291
+ * @returns {string} Icon
292
+ */
293
+ getSeverityIcon(severity) {
294
+ switch (severity) {
295
+ case SEVERITY.CRITICAL:
296
+ return '🔴';
297
+ case SEVERITY.HIGH:
298
+ return '🟠';
299
+ case SEVERITY.MEDIUM:
300
+ return '🟡';
301
+ case SEVERITY.LOW:
302
+ return '🟢';
303
+ default:
304
+ return '⚪';
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Escape XML special characters
310
+ * @param {string} str - String to escape
311
+ * @returns {string} Escaped string
312
+ */
313
+ escapeXml(str) {
314
+ if (!str) return '';
315
+ return str
316
+ .replace(/&/g, '&amp;')
317
+ .replace(/</g, '&lt;')
318
+ .replace(/>/g, '&gt;')
319
+ .replace(/"/g, '&quot;')
320
+ .replace(/'/g, '&apos;');
321
+ }
322
+
323
+ /**
324
+ * Print to stdout
325
+ * @param {string} report - Report string
326
+ */
327
+ print(report) {
328
+ console.log(report);
329
+ }
330
+ }
331
+
332
+ module.exports = {
333
+ CIReporter,
334
+ OUTPUT_FORMAT,
335
+ EXIT_CODE
336
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Constitutional Module
3
+ *
4
+ * Requirement: IMP-6.2-005, IMP-6.2-007
5
+ */
6
+
7
+ const { ConstitutionalChecker, ARTICLES, SEVERITY } = require('./checker');
8
+ const { PhaseMinusOneGate, GATE_STATUS } = require('./phase-minus-one');
9
+ const { SteeringSync } = require('./steering-sync');
10
+ const { CIReporter, OUTPUT_FORMAT, EXIT_CODE } = require('./ci-reporter');
11
+
12
+ module.exports = {
13
+ ConstitutionalChecker,
14
+ PhaseMinusOneGate,
15
+ SteeringSync,
16
+ CIReporter,
17
+ ARTICLES,
18
+ SEVERITY,
19
+ GATE_STATUS,
20
+ OUTPUT_FORMAT,
21
+ EXIT_CODE
22
+ };