tribunal-kit 4.4.0 → 4.4.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.
Files changed (85) hide show
  1. package/.agent/agents/api-architect.md +66 -66
  2. package/.agent/agents/db-latency-auditor.md +216 -216
  3. package/.agent/agents/precedence-reviewer.md +250 -250
  4. package/.agent/agents/resilience-reviewer.md +88 -88
  5. package/.agent/agents/schema-reviewer.md +67 -67
  6. package/.agent/agents/throughput-optimizer.md +299 -299
  7. package/.agent/agents/ui-ux-auditor.md +292 -292
  8. package/.agent/agents/vitals-reviewer.md +223 -223
  9. package/.agent/scripts/_colors.js +18 -18
  10. package/.agent/scripts/_utils.js +42 -42
  11. package/.agent/scripts/append_flow.js +72 -72
  12. package/.agent/scripts/auto_preview.js +197 -197
  13. package/.agent/scripts/bundle_analyzer.js +290 -290
  14. package/.agent/scripts/case_law_manager.js +17 -6
  15. package/.agent/scripts/checklist.js +266 -266
  16. package/.agent/scripts/colors.js +17 -17
  17. package/.agent/scripts/compress_skills.js +141 -141
  18. package/.agent/scripts/consolidate_skills.js +149 -149
  19. package/.agent/scripts/context_broker.js +611 -609
  20. package/.agent/scripts/deep_compress.js +150 -150
  21. package/.agent/scripts/dependency_analyzer.js +272 -272
  22. package/.agent/scripts/graph_builder.js +313 -311
  23. package/.agent/scripts/graph_visualizer.js +384 -384
  24. package/.agent/scripts/inner_loop_validator.js +451 -465
  25. package/.agent/scripts/lint_runner.js +187 -187
  26. package/.agent/scripts/minify_context.js +100 -100
  27. package/.agent/scripts/mutation_runner.js +280 -280
  28. package/.agent/scripts/patch_skills_meta.js +156 -156
  29. package/.agent/scripts/patch_skills_output.js +244 -244
  30. package/.agent/scripts/schema_validator.js +297 -297
  31. package/.agent/scripts/security_scan.js +303 -303
  32. package/.agent/scripts/session_manager.js +276 -276
  33. package/.agent/scripts/skill_evolution.js +644 -644
  34. package/.agent/scripts/skill_integrator.js +313 -313
  35. package/.agent/scripts/strengthen_skills.js +193 -193
  36. package/.agent/scripts/strip_tribunal.js +47 -47
  37. package/.agent/scripts/swarm_dispatcher.js +360 -360
  38. package/.agent/scripts/test_runner.js +193 -193
  39. package/.agent/scripts/utils.js +32 -32
  40. package/.agent/scripts/verify_all.js +257 -256
  41. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +1 -1
  42. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
  43. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +1 -1
  44. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
  45. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +1 -1
  46. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +1 -1
  47. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +1 -1
  48. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +1 -1
  49. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +1 -1
  50. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +1 -1
  51. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +1 -1
  52. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +1 -1
  53. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +1 -1
  54. package/.agent/skills/doc.md +1 -1
  55. package/.agent/skills/knowledge-graph/SKILL.md +52 -52
  56. package/.agent/skills/ui-ux-pro-max/SKILL.md +562 -562
  57. package/.agent/workflows/generate.md +183 -183
  58. package/.agent/workflows/tribunal-speed.md +183 -183
  59. package/README.md +1 -1
  60. package/bin/tribunal-kit.js +76 -87
  61. package/package.json +6 -3
  62. package/scripts/changelog.js +167 -167
  63. package/scripts/sync-version.js +81 -81
  64. package/.agent/history/architecture-explorer.html +0 -352
  65. package/.agent/history/architecture-graph.yaml +0 -109
  66. package/.agent/history/graph-cache.json +0 -215
  67. package/.agent/history/snapshots/migrate_refs.js.json +0 -11
  68. package/.agent/history/snapshots/scripts__changelog.js.json +0 -12
  69. package/.agent/history/snapshots/scripts__sync-version.js.json +0 -11
  70. package/.agent/history/snapshots/scripts__validate-payload.js.json +0 -11
  71. package/.agent/history/snapshots/test__integration__bridges.test.js.json +0 -13
  72. package/.agent/history/snapshots/test__integration__init.test.js.json +0 -13
  73. package/.agent/history/snapshots/test__integration__routing.test.js.json +0 -11
  74. package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +0 -13
  75. package/.agent/history/snapshots/test__integration__wave2.test.js.json +0 -13
  76. package/.agent/history/snapshots/test__unit__args.test.js.json +0 -10
  77. package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +0 -10
  78. package/.agent/history/snapshots/test__unit__copyDir.test.js.json +0 -13
  79. package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +0 -11
  80. package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +0 -13
  81. package/.agent/history/snapshots/test__unit__semver.test.js.json +0 -10
  82. package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +0 -11
  83. package/.agent/scripts/__pycache__/_colors.cpython-311.pyc +0 -0
  84. package/.agent/scripts/__pycache__/_utils.cpython-311.pyc +0 -0
  85. package/.agent/scripts/__pycache__/case_law_manager.cpython-311.pyc +0 -0
