tribunal-kit 9.2.6 → 9.2.7

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 (63) hide show
  1. package/.agent/ARCHITECTURE.md +2 -2
  2. package/.agent/history/architecture-intelligence.html +1487 -401
  3. package/.agent/history/architecture-model.json +9365 -5697
  4. package/.agent/history/architecture-verification.json +37 -9
  5. package/.agent/routing_index.json +2069 -3340
  6. package/.agent/rules/GEMINI.md +1 -1
  7. package/.agent/scripts/adaptive_execution.js +511 -0
  8. package/.agent/scripts/architecture_diff.js +303 -260
  9. package/.agent/scripts/architecture_extractor.js +792 -753
  10. package/.agent/scripts/architecture_health.js +293 -256
  11. package/.agent/scripts/architecture_intelligence.js +3100 -0
  12. package/.agent/scripts/architecture_verifier.js +303 -270
  13. package/.agent/scripts/architecture_visualizer.js +880 -873
  14. package/.agent/scripts/blast_radius_engine.js +274 -234
  15. package/.agent/scripts/capability_evolution.js +453 -0
  16. package/.agent/scripts/concept_extractor.js +240 -0
  17. package/.agent/scripts/consideration_engine.js +284 -0
  18. package/.agent/scripts/coverage_engine.js +102 -0
  19. package/.agent/scripts/cross_domain_engine.js +1883 -0
  20. package/.agent/scripts/engineering_memory.js +1313 -0
  21. package/.agent/scripts/integrity_manifest.js +1 -5
  22. package/.agent/scripts/judgment_engine.js +790 -0
  23. package/.agent/scripts/materiality_engine.js +251 -0
  24. package/.agent/scripts/outcome_engine.js +624 -0
  25. package/.agent/scripts/outcome_optimization_engine.js +1632 -0
  26. package/.agent/scripts/simulation_engine.js +1478 -0
  27. package/.agent/scripts/skill_intelligence.js +1371 -0
  28. package/.agent/scripts/validation_engine.js +236 -236
  29. package/.agent/scripts/verification_auditor.js +266 -0
  30. package/.agent/skill-intelligence/taxonomy/engineering-concepts.json +555 -0
  31. package/.agent/skill_topic_map.json +301 -341
  32. package/.agent/skills_inventory.json +4272 -6922
  33. package/.agent/workflows/arch.md +96 -80
  34. package/.agent/workflows/skill-intelligence.md +216 -0
  35. package/CONTRIBUTING.md +2 -2
  36. package/README.md +24 -24
  37. package/bin/adapter-install.js +1 -1
  38. package/package.json +8 -9
  39. package/plugins/core-plugin.json +1 -1
  40. package/plugins/gemini/extension.json +1 -1
  41. package/src/browser/index.js +2 -1
  42. package/src/browser/launcher.js +41 -1
  43. package/src/cli.js +3 -1
  44. package/src/commands/arch.js +261 -172
  45. package/src/commands/context.js +27 -8
  46. package/src/commands/learn.js +21 -6
  47. package/src/commands/memory.js +10 -1
  48. package/src/commands/native.js +12 -3
  49. package/src/commands/skill-intelligence.js +1165 -0
  50. package/src/commands/system1.js +105 -4
  51. package/src/context/ranker.js +337 -339
  52. package/src/evolution/engine.js +296 -295
  53. package/src/synthesis/claim_check_separator.js +294 -271
  54. package/src/system1/browser_intelligence.js +300 -0
  55. package/src/system1/constants.js +49 -0
  56. package/src/system1/decision_engine.js +498 -0
  57. package/src/system1/evidence_engine.js +602 -0
  58. package/src/system1/evolution_pipeline.js +581 -0
  59. package/src/system1/index.d.ts +114 -0
  60. package/src/system1/index.js +102 -0
  61. package/src/system1/reviewer_orchestrator.js +477 -0
  62. package/src/tui/banner.js +1 -1
  63. package/src/utils/fs.js +27 -0
