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,234 +1,274 @@
1
- #!/usr/bin/env node
2
- /**
3
- * blast_radius_engine.js — Tribunal Architectural Blast Radius Engine
4
- * ═════════════════════════════════════════════════════════════════════════════
5
- * Answers: "If I modify or break this component, what is affected?"
6
- *
7
- * Computes:
8
- * - Direct Dependents (Immediate Callers/Importers)
9
- * - Transitive Closure (Full Downstream Blast Radius)
10
- * - Affected API Routes & Endpoints
11
- * - Affected Data Stores & Queries
12
- * - Affected Trust Boundaries & Security Zones
13
- * - Required Test Suites to Re-verify
14
- * - Architectural Centrality & Risk Score (0.0 to 1.0)
15
- *
16
- * Zero external dependencies.
17
- */
18
-
19
- 'use strict';
20
-
21
- const fs = require('fs');
22
- const path = require('path');
23
- const { GREEN, YELLOW, CYAN, RED, BOLD, RESET, BOX, banner, timer, formatMs } = require('./_colors');
24
-
25
- class BlastRadiusEngine {
26
- constructor(model) {
27
- this.model = model;
28
- this.entityMap = new Map();
29
- this.dependentsMap = new Map(); // target -> Set of sources that depend on target
30
- this.dependenciesMap = new Map(); // source -> Set of targets that source depends on
31
-
32
- this.initGraph();
33
- }
34
-
35
- initGraph() {
36
- for (const e of this.model.entities || []) {
37
- this.entityMap.set(e.id, e);
38
- this.dependentsMap.set(e.id, new Set());
39
- this.dependenciesMap.set(e.id, new Set());
40
- }
41
-
42
- for (const rel of this.model.relationships || []) {
43
- if (this.dependentsMap.has(rel.targetId)) {
44
- this.dependentsMap.get(rel.targetId).add(rel.sourceId);
45
- }
46
- if (this.dependenciesMap.has(rel.sourceId)) {
47
- this.dependenciesMap.get(rel.sourceId).add(rel.targetId);
48
- }
49
- }
50
- }
51
-
52
- findEntity(query) {
53
- if (!query) return null;
54
- const clean = query.trim();
55
-
56
- // Direct ID match
57
- if (this.entityMap.has(clean)) return this.entityMap.get(clean);
58
-
59
- // Path or partial match
60
- for (const [id, e] of this.entityMap.entries()) {
61
- if (e.name === clean || e.scope === clean) return e;
62
- if (e.sources && e.sources.some((s) => s.file === clean || s.file.endsWith(clean))) return e;
63
- if (id.toLowerCase().includes(clean.toLowerCase())) return e;
64
- }
65
- return null;
66
- }
67
-
68
- computeBlastRadius(targetQuery, maxDepth = 15) {
69
- const rootEntity = this.findEntity(targetQuery);
70
- if (!rootEntity) {
71
- return {
72
- found: false,
73
- query: targetQuery,
74
- error: `Component or file "${targetQuery}" not found in architectural model.`,
75
- };
76
- }
77
-
78
- const visited = new Set([rootEntity.id]);
79
- const directDependents = new Set();
80
- const transitiveRings = []; // Array of Sets per depth level
81
-
82
- let currentLevel = new Set(this.dependentsMap.get(rootEntity.id) || []);
83
- for (const d of currentLevel) directDependents.add(d);
84
-
85
- let depth = 1;
86
- while (currentLevel.size > 0 && depth <= maxDepth) {
87
- const ringEntities = [];
88
- const nextLevel = new Set();
89
-
90
- for (const id of currentLevel) {
91
- if (!visited.has(id)) {
92
- visited.add(id);
93
- const entity = this.entityMap.get(id);
94
- if (entity) ringEntities.push(entity);
95
-
96
- const upstreams = this.dependentsMap.get(id) || [];
97
- for (const up of upstreams) {
98
- if (!visited.has(up)) {
99
- nextLevel.add(up);
100
- }
101
- }
102
- }
103
- }
104
-
105
- if (ringEntities.length > 0) {
106
- transitiveRings.push({ depth, count: ringEntities.length, entities: ringEntities });
107
- }
108
- currentLevel = nextLevel;
109
- depth++;
110
- }
111
-
112
- // Collect all affected entities (excluding root)
113
- const affectedEntities = Array.from(visited)
114
- .filter((id) => id !== rootEntity.id)
115
- .map((id) => this.entityMap.get(id))
116
- .filter(Boolean);
117
-
118
- // Categorize Impact
119
- const affectedEndpoints = affectedEntities.filter((e) => e.kind === 'endpoint');
120
- const affectedDatastores = affectedEntities.filter((e) => e.kind === 'datastore' || e.kind === 'cache');
121
- const affectedQueues = affectedEntities.filter((e) => e.kind === 'queue' || e.kind === 'event_bus');
122
- const affectedTests = affectedEntities.filter((e) => e.kind === 'test_suite' || e.scope?.includes('test'));
123
-
124
- // Check Trust Zone Crossings
125
- const rootZone = rootEntity.trustZone || 'unknown';
126
- const crossedZones = new Set();
127
- for (const e of affectedEntities) {
128
- if (e.trustZone && e.trustZone !== rootZone) {
129
- crossedZones.add(e.trustZone);
130
- }
131
- }
132
-
133
- // Risk Calculation (0.0 to 1.0)
134
- // Centrality + Blast scope + Security Crossing
135
- const totalEntities = this.entityMap.size || 1;
136
- const impactRatio = affectedEntities.length / totalEntities;
137
- const directCoupling = directDependents.size;
138
- const securityPenalty = crossedZones.has('public_untrusted') || crossedZones.has('dmz_gateway') ? 0.3 : 0.1;
139
-
140
- let riskScore = Math.min(1.0, impactRatio * 2.5 + directCoupling * 0.05 + securityPenalty);
141
- riskScore = Math.round(riskScore * 100) / 100;
142
-
143
- let riskTier = 'LOW';
144
- if (riskScore >= 0.7) riskTier = 'CRITICAL';
145
- else if (riskScore >= 0.4) riskTier = 'HIGH';
146
- else if (riskScore >= 0.2) riskTier = 'MEDIUM';
147
-
148
- return {
149
- found: true,
150
- target: {
151
- id: rootEntity.id,
152
- name: rootEntity.name,
153
- kind: rootEntity.kind,
154
- scope: rootEntity.scope,
155
- trustZone: rootEntity.trustZone,
156
- },
157
- metrics: {
158
- directDependentsCount: directDependents.size,
159
- transitiveDependentsCount: affectedEntities.length,
160
- maxDepthReached: transitiveRings.length,
161
- riskScore,
162
- riskTier,
163
- },
164
- impactBreakdown: {
165
- endpoints: affectedEndpoints.map((e) => ({ id: e.id, name: e.name, scope: e.scope })),
166
- datastores: affectedDatastores.map((e) => ({ id: e.id, name: e.name })),
167
- queues: affectedQueues.map((e) => ({ id: e.id, name: e.name })),
168
- testSuites: affectedTests.map((e) => ({ id: e.id, name: e.name, scope: e.scope })),
169
- securityZonesCrossed: Array.from(crossedZones),
170
- },
171
- transitiveRings,
172
- };
173
- }
174
- }
175
-
176
- // ── Standalone CLI ────────────────────────────────────────────────────────────
177
- if (require.main === module) {
178
- const t = timer();
179
- const repoRoot = process.cwd();
180
- const modelFile = path.join(repoRoot, '.agent', 'history', 'architecture-model.json');
181
-
182
- const query = process.argv[2];
183
- if (!query) {
184
- console.log(banner('Tribunal Architectural Blast Radius Engine'));
185
- console.error(` ${YELLOW}Usage:${RESET} node blast_radius_engine.js <component-id-or-filepath>\n`);
186
- process.exit(1);
187
- }
188
-
189
- if (!fs.existsSync(modelFile)) {
190
- console.error(` ${RED}✖ architecture-model.json not found.${RESET} Run architecture_extractor.js first.`);
191
- process.exit(1);
192
- }
193
-
194
- const model = JSON.parse(fs.readFileSync(modelFile, 'utf8'));
195
- const engine = new BlastRadiusEngine(model);
196
- const result = engine.computeBlastRadius(query);
197
-
198
- console.log(banner('Tribunal Architectural Blast Radius Engine'));
199
-
200
- if (!result.found) {
201
- console.error(` ${RED}✖ ${result.error}${RESET}`);
202
- process.exit(1);
203
- }
204
-
205
- const tierColors = {
206
- CRITICAL: RED,
207
- HIGH: YELLOW,
208
- MEDIUM: CYAN,
209
- LOW: GREEN,
210
- };
211
- const color = tierColors[result.metrics.riskTier] || GREEN;
212
-
213
- console.log(` Target: ${BOLD}${result.target.name}${RESET} (${result.target.id})`);
214
- console.log(` Kind: ${CYAN}${result.target.kind}${RESET} | Trust Zone: ${YELLOW}${result.target.trustZone}${RESET}`);
215
- console.log(` Blast Radius Risk: ${color}${BOLD}${result.metrics.riskTier} (${result.metrics.riskScore})${RESET}`);
216
- console.log(` Direct Callers: ${BOLD}${result.metrics.directDependentsCount}${RESET}`);
217
- console.log(` Transitive Dependents: ${BOLD}${result.metrics.transitiveDependentsCount}${RESET} across ${result.metrics.maxDepthReached} depth rings`);
218
- console.log(` Impacted Endpoints: ${result.impactBreakdown.endpoints.length > 0 ? YELLOW : GREEN}${result.impactBreakdown.endpoints.length}${RESET}`);
219
- console.log(` Impacted Test Suites: ${CYAN}${result.impactBreakdown.testSuites.length}${RESET}`);
220
- console.log(` Zones Crossed: ${result.impactBreakdown.securityZonesCrossed.join(', ') || 'None'}`);
221
-
222
- if (result.transitiveRings.length > 0) {
223
- console.log(`\n ${BOLD}Concentric Blast Rings:${RESET}`);
224
- for (const ring of result.transitiveRings) {
225
- console.log(` Ring ${ring.depth} (depth ${ring.depth}): ${BOLD}${ring.count}${RESET} downstream components`);
226
- }
227
- }
228
-
229
- console.log(`\n ${GREEN}${BOX.check} Blast Radius Analysis Complete${RESET} in ${formatMs(t())}\n`);
230
- }
231
-
232
- module.exports = {
233
- BlastRadiusEngine,
234
- };
1
+ #!/usr/bin/env node
2
+ /**
3
+ * blast_radius_engine.js — Tribunal Architectural Blast Radius Engine
4
+ * ═════════════════════════════════════════════════════════════════════════════
5
+ * Answers: "If I modify or break this component, what is affected?"
6
+ *
7
+ * Computes:
8
+ * - Direct Dependents (Immediate Callers/Importers)
9
+ * - Transitive Closure (Full Downstream Blast Radius)
10
+ * - Affected API Routes & Endpoints
11
+ * - Affected Data Stores & Queries
12
+ * - Affected Trust Boundaries & Security Zones
13
+ * - Required Test Suites to Re-verify
14
+ * - Architectural Centrality & Risk Score (0.0 to 1.0)
15
+ *
16
+ * Zero external dependencies.
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const {
24
+ GREEN,
25
+ YELLOW,
26
+ CYAN,
27
+ RED,
28
+ BOLD,
29
+ RESET,
30
+ BOX,
31
+ banner,
32
+ timer,
33
+ formatMs,
34
+ } = require('./_colors');
35
+
36
+ class BlastRadiusEngine {
37
+ constructor(model) {
38
+ this.model = model;
39
+ this.entityMap = new Map();
40
+ this.dependentsMap = new Map(); // target -> Set of sources that depend on target
41
+ this.dependenciesMap = new Map(); // source -> Set of targets that source depends on
42
+
43
+ this.initGraph();
44
+ }
45
+
46
+ initGraph() {
47
+ for (const e of this.model.entities || []) {
48
+ this.entityMap.set(e.id, e);
49
+ this.dependentsMap.set(e.id, new Set());
50
+ this.dependenciesMap.set(e.id, new Set());
51
+ }
52
+
53
+ for (const rel of this.model.relationships || []) {
54
+ if (this.dependentsMap.has(rel.targetId)) {
55
+ this.dependentsMap.get(rel.targetId).add(rel.sourceId);
56
+ }
57
+ if (this.dependenciesMap.has(rel.sourceId)) {
58
+ this.dependenciesMap.get(rel.sourceId).add(rel.targetId);
59
+ }
60
+ }
61
+ }
62
+
63
+ findEntity(query) {
64
+ if (!query) return null;
65
+ const clean = query.trim();
66
+
67
+ // Direct ID match
68
+ if (this.entityMap.has(clean)) return this.entityMap.get(clean);
69
+
70
+ // Path or partial match
71
+ for (const [id, e] of this.entityMap.entries()) {
72
+ if (e.name === clean || e.scope === clean) return e;
73
+ if (e.sources && e.sources.some(s => s.file === clean || s.file.endsWith(clean))) return e;
74
+ if (id.toLowerCase().includes(clean.toLowerCase())) return e;
75
+ }
76
+ return null;
77
+ }
78
+
79
+ computeBlastRadius(targetQuery, maxDepth = 15) {
80
+ const rootEntity = this.findEntity(targetQuery);
81
+ if (!rootEntity) {
82
+ return {
83
+ found: false,
84
+ query: targetQuery,
85
+ error: `Component or file "${targetQuery}" not found in architectural model.`,
86
+ };
87
+ }
88
+
89
+ const visited = new Set([rootEntity.id]);
90
+ const directDependents = new Set();
91
+ const transitiveRings = []; // Array of Sets per depth level
92
+
93
+ let currentLevel = new Set(this.dependentsMap.get(rootEntity.id) || []);
94
+ for (const d of currentLevel) directDependents.add(d);
95
+
96
+ let depth = 1;
97
+ while (currentLevel.size > 0 && depth <= maxDepth) {
98
+ const ringEntities = [];
99
+ const nextLevel = new Set();
100
+
101
+ for (const id of currentLevel) {
102
+ if (!visited.has(id)) {
103
+ visited.add(id);
104
+ const entity = this.entityMap.get(id);
105
+ if (entity) ringEntities.push(entity);
106
+
107
+ const upstreams = this.dependentsMap.get(id) || [];
108
+ for (const up of upstreams) {
109
+ if (!visited.has(up)) {
110
+ nextLevel.add(up);
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ if (ringEntities.length > 0) {
117
+ transitiveRings.push({ depth, count: ringEntities.length, entities: ringEntities });
118
+ }
119
+ currentLevel = nextLevel;
120
+ depth++;
121
+ }
122
+
123
+ // Collect all affected entities (excluding root)
124
+ const affectedEntities = Array.from(visited)
125
+ .filter(id => id !== rootEntity.id)
126
+ .map(id => this.entityMap.get(id))
127
+ .filter(Boolean);
128
+
129
+ // Categorize Impact
130
+ const affectedEndpoints = affectedEntities.filter(e => e.kind === 'endpoint');
131
+ const affectedDatastores = affectedEntities.filter(
132
+ e => e.kind === 'datastore' || e.kind === 'cache',
133
+ );
134
+ const affectedQueues = affectedEntities.filter(
135
+ e => e.kind === 'queue' || e.kind === 'event_bus',
136
+ );
137
+ const affectedTests = affectedEntities.filter(
138
+ e => e.kind === 'test_suite' || e.scope?.includes('test'),
139
+ );
140
+
141
+ // Check Trust Zone Crossings
142
+ const rootZone = rootEntity.trustZone || 'unknown';
143
+ const crossedZones = new Set();
144
+ for (const e of affectedEntities) {
145
+ if (e.trustZone && e.trustZone !== rootZone) {
146
+ crossedZones.add(e.trustZone);
147
+ }
148
+ }
149
+
150
+ // Risk Calculation (0.0 to 1.0)
151
+ // Centrality + Blast scope + Security Crossing
152
+ const totalEntities = this.entityMap.size || 1;
153
+ const impactRatio = affectedEntities.length / totalEntities;
154
+ const directCoupling = directDependents.size;
155
+ const securityPenalty =
156
+ crossedZones.has('public_untrusted') || crossedZones.has('dmz_gateway') ? 0.3 : 0.1;
157
+
158
+ let riskScore = Math.min(1.0, impactRatio * 2.5 + directCoupling * 0.05 + securityPenalty);
159
+ riskScore = Math.round(riskScore * 100) / 100;
160
+
161
+ let riskTier = 'LOW';
162
+ if (riskScore >= 0.7) riskTier = 'CRITICAL';
163
+ else if (riskScore >= 0.4) riskTier = 'HIGH';
164
+ else if (riskScore >= 0.2) riskTier = 'MEDIUM';
165
+
166
+ return {
167
+ found: true,
168
+ target: {
169
+ id: rootEntity.id,
170
+ name: rootEntity.name,
171
+ kind: rootEntity.kind,
172
+ scope: rootEntity.scope,
173
+ trustZone: rootEntity.trustZone,
174
+ },
175
+ metrics: {
176
+ directDependentsCount: directDependents.size,
177
+ transitiveDependentsCount: affectedEntities.length,
178
+ maxDepthReached: transitiveRings.length,
179
+ riskScore,
180
+ riskTier,
181
+ },
182
+ impactBreakdown: {
183
+ endpoints: affectedEndpoints.map(e => ({ id: e.id, name: e.name, scope: e.scope })),
184
+ datastores: affectedDatastores.map(e => ({ id: e.id, name: e.name })),
185
+ queues: affectedQueues.map(e => ({ id: e.id, name: e.name })),
186
+ testSuites: affectedTests.map(e => ({ id: e.id, name: e.name, scope: e.scope })),
187
+ securityZonesCrossed: Array.from(crossedZones),
188
+ },
189
+ transitiveRings,
190
+ };
191
+ }
192
+ }
193
+
194
+ // ── Standalone CLI ────────────────────────────────────────────────────────────
195
+ if (require.main === module) {
196
+ const t = timer();
197
+ const repoRoot = process.cwd();
198
+ const modelFile = path.join(repoRoot, '.agent', 'history', 'architecture-model.json');
199
+
200
+ const query = process.argv[2];
201
+ if (!query) {
202
+ console.log(banner('Tribunal Architectural Blast Radius Engine'));
203
+ console.error(
204
+ ` ${YELLOW}Usage:${RESET} node blast_radius_engine.js <component-id-or-filepath>\n`,
205
+ );
206
+ process.exit(1);
207
+ }
208
+
209
+ if (!fs.existsSync(modelFile)) {
210
+ console.error(
211
+ ` ${RED}✖ architecture-model.json not found.${RESET} Run architecture_extractor.js first.`,
212
+ );
213
+ process.exit(1);
214
+ }
215
+
216
+ const model = JSON.parse(fs.readFileSync(modelFile, 'utf8'));
217
+ const engine = new BlastRadiusEngine(model);
218
+ const result = engine.computeBlastRadius(query);
219
+
220
+ console.log(banner('Tribunal Architectural Blast Radius Engine'));
221
+
222
+ if (!result.found) {
223
+ console.error(` ${RED}✖ ${result.error}${RESET}`);
224
+ process.exit(1);
225
+ }
226
+
227
+ const tierColors = {
228
+ CRITICAL: RED,
229
+ HIGH: YELLOW,
230
+ MEDIUM: CYAN,
231
+ LOW: GREEN,
232
+ };
233
+ const color = tierColors[result.metrics.riskTier] || GREEN;
234
+
235
+ console.log(
236
+ ` Target: ${BOLD}${result.target.name}${RESET} (${result.target.id})`,
237
+ );
238
+ console.log(
239
+ ` Kind: ${CYAN}${result.target.kind}${RESET} | Trust Zone: ${YELLOW}${result.target.trustZone}${RESET}`,
240
+ );
241
+ console.log(
242
+ ` Blast Radius Risk: ${color}${BOLD}${result.metrics.riskTier} (${result.metrics.riskScore})${RESET}`,
243
+ );
244
+ console.log(` Direct Callers: ${BOLD}${result.metrics.directDependentsCount}${RESET}`);
245
+ console.log(
246
+ ` Transitive Dependents: ${BOLD}${result.metrics.transitiveDependentsCount}${RESET} across ${result.metrics.maxDepthReached} depth rings`,
247
+ );
248
+ console.log(
249
+ ` Impacted Endpoints: ${result.impactBreakdown.endpoints.length > 0 ? YELLOW : GREEN}${result.impactBreakdown.endpoints.length}${RESET}`,
250
+ );
251
+ console.log(
252
+ ` Impacted Test Suites: ${CYAN}${result.impactBreakdown.testSuites.length}${RESET}`,
253
+ );
254
+ console.log(
255
+ ` Zones Crossed: ${result.impactBreakdown.securityZonesCrossed.join(', ') || 'None'}`,
256
+ );
257
+
258
+ if (result.transitiveRings.length > 0) {
259
+ console.log(`\n ${BOLD}Concentric Blast Rings:${RESET}`);
260
+ for (const ring of result.transitiveRings) {
261
+ console.log(
262
+ ` Ring ${ring.depth} (depth ${ring.depth}): ${BOLD}${ring.count}${RESET} downstream components`,
263
+ );
264
+ }
265
+ }
266
+
267
+ console.log(
268
+ `\n ${GREEN}${BOX.check} Blast Radius Analysis Complete${RESET} in ${formatMs(t())}\n`,
269
+ );
270
+ }
271
+
272
+ module.exports = {
273
+ BlastRadiusEngine,
274
+ };