musubi-sdd 6.2.0 → 6.2.2

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,338 @@
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 using Environment Files (GITHUB_OUTPUT)
222
+ // Note: In GitHub Actions, these would be written to $GITHUB_OUTPUT file
223
+ // Format: echo "name=value" >> $GITHUB_OUTPUT
224
+ lines.push('');
225
+ lines.push(`violations=${results.summary.totalViolations}`);
226
+ lines.push(`blocked=${blockDecision.shouldBlock}`);
227
+ lines.push(`phase_minus_one=${blockDecision.requiresPhaseMinusOne}`);
228
+
229
+ return lines.join('\n');
230
+ }
231
+
232
+ /**
233
+ * Format as JUnit XML
234
+ * @param {Object} results - Check results
235
+ * @param {Object} _blockDecision - Block decision (unused, for interface compatibility)
236
+ * @returns {string} JUnit XML
237
+ */
238
+ formatJUnit(results, _blockDecision) {
239
+ const lines = [];
240
+
241
+ lines.push('<?xml version="1.0" encoding="UTF-8"?>');
242
+ lines.push(`<testsuites name="Constitutional Compliance" tests="${results.summary.filesChecked}" failures="${results.summary.filesFailed}" errors="0">`);
243
+
244
+ for (const result of results.results) {
245
+ const testName = result.filePath.replace(/[<>&'"]/g, '_');
246
+
247
+ lines.push(` <testsuite name="${testName}" tests="1" failures="${result.violations.length > 0 ? 1 : 0}" errors="0">`);
248
+ lines.push(` <testcase name="constitutional-check" classname="${testName}">`);
249
+
250
+ if (result.violations.length > 0) {
251
+ for (const v of result.violations) {
252
+ const type = `Article${v.article}Violation`;
253
+ const message = this.escapeXml(v.message);
254
+ const details = this.escapeXml(`${v.articleName}: ${v.suggestion}`);
255
+
256
+ lines.push(` <failure type="${type}" message="${message}">`);
257
+ lines.push(` ${details}`);
258
+ if (v.line) {
259
+ lines.push(` Line: ${v.line}`);
260
+ }
261
+ lines.push(` </failure>`);
262
+ }
263
+ }
264
+
265
+ lines.push(` </testcase>`);
266
+ lines.push(` </testsuite>`);
267
+ }
268
+
269
+ lines.push('</testsuites>');
270
+
271
+ return lines.join('\n');
272
+ }
273
+
274
+ /**
275
+ * Determine exit code
276
+ * @param {Object} results - Results
277
+ * @param {Object} blockDecision - Block decision
278
+ * @returns {number} Exit code
279
+ */
280
+ determineExitCode(results, blockDecision) {
281
+ if (blockDecision.shouldBlock) {
282
+ return EXIT_CODE.FAILURES;
283
+ }
284
+ if (this.config.failOnWarning && results.summary.totalViolations > 0) {
285
+ return EXIT_CODE.WARNINGS;
286
+ }
287
+ return EXIT_CODE.SUCCESS;
288
+ }
289
+
290
+ /**
291
+ * Get severity icon
292
+ * @param {string} severity - Severity
293
+ * @returns {string} Icon
294
+ */
295
+ getSeverityIcon(severity) {
296
+ switch (severity) {
297
+ case SEVERITY.CRITICAL:
298
+ return '🔴';
299
+ case SEVERITY.HIGH:
300
+ return '🟠';
301
+ case SEVERITY.MEDIUM:
302
+ return '🟡';
303
+ case SEVERITY.LOW:
304
+ return '🟢';
305
+ default:
306
+ return '⚪';
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Escape XML special characters
312
+ * @param {string} str - String to escape
313
+ * @returns {string} Escaped string
314
+ */
315
+ escapeXml(str) {
316
+ if (!str) return '';
317
+ return str
318
+ .replace(/&/g, '&amp;')
319
+ .replace(/</g, '&lt;')
320
+ .replace(/>/g, '&gt;')
321
+ .replace(/"/g, '&quot;')
322
+ .replace(/'/g, '&apos;');
323
+ }
324
+
325
+ /**
326
+ * Print to stdout
327
+ * @param {string} report - Report string
328
+ */
329
+ print(report) {
330
+ console.log(report);
331
+ }
332
+ }
333
+
334
+ module.exports = {
335
+ CIReporter,
336
+ OUTPUT_FORMAT,
337
+ EXIT_CODE
338
+ };
@@ -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
+ };