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
@@ -18,9 +18,14 @@
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 { loadJson, runCommand } = require('./_utils');
24
29
 
25
30
  const HEAVY_PACKAGES = {
26
31
  "moment": "Use date-fns or dayjs instead (~2KB vs ~230KB)",
@@ -35,26 +40,6 @@ const HEAVY_PACKAGES = {
35
40
  "antd": "Use modular imports with babel-plugin-import",
36
41
  };
37
42
 
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
43
  function formatSize(sizeBytes) {
59
44
  if (sizeBytes < 1024) return `${sizeBytes}B`;
60
45
  if (sizeBytes < 1024 * 1024) return `${(sizeBytes / 1024).toFixed(1)}KB`;
@@ -62,22 +47,19 @@ function formatSize(sizeBytes) {
62
47
  }
63
48
 
64
49
  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 {}
50
+ const pkg = loadJson(path.join(projectRoot, "package.json"));
51
+ if (!pkg) return null;
52
+
53
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
54
+
55
+ if (deps.vite) return "vite";
56
+ if (deps.next) return "next";
57
+ if (deps.webpack) return "webpack";
58
+
59
+ if (fs.existsSync(path.join(projectRoot, "webpack.config.js")) ||
60
+ fs.existsSync(path.join(projectRoot, "webpack.config.ts"))) {
61
+ return "webpack";
62
+ }
81
63
 
82
64
  return null;
83
65
  }
@@ -91,16 +73,21 @@ function findDistDir(projectRoot) {
91
73
  return null;
92
74
  }
93
75
 
94
- function analyzeDist(distDir, thresholdKb) {
76
+ /**
77
+ * Analyze dist directory. Uses inline walker (not shared _utils.walkDir)
78
+ * because it needs to collect file sizes and totals in one pass.
79
+ */
80
+ function analyzeDist(distDir) {
95
81
  const files = [];
96
82
  let total = 0;
97
83
 
98
- function walkDir(dir) {
99
- const items = fs.readdirSync(dir, { withFileTypes: true });
84
+ function _walk(dir) {
85
+ let items;
86
+ try { items = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
100
87
  for (const item of items) {
101
88
  const fpath = path.join(dir, item.name);
102
89
  if (item.isDirectory()) {
103
- walkDir(fpath);
90
+ _walk(fpath);
104
91
  } else {
105
92
  const size = fs.statSync(fpath).size;
106
93
  total += size;
@@ -109,82 +96,58 @@ function analyzeDist(distDir, thresholdKb) {
109
96
  }
110
97
  }
111
98
 
112
- try {
113
- walkDir(distDir);
114
- } catch {}
115
-
99
+ _walk(distDir);
116
100
  files.sort((a, b) => b[1] - a[1]);
117
101
  return { total, files };
118
102
  }
119
103
 
120
104
  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
- }
105
+ const pkg = loadJson(path.join(projectRoot, "package.json"));
106
+ if (!pkg) return [];
107
+
108
+ const deps = Object.keys(pkg.dependencies || {});
109
+ const found = [];
110
+
111
+ for (const [pkgName, suggestion] of Object.entries(HEAVY_PACKAGES)) {
112
+ if (deps.includes(pkgName)) {
113
+ found.push([pkgName, suggestion]);
133
114
  }
134
- return found;
135
- } catch {
136
- return [];
137
115
  }
116
+ return found;
138
117
  }
139
118
 
140
119
  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 {}
120
+ const pkg = loadJson(path.join(projectRoot, "package.json"));
121
+ if (pkg && (!pkg.scripts || !pkg.scripts.build)) {
122
+ skip("No 'build' script found in package.json");
123
+ return true;
150
124
  }
151
125
 
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
- }
126
+ const elapsed = timer();
127
+ const result = runCommand('npm', ['run', 'build'], {
128
+ cwd: projectRoot,
129
+ timeout: 300000,
130
+ });
165
131
 
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
- }
132
+ const ms = elapsed();
133
+ if (result.ok) {
134
+ ok(`Build completed successfully ${DIM}(${formatMs(ms)})${RESET}`);
135
+ return true;
136
+ }
137
+
138
+ fail(`Build failed ${DIM}(${formatMs(ms)})${RESET}`);
139
+ const output = (result.stdout + "\n" + result.stderr).trim();
140
+ if (output) {
141
+ for (const line of output.split("\n").slice(0, 10)) {
142
+ console.log(` ${line}`);
177
143
  }
178
- return false;
179
- } catch (e) {
180
- failPrint(`Execution error: ${e.message}`);
181
- return false;
182
144
  }
145
+ return false;
183
146
  }
184
147
 
185
148
  function main() {
186
149
  const args = process.argv.slice(2);
187
-
150
+
188
151
  let targetPath = null;
189
152
  let buildFlag = false;
190
153
  let threshold = 250;
@@ -193,6 +156,9 @@ function main() {
193
156
  if (args[i] === '--build') buildFlag = true;
194
157
  else if (args[i] === '--threshold' && i + 1 < args.length) {
195
158
  threshold = parseInt(args[++i], 10);
159
+ } else if (args[i] === '-h' || args[i] === '--help') {
160
+ console.log("Usage: node bundle_analyzer.js <path> [--build] [--threshold <kb>]");
161
+ process.exit(0);
196
162
  } else if (args[i].startsWith('-')) {
197
163
  console.log("Usage: node bundle_analyzer.js <path> [--build] [--threshold <kb>]");
198
164
  process.exit(1);
@@ -208,18 +174,19 @@ function main() {
208
174
 
209
175
  const projectRoot = path.resolve(targetPath);
210
176
  if (!fs.existsSync(projectRoot) || !fs.statSync(projectRoot).isDirectory()) {
211
- failPrint(`Directory not found: ${projectRoot}`);
177
+ fail(`Directory not found: ${projectRoot}`);
212
178
  process.exit(1);
213
179
  }
214
180
 
215
- console.log(`${BOLD}Tribunal — bundle_analyzer.js${RESET}`);
216
- console.log(`Project: ${projectRoot}`);
217
-
218
181
  const bundler = detectBundler(projectRoot);
219
- if (bundler) console.log(` Bundler: ${bundler}`);
182
+ console.log(banner('bundle_analyzer.js', {
183
+ Project: projectRoot,
184
+ Bundler: bundler || 'auto-detect',
185
+ Threshold: `${threshold}KB`,
186
+ }));
220
187
 
221
188
  if (buildFlag) {
222
- header("Building project");
189
+ console.log(sectionHeader('Building Project'));
223
190
  if (!runBuild(projectRoot)) {
224
191
  process.exit(1);
225
192
  }
@@ -228,18 +195,21 @@ function main() {
228
195
  const distDir = findDistDir(projectRoot);
229
196
  const heavy = checkHeavyDependencies(projectRoot);
230
197
 
198
+ // PERFORMANCE FIX: Cache analyzeDist result — was called TWICE before
199
+ let distResult = null;
200
+
231
201
  if (!distDir) {
232
202
  skip("No build output directory found (dist/, build/, .next/, out/)");
233
203
  skip("Run with --build to create a build first, or build manually");
234
204
  } 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}`);
205
+ console.log(sectionHeader(`Bundle Size Analysis (${path.relative(projectRoot, distDir)}/)`));
206
+ distResult = analyzeDist(distDir);
207
+ console.log(`\n Total bundle size: ${BOLD}${formatSize(distResult.total)}${RESET}`);
238
208
 
239
209
  const thresholdBytes = threshold * 1024;
240
210
  console.log(`\n ${BOLD}Top files by size:${RESET}`);
241
211
  let count = 0;
242
- for (const [filepath, size] of files) {
212
+ for (const [filepath, size] of distResult.files) {
243
213
  if (count++ >= 10) break;
244
214
  const sizeStr = formatSize(size).padStart(10, ' ');
245
215
  if (size > thresholdBytes) {
@@ -249,13 +219,13 @@ function main() {
249
219
  }
250
220
  }
251
221
 
252
- const largeJs = files.filter(([f, s]) => (f.endsWith('.js') || f.endsWith('.mjs')) && s > thresholdBytes);
222
+ const largeJs = distResult.files.filter(([f, s]) => (f.endsWith('.js') || f.endsWith('.mjs')) && s > thresholdBytes);
253
223
  if (largeJs.length > 0) {
254
224
  console.log(`\n ${YELLOW}${largeJs.length} JS file(s) exceed ${threshold}KB threshold${RESET}`);
255
225
  }
256
226
  }
257
227
 
258
- header("Dependency Weight Check");
228
+ console.log(sectionHeader('Dependency Weight Check'));
259
229
  if (heavy.length > 0) {
260
230
  for (const [pkgName, suggestion] of heavy) {
261
231
  warn(`'${pkgName}' is a heavy dependency`);
@@ -265,13 +235,13 @@ function main() {
265
235
  ok("No known-heavy packages detected");
266
236
  }
267
237
 
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) {
238
+ // ━━━ Summary ━━━ (reuses cached distResult instead of re-scanning)
239
+ console.log(`\n${BOLD}${CYAN}━━━ Bundle Analysis Summary ━━━${RESET}`);
240
+ if (distResult) {
241
+ const sizeStr = formatSize(distResult.total);
242
+ if (distResult.total > 5 * 1024 * 1024) {
243
+ fail(`Total bundle: ${sizeStr} — consider code splitting`);
244
+ } else if (distResult.total > 2 * 1024 * 1024) {
275
245
  warn(`Total bundle: ${sizeStr} — review for optimization opportunities`);
276
246
  } else {
277
247
  ok(`Total bundle: ${sizeStr}`);
@@ -280,9 +250,10 @@ function main() {
280
250
 
281
251
  if (heavy.length > 0) {
282
252
  warn(`${heavy.length} heavy dependency suggestion(s) — see above`);
283
- } else if (distDir && heavy.length === 0) {
253
+ } else if (distResult && heavy.length === 0) {
284
254
  ok("No optimization suggestions");
285
255
  }
256
+ console.log();
286
257
  }
287
258
 
288
259
  if (require.main === module) {
@@ -26,14 +26,7 @@ const crypto = require('crypto');
26
26
  const readline = require('readline');
27
27
 
28
28
  // ── Colours ──────────────────────────────────────────────────────────────────
29
- const GREEN = '\x1b[92m';
30
- const YELLOW = '\x1b[93m';
31
- const CYAN = '\x1b[96m';
32
- const RED = '\x1b[91m';
33
- const BLUE = '\x1b[94m';
34
- const BOLD = '\x1b[1m';
35
- const DIM = '\x1b[2m';
36
- const RESET = '\x1b[0m';
29
+ const { GREEN, YELLOW, CYAN, RED, BOLD, DIM, RESET } = require('./_colors');
37
30
 
38
31
  // ── Find .agent directory ─────────────────────────────────────────────────────
39
32
  function findAgentDir() {
@@ -48,10 +41,17 @@ function findAgentDir() {
48
41
  process.exit(1);
49
42
  }
50
43
 
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');
44
+ // ── Lazy path resolution (avoids side effects at require-time) ───────────────
45
+ let _paths = null;
46
+ function getPaths() {
47
+ if (_paths) return _paths;
48
+ const agentDir = findAgentDir();
49
+ const historyDir = path.join(agentDir, 'history', 'case-law');
50
+ const casesDir = path.join(historyDir, 'cases');
51
+ const indexFile = path.join(historyDir, 'index.json');
52
+ _paths = { AGENT_DIR: agentDir, HISTORY_DIR: historyDir, CASES_DIR: casesDir, INDEX_FILE: indexFile };
53
+ return _paths;
54
+ }
55
55
 
56
56
  const VALID_DOMAINS = new Set(['backend', 'frontend', 'database', 'security', 'performance', 'mobile', 'testing', 'devops', 'general']);
57
57
  const VALID_VERDICTS = new Set(['REJECTED', 'APPROVED_WITH_CONDITIONS', 'PRECEDENT_SET', 'OVERRULED']);
@@ -108,12 +108,14 @@ function contentHash(text) {
108
108
 
109
109
  // ── Index helpers ─────────────────────────────────────────────────────────────
110
110
  function ensureDirs() {
111
+ const { HISTORY_DIR, CASES_DIR } = getPaths();
111
112
  fs.mkdirSync(HISTORY_DIR, { recursive: true });
112
113
  fs.mkdirSync(CASES_DIR, { recursive: true });
113
114
  }
114
115
 
115
116
  function loadIndex() {
116
117
  ensureDirs();
118
+ const { INDEX_FILE } = getPaths();
117
119
  if (fs.existsSync(INDEX_FILE)) {
118
120
  try { return JSON.parse(fs.readFileSync(INDEX_FILE, 'utf8')); } catch { /* fallthrough */ }
119
121
  }
@@ -122,18 +124,21 @@ function loadIndex() {
122
124
 
123
125
  function saveIndex(index) {
124
126
  ensureDirs();
127
+ const { INDEX_FILE } = getPaths();
125
128
  const tmp = INDEX_FILE + '.tmp';
126
129
  fs.writeFileSync(tmp, JSON.stringify(index, null, 2), 'utf8');
127
130
  fs.renameSync(tmp, INDEX_FILE);
128
131
  }
129
132
 
130
133
  function loadCase(caseId) {
134
+ const { CASES_DIR } = getPaths();
131
135
  const p = path.join(CASES_DIR, `case-${String(caseId).padStart(4, '0')}.json`);
132
136
  if (!fs.existsSync(p)) return null;
133
137
  try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
134
138
  }
135
139
 
136
140
  function saveCase(caseRecord) {
141
+ const { CASES_DIR } = getPaths();
137
142
  const p = path.join(CASES_DIR, `case-${String(caseRecord.id).padStart(4, '0')}.json`);
138
143
  fs.writeFileSync(p, JSON.stringify(caseRecord, null, 2), 'utf8');
139
144
  }
@@ -497,7 +502,7 @@ function cmdExport(args) {
497
502
  const content = lines.join('\n');
498
503
  if (toStdout) { console.log(content); return; }
499
504
 
500
- const outPath = path.join(HISTORY_DIR, 'case-law-export.md');
505
+ const outPath = path.join(getPaths().HISTORY_DIR, 'case-law-export.md');
501
506
  fs.writeFileSync(outPath, content, 'utf8');
502
507
  console.log(`${GREEN}✔ Exported ${cases.length} cases to ${outPath}${RESET}`);
503
508
  }