@@ -1,311 +1,313 @@
1
- #!/usr/bin/env node
2
- /**
3
- * graph_builder.js — Tribunal Kit Macro Graph Mapper
4
- * Parses project structure for imports, exports, and dependencies
5
- * using incremental caching and zero external dependencies.
6
- * Now includes Blast Radius calculation and robust token stripping.
7
- */
8
-
9
- 'use strict';
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
-
14
- const AGENT_DIR = path.join(process.cwd(), '.agent');
15
- const HISTORY_DIR = path.join(AGENT_DIR, 'history');
16
- const CACHE_FILE = path.join(HISTORY_DIR, 'graph-cache.json');
17
- const GRAPH_FILE = path.join(HISTORY_DIR, 'architecture-graph.yaml');
18
-
19
- // ── Exclusions & Safety ───────────────────────────────────────────────────────
20
- const DEFAULT_EXCLUSIONS = new Set([
21
- 'node_modules', '.git', '.next', 'dist', 'build', 'coverage', '.agent', 'artifacts'
22
- ]);
23
-
24
- function loadGitIgnore() {
25
- const gitignorePath = path.join(process.cwd(), '.gitignore');
26
- if (!fs.existsSync(gitignorePath)) return [];
27
-
28
- return fs.readFileSync(gitignorePath, 'utf8')
29
- .split('\n')
30
- .map(line => line.trim())
31
- .filter(line => line && !line.startsWith('#'))
32
- .map(line => line.replace(/\/$/, '').replace(/^\//, ''));
33
- }
34
-
35
- const customExclusions = loadGitIgnore();
36
-
37
- function isExcluded(filePath) {
38
- const parts = filePath.split(path.sep);
39
- if (parts.some(p => DEFAULT_EXCLUSIONS.has(p))) return true;
40
-
41
- const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, '/');
42
- for (const pattern of customExclusions) {
43
- if (relativePath.includes(pattern)) return true;
44
- }
45
- return false;
46
- }
47
-
48
- // ── Traversal ─────────────────────────────────────────────────────────────────
49
- function walkDir(dir, fileList = []) {
50
- if (!fs.existsSync(dir) || isExcluded(dir)) return fileList;
51
-
52
- let files;
53
- try {
54
- files = fs.readdirSync(dir);
55
- } catch (err) {
56
- return fileList;
57
- }
58
-
59
- for (const file of files) {
60
- const filePath = path.join(dir, file);
61
- if (isExcluded(filePath)) continue;
62
-
63
- if (fs.statSync(filePath).isDirectory()) {
64
- walkDir(filePath, fileList);
65
- } else {
66
- if (/\.(js|jsx|ts|tsx|mjs|cjs)$/.test(file)) {
67
- fileList.push(filePath);
68
- }
69
- }
70
- }
71
- return fileList;
72
- }
73
-
74
- // ── Regex AST Extraction ──────────────────────────────────────────────────────
75
- function parseFile(content) {
76
- const imports = new Set();
77
- const exports = new Set();
78
-
79
- const cleanContent = stripStringsAndComments(content);
80
-
81
- const importRegex = /import(?:(?:[\w*\s{},]*)\sfrom\s+)?([^\s;]+)/g; // Match bare specifier if possible, strings are stripped, but wait...
82
- // WAIT: If I stripped strings, how do I get the import path?
83
- // The previous implementation used strings `['"]([^'"]+)['"]`.
84
- // If I strip strings, the import path is lost!
85
- // Let's rollback that logic or adapt it.
86
- // Instead of stripping all strings, we should only strip strings if they are NOT following 'import ' or 'require('
87
- // To do this simply, let's keep strings, but just be careful.
88
- // Actually, string literals inside `require("...")` are what we want.
89
- // So `parseFile` should probably NOT strip strings, but just use a safer regex.
90
- // The false positive in `dependency_analyzer` was because of `const diff = "import a from 'a'"`.
91
- // Let's use `stripStringsAndComments` but we DO NOT strip strings.
92
- // We only strip comments.
93
-
94
- // I'll define an inner function to just strip comments to be safe for imports.
95
- // Let's stick to the simple `.replace` for comments for now, and rely on regex boundaries.
96
- const semiCleanContent = content.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
97
-
98
- const importRegex2 = /^[\s]*import(?:(?:[\w*\s{},]*)\sfrom\s+)?['"]([^'"]+)['"]/gm;
99
- const requireRegex = /require\(['"]([^'"]+)['"]\)/g;
100
- const dynamicImportRegex = /import\(['"]([^'"]+)['"]\)/g;
101
-
102
- const exportRegex = /^[\s]*export\s+(?:const|let|var|function|class)\s+([a-zA-Z0-9_]+)/gm;
103
- const moduleExportRegex = /module\.exports\s*=\s*\{([^}]+)\}/g;
104
- const defaultExportRegex = /^[\s]*export\s+default\s+([a-zA-Z0-9_]+)/gm;
105
-
106
- let match;
107
- while ((match = importRegex2.exec(semiCleanContent)) !== null) imports.add(match[1]);
108
- while ((match = requireRegex.exec(semiCleanContent)) !== null) imports.add(match[1]);
109
- while ((match = dynamicImportRegex.exec(semiCleanContent)) !== null) imports.add(match[1]);
110
-
111
- while ((match = exportRegex.exec(semiCleanContent)) !== null) exports.add(match[1]);
112
- while ((match = defaultExportRegex.exec(semiCleanContent)) !== null) exports.add(match[1]);
113
-
114
- while ((match = moduleExportRegex.exec(semiCleanContent)) !== null) {
115
- const tokens = match[1].split(',').map(s => s.trim().split(':')[0].trim());
116
- tokens.forEach(t => t && exports.add(t));
117
- }
118
-
119
- return {
120
- imports: Array.from(imports),
121
- exports: Array.from(exports)
122
- };
123
- }
124
-
125
- // ── YAML Generation ───────────────────────────────────────────────────────────
126
- function generateYAML(data) {
127
- let yaml = '# Auto-generated Architecture Graph by Tribunal Kit\n';
128
- yaml += '# DO NOT EDIT MANUALLY - Auto-updates via incremental cache\n\n';
129
-
130
- for (const [file, info] of Object.entries(data)) {
131
- if (info.imports.length === 0 && info.exports.length === 0 && (!info.dependents || info.dependents.length === 0)) continue;
132
-
133
- yaml += `"${file}":\n`;
134
- yaml += ` riskScore: "${info.riskScore || 'Low'}"\n`;
135
- yaml += ` blastRadius: ${info.blastRadius || 0}\n`;
136
-
137
- if (info.imports && info.imports.length > 0) {
138
- yaml += ` imports:\n`;
139
- info.imports.forEach(i => yaml += ` - "${i}"\n`);
140
- }
141
- if (info.exports && info.exports.length > 0) {
142
- yaml += ` exports:\n`;
143
- info.exports.forEach(e => yaml += ` - "${e}"\n`);
144
- }
145
- if (info.dependents && info.dependents.length > 0) {
146
- yaml += ` dependents:\n`;
147
- info.dependents.forEach(d => yaml += ` - "${d}"\n`);
148
- }
149
- }
150
- return yaml;
151
- }
152
-
153
- // ── Main Execution ────────────────────────────────────────────────────────────
154
- function main() {
155
- if (!fs.existsSync(AGENT_DIR)) {
156
- console.error('\x1b[31m✖ Error: .agent directory not found.\x1b[0m');
157
- process.exit(1);
158
- }
159
-
160
- if (!fs.existsSync(HISTORY_DIR)) fs.mkdirSync(HISTORY_DIR, { recursive: true });
161
-
162
- let cache = {};
163
- if (fs.existsSync(CACHE_FILE)) {
164
- try { cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')); } catch(e) {}
165
- }
166
-
167
- console.log('\x1b[96m✦ Building Architecture Graph...\x1b[0m');
168
- const files = walkDir(process.cwd());
169
- const graphData = {};
170
-
171
- let parsedCount = 0;
172
- let cachedCount = 0;
173
-
174
- for (const file of files) {
175
- const stat = fs.statSync(file);
176
- const relativePath = path.relative(process.cwd(), file).replace(/\\/g, '/');
177
-
178
- if (cache[relativePath] && cache[relativePath].mtimeMs === stat.mtimeMs) {
179
- graphData[relativePath] = { imports: cache[relativePath].imports, exports: cache[relativePath].exports };
180
- cachedCount++;
181
- } else {
182
- try {
183
- const content = fs.readFileSync(file, 'utf8');
184
- const parsed = parseFile(content);
185
- graphData[relativePath] = parsed;
186
-
187
- cache[relativePath] = {
188
- mtimeMs: stat.mtimeMs,
189
- imports: parsed.imports,
190
- exports: parsed.exports
191
- };
192
- parsedCount++;
193
- } catch (err) {}
194
- }
195
- }
196
-
197
- // Compute Dependents
198
- for (const [file, info] of Object.entries(graphData)) info.dependents = [];
199
-
200
- const fileKeys = Object.keys(graphData);
201
- for (const [file, info] of Object.entries(graphData)) {
202
- for (const imp of info.imports) {
203
- if (imp.startsWith('.')) {
204
- let resolved = path.posix.join(path.dirname(file), imp);
205
- // Look for direct match or .js / index.js
206
- let matchingKey = fileKeys.find(k =>
207
- k === resolved || k === resolved + '.js' || k === resolved + '.ts' || k === resolved + '/index.js'
208
- );
209
- if (matchingKey) {
210
- if (!graphData[matchingKey].dependents.includes(file)) {
211
- graphData[matchingKey].dependents.push(file);
212
- }
213
- }
214
- }
215
- }
216
- }
217
-
218
- // Compute Risk Score
219
- function computeRisk(file) {
220
- const visited = new Set();
221
- function visit(node) {
222
- if (visited.has(node)) return;
223
- visited.add(node);
224
- const deps = graphData[node]?.dependents || [];
225
- deps.forEach(visit);
226
- }
227
- visit(file);
228
- const radius = visited.size - 1;
229
- let score = 'Low';
230
- if (radius > 10) score = 'Critical';
231
- else if (radius >= 5) score = 'High';
232
- else if (radius >= 2) score = 'Medium';
233
- return { score, count: Math.max(0, radius) };
234
- }
235
-
236
- for (const file of fileKeys) {
237
- const risk = computeRisk(file);
238
- graphData[file].riskScore = risk.score;
239
- graphData[file].blastRadius = risk.count;
240
-
241
- // Update cache with these values so visualizer can use it
242
- if (cache[file]) {
243
- cache[file].dependents = graphData[file].dependents;
244
- cache[file].riskScore = risk.score;
245
- cache[file].blastRadius = risk.count;
246
- }
247
- }
248
-
249
- fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
250
- fs.writeFileSync(GRAPH_FILE, generateYAML(graphData));
251
-
252
- // ── Pre-Computed Context Snapshots (Option C) ───────────────────────────
253
- const SNAPSHOTS_DIR = path.join(HISTORY_DIR, 'snapshots');
254
- if (!fs.existsSync(SNAPSHOTS_DIR)) {
255
- fs.mkdirSync(SNAPSHOTS_DIR, { recursive: true });
256
- } else {
257
- // Clear stale snapshots
258
- try {
259
- const oldSnapshots = fs.readdirSync(SNAPSHOTS_DIR);
260
- for (const os of oldSnapshots) fs.unlinkSync(path.join(SNAPSHOTS_DIR, os));
261
- } catch (e) {}
262
- }
263
-
264
- console.log('\x1b[96m✦ Generating Context Snapshots...\x1b[0m');
265
- for (const file of fileKeys) {
266
- const info = graphData[file];
267
- const snapshotFile = file.replace(/[\\/]/g, '__') + '.json';
268
- const snapshotPath = path.join(SNAPSHOTS_DIR, snapshotFile);
269
-
270
- let content = '';
271
- try {
272
- content = fs.readFileSync(path.join(process.cwd(), file), 'utf8');
273
- } catch (e) {
274
- continue;
275
- }
276
-
277
- const snapshot = {
278
- file: file,
279
- riskScore: info.riskScore,
280
- blastRadius: info.blastRadius,
281
- imports: {},
282
- dependents: info.dependents || [],
283
- content: content
284
- };
285
-
286
- for (const imp of info.imports) {
287
- if (imp.startsWith('.')) {
288
- let resolved = path.posix.join(path.dirname(file), imp);
289
- let matchingKey = fileKeys.find(k =>
290
- k === resolved || k === resolved + '.js' || k === resolved + '.ts' || k === resolved + '/index.js'
291
- );
292
- if (matchingKey && graphData[matchingKey]) {
293
- snapshot.imports[imp] = graphData[matchingKey].exports;
294
- } else {
295
- snapshot.imports[imp] = [];
296
- }
297
- } else {
298
- snapshot.imports[imp] = [];
299
- }
300
- }
301
-
302
- fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
303
- }
304
- console.log(` \x1b[2mSaved ${fileKeys.length} snapshots to: ${SNAPSHOTS_DIR}\x1b[0m`);
305
-
306
- console.log(`\n\x1b[32m✔ Graph successfully built.\x1b[0m`);
307
- console.log(` \x1b[2mParsed: ${parsedCount} files | Cached: ${cachedCount} files\x1b[0m`);
308
- console.log(` \x1b[2mSaved to: ${GRAPH_FILE}\x1b[0m`);
309
- }
310
-
311
- main();
1
+ #!/usr/bin/env node
2
+ /**
3
+ * graph_builder.js — Tribunal Kit Macro Graph Mapper
4
+ * Parses project structure for imports, exports, and dependencies
5
+ * using incremental caching and zero external dependencies.
6
+ * Now includes Blast Radius calculation and robust token stripping.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const AGENT_DIR = path.join(process.cwd(), '.agent');
15
+ const HISTORY_DIR = path.join(AGENT_DIR, 'history');
16
+ const CACHE_FILE = path.join(HISTORY_DIR, 'graph-cache.json');
17
+ const GRAPH_FILE = path.join(HISTORY_DIR, 'architecture-graph.yaml');
18
+
19
+ // ── Exclusions & Safety ───────────────────────────────────────────────────────
20
+ const DEFAULT_EXCLUSIONS = new Set([
21
+ 'node_modules', '.git', '.next', 'dist', 'build', 'coverage', '.agent', 'artifacts'
22
+ ]);
23
+
24
+ function loadGitIgnore() {
25
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
26
+ if (!fs.existsSync(gitignorePath)) return [];
27
+
28
+ return fs.readFileSync(gitignorePath, 'utf8')
29
+ .split('\n')
30
+ .map(line => line.trim())
31
+ .filter(line => line && !line.startsWith('#'))
32
+ .map(line => line.replace(/\/$/, '').replace(/^\//, ''));
33
+ }
34
+
35
+ const customExclusions = loadGitIgnore();
36
+
37
+ function isExcluded(filePath) {
38
+ const parts = filePath.split(path.sep);
39
+ if (parts.some(p => DEFAULT_EXCLUSIONS.has(p))) return true;
40
+
41
+ const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, '/');
42
+ for (const pattern of customExclusions) {
43
+ if (relativePath.includes(pattern)) return true;
44
+ }
45
+ return false;
46
+ }
47
+
48
+ // ── Traversal ─────────────────────────────────────────────────────────────────
49
+ function walkDir(dir, fileList = []) {
50
+ if (!fs.existsSync(dir) || isExcluded(dir)) return fileList;
51
+
52
+ let files;
53
+ try {
54
+ files = fs.readdirSync(dir);
55
+ } catch (_err) {
56
+ return fileList;
57
+ }
58
+
59
+ for (const file of files) {
60
+ const filePath = path.join(dir, file);
61
+ if (isExcluded(filePath)) continue;
62
+
63
+ if (fs.statSync(filePath).isDirectory()) {
64
+ walkDir(filePath, fileList);
65
+ } else {
66
+ if (/\.(js|jsx|ts|tsx|mjs|cjs)$/.test(file)) {
67
+ fileList.push(filePath);
68
+ }
69
+ }
70
+ }
71
+ return fileList;
72
+ }
73
+
74
+ // ── Regex AST Extraction ──────────────────────────────────────────────────────
75
+ function parseFile(content) {
76
+ const imports = new Set();
77
+ const exports = new Set();
78
+
79
+ // Parse from semi-cleaned content (comments removed)
80
+ // WAIT: If I stripped strings, how do I get the import path?
81
+ // The previous implementation used strings `['"]([^'"]+)['"]`.
82
+ // If I strip strings, the import path is lost!
83
+ // Let's rollback that logic or adapt it.
84
+ // Instead of stripping all strings, we should only strip strings if they are NOT following 'import ' or 'require('
85
+ // To do this simply, let's keep strings, but just be careful.
86
+ // Actually, string literals inside `require("...")` are what we want.
87
+ // So `parseFile` should probably NOT strip strings, but just use a safer regex.
88
+ // The false positive in `dependency_analyzer` was because of `const diff = "import a from 'a'"`.
89
+ // Let's use `stripStringsAndComments` but we DO NOT strip strings.
90
+ // We only strip comments.
91
+
92
+ // I'll define an inner function to just strip comments to be safe for imports.
93
+ // Let's stick to the simple `.replace` for comments for now, and rely on regex boundaries.
94
+ const semiCleanContent = content.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
95
+
96
+ const importRegex2 = /^[\s]*import(?:(?:[\w*\s{},]*)\sfrom\s+)?['"]([^'"]+)['"]/gm;
97
+ const requireRegex = /require\(['"]([^'"]+)['"]\)/g;
98
+ const dynamicImportRegex = /import\(['"]([^'"]+)['"]\)/g;
99
+
100
+ const exportRegex = /^[\s]*export\s+(?:const|let|var|function|class)\s+([a-zA-Z0-9_]+)/gm;
101
+ const moduleExportRegex = /module\.exports\s*=\s*\{([^}]+)\}/g;
102
+ const defaultExportRegex = /^[\s]*export\s+default\s+([a-zA-Z0-9_]+)/gm;
103
+
104
+ let match;
105
+ while ((match = importRegex2.exec(semiCleanContent)) !== null) imports.add(match[1]);
106
+ while ((match = requireRegex.exec(semiCleanContent)) !== null) imports.add(match[1]);
107
+ while ((match = dynamicImportRegex.exec(semiCleanContent)) !== null) imports.add(match[1]);
108
+
109
+ while ((match = exportRegex.exec(semiCleanContent)) !== null) exports.add(match[1]);
110
+ while ((match = defaultExportRegex.exec(semiCleanContent)) !== null) exports.add(match[1]);
111
+
112
+ while ((match = moduleExportRegex.exec(semiCleanContent)) !== null) {
113
+ const tokens = match[1].split(',').map(s => s.trim().split(':')[0].trim());
114
+ tokens.forEach(t => t && exports.add(t));
115
+ }
116
+
117
+ return {
118
+ imports: Array.from(imports),
119
+ exports: Array.from(exports)
120
+ };
121
+ }
122
+
123
+ // ── YAML Generation ───────────────────────────────────────────────────────────
124
+ function generateYAML(data) {
125
+ let yaml = '# Auto-generated Architecture Graph by Tribunal Kit\n';
126
+ yaml += '# DO NOT EDIT MANUALLY - Auto-updates via incremental cache\n\n';
127
+
128
+ for (const [file, info] of Object.entries(data)) {
129
+ if (info.imports.length === 0 && info.exports.length === 0 && (!info.dependents || info.dependents.length === 0)) continue;
130
+
131
+ yaml += `"${file}":\n`;
132
+ yaml += ` riskScore: "${info.riskScore || 'Low'}"\n`;
133
+ yaml += ` blastRadius: ${info.blastRadius || 0}\n`;
134
+
135
+ if (info.imports && info.imports.length > 0) {
136
+ yaml += ` imports:\n`;
137
+ info.imports.forEach(i => yaml += ` - "${i}"\n`);
138
+ }
139
+ if (info.exports && info.exports.length > 0) {
140
+ yaml += ` exports:\n`;
141
+ info.exports.forEach(e => yaml += ` - "${e}"\n`);
142
+ }
143
+ if (info.dependents && info.dependents.length > 0) {
144
+ yaml += ` dependents:\n`;
145
+ info.dependents.forEach(d => yaml += ` - "${d}"\n`);
146
+ }
147
+ }
148
+ return yaml;
149
+ }
150
+
151
+ // ── Main Execution ────────────────────────────────────────────────────────────
152
+ function main() {
153
+ if (!fs.existsSync(AGENT_DIR)) {
154
+ console.error('\x1b[31m✖ Error: .agent directory not found.\x1b[0m');
155
+ process.exit(1);
156
+ }
157
+
158
+ if (!fs.existsSync(HISTORY_DIR)) fs.mkdirSync(HISTORY_DIR, { recursive: true });
159
+
160
+ let cache = {};
161
+ if (fs.existsSync(CACHE_FILE)) {
162
+ try { cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')); } catch { /* ignore */ }
163
+ }
164
+
165
+ console.log('\x1b[96m✦ Building Architecture Graph...\x1b[0m');
166
+ const files = walkDir(process.cwd());
167
+ const graphData = {};
168
+
169
+ let parsedCount = 0;
170
+ let cachedCount = 0;
171
+
172
+ for (const file of files) {
173
+ const stat = fs.statSync(file);
174
+ const relativePath = path.relative(process.cwd(), file).replace(/\\/g, '/');
175
+
176
+ if (cache[relativePath] && cache[relativePath].mtimeMs === stat.mtimeMs) {
177
+ graphData[relativePath] = { imports: cache[relativePath].imports, exports: cache[relativePath].exports };
178
+ cachedCount++;
179
+ } else {
180
+ try {
181
+ const content = fs.readFileSync(file, 'utf8');
182
+ const parsed = parseFile(content);
183
+ graphData[relativePath] = parsed;
184
+
185
+ cache[relativePath] = {
186
+ mtimeMs: stat.mtimeMs,
187
+ imports: parsed.imports,
188
+ exports: parsed.exports
189
+ };
190
+ parsedCount++;
191
+ } catch { /* ignore */ }
192
+ }
193
+ }
194
+
195
+ // Compute Dependents
196
+ for (const [_file, info] of Object.entries(graphData)) info.dependents = [];
197
+
198
+ const fileKeys = Object.keys(graphData);
199
+ for (const [file, info] of Object.entries(graphData)) {
200
+ for (const imp of info.imports) {
201
+ if (imp.startsWith('.')) {
202
+ let resolved = path.posix.join(path.dirname(file), imp);
203
+ // Look for direct match or .js / index.js
204
+ let matchingKey = fileKeys.find(k =>
205
+ k === resolved || k === resolved + '.js' || k === resolved + '.ts' || k === resolved + '/index.js'
206
+ );
207
+ if (matchingKey) {
208
+ if (!graphData[matchingKey].dependents.includes(file)) {
209
+ graphData[matchingKey].dependents.push(file);
210
+ }
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ // Compute Risk Score
217
+ function computeRisk(file) {
218
+ const visited = new Set();
219
+ function visit(node) {
220
+ if (visited.has(node)) return;
221
+ visited.add(node);
222
+ const deps = graphData[node]?.dependents || [];
223
+ deps.forEach(visit);
224
+ }
225
+ visit(file);
226
+ const radius = visited.size - 1;
227
+ let score = 'Low';
228
+ if (radius > 10) score = 'Critical';
229
+ else if (radius >= 5) score = 'High';
230
+ else if (radius >= 2) score = 'Medium';
231
+ return { score, count: Math.max(0, radius) };
232
+ }
233
+
234
+ for (const file of fileKeys) {
235
+ const risk = computeRisk(file);
236
+ graphData[file].riskScore = risk.score;
237
+ graphData[file].blastRadius = risk.count;
238
+
239
+ // Update cache with these values so visualizer can use it
240
+ if (cache[file]) {
241
+ cache[file].dependents = graphData[file].dependents;
242
+ cache[file].riskScore = risk.score;
243
+ cache[file].blastRadius = risk.count;
244
+ }
245
+ }
246
+
247
+ fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
248
+ fs.writeFileSync(GRAPH_FILE, generateYAML(graphData));
249
+
250
+ // ── Pre-Computed Context Snapshots (Option C) ───────────────────────────
251
+ const SNAPSHOTS_DIR = path.join(HISTORY_DIR, 'snapshots');
252
+ if (!fs.existsSync(SNAPSHOTS_DIR)) {
253
+ fs.mkdirSync(SNAPSHOTS_DIR, { recursive: true });
254
+ } else {
255
+ // Clear stale snapshots
256
+ try {
257
+ const oldSnapshots = fs.readdirSync(SNAPSHOTS_DIR);
258
+ for (const f of oldSnapshots) fs.unlinkSync(path.join(SNAPSHOTS_DIR, f));
259
+ } catch { /* ignore */ }
260
+ }
261
+
262
+ console.log('\x1b[96m✦ Generating Context Snapshots...\x1b[0m');
263
+ for (const file of fileKeys) {
264
+ const info = graphData[file];
265
+ const snapshotFile = file.replace(/[\\/]/g, '__') + '.json';
266
+ const snapshotPath = path.join(SNAPSHOTS_DIR, snapshotFile);
267
+
268
+ let content = '';
269
+ try {
270
+ content = fs.readFileSync(path.join(process.cwd(), file), 'utf8');
271
+ } catch {
272
+ continue;
273
+ }
274
+
275
+ const snapshot = {
276
+ file: file,
277
+ riskScore: info.riskScore,
278
+ blastRadius: info.blastRadius,
279
+ imports: {},
280
+ dependents: info.dependents || [],
281
+ content: content
282
+ };
283
+
284
+ for (const imp of info.imports) {
285
+ if (imp.startsWith('.')) {
286
+ let resolved = path.posix.join(path.dirname(file), imp);
287
+ let matchingKey = fileKeys.find(k =>
288
+ k === resolved || k === resolved + '.js' || k === resolved + '.ts' || k === resolved + '/index.js'
289
+ );
290
+ if (matchingKey && graphData[matchingKey]) {
291
+ snapshot.imports[imp] = graphData[matchingKey].exports;
292
+ } else {
293
+ snapshot.imports[imp] = [];
294
+ }
295
+ } else {
296
+ snapshot.imports[imp] = [];
297
+ }
298
+ }
299
+
300
+ fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
301
+ }
302
+ console.log(` \x1b[2mSaved ${fileKeys.length} snapshots to: ${SNAPSHOTS_DIR}\x1b[0m`);
303
+
304
+ console.log(`\n\x1b[32m✔ Graph successfully built.\x1b[0m`);
305
+ console.log(` \x1b[2mParsed: ${parsedCount} files | Cached: ${cachedCount} files\x1b[0m`);
306
+ console.log(` \x1b[2mSaved to: ${GRAPH_FILE}\x1b[0m`);
307
+ }
308
+ // ── Exports (for testing & programmatic use) ─────────────────────────────────
309
+ module.exports = { parseFile, generateYAML, walkDir, isExcluded, main };
310
+
311
+ if (require.main === module) {
312
+ main();
313
+ }