tribunal-kit 4.3.1 → 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 (67) 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 +151 -37
  23. package/.agent/scripts/graph_visualizer.js +384 -0
  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 -0
  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 +32 -16
  56. package/.agent/skills/testing-patterns/SKILL.md +19 -2
  57. package/.agent/skills/ui-ux-pro-max/SKILL.md +480 -43
  58. package/.agent/workflows/generate.md +183 -183
  59. package/.agent/workflows/tribunal-speed.md +183 -183
  60. package/README.md +1 -1
  61. package/bin/tribunal-kit.js +134 -17
  62. package/package.json +6 -3
  63. package/scripts/changelog.js +167 -167
  64. package/scripts/sync-version.js +81 -81
  65. package/.agent/scripts/__pycache__/_colors.cpython-311.pyc +0 -0
  66. package/.agent/scripts/__pycache__/_utils.cpython-311.pyc +0 -0
  67. package/.agent/scripts/__pycache__/case_law_manager.cpython-311.pyc +0 -0
@@ -1,290 +1,290 @@
1
- #!/usr/bin/env node
2
- /**
3
- * bundle_analyzer.js — JS/TS bundle size analyzer for the Tribunal Agent Kit.
4
- *
5
- * Analyzes build output for:
6
- * - Total bundle size
7
- * - Largest files in dist/
8
- * - Suggested tree-shaking opportunities
9
- * - Bundler-specific analysis (Vite / Webpack)
10
- *
11
- * Usage:
12
- * node .agent/scripts/bundle_analyzer.js .
13
- * node .agent/scripts/bundle_analyzer.js . --build
14
- * node .agent/scripts/bundle_analyzer.js . --threshold 500
15
- */
16
-
17
- 'use strict';
18
-
19
- const fs = require('fs');
20
- const path = require('path');
21
- const { spawnSync } = require('child_process');
22
-
23
- const { RED, GREEN, YELLOW, BLUE, BOLD, RESET } = require('./colors.js');
24
-
25
- const HEAVY_PACKAGES = {
26
- "moment": "Use date-fns or dayjs instead (~2KB vs ~230KB)",
27
- "lodash": "Import specific functions: lodash/debounce instead of full lodash",
28
- "rxjs": "Import specific operators to enable tree-shaking",
29
- "aws-sdk": "Use @aws-sdk/client-* v3 modular imports",
30
- "firebase": "Use modular imports: firebase/auth, firebase/firestore",
31
- "chart.js": "Register only needed components",
32
- "three": "Import specific modules from three/examples/jsm/",
33
- "@mui/material": "Ensure babel-plugin-import or modular imports",
34
- "@mui/icons-material": "Import specific icons, never the barrel",
35
- "antd": "Use modular imports with babel-plugin-import",
36
- };
37
-
38
- function header(title) {
39
- console.log(`\n${BOLD}${BLUE}━━━ ${title} ━━━${RESET}`);
40
- }
41
-
42
- function ok(msg) {
43
- console.log(` ${GREEN}✅ ${msg}${RESET}`);
44
- }
45
-
46
- function failPrint(msg) {
47
- console.log(` ${RED}❌ ${msg}${RESET}`);
48
- }
49
-
50
- function warn(msg) {
51
- console.log(` ${YELLOW}⚠️ ${msg}${RESET}`);
52
- }
53
-
54
- function skip(msg) {
55
- console.log(` ${YELLOW}⏭️ ${msg}${RESET}`);
56
- }
57
-
58
- function formatSize(sizeBytes) {
59
- if (sizeBytes < 1024) return `${sizeBytes}B`;
60
- if (sizeBytes < 1024 * 1024) return `${(sizeBytes / 1024).toFixed(1)}KB`;
61
- return `${(sizeBytes / (1024 * 1024)).toFixed(1)}MB`;
62
- }
63
-
64
- function detectBundler(projectRoot) {
65
- const pkgPath = path.join(projectRoot, "package.json");
66
- if (!fs.existsSync(pkgPath)) return null;
67
-
68
- try {
69
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
70
- const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
71
-
72
- if (deps.vite) return "vite";
73
- if (deps.next) return "next";
74
- if (deps.webpack) return "webpack";
75
-
76
- if (fs.existsSync(path.join(projectRoot, "webpack.config.js")) ||
77
- fs.existsSync(path.join(projectRoot, "webpack.config.ts"))) {
78
- return "webpack";
79
- }
80
- } catch {}
81
-
82
- return null;
83
- }
84
-
85
- function findDistDir(projectRoot) {
86
- const candidates = ["dist", "build", ".next", "out", "public/build"];
87
- for (const c of candidates) {
88
- const d = path.join(projectRoot, c);
89
- if (fs.existsSync(d) && fs.statSync(d).isDirectory()) return d;
90
- }
91
- return null;
92
- }
93
-
94
- function analyzeDist(distDir, thresholdKb) {
95
- const files = [];
96
- let total = 0;
97
-
98
- function walkDir(dir) {
99
- const items = fs.readdirSync(dir, { withFileTypes: true });
100
- for (const item of items) {
101
- const fpath = path.join(dir, item.name);
102
- if (item.isDirectory()) {
103
- walkDir(fpath);
104
- } else {
105
- const size = fs.statSync(fpath).size;
106
- total += size;
107
- files.push([path.relative(distDir, fpath), size]);
108
- }
109
- }
110
- }
111
-
112
- try {
113
- walkDir(distDir);
114
- } catch {}
115
-
116
- files.sort((a, b) => b[1] - a[1]);
117
- return { total, files };
118
- }
119
-
120
- function checkHeavyDependencies(projectRoot) {
121
- const pkgPath = path.join(projectRoot, "package.json");
122
- if (!fs.existsSync(pkgPath)) return [];
123
-
124
- try {
125
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
126
- const deps = Object.keys(pkg.dependencies || {});
127
- const found = [];
128
-
129
- for (const [pkgName, suggestion] of Object.entries(HEAVY_PACKAGES)) {
130
- if (deps.includes(pkgName)) {
131
- found.push([pkgName, suggestion]);
132
- }
133
- }
134
- return found;
135
- } catch {
136
- return [];
137
- }
138
- }
139
-
140
- function runBuild(projectRoot) {
141
- const pkgPath = path.join(projectRoot, "package.json");
142
- if (fs.existsSync(pkgPath)) {
143
- try {
144
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
145
- if (!pkg.scripts || !pkg.scripts.build) {
146
- skip("No 'build' script found in package.json");
147
- return true;
148
- }
149
- } catch {}
150
- }
151
-
152
- try {
153
- const executable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
154
- const result = spawnSync(executable, ["run", "build"], {
155
- cwd: projectRoot,
156
- encoding: 'utf8',
157
- timeout: 300000,
158
- shell: process.platform === 'win32'
159
- });
160
-
161
- if (result.status === 0) {
162
- ok("Build completed successfully");
163
- return true;
164
- }
165
-
166
- failPrint("Build failed");
167
- if (result.error) {
168
- console.log(` Error: ${result.error.message}`);
169
- }
170
- const out = result.stdout ? result.stdout.toString() : '';
171
- const err = result.stderr ? result.stderr.toString() : '';
172
- const output = (out + "\n" + err).trim();
173
- if (output) {
174
- for (const line of output.split("\n").slice(0, 10)) {
175
- console.log(` ${line}`);
176
- }
177
- }
178
- return false;
179
- } catch (e) {
180
- failPrint(`Execution error: ${e.message}`);
181
- return false;
182
- }
183
- }
184
-
185
- function main() {
186
- const args = process.argv.slice(2);
187
-
188
- let targetPath = null;
189
- let buildFlag = false;
190
- let threshold = 250;
191
-
192
- for (let i = 0; i < args.length; i++) {
193
- if (args[i] === '--build') buildFlag = true;
194
- else if (args[i] === '--threshold' && i + 1 < args.length) {
195
- threshold = parseInt(args[++i], 10);
196
- } else if (args[i].startsWith('-')) {
197
- console.log("Usage: node bundle_analyzer.js <path> [--build] [--threshold <kb>]");
198
- process.exit(1);
199
- } else if (!targetPath) {
200
- targetPath = args[i];
201
- }
202
- }
203
-
204
- if (!targetPath) {
205
- console.log("Usage: node bundle_analyzer.js <path> [--build] [--threshold <kb>]");
206
- process.exit(1);
207
- }
208
-
209
- const projectRoot = path.resolve(targetPath);
210
- if (!fs.existsSync(projectRoot) || !fs.statSync(projectRoot).isDirectory()) {
211
- failPrint(`Directory not found: ${projectRoot}`);
212
- process.exit(1);
213
- }
214
-
215
- console.log(`${BOLD}Tribunal — bundle_analyzer.js${RESET}`);
216
- console.log(`Project: ${projectRoot}`);
217
-
218
- const bundler = detectBundler(projectRoot);
219
- if (bundler) console.log(` Bundler: ${bundler}`);
220
-
221
- if (buildFlag) {
222
- header("Building project");
223
- if (!runBuild(projectRoot)) {
224
- process.exit(1);
225
- }
226
- }
227
-
228
- const distDir = findDistDir(projectRoot);
229
- const heavy = checkHeavyDependencies(projectRoot);
230
-
231
- if (!distDir) {
232
- skip("No build output directory found (dist/, build/, .next/, out/)");
233
- skip("Run with --build to create a build first, or build manually");
234
- } else {
235
- header(`Bundle Size Analysis (${path.relative(projectRoot, distDir)}/)`);
236
- const { total, files } = analyzeDist(distDir, threshold);
237
- console.log(`\n Total bundle size: ${BOLD}${formatSize(total)}${RESET}`);
238
-
239
- const thresholdBytes = threshold * 1024;
240
- console.log(`\n ${BOLD}Top files by size:${RESET}`);
241
- let count = 0;
242
- for (const [filepath, size] of files) {
243
- if (count++ >= 10) break;
244
- const sizeStr = formatSize(size).padStart(10, ' ');
245
- if (size > thresholdBytes) {
246
- warn(`${sizeStr} ${filepath}`);
247
- } else {
248
- console.log(` ${sizeStr} ${filepath}`);
249
- }
250
- }
251
-
252
- const largeJs = files.filter(([f, s]) => (f.endsWith('.js') || f.endsWith('.mjs')) && s > thresholdBytes);
253
- if (largeJs.length > 0) {
254
- console.log(`\n ${YELLOW}${largeJs.length} JS file(s) exceed ${threshold}KB threshold${RESET}`);
255
- }
256
- }
257
-
258
- header("Dependency Weight Check");
259
- if (heavy.length > 0) {
260
- for (const [pkgName, suggestion] of heavy) {
261
- warn(`'${pkgName}' is a heavy dependency`);
262
- console.log(` → ${suggestion}`);
263
- }
264
- } else {
265
- ok("No known-heavy packages detected");
266
- }
267
-
268
- console.log(`\n${BOLD}━━━ Bundle Analysis Summary ━━━${RESET}`);
269
- if (distDir) {
270
- const { total } = analyzeDist(distDir, threshold);
271
- const sizeStr = formatSize(total);
272
- if (total > 5 * 1024 * 1024) {
273
- failPrint(`Total bundle: ${sizeStr} — consider code splitting`);
274
- } else if (total > 2 * 1024 * 1024) {
275
- warn(`Total bundle: ${sizeStr} — review for optimization opportunities`);
276
- } else {
277
- ok(`Total bundle: ${sizeStr}`);
278
- }
279
- }
280
-
281
- if (heavy.length > 0) {
282
- warn(`${heavy.length} heavy dependency suggestion(s) — see above`);
283
- } else if (distDir && heavy.length === 0) {
284
- ok("No optimization suggestions");
285
- }
286
- }
287
-
288
- if (require.main === module) {
289
- main();
290
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bundle_analyzer.js — JS/TS bundle size analyzer for the Tribunal Agent Kit.
4
+ *
5
+ * Analyzes build output for:
6
+ * - Total bundle size
7
+ * - Largest files in dist/
8
+ * - Suggested tree-shaking opportunities
9
+ * - Bundler-specific analysis (Vite / Webpack)
10
+ *
11
+ * Usage:
12
+ * node .agent/scripts/bundle_analyzer.js .
13
+ * node .agent/scripts/bundle_analyzer.js . --build
14
+ * node .agent/scripts/bundle_analyzer.js . --threshold 500
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { spawnSync } = require('child_process');
22
+
23
+ const { RED, GREEN, YELLOW, BLUE, BOLD, RESET } = require('./colors.js');
24
+
25
+ const HEAVY_PACKAGES = {
26
+ "moment": "Use date-fns or dayjs instead (~2KB vs ~230KB)",
27
+ "lodash": "Import specific functions: lodash/debounce instead of full lodash",
28
+ "rxjs": "Import specific operators to enable tree-shaking",
29
+ "aws-sdk": "Use @aws-sdk/client-* v3 modular imports",
30
+ "firebase": "Use modular imports: firebase/auth, firebase/firestore",
31
+ "chart.js": "Register only needed components",
32
+ "three": "Import specific modules from three/examples/jsm/",
33
+ "@mui/material": "Ensure babel-plugin-import or modular imports",
34
+ "@mui/icons-material": "Import specific icons, never the barrel",
35
+ "antd": "Use modular imports with babel-plugin-import",
36
+ };
37
+
38
+ function header(title) {
39
+ console.log(`\n${BOLD}${BLUE}━━━ ${title} ━━━${RESET}`);
40
+ }
41
+
42
+ function ok(msg) {
43
+ console.log(` ${GREEN}✅ ${msg}${RESET}`);
44
+ }
45
+
46
+ function failPrint(msg) {
47
+ console.log(` ${RED}❌ ${msg}${RESET}`);
48
+ }
49
+
50
+ function warn(msg) {
51
+ console.log(` ${YELLOW}⚠️ ${msg}${RESET}`);
52
+ }
53
+
54
+ function skip(msg) {
55
+ console.log(` ${YELLOW}⏭️ ${msg}${RESET}`);
56
+ }
57
+
58
+ function formatSize(sizeBytes) {
59
+ if (sizeBytes < 1024) return `${sizeBytes}B`;
60
+ if (sizeBytes < 1024 * 1024) return `${(sizeBytes / 1024).toFixed(1)}KB`;
61
+ return `${(sizeBytes / (1024 * 1024)).toFixed(1)}MB`;
62
+ }
63
+
64
+ function detectBundler(projectRoot) {
65
+ const pkgPath = path.join(projectRoot, "package.json");
66
+ if (!fs.existsSync(pkgPath)) return null;
67
+
68
+ try {
69
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
70
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
71
+
72
+ if (deps.vite) return "vite";
73
+ if (deps.next) return "next";
74
+ if (deps.webpack) return "webpack";
75
+
76
+ if (fs.existsSync(path.join(projectRoot, "webpack.config.js")) ||
77
+ fs.existsSync(path.join(projectRoot, "webpack.config.ts"))) {
78
+ return "webpack";
79
+ }
80
+ } catch {}
81
+
82
+ return null;
83
+ }
84
+
85
+ function findDistDir(projectRoot) {
86
+ const candidates = ["dist", "build", ".next", "out", "public/build"];
87
+ for (const c of candidates) {
88
+ const d = path.join(projectRoot, c);
89
+ if (fs.existsSync(d) && fs.statSync(d).isDirectory()) return d;
90
+ }
91
+ return null;
92
+ }
93
+
94
+ function analyzeDist(distDir, _thresholdKb) {
95
+ const files = [];
96
+ let total = 0;
97
+
98
+ function walkDir(dir) {
99
+ const items = fs.readdirSync(dir, { withFileTypes: true });
100
+ for (const item of items) {
101
+ const fpath = path.join(dir, item.name);
102
+ if (item.isDirectory()) {
103
+ walkDir(fpath);
104
+ } else {
105
+ const size = fs.statSync(fpath).size;
106
+ total += size;
107
+ files.push([path.relative(distDir, fpath), size]);
108
+ }
109
+ }
110
+ }
111
+
112
+ try {
113
+ walkDir(distDir);
114
+ } catch {}
115
+
116
+ files.sort((a, b) => b[1] - a[1]);
117
+ return { total, files };
118
+ }
119
+
120
+ function checkHeavyDependencies(projectRoot) {
121
+ const pkgPath = path.join(projectRoot, "package.json");
122
+ if (!fs.existsSync(pkgPath)) return [];
123
+
124
+ try {
125
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
126
+ const deps = Object.keys(pkg.dependencies || {});
127
+ const found = [];
128
+
129
+ for (const [pkgName, suggestion] of Object.entries(HEAVY_PACKAGES)) {
130
+ if (deps.includes(pkgName)) {
131
+ found.push([pkgName, suggestion]);
132
+ }
133
+ }
134
+ return found;
135
+ } catch {
136
+ return [];
137
+ }
138
+ }
139
+
140
+ function runBuild(projectRoot) {
141
+ const pkgPath = path.join(projectRoot, "package.json");
142
+ if (fs.existsSync(pkgPath)) {
143
+ try {
144
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
145
+ if (!pkg.scripts || !pkg.scripts.build) {
146
+ skip("No 'build' script found in package.json");
147
+ return true;
148
+ }
149
+ } catch {}
150
+ }
151
+
152
+ try {
153
+ const executable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
154
+ const result = spawnSync(executable, ["run", "build"], {
155
+ cwd: projectRoot,
156
+ encoding: 'utf8',
157
+ timeout: 300000,
158
+ shell: process.platform === 'win32'
159
+ });
160
+
161
+ if (result.status === 0) {
162
+ ok("Build completed successfully");
163
+ return true;
164
+ }
165
+
166
+ failPrint("Build failed");
167
+ if (result.error) {
168
+ console.log(` Error: ${result.error.message}`);
169
+ }
170
+ const out = result.stdout ? result.stdout.toString() : '';
171
+ const err = result.stderr ? result.stderr.toString() : '';
172
+ const output = (out + "\n" + err).trim();
173
+ if (output) {
174
+ for (const line of output.split("\n").slice(0, 10)) {
175
+ console.log(` ${line}`);
176
+ }
177
+ }
178
+ return false;
179
+ } catch (e) {
180
+ failPrint(`Execution error: ${e.message}`);
181
+ return false;
182
+ }
183
+ }
184
+
185
+ function main() {
186
+ const args = process.argv.slice(2);
187
+
188
+ let targetPath = null;
189
+ let buildFlag = false;
190
+ let threshold = 250;
191
+
192
+ for (let i = 0; i < args.length; i++) {
193
+ if (args[i] === '--build') buildFlag = true;
194
+ else if (args[i] === '--threshold' && i + 1 < args.length) {
195
+ threshold = parseInt(args[++i], 10);
196
+ } else if (args[i].startsWith('-')) {
197
+ console.log("Usage: node bundle_analyzer.js <path> [--build] [--threshold <kb>]");
198
+ process.exit(1);
199
+ } else if (!targetPath) {
200
+ targetPath = args[i];
201
+ }
202
+ }
203
+
204
+ if (!targetPath) {
205
+ console.log("Usage: node bundle_analyzer.js <path> [--build] [--threshold <kb>]");
206
+ process.exit(1);
207
+ }
208
+
209
+ const projectRoot = path.resolve(targetPath);
210
+ if (!fs.existsSync(projectRoot) || !fs.statSync(projectRoot).isDirectory()) {
211
+ failPrint(`Directory not found: ${projectRoot}`);
212
+ process.exit(1);
213
+ }
214
+
215
+ console.log(`${BOLD}Tribunal — bundle_analyzer.js${RESET}`);
216
+ console.log(`Project: ${projectRoot}`);
217
+
218
+ const bundler = detectBundler(projectRoot);
219
+ if (bundler) console.log(` Bundler: ${bundler}`);
220
+
221
+ if (buildFlag) {
222
+ header("Building project");
223
+ if (!runBuild(projectRoot)) {
224
+ process.exit(1);
225
+ }
226
+ }
227
+
228
+ const distDir = findDistDir(projectRoot);
229
+ const heavy = checkHeavyDependencies(projectRoot);
230
+
231
+ if (!distDir) {
232
+ skip("No build output directory found (dist/, build/, .next/, out/)");
233
+ skip("Run with --build to create a build first, or build manually");
234
+ } else {
235
+ header(`Bundle Size Analysis (${path.relative(projectRoot, distDir)}/)`);
236
+ const { total, files } = analyzeDist(distDir, threshold);
237
+ console.log(`\n Total bundle size: ${BOLD}${formatSize(total)}${RESET}`);
238
+
239
+ const thresholdBytes = threshold * 1024;
240
+ console.log(`\n ${BOLD}Top files by size:${RESET}`);
241
+ let count = 0;
242
+ for (const [filepath, size] of files) {
243
+ if (count++ >= 10) break;
244
+ const sizeStr = formatSize(size).padStart(10, ' ');
245
+ if (size > thresholdBytes) {
246
+ warn(`${sizeStr} ${filepath}`);
247
+ } else {
248
+ console.log(` ${sizeStr} ${filepath}`);
249
+ }
250
+ }
251
+
252
+ const largeJs = files.filter(([f, s]) => (f.endsWith('.js') || f.endsWith('.mjs')) && s > thresholdBytes);
253
+ if (largeJs.length > 0) {
254
+ console.log(`\n ${YELLOW}${largeJs.length} JS file(s) exceed ${threshold}KB threshold${RESET}`);
255
+ }
256
+ }
257
+
258
+ header("Dependency Weight Check");
259
+ if (heavy.length > 0) {
260
+ for (const [pkgName, suggestion] of heavy) {
261
+ warn(`'${pkgName}' is a heavy dependency`);
262
+ console.log(` → ${suggestion}`);
263
+ }
264
+ } else {
265
+ ok("No known-heavy packages detected");
266
+ }
267
+
268
+ console.log(`\n${BOLD}━━━ Bundle Analysis Summary ━━━${RESET}`);
269
+ if (distDir) {
270
+ const { total } = analyzeDist(distDir, threshold);
271
+ const sizeStr = formatSize(total);
272
+ if (total > 5 * 1024 * 1024) {
273
+ failPrint(`Total bundle: ${sizeStr} — consider code splitting`);
274
+ } else if (total > 2 * 1024 * 1024) {
275
+ warn(`Total bundle: ${sizeStr} — review for optimization opportunities`);
276
+ } else {
277
+ ok(`Total bundle: ${sizeStr}`);
278
+ }
279
+ }
280
+
281
+ if (heavy.length > 0) {
282
+ warn(`${heavy.length} heavy dependency suggestion(s) — see above`);
283
+ } else if (distDir && heavy.length === 0) {
284
+ ok("No optimization suggestions");
285
+ }
286
+ }
287
+
288
+ if (require.main === module) {
289
+ main();
290
+ }
@@ -30,7 +30,6 @@ const GREEN = '\x1b[92m';
30
30
  const YELLOW = '\x1b[93m';
31
31
  const CYAN = '\x1b[96m';
32
32
  const RED = '\x1b[91m';
33
- const BLUE = '\x1b[94m';
34
33
  const BOLD = '\x1b[1m';
35
34
  const DIM = '\x1b[2m';
36
35
  const RESET = '\x1b[0m';
@@ -48,10 +47,17 @@ function findAgentDir() {
48
47
  process.exit(1);
49
48
  }
50
49
 
51
- const AGENT_DIR = findAgentDir();
52
- const HISTORY_DIR = path.join(AGENT_DIR, 'history', 'case-law');
53
- const CASES_DIR = path.join(HISTORY_DIR, 'cases');
54
- const INDEX_FILE = path.join(HISTORY_DIR, 'index.json');
50
+ // ── Lazy path resolution (avoids side effects at require-time) ───────────────
51
+ let _paths = null;
52
+ function getPaths() {
53
+ if (_paths) return _paths;
54
+ const agentDir = findAgentDir();
55
+ const historyDir = path.join(agentDir, 'history', 'case-law');
56
+ const casesDir = path.join(historyDir, 'cases');
57
+ const indexFile = path.join(historyDir, 'index.json');
58
+ _paths = { AGENT_DIR: agentDir, HISTORY_DIR: historyDir, CASES_DIR: casesDir, INDEX_FILE: indexFile };
59
+ return _paths;
60
+ }
55
61
 
56
62
  const VALID_DOMAINS = new Set(['backend', 'frontend', 'database', 'security', 'performance', 'mobile', 'testing', 'devops', 'general']);
57
63
  const VALID_VERDICTS = new Set(['REJECTED', 'APPROVED_WITH_CONDITIONS', 'PRECEDENT_SET', 'OVERRULED']);
@@ -108,12 +114,14 @@ function contentHash(text) {
108
114
 
109
115
  // ── Index helpers ─────────────────────────────────────────────────────────────
110
116
  function ensureDirs() {
117
+ const { HISTORY_DIR, CASES_DIR } = getPaths();
111
118
  fs.mkdirSync(HISTORY_DIR, { recursive: true });
112
119
  fs.mkdirSync(CASES_DIR, { recursive: true });
113
120
  }
114
121
 
115
122
  function loadIndex() {
116
123
  ensureDirs();
124
+ const { INDEX_FILE } = getPaths();
117
125
  if (fs.existsSync(INDEX_FILE)) {
118
126
  try { return JSON.parse(fs.readFileSync(INDEX_FILE, 'utf8')); } catch { /* fallthrough */ }
119
127
  }
@@ -122,18 +130,21 @@ function loadIndex() {
122
130
 
123
131
  function saveIndex(index) {
124
132
  ensureDirs();
133
+ const { INDEX_FILE } = getPaths();
125
134
  const tmp = INDEX_FILE + '.tmp';
126
135
  fs.writeFileSync(tmp, JSON.stringify(index, null, 2), 'utf8');
127
136
  fs.renameSync(tmp, INDEX_FILE);
128
137
  }
129
138
 
130
139
  function loadCase(caseId) {
140
+ const { CASES_DIR } = getPaths();
131
141
  const p = path.join(CASES_DIR, `case-${String(caseId).padStart(4, '0')}.json`);
132
142
  if (!fs.existsSync(p)) return null;
133
143
  try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
134
144
  }
135
145
 
136
146
  function saveCase(caseRecord) {
147
+ const { CASES_DIR } = getPaths();
137
148
  const p = path.join(CASES_DIR, `case-${String(caseRecord.id).padStart(4, '0')}.json`);
138
149
  fs.writeFileSync(p, JSON.stringify(caseRecord, null, 2), 'utf8');
139
150
  }
@@ -497,7 +508,7 @@ function cmdExport(args) {
497
508
  const content = lines.join('\n');
498
509
  if (toStdout) { console.log(content); return; }
499
510
 
500
- const outPath = path.join(HISTORY_DIR, 'case-law-export.md');
511
+ const outPath = path.join(getPaths().HISTORY_DIR, 'case-law-export.md');
501
512
  fs.writeFileSync(outPath, content, 'utf8');
502
513
  console.log(`${GREEN}✔ Exported ${cases.length} cases to ${outPath}${RESET}`);
503
514
  }