tribunal-kit 4.4.0 → 4.4.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.
Files changed (90) 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/history/architecture-graph.yaml +32 -1
  10. package/.agent/history/graph-cache.json +66 -19
  11. package/.agent/history/snapshots/bin__tribunal-kit.js.json +19 -0
  12. package/.agent/history/snapshots/eslint.config.js.json +9 -0
  13. package/.agent/history/snapshots/migrate_refs.js.json +3 -3
  14. package/.agent/history/snapshots/scripts__changelog.js.json +2 -1
  15. package/.agent/history/snapshots/scripts__sync-version.js.json +2 -1
  16. package/.agent/history/snapshots/scripts__validate-payload.js.json +1 -0
  17. package/.agent/history/snapshots/test__integration__bridges.test.js.json +2 -1
  18. package/.agent/history/snapshots/test__integration__init.test.js.json +1 -0
  19. package/.agent/history/snapshots/test__integration__routing.test.js.json +1 -0
  20. package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +2 -1
  21. package/.agent/history/snapshots/test__integration__wave2.test.js.json +2 -1
  22. package/.agent/history/snapshots/test__unit__args.test.js.json +11 -1
  23. package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +1 -0
  24. package/.agent/history/snapshots/test__unit__context_broker.test.js.json +11 -0
  25. package/.agent/history/snapshots/test__unit__copyDir.test.js.json +11 -1
  26. package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +1 -0
  27. package/.agent/history/snapshots/test__unit__inner_loop_validator.test.js.json +11 -0
  28. package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +11 -1
  29. package/.agent/history/snapshots/test__unit__semver.test.js.json +11 -1
  30. package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +1 -0
  31. package/.agent/scripts/_colors.js +154 -2
  32. package/.agent/scripts/_utils.js +205 -3
  33. package/.agent/scripts/append_flow.js +72 -72
  34. package/.agent/scripts/auto_preview.js +197 -197
  35. package/.agent/scripts/bundle_analyzer.js +90 -119
  36. package/.agent/scripts/case_law_manager.js +18 -13
  37. package/.agent/scripts/checklist.js +100 -88
  38. package/.agent/scripts/colors.js +7 -13
  39. package/.agent/scripts/compress_skills.js +141 -141
  40. package/.agent/scripts/consolidate_skills.js +149 -149
  41. package/.agent/scripts/context_broker.js +605 -609
  42. package/.agent/scripts/deep_compress.js +150 -150
  43. package/.agent/scripts/dependency_analyzer.js +68 -106
  44. package/.agent/scripts/graph_builder.js +341 -311
  45. package/.agent/scripts/graph_visualizer.js +390 -384
  46. package/.agent/scripts/graph_zoom.js +6 -4
  47. package/.agent/scripts/inner_loop_validator.js +445 -465
  48. package/.agent/scripts/lint_runner.js +27 -28
  49. package/.agent/scripts/minify_context.js +100 -100
  50. package/.agent/scripts/mutation_runner.js +280 -280
  51. package/.agent/scripts/patch_skills_meta.js +156 -156
  52. package/.agent/scripts/patch_skills_output.js +244 -244
  53. package/.agent/scripts/schema_validator.js +280 -297
  54. package/.agent/scripts/security_scan.js +37 -64
  55. package/.agent/scripts/session_manager.js +270 -276
  56. package/.agent/scripts/skill_evolution.js +637 -644
  57. package/.agent/scripts/skill_integrator.js +307 -313
  58. package/.agent/scripts/strengthen_skills.js +193 -193
  59. package/.agent/scripts/strip_tribunal.js +47 -47
  60. package/.agent/scripts/swarm_dispatcher.js +360 -360
  61. package/.agent/scripts/test_runner.js +32 -39
  62. package/.agent/scripts/utils.js +10 -25
  63. package/.agent/scripts/verify_all.js +84 -92
  64. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +1 -1
  65. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
  66. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +1 -1
  67. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
  68. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +1 -1
  69. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +1 -1
  70. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +1 -1
  71. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +1 -1
  72. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +1 -1
  73. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +1 -1
  74. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +1 -1
  75. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +1 -1
  76. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +1 -1
  77. package/.agent/skills/doc.md +1 -1
  78. package/.agent/skills/knowledge-graph/SKILL.md +52 -52
  79. package/.agent/skills/ui-ux-pro-max/SKILL.md +562 -562
  80. package/.agent/workflows/generate.md +183 -183
  81. package/.agent/workflows/tribunal-speed.md +183 -183
  82. package/README.md +1 -1
  83. package/bin/tribunal-kit.js +76 -87
  84. package/package.json +6 -3
  85. package/scripts/changelog.js +167 -167
  86. package/scripts/sync-version.js +81 -81
  87. package/.agent/history/architecture-explorer.html +0 -352
  88. package/.agent/scripts/__pycache__/_colors.cpython-311.pyc +0 -0
  89. package/.agent/scripts/__pycache__/_utils.cpython-311.pyc +0 -0
  90. package/.agent/scripts/__pycache__/case_law_manager.cpython-311.pyc +0 -0