@@ -1,256 +1,293 @@
1
- #!/usr/bin/env node
2
- /**
3
- * architecture_health.js — Tribunal Architectural Health & Risk Auditor
4
- * ═════════════════════════════════════════════════════════════════════════════
5
- * Produces explainable architectural signals backed by code evidence.
6
- * No arbitrary "magic scores" — every signal links directly to source locations.
7
- *
8
- * Detects:
9
- * - Circular Dependencies (Cycles with exact paths)
10
- * - Single Points of Failure (SPOFs / High Fan-In bottlenecks)
11
- * - Excessive Coupling (God Modules / High Fan-Out)
12
- * - Security Trust Boundary Violations (Direct DB access from public routes)
13
- * - Resilience Deficits (External HTTP calls lacking timeouts/retries)
14
- * - Test Coverage Gaps on Critical Core Components
15
- * - Orphaned / Dead Code Components
16
- *
17
- * Zero external dependencies.
18
- */
19
-
20
- 'use strict';
21
-
22
- const fs = require('fs');
23
- const path = require('path');
24
- const { GREEN, YELLOW, CYAN, RED, BOLD, DIM, RESET, BOX, banner, timer, formatMs } = require('./_colors');
25
-
26
- class ArchitectureHealthAuditor {
27
- constructor(model) {
28
- this.model = model;
29
- this.entities = new Map((model.entities || []).map((e) => [e.id, e]));
30
- this.adjList = new Map(); // source -> array of target IDs
31
- this.inDegree = new Map(); // target -> count
32
- this.outDegree = new Map(); // source -> count
33
-
34
- this.initAdjacency();
35
- }
36
-
37
- initAdjacency() {
38
- for (const e of this.entities.keys()) {
39
- this.adjList.set(e, []);
40
- this.inDegree.set(e, 0);
41
- this.outDegree.set(e, 0);
42
- }
43
-
44
- for (const rel of this.model.relationships || []) {
45
- if (this.adjList.has(rel.sourceId)) {
46
- this.adjList.get(rel.sourceId).push({ targetId: rel.targetId, rel });
47
- this.outDegree.set(rel.sourceId, (this.outDegree.get(rel.sourceId) || 0) + 1);
48
- this.inDegree.set(rel.targetId, (this.inDegree.get(rel.targetId) || 0) + 1);
49
- }
50
- }
51
- }
52
-
53
- audit() {
54
- const signals = [];
55
-
56
- // 1. Detect Circular Dependencies (Tarjan's SCC)
57
- const cycles = this.findCycles();
58
- for (const cycle of cycles) {
59
- const names = cycle.map((id) => this.entities.get(id)?.name || id).join(' → ');
60
- signals.push({
61
- code: 'ARCH_CIRCULAR_DEPENDENCY',
62
- severity: 'HIGH',
63
- category: 'coupling',
64
- title: 'Circular Dependency Detected',
65
- message: `Circular dependency loop identified: ${names}`,
66
- entities: cycle,
67
- recommendation: 'Break cycle by inverting dependency, introducing an interface, or extracting common logic.',
68
- });
69
- }
70
-
71
- // 2. Detect Single Points of Failure (High Fan-In Bottlenecks)
72
- for (const [id, count] of this.inDegree.entries()) {
73
- const entity = this.entities.get(id);
74
- if (entity && entity.kind === 'module' && !entity.tags?.includes('skill') && count >= 20) {
75
- signals.push({
76
- code: 'ARCH_SINGLE_POINT_OF_FAILURE',
77
- severity: 'MEDIUM',
78
- category: 'resilience',
79
- title: 'High Centrality Bottleneck (SPOF)',
80
- message: `Component "${entity.name}" has ${count} direct callers. Any breaking change will impact a large portion of the system.`,
81
- entities: [id],
82
- recommendation: 'Ensure 100% unit test coverage, freeze interface contracts, and consider modular partitioning.',
83
- });
84
- }
85
- }
86
-
87
- // 3. Detect Excessive Coupling (High Fan-Out God Modules)
88
- for (const [id, count] of this.outDegree.entries()) {
89
- const entity = this.entities.get(id);
90
- if (entity && entity.kind === 'module' && !entity.tags?.includes('skill') && count >= 15 && !entity.scope.includes('test')) {
91
- signals.push({
92
- code: 'ARCH_EXCESSIVE_COUPLING',
93
- severity: 'MEDIUM',
94
- category: 'architecture_smell',
95
- title: 'Excessive Fan-Out (God Component)',
96
- message: `Component "${entity.name}" depends directly on ${count} other modules.`,
97
- entities: [id],
98
- recommendation: 'Decompose module into single-responsibility sub-services or handlers.',
99
- });
100
- }
101
- }
102
-
103
- // 4. Detect Security Boundary Violations (Public to Datastore Direct Call)
104
- for (const rel of this.model.relationships || []) {
105
- const src = this.entities.get(rel.sourceId);
106
- const tgt = this.entities.get(rel.targetId);
107
- if (src && tgt) {
108
- if (src.trustZone === 'public_untrusted' && tgt.trustZone === 'isolated_datastore') {
109
- signals.push({
110
- code: 'ARCH_SECURITY_BOUNDARY_VIOLATION',
111
- severity: 'CRITICAL',
112
- category: 'security',
113
- title: 'Unauthenticated Public Access to Datastore',
114
- message: `Public component "${src.name}" directly communicates with isolated datastore "${tgt.name}" without passing through an authenticated service mesh or API gateway.`,
115
- entities: [src.id, tgt.id],
116
- evidence: rel.evidence || [],
117
- recommendation: 'Route request through authenticated API middleware and isolate direct database connections.',
118
- });
119
- }
120
- }
121
- }
122
-
123
- // 5. Detect Resilience Deficits (External Calls Lacking Timeouts/Circuit Breakers)
124
- for (const rel of this.model.relationships || []) {
125
- const tgt = this.entities.get(rel.targetId);
126
- if (tgt && tgt.kind === 'external_api') {
127
- const failureMode = rel.failureMode || {};
128
- if (!failureMode.hasTimeout) {
129
- const src = this.entities.get(rel.sourceId);
130
- signals.push({
131
- code: 'ARCH_MISSING_TIMEOUT',
132
- severity: 'HIGH',
133
- category: 'resilience',
134
- title: 'External API Call Lacks Timeout Protection',
135
- message: `Component "${src?.name || rel.sourceId}" calls external provider "${tgt.name}" without an explicit timeout or AbortController. Network hangs can exhaust server worker pools.`,
136
- entities: [rel.sourceId, rel.targetId],
137
- evidence: rel.evidence || [],
138
- recommendation: 'Wrap external call with an AbortSignal.timeout(ms) or circuit breaker.',
139
- });
140
- }
141
- }
142
- }
143
-
144
- // 6. Detect Critical Path Test Coverage Gaps
145
- for (const [id, entity] of this.entities.entries()) {
146
- if (entity.kind === 'module' && !entity.tags?.includes('skill') && (entity.blastRadius?.riskScore >= 0.7 || this.inDegree.get(id) >= 10)) {
147
- // Check if any test suite imports this entity
148
- const isTested = Array.from(this.adjList.entries()).some(([srcId, targets]) => {
149
- const src = this.entities.get(srcId);
150
- return src && src.kind === 'test_suite' && targets.some((t) => t.targetId === id);
151
- });
152
-
153
- if (!isTested) {
154
- signals.push({
155
- code: 'ARCH_CRITICAL_TEST_GAP',
156
- severity: 'HIGH',
157
- category: 'testing',
158
- title: 'Test Coverage Gap on High-Centrality Component',
159
- message: `Critical component "${entity.name}" (risk score ${entity.blastRadius?.riskScore || 0.8}, ${this.inDegree.get(id)} callers) has no direct test suite coverage in the architecture graph.`,
160
- entities: [id],
161
- recommendation: 'Add unit and integration tests covering the public interfaces of this component.',
162
- });
163
- }
164
- }
165
- }
166
-
167
- return {
168
- timestamp: new Date().toISOString(),
169
- summary: {
170
- totalSignals: signals.length,
171
- critical: signals.filter((s) => s.severity === 'CRITICAL').length,
172
- high: signals.filter((s) => s.severity === 'HIGH').length,
173
- medium: signals.filter((s) => s.severity === 'MEDIUM').length,
174
- low: signals.filter((s) => s.severity === 'LOW').length,
175
- },
176
- signals,
177
- };
178
- }
179
-
180
- findCycles() {
181
- const visited = new Set();
182
- const recStack = new Set();
183
- const cycles = [];
184
- const pathStack = [];
185
-
186
- const dfs = (nodeId) => {
187
- visited.add(nodeId);
188
- recStack.add(nodeId);
189
- pathStack.push(nodeId);
190
-
191
- const neighbors = this.adjList.get(nodeId) || [];
192
- for (const { targetId } of neighbors) {
193
- if (!visited.has(targetId)) {
194
- dfs(targetId);
195
- } else if (recStack.has(targetId)) {
196
- // Found cycle
197
- const cycleStartIndex = pathStack.indexOf(targetId);
198
- if (cycleStartIndex !== -1) {
199
- const cycle = pathStack.slice(cycleStartIndex);
200
- cycles.push([...cycle, targetId]);
201
- }
202
- }
203
- }
204
-
205
- pathStack.pop();
206
- recStack.delete(nodeId);
207
- };
208
-
209
- for (const nodeId of this.entities.keys()) {
210
- if (!visited.has(nodeId)) {
211
- dfs(nodeId);
212
- }
213
- }
214
-
215
- return cycles;
216
- }
217
- }
218
-
219
- // ── Standalone CLI ────────────────────────────────────────────────────────────
220
- if (require.main === module) {
221
- const t = timer();
222
- const repoRoot = process.cwd();
223
- const modelFile = path.join(repoRoot, '.agent', 'history', 'architecture-model.json');
224
-
225
- console.log(banner('Tribunal Architectural Health & Risk Auditor'));
226
-
227
- if (!fs.existsSync(modelFile)) {
228
- console.error(` ${RED}✖ architecture-model.json not found.${RESET} Run architecture_extractor.js first.`);
229
- process.exit(1);
230
- }
231
-
232
- const model = JSON.parse(fs.readFileSync(modelFile, 'utf8'));
233
- const auditor = new ArchitectureHealthAuditor(model);
234
- const report = auditor.audit();
235
-
236
- console.log(` Signals Found: ${report.summary.totalSignals}`);
237
- console.log(` Critical Violations: ${report.summary.critical > 0 ? RED : GREEN}${report.summary.critical}${RESET}`);
238
- console.log(` High Risk Issues: ${report.summary.high > 0 ? YELLOW : GREEN}${report.summary.high}${RESET}`);
239
- console.log(` Medium Smells: ${report.summary.medium > 0 ? CYAN : GREEN}${report.summary.medium}${RESET}`);
240
-
241
- if (report.signals.length > 0) {
242
- console.log(`\n ${BOLD}Top Architectural Signals:${RESET}`);
243
- for (const s of report.signals.slice(0, 8)) {
244
- const color = s.severity === 'CRITICAL' ? RED : s.severity === 'HIGH' ? YELLOW : CYAN;
245
- console.log(` ${color}[${s.severity}]${RESET} ${BOLD}${s.title}${RESET}`);
246
- console.log(` ${DIM}${s.message}${RESET}`);
247
- console.log(` ${GREEN}Fix:${RESET} ${s.recommendation}\n`);
248
- }
249
- }
250
-
251
- console.log(` ${GREEN}${BOX.check} Architectural Health Audit Complete${RESET} in ${formatMs(t())}\n`);
252
- }
253
-
254
- module.exports = {
255
- ArchitectureHealthAuditor,
256
- };
1
+ #!/usr/bin/env node
2
+ /**
3
+ * architecture_health.js — Tribunal Architectural Health & Risk Auditor
4
+ * ═════════════════════════════════════════════════════════════════════════════
5
+ * Produces explainable architectural signals backed by code evidence.
6
+ * No arbitrary "magic scores" — every signal links directly to source locations.
7
+ *
8
+ * Detects:
9
+ * - Circular Dependencies (Cycles with exact paths)
10
+ * - Single Points of Failure (SPOFs / High Fan-In bottlenecks)
11
+ * - Excessive Coupling (God Modules / High Fan-Out)
12
+ * - Security Trust Boundary Violations (Direct DB access from public routes)
13
+ * - Resilience Deficits (External HTTP calls lacking timeouts/retries)
14
+ * - Test Coverage Gaps on Critical Core Components
15
+ * - Orphaned / Dead Code Components
16
+ *
17
+ * Zero external dependencies.
18
+ */
19
+
20
+ 'use strict';
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const {
25
+ GREEN,
26
+ YELLOW,
27
+ CYAN,
28
+ RED,
29
+ BOLD,
30
+ DIM,
31
+ RESET,
32
+ BOX,
33
+ banner,
34
+ timer,
35
+ formatMs,
36
+ } = require('./_colors');
37
+
38
+ class ArchitectureHealthAuditor {
39
+ constructor(model) {
40
+ this.model = model;
41
+ this.entities = new Map((model.entities || []).map(e => [e.id, e]));
42
+ this.adjList = new Map(); // source -> array of target IDs
43
+ this.inDegree = new Map(); // target -> count
44
+ this.outDegree = new Map(); // source -> count
45
+
46
+ this.initAdjacency();
47
+ }
48
+
49
+ initAdjacency() {
50
+ for (const e of this.entities.keys()) {
51
+ this.adjList.set(e, []);
52
+ this.inDegree.set(e, 0);
53
+ this.outDegree.set(e, 0);
54
+ }
55
+
56
+ for (const rel of this.model.relationships || []) {
57
+ if (this.adjList.has(rel.sourceId)) {
58
+ this.adjList.get(rel.sourceId).push({ targetId: rel.targetId, rel });
59
+ this.outDegree.set(rel.sourceId, (this.outDegree.get(rel.sourceId) || 0) + 1);
60
+ this.inDegree.set(rel.targetId, (this.inDegree.get(rel.targetId) || 0) + 1);
61
+ }
62
+ }
63
+ }
64
+
65
+ audit() {
66
+ const signals = [];
67
+
68
+ // 1. Detect Circular Dependencies (Tarjan's SCC)
69
+ const cycles = this.findCycles();
70
+ for (const cycle of cycles) {
71
+ const names = cycle.map(id => this.entities.get(id)?.name || id).join(' → ');
72
+ signals.push({
73
+ code: 'ARCH_CIRCULAR_DEPENDENCY',
74
+ severity: 'HIGH',
75
+ category: 'coupling',
76
+ title: 'Circular Dependency Detected',
77
+ message: `Circular dependency loop identified: ${names}`,
78
+ entities: cycle,
79
+ recommendation:
80
+ 'Break cycle by inverting dependency, introducing an interface, or extracting common logic.',
81
+ });
82
+ }
83
+
84
+ // 2. Detect Single Points of Failure (High Fan-In Bottlenecks)
85
+ for (const [id, count] of this.inDegree.entries()) {
86
+ const entity = this.entities.get(id);
87
+ if (entity && entity.kind === 'module' && !entity.tags?.includes('skill') && count >= 20) {
88
+ signals.push({
89
+ code: 'ARCH_SINGLE_POINT_OF_FAILURE',
90
+ severity: 'MEDIUM',
91
+ category: 'resilience',
92
+ title: 'High Centrality Bottleneck (SPOF)',
93
+ message: `Component "${entity.name}" has ${count} direct callers. Any breaking change will impact a large portion of the system.`,
94
+ entities: [id],
95
+ recommendation:
96
+ 'Ensure 100% unit test coverage, freeze interface contracts, and consider modular partitioning.',
97
+ });
98
+ }
99
+ }
100
+
101
+ // 3. Detect Excessive Coupling (High Fan-Out God Modules)
102
+ for (const [id, count] of this.outDegree.entries()) {
103
+ const entity = this.entities.get(id);
104
+ if (
105
+ entity &&
106
+ entity.kind === 'module' &&
107
+ !entity.tags?.includes('skill') &&
108
+ count >= 15 &&
109
+ !entity.scope.includes('test')
110
+ ) {
111
+ signals.push({
112
+ code: 'ARCH_EXCESSIVE_COUPLING',
113
+ severity: 'MEDIUM',
114
+ category: 'architecture_smell',
115
+ title: 'Excessive Fan-Out (God Component)',
116
+ message: `Component "${entity.name}" depends directly on ${count} other modules.`,
117
+ entities: [id],
118
+ recommendation: 'Decompose module into single-responsibility sub-services or handlers.',
119
+ });
120
+ }
121
+ }
122
+
123
+ // 4. Detect Security Boundary Violations (Public to Datastore Direct Call)
124
+ for (const rel of this.model.relationships || []) {
125
+ const src = this.entities.get(rel.sourceId);
126
+ const tgt = this.entities.get(rel.targetId);
127
+ if (src && tgt) {
128
+ if (src.trustZone === 'public_untrusted' && tgt.trustZone === 'isolated_datastore') {
129
+ signals.push({
130
+ code: 'ARCH_SECURITY_BOUNDARY_VIOLATION',
131
+ severity: 'CRITICAL',
132
+ category: 'security',
133
+ title: 'Unauthenticated Public Access to Datastore',
134
+ message: `Public component "${src.name}" directly communicates with isolated datastore "${tgt.name}" without passing through an authenticated service mesh or API gateway.`,
135
+ entities: [src.id, tgt.id],
136
+ evidence: rel.evidence || [],
137
+ recommendation:
138
+ 'Route request through authenticated API middleware and isolate direct database connections.',
139
+ });
140
+ }
141
+ }
142
+ }
143
+
144
+ // 5. Detect Resilience Deficits (External Calls Lacking Timeouts/Circuit Breakers)
145
+ for (const rel of this.model.relationships || []) {
146
+ const tgt = this.entities.get(rel.targetId);
147
+ if (tgt && tgt.kind === 'external_api') {
148
+ const failureMode = rel.failureMode || {};
149
+ if (!failureMode.hasTimeout) {
150
+ const src = this.entities.get(rel.sourceId);
151
+ signals.push({
152
+ code: 'ARCH_MISSING_TIMEOUT',
153
+ severity: 'HIGH',
154
+ category: 'resilience',
155
+ title: 'External API Call Lacks Timeout Protection',
156
+ message: `Component "${src?.name || rel.sourceId}" calls external provider "${tgt.name}" without an explicit timeout or AbortController. Network hangs can exhaust server worker pools.`,
157
+ entities: [rel.sourceId, rel.targetId],
158
+ evidence: rel.evidence || [],
159
+ recommendation:
160
+ 'Wrap external call with an AbortSignal.timeout(ms) or circuit breaker.',
161
+ });
162
+ }
163
+ }
164
+ }
165
+
166
+ // 6. Detect Critical Path Test Coverage Gaps
167
+ for (const [id, entity] of this.entities.entries()) {
168
+ if (
169
+ entity.kind === 'module' &&
170
+ !entity.tags?.includes('skill') &&
171
+ (entity.blastRadius?.riskScore >= 0.7 || this.inDegree.get(id) >= 10)
172
+ ) {
173
+ // Check if any test suite imports this entity
174
+ const isTested = Array.from(this.adjList.entries()).some(([srcId, targets]) => {
175
+ const src = this.entities.get(srcId);
176
+ return src && src.kind === 'test_suite' && targets.some(t => t.targetId === id);
177
+ });
178
+
179
+ if (!isTested) {
180
+ signals.push({
181
+ code: 'ARCH_CRITICAL_TEST_GAP',
182
+ severity: 'HIGH',
183
+ category: 'testing',
184
+ title: 'Test Coverage Gap on High-Centrality Component',
185
+ message: `Critical component "${entity.name}" (risk score ${entity.blastRadius?.riskScore || 0.8}, ${this.inDegree.get(id)} callers) has no direct test suite coverage in the architecture graph.`,
186
+ entities: [id],
187
+ recommendation:
188
+ 'Add unit and integration tests covering the public interfaces of this component.',
189
+ });
190
+ }
191
+ }
192
+ }
193
+
194
+ return {
195
+ timestamp: new Date().toISOString(),
196
+ summary: {
197
+ totalSignals: signals.length,
198
+ critical: signals.filter(s => s.severity === 'CRITICAL').length,
199
+ high: signals.filter(s => s.severity === 'HIGH').length,
200
+ medium: signals.filter(s => s.severity === 'MEDIUM').length,
201
+ low: signals.filter(s => s.severity === 'LOW').length,
202
+ },
203
+ signals,
204
+ };
205
+ }
206
+
207
+ findCycles() {
208
+ const visited = new Set();
209
+ const recStack = new Set();
210
+ const cycles = [];
211
+ const pathStack = [];
212
+
213
+ const dfs = nodeId => {
214
+ visited.add(nodeId);
215
+ recStack.add(nodeId);
216
+ pathStack.push(nodeId);
217
+
218
+ const neighbors = this.adjList.get(nodeId) || [];
219
+ for (const { targetId } of neighbors) {
220
+ if (!visited.has(targetId)) {
221
+ dfs(targetId);
222
+ } else if (recStack.has(targetId)) {
223
+ // Found cycle
224
+ const cycleStartIndex = pathStack.indexOf(targetId);
225
+ if (cycleStartIndex !== -1) {
226
+ const cycle = pathStack.slice(cycleStartIndex);
227
+ cycles.push([...cycle, targetId]);
228
+ }
229
+ }
230
+ }
231
+
232
+ pathStack.pop();
233
+ recStack.delete(nodeId);
234
+ };
235
+
236
+ for (const nodeId of this.entities.keys()) {
237
+ if (!visited.has(nodeId)) {
238
+ dfs(nodeId);
239
+ }
240
+ }
241
+
242
+ return cycles;
243
+ }
244
+ }
245
+
246
+ // ── Standalone CLI ────────────────────────────────────────────────────────────
247
+ if (require.main === module) {
248
+ const t = timer();
249
+ const repoRoot = process.cwd();
250
+ const modelFile = path.join(repoRoot, '.agent', 'history', 'architecture-model.json');
251
+
252
+ console.log(banner('Tribunal Architectural Health & Risk Auditor'));
253
+
254
+ if (!fs.existsSync(modelFile)) {
255
+ console.error(
256
+ ` ${RED}✖ architecture-model.json not found.${RESET} Run architecture_extractor.js first.`,
257
+ );
258
+ process.exit(1);
259
+ }
260
+
261
+ const model = JSON.parse(fs.readFileSync(modelFile, 'utf8'));
262
+ const auditor = new ArchitectureHealthAuditor(model);
263
+ const report = auditor.audit();
264
+
265
+ console.log(` Signals Found: ${report.summary.totalSignals}`);
266
+ console.log(
267
+ ` Critical Violations: ${report.summary.critical > 0 ? RED : GREEN}${report.summary.critical}${RESET}`,
268
+ );
269
+ console.log(
270
+ ` High Risk Issues: ${report.summary.high > 0 ? YELLOW : GREEN}${report.summary.high}${RESET}`,
271
+ );
272
+ console.log(
273
+ ` Medium Smells: ${report.summary.medium > 0 ? CYAN : GREEN}${report.summary.medium}${RESET}`,
274
+ );
275
+
276
+ if (report.signals.length > 0) {
277
+ console.log(`\n ${BOLD}Top Architectural Signals:${RESET}`);
278
+ for (const s of report.signals.slice(0, 8)) {
279
+ const color = s.severity === 'CRITICAL' ? RED : s.severity === 'HIGH' ? YELLOW : CYAN;
280
+ console.log(` ${color}[${s.severity}]${RESET} ${BOLD}${s.title}${RESET}`);
281
+ console.log(` ${DIM}${s.message}${RESET}`);
282
+ console.log(` ${GREEN}Fix:${RESET} ${s.recommendation}\n`);
283
+ }
284
+ }
285
+
286
+ console.log(
287
+ ` ${GREEN}${BOX.check} Architectural Health Audit Complete${RESET} in ${formatMs(t())}\n`,
288
+ );
289
+ }
290
+
291
+ module.exports = {
292
+ ArchitectureHealthAuditor,
293
+ };