@@ -1,150 +1,150 @@
1
- #!/usr/bin/env node
2
- /**
3
- * deep_compress.js - Deep surgical compression for .agent/ markdown files (skills, agents, workflows).
4
- */
5
-
6
- 'use strict';
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
-
11
- const BASE_DIRS = ['.agent/skills', '.agent/agents', '.agent/workflows'];
12
-
13
- const REMOVE_SECTIONS = [
14
- /^## 🏛️ Tribunal Integration[\s\S]*?(?=\n## |$)/gm,
15
- /^## Tribunal Integration[\s\S]*?(?=\n## |$)/gm,
16
- /^### ✅ Pre-Flight Self-Audit[\s\S]*?(?=\n### |\n## |$)/gm,
17
- /^## Pre-Flight Self-Audit[\s\S]*?(?=\n## |$)/gm,
18
- /^## Cross-Workflow Navigation[\s\S]*?(?=\n## |$)/gm,
19
- /^## LLM Traps[\s\S]*?(?=\n## |$)/gm,
20
- /^## VBC Protocol[\s\S]*?(?=\n## |$)/gm,
21
- /^## Output Format\n```[\s\S]*?```\n/gm,
22
- /^## 🤖 LLM-Specific Traps[\s\S]*?(?=\n## |$)/gm,
23
- ];
24
-
25
- const VERBOSE_COMMENT_PATTERNS = [
26
- /^(\s*)\/\/\s*(?:Any HTML or SVG element|motion\.div, motion\.span|The MAGIC of|This is the key performance|The pattern that|Compound components share|Note that children|The action receives|Children inherit the|Import first|Parent controls when|It's always motion)\b[^\n]*\n/gm,
27
- /^(\s*)#\s*(?:TypedDict gives you|Usage:|Note:|Return user|Return None|Automatically)\b[^\n]*\n/gm,
28
- /^\s*\/\/\s*Usage:\s*\n(?=\s*[<{])/gm,
29
- /^\s*#\s*Usage:\s*\n(?=\s*[{])/gm,
30
- /^\s*\/\/\s*When (?:server responds|a component|React can interrupt|the React Compiler)[^\n]*\n/gm,
31
- ];
32
-
33
- function stripChattyOpeners(content) {
34
- return content.replace(/(^# .+\n)\n.{60,}\n.{30,}\n(?:\n---)/gm, '$1\n---');
35
- }
36
-
37
- function compressLegacyModernBlocks(content) {
38
- const pattern = /```(\w+)\n((?:.*\n)*?.*(?:\/\/|#) ❌ LEGACY[^\n]*\n(?:.*\n)*?)```\n\n```\w+\n((?:.*\n)*?.*(?:\/\/|#) ✅ MODERN[^\n]*\n(?:.*\n)*?)```/gm;
39
- return content.replace(pattern, (match, lang, legacy, modern) => {
40
- const totalLines = (legacy.match(/\n/g) || []).length + (modern.match(/\n/g) || []).length;
41
- if (totalLines > 28) return match;
42
- return `\`\`\`${lang}\n// ❌ LEGACY\n${legacy.trim()}\n\n// ✅ MODERN\n${modern.trim()}\n\`\`\``;
43
- });
44
- }
45
-
46
- function stripEmptyComments(content) {
47
- content = content.replace(/^\s*\/\/\s*$\n/gm, '');
48
- content = content.replace(/^\s*#\s*$\n/gm, '');
49
- return content;
50
- }
51
-
52
- function dedupBulletPoints(content) {
53
- const lines = content.split('\n');
54
- const seenBullets = {};
55
- const output = [];
56
- for (let i = 0; i < lines.length; i++) {
57
- const line = lines[i];
58
- const stripped = line.trim();
59
- if (/^(✅|❌|- ✅|- ❌)/.test(stripped)) {
60
- if (seenBullets[stripped] !== undefined && (i - seenBullets[stripped]) < 80) {
61
- continue;
62
- }
63
- seenBullets[stripped] = i;
64
- }
65
- output.push(line);
66
- }
67
- return output.join('\n');
68
- }
69
-
70
- function collapseBlanks(content) {
71
- return content.replace(/\n{3,}/g, '\n\n');
72
- }
73
-
74
- function compressFile(filePath) {
75
- const original = fs.readFileSync(filePath, 'utf8');
76
- let content = original;
77
-
78
- for (const pattern of REMOVE_SECTIONS) {
79
- content = content.replace(pattern, '');
80
- }
81
-
82
- content = stripChattyOpeners(content);
83
- content = compressLegacyModernBlocks(content);
84
-
85
- for (const pattern of VERBOSE_COMMENT_PATTERNS) {
86
- content = content.replace(pattern, '');
87
- }
88
-
89
- content = stripEmptyComments(content);
90
- content = dedupBulletPoints(content);
91
- content = collapseBlanks(content);
92
-
93
- if (content.trim() !== original.trim()) {
94
- fs.writeFileSync(filePath, content.trim() + '\n', 'utf8');
95
- }
96
-
97
- return [Buffer.byteLength(original, 'utf8'), Buffer.byteLength(content, 'utf8')];
98
- }
99
-
100
- function main() {
101
- let totalOrig = 0;
102
- let totalNew = 0;
103
- const fileResults = [];
104
-
105
- function walkDir(dir) {
106
- let items;
107
- try { items = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
108
- for (const item of items) {
109
- const fpath = path.join(dir, item.name);
110
- if (item.isDirectory()) walkDir(fpath);
111
- else if (item.name.endsWith('.md')) {
112
- const [orig, newL] = compressFile(fpath);
113
- totalOrig += orig;
114
- totalNew += newL;
115
- const saved = orig - newL;
116
- if (saved > 200) {
117
- fileResults.push([saved, fpath]);
118
- }
119
- }
120
- }
121
- }
122
-
123
- for (const base of BASE_DIRS) {
124
- if (fs.existsSync(base)) walkDir(base);
125
- }
126
-
127
- fileResults.sort((a, b) => b[0] - a[0]);
128
-
129
- const savedTotal = totalOrig - totalNew;
130
- const pct = totalOrig ? (savedTotal / totalOrig * 100) : 0;
131
-
132
- console.log(`\n${'='.repeat(58)}`);
133
- console.log(` Deep Compression Complete`);
134
- console.log(`${'='.repeat(58)}`);
135
- console.log(` Original : ${totalOrig} bytes (${Math.floor(totalOrig / 1024)}KB)`);
136
- console.log(` After : ${totalNew} bytes (${Math.floor(totalNew / 1024)}KB)`);
137
- console.log(` Saved : ${savedTotal} bytes (${Math.floor(savedTotal / 1024)}KB) — ${pct.toFixed(1)}%`);
138
- console.log(`\n Top savings:`);
139
-
140
- for (const [saved, filePath] of fileResults.slice(0, 20)) {
141
- const parts = filePath.split(path.sep);
142
- const skill = parts.length >= 2 ? `${parts[parts.length - 2]}/${parts[parts.length - 1]}` : filePath;
143
- console.log(` -${Math.floor(saved / 1024).toString().padStart(2, ' ')}KB ${skill}`);
144
- }
145
- console.log();
146
- }
147
-
148
- if (require.main === module) {
149
- main();
150
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * deep_compress.js - Deep surgical compression for .agent/ markdown files (skills, agents, workflows).
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const BASE_DIRS = ['.agent/skills', '.agent/agents', '.agent/workflows'];
12
+
13
+ const REMOVE_SECTIONS = [
14
+ /^## 🏛️ Tribunal Integration[\s\S]*?(?=\n## |$)/gm,
15
+ /^## Tribunal Integration[\s\S]*?(?=\n## |$)/gm,
16
+ /^### ✅ Pre-Flight Self-Audit[\s\S]*?(?=\n### |\n## |$)/gm,
17
+ /^## Pre-Flight Self-Audit[\s\S]*?(?=\n## |$)/gm,
18
+ /^## Cross-Workflow Navigation[\s\S]*?(?=\n## |$)/gm,
19
+ /^## LLM Traps[\s\S]*?(?=\n## |$)/gm,
20
+ /^## VBC Protocol[\s\S]*?(?=\n## |$)/gm,
21
+ /^## Output Format\n```[\s\S]*?```\n/gm,
22
+ /^## 🤖 LLM-Specific Traps[\s\S]*?(?=\n## |$)/gm,
23
+ ];
24
+
25
+ const VERBOSE_COMMENT_PATTERNS = [
26
+ /^(\s*)\/\/\s*(?:Any HTML or SVG element|motion\.div, motion\.span|The MAGIC of|This is the key performance|The pattern that|Compound components share|Note that children|The action receives|Children inherit the|Import first|Parent controls when|It's always motion)\b[^\n]*\n/gm,
27
+ /^(\s*)#\s*(?:TypedDict gives you|Usage:|Note:|Return user|Return None|Automatically)\b[^\n]*\n/gm,
28
+ /^\s*\/\/\s*Usage:\s*\n(?=\s*[<{])/gm,
29
+ /^\s*#\s*Usage:\s*\n(?=\s*[{])/gm,
30
+ /^\s*\/\/\s*When (?:server responds|a component|React can interrupt|the React Compiler)[^\n]*\n/gm,
31
+ ];
32
+
33
+ function stripChattyOpeners(content) {
34
+ return content.replace(/(^# .+\n)\n.{60,}\n.{30,}\n(?:\n---)/gm, '$1\n---');
35
+ }
36
+
37
+ function compressLegacyModernBlocks(content) {
38
+ const pattern = /```(\w+)\n((?:.*\n)*?.*(?:\/\/|#) ❌ LEGACY[^\n]*\n(?:.*\n)*?)```\n\n```\w+\n((?:.*\n)*?.*(?:\/\/|#) ✅ MODERN[^\n]*\n(?:.*\n)*?)```/gm;
39
+ return content.replace(pattern, (match, lang, legacy, modern) => {
40
+ const totalLines = (legacy.match(/\n/g) || []).length + (modern.match(/\n/g) || []).length;
41
+ if (totalLines > 28) return match;
42
+ return `\`\`\`${lang}\n// ❌ LEGACY\n${legacy.trim()}\n\n// ✅ MODERN\n${modern.trim()}\n\`\`\``;
43
+ });
44
+ }
45
+
46
+ function stripEmptyComments(content) {
47
+ content = content.replace(/^\s*\/\/\s*$\n/gm, '');
48
+ content = content.replace(/^\s*#\s*$\n/gm, '');
49
+ return content;
50
+ }
51
+
52
+ function dedupBulletPoints(content) {
53
+ const lines = content.split('\n');
54
+ const seenBullets = {};
55
+ const output = [];
56
+ for (let i = 0; i < lines.length; i++) {
57
+ const line = lines[i];
58
+ const stripped = line.trim();
59
+ if (/^(✅|❌|- ✅|- ❌)/.test(stripped)) {
60
+ if (seenBullets[stripped] !== undefined && (i - seenBullets[stripped]) < 80) {
61
+ continue;
62
+ }
63
+ seenBullets[stripped] = i;
64
+ }
65
+ output.push(line);
66
+ }
67
+ return output.join('\n');
68
+ }
69
+
70
+ function collapseBlanks(content) {
71
+ return content.replace(/\n{3,}/g, '\n\n');
72
+ }
73
+
74
+ function compressFile(filePath) {
75
+ const original = fs.readFileSync(filePath, 'utf8');
76
+ let content = original;
77
+
78
+ for (const pattern of REMOVE_SECTIONS) {
79
+ content = content.replace(pattern, '');
80
+ }
81
+
82
+ content = stripChattyOpeners(content);
83
+ content = compressLegacyModernBlocks(content);
84
+
85
+ for (const pattern of VERBOSE_COMMENT_PATTERNS) {
86
+ content = content.replace(pattern, '');
87
+ }
88
+
89
+ content = stripEmptyComments(content);
90
+ content = dedupBulletPoints(content);
91
+ content = collapseBlanks(content);
92
+
93
+ if (content.trim() !== original.trim()) {
94
+ fs.writeFileSync(filePath, content.trim() + '\n', 'utf8');
95
+ }
96
+
97
+ return [Buffer.byteLength(original, 'utf8'), Buffer.byteLength(content, 'utf8')];
98
+ }
99
+
100
+ function main() {
101
+ let totalOrig = 0;
102
+ let totalNew = 0;
103
+ const fileResults = [];
104
+
105
+ function walkDir(dir) {
106
+ let items;
107
+ try { items = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
108
+ for (const item of items) {
109
+ const fpath = path.join(dir, item.name);
110
+ if (item.isDirectory()) walkDir(fpath);
111
+ else if (item.name.endsWith('.md')) {
112
+ const [orig, newL] = compressFile(fpath);
113
+ totalOrig += orig;
114
+ totalNew += newL;
115
+ const saved = orig - newL;
116
+ if (saved > 200) {
117
+ fileResults.push([saved, fpath]);
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ for (const base of BASE_DIRS) {
124
+ if (fs.existsSync(base)) walkDir(base);
125
+ }
126
+
127
+ fileResults.sort((a, b) => b[0] - a[0]);
128
+
129
+ const savedTotal = totalOrig - totalNew;
130
+ const pct = totalOrig ? (savedTotal / totalOrig * 100) : 0;
131
+
132
+ console.log(`\n${'='.repeat(58)}`);
133
+ console.log(` Deep Compression Complete`);
134
+ console.log(`${'='.repeat(58)}`);
135
+ console.log(` Original : ${totalOrig} bytes (${Math.floor(totalOrig / 1024)}KB)`);
136
+ console.log(` After : ${totalNew} bytes (${Math.floor(totalNew / 1024)}KB)`);
137
+ console.log(` Saved : ${savedTotal} bytes (${Math.floor(savedTotal / 1024)}KB) — ${pct.toFixed(1)}%`);
138
+ console.log(`\n Top savings:`);
139
+
140
+ for (const [saved, filePath] of fileResults.slice(0, 20)) {
141
+ const parts = filePath.split(path.sep);
142
+ const skill = parts.length >= 2 ? `${parts[parts.length - 2]}/${parts[parts.length - 1]}` : filePath;
143
+ console.log(` -${Math.floor(saved / 1024).toString().padStart(2, ' ')}KB ${skill}`);
144
+ }
145
+ console.log();
146
+ }
147
+
148
+ if (require.main === module) {
149
+ main();
150
+ }
@@ -18,12 +18,16 @@
18
18
 
19
19
  const fs = require('fs');
20
20
  const path = require('path');
21
- const { spawnSync } = require('child_process');
22
21
 
23
- const { RED, GREEN, YELLOW, BLUE, BOLD, RESET } = require('./colors.js');
22
+ const {
23
+ RED, GREEN, YELLOW, BLUE, BOLD, DIM, CYAN, RESET,
24
+ banner, sectionHeader, timer, formatMs,
25
+ ok, fail, warn, skip,
26
+ } = require('./_colors');
27
+
28
+ const { walkDir, loadJson, runCommand } = require('./_utils');
24
29
 
25
30
  const SOURCE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
26
- const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next", ".agent", "__pycache__", "test", "tests", "__tests__"]);
27
31
 
28
32
  const NODE_BUILTINS = new Set([
29
33
  "fs", "path", "os", "crypto", "http", "https", "url", "util",
@@ -37,36 +41,6 @@ const NODE_BUILTINS = new Set([
37
41
  "node:perf_hooks", "node:worker_threads", "node:timers"
38
42
  ]);
39
43
 
40
- function header(title) {
41
- console.log(`\n${BOLD}${BLUE}━━━ ${title} ━━━${RESET}`);
42
- }
43
-
44
- function ok(msg) {
45
- console.log(` ${GREEN}✅ ${msg}${RESET}`);
46
- }
47
-
48
- function fail(msg) {
49
- console.log(` ${RED}❌ ${msg}${RESET}`);
50
- }
51
-
52
- function warn(msg) {
53
- console.log(` ${YELLOW}⚠️ ${msg}${RESET}`);
54
- }
55
-
56
- function skip(msg) {
57
- console.log(` ${YELLOW}⏭️ ${msg}${RESET}`);
58
- }
59
-
60
- function loadPackageJson(projectRoot) {
61
- const pkgPath = path.resolve(projectRoot, "package.json");
62
- if (!fs.existsSync(pkgPath)) return null;
63
- try {
64
- return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
65
- } catch {
66
- return null;
67
- }
68
- }
69
-
70
44
  function extractImports(projectRoot) {
71
45
  const imports = new Set();
72
46
  const importPatterns = [
@@ -75,42 +49,30 @@ function extractImports(projectRoot) {
75
49
  /import\s*\(\s*["']([^"'.][^"']*)["']/g
76
50
  ];
77
51
 
78
- function walk(dir) {
79
- let items;
80
- try {
81
- items = fs.readdirSync(dir, { withFileTypes: true });
82
- } catch {
83
- return;
84
- }
85
-
86
- for (const item of items) {
87
- if (item.isDirectory()) {
88
- if (!SKIP_DIRS.has(item.name)) walk(path.join(dir, item.name));
89
- } else {
90
- const ext = path.extname(item.name);
91
- if (SOURCE_EXTENSIONS.has(ext)) {
92
- try {
93
- const content = fs.readFileSync(path.join(dir, item.name), 'utf8');
94
- for (const pattern of importPatterns) {
95
- let match;
96
- while ((match = pattern.exec(content)) !== null) {
97
- let pkg = match[1];
98
- if (pkg.startsWith("@")) {
99
- const parts = pkg.split("/");
100
- pkg = parts.length >= 2 ? `${parts[0]}/${parts[1]}` : pkg;
101
- } else {
102
- pkg = pkg.split("/")[0];
103
- }
104
- imports.add(pkg);
105
- }
106
- }
107
- } catch {}
52
+ // Use shared walkDir from _utils.js
53
+ const files = walkDir(projectRoot, { extensions: SOURCE_EXTENSIONS });
54
+
55
+ for (const fullPath of files) {
56
+ let content;
57
+ try { content = fs.readFileSync(fullPath, 'utf8'); } catch { continue; }
58
+
59
+ for (const pattern of importPatterns) {
60
+ // Reset regex state for each file
61
+ pattern.lastIndex = 0;
62
+ let match;
63
+ while ((match = pattern.exec(content)) !== null) {
64
+ let pkg = match[1];
65
+ if (pkg.startsWith("@")) {
66
+ const parts = pkg.split("/");
67
+ pkg = parts.length >= 2 ? `${parts[0]}/${parts[1]}` : pkg;
68
+ } else {
69
+ pkg = pkg.split("/")[0];
108
70
  }
71
+ imports.add(pkg);
109
72
  }
110
73
  }
111
74
  }
112
75
 
113
- walk(projectRoot);
114
76
  return imports;
115
77
  }
116
78
 
@@ -151,44 +113,41 @@ function checkPhantom(pkg, usedImports) {
151
113
  }
152
114
 
153
115
  function runNpmAudit(projectRoot) {
116
+ const result = runCommand('npm', ['audit', '--json'], {
117
+ cwd: projectRoot,
118
+ timeout: 60000,
119
+ });
120
+
121
+ if (result.status === null && !result.stdout) {
122
+ skip("npm not installed — skipping audit");
123
+ return true;
124
+ }
125
+
154
126
  try {
155
- const executable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
156
- const result = spawnSync(executable, ["audit", "--json"], {
157
- cwd: projectRoot,
158
- encoding: 'utf8',
159
- timeout: 60000,
160
- shell: process.platform === 'win32'
161
- });
162
-
163
- try {
164
- const auditData = JSON.parse(result.stdout);
165
- const vulns = (auditData.metadata && auditData.metadata.vulnerabilities) || {};
166
- const critical = vulns.critical || 0;
167
- const high = vulns.high || 0;
168
- const moderate = vulns.moderate || 0;
169
- const low = vulns.low || 0;
170
-
171
- if (critical + high > 0) {
172
- fail(`npm audit: ${critical} critical, ${high} high, ${moderate} moderate, ${low} low`);
173
- return false;
174
- } else if (moderate + low > 0) {
175
- warn(`npm audit: ${moderate} moderate, ${low} low vulnerabilities`);
176
- return true;
177
- } else {
178
- ok("npm audit — no known vulnerabilities");
179
- return true;
180
- }
181
- } catch {
182
- if (result.status === 0) {
183
- ok("npm audit — clean");
184
- return true;
185
- }
186
- fail("npm audit returned errors");
127
+ const auditData = JSON.parse(result.stdout);
128
+ const vulns = (auditData.metadata && auditData.metadata.vulnerabilities) || {};
129
+ const critical = vulns.critical || 0;
130
+ const high = vulns.high || 0;
131
+ const moderate = vulns.moderate || 0;
132
+ const low = vulns.low || 0;
133
+
134
+ if (critical + high > 0) {
135
+ fail(`npm audit: ${critical} critical, ${high} high, ${moderate} moderate, ${low} low`);
187
136
  return false;
137
+ } else if (moderate + low > 0) {
138
+ warn(`npm audit: ${moderate} moderate, ${low} low vulnerabilities`);
139
+ return true;
140
+ } else {
141
+ ok("npm audit — no known vulnerabilities");
142
+ return true;
188
143
  }
189
144
  } catch {
190
- skip("npm not installed — skipping audit");
191
- return true;
145
+ if (result.ok) {
146
+ ok("npm audit — clean");
147
+ return true;
148
+ }
149
+ fail("npm audit returned errors");
150
+ return false;
192
151
  }
193
152
  }
194
153
 
@@ -220,21 +179,23 @@ function main() {
220
179
  process.exit(1);
221
180
  }
222
181
 
223
- console.log(`${BOLD}Tribunal — dependency_analyzer.js${RESET}`);
224
- console.log(`Project: ${projectRoot}`);
182
+ console.log(banner('dependency_analyzer.js', { Project: projectRoot }));
225
183
 
226
- const pkg = loadPackageJson(projectRoot);
184
+ const pkg = loadJson(path.join(projectRoot, 'package.json'));
227
185
  if (!pkg) {
228
186
  skip("No package.json found — dependency analysis requires a Node.js project");
229
187
  process.exit(0);
230
188
  }
231
189
 
232
190
  let issues = 0;
191
+ const elapsed = timer();
233
192
  const usedImports = extractImports(projectRoot);
234
- console.log(`\n Found ${usedImports.size} unique external imports in source code`);
193
+ const scanMs = elapsed();
194
+
195
+ console.log(`\n ${DIM}Found ${usedImports.size} unique external imports in ${formatMs(scanMs)}${RESET}`);
235
196
 
236
197
  if (!checkUnusedFlag) {
237
- header("Phantom Imports (not in package.json)");
198
+ console.log(sectionHeader('Phantom Imports (not in package.json)'));
238
199
  const phantom = checkPhantom(pkg, usedImports);
239
200
  if (phantom.length > 0) {
240
201
  for (const p of phantom) fail(`'${p}' is imported but not in package.json — possible hallucination`);
@@ -244,7 +205,7 @@ function main() {
244
205
  }
245
206
  }
246
207
 
247
- header("Unused Dependencies");
208
+ console.log(sectionHeader('Unused Dependencies'));
248
209
  const unused = checkUnused(pkg, usedImports);
249
210
  if (unused.length > 0) {
250
211
  for (const u of unused) warn(`'${u}' is in package.json but never imported — may be unused`);
@@ -253,16 +214,17 @@ function main() {
253
214
  }
254
215
 
255
216
  if (auditFlag) {
256
- header("Vulnerability Audit");
217
+ console.log(sectionHeader('Vulnerability Audit'));
257
218
  if (!runNpmAudit(projectRoot)) issues += 1;
258
219
  }
259
220
 
260
- console.log(`\n${BOLD}━━━ Dependency Analysis Summary ━━━${RESET}`);
221
+ console.log(`\n${BOLD}${CYAN}━━━ Dependency Analysis Summary ━━━${RESET}`);
261
222
  if (issues === 0) {
262
223
  ok("All dependency checks passed");
263
224
  } else {
264
225
  fail(`${issues} issue(s) found — review above`);
265
226
  }
227
+ console.log();
266
228
 
267
229
  process.exit(issues > 0 ? 1 : 0);
268
230
  }