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.
- package/.agent/agents/api-architect.md +66 -66
- package/.agent/agents/db-latency-auditor.md +216 -216
- package/.agent/agents/precedence-reviewer.md +250 -250
- package/.agent/agents/resilience-reviewer.md +88 -88
- package/.agent/agents/schema-reviewer.md +67 -67
- package/.agent/agents/throughput-optimizer.md +299 -299
- package/.agent/agents/ui-ux-auditor.md +292 -292
- package/.agent/agents/vitals-reviewer.md +223 -223
- package/.agent/scripts/_colors.js +18 -18
- package/.agent/scripts/_utils.js +42 -42
- package/.agent/scripts/append_flow.js +72 -72
- package/.agent/scripts/auto_preview.js +197 -197
- package/.agent/scripts/bundle_analyzer.js +290 -290
- package/.agent/scripts/case_law_manager.js +17 -6
- package/.agent/scripts/checklist.js +266 -266
- package/.agent/scripts/colors.js +17 -17
- package/.agent/scripts/compress_skills.js +141 -141
- package/.agent/scripts/consolidate_skills.js +149 -149
- package/.agent/scripts/context_broker.js +611 -609
- package/.agent/scripts/deep_compress.js +150 -150
- package/.agent/scripts/dependency_analyzer.js +272 -272
- package/.agent/scripts/graph_builder.js +313 -311
- package/.agent/scripts/graph_visualizer.js +384 -384
- package/.agent/scripts/inner_loop_validator.js +451 -465
- package/.agent/scripts/lint_runner.js +187 -187
- package/.agent/scripts/minify_context.js +100 -100
- package/.agent/scripts/mutation_runner.js +280 -280
- package/.agent/scripts/patch_skills_meta.js +156 -156
- package/.agent/scripts/patch_skills_output.js +244 -244
- package/.agent/scripts/schema_validator.js +297 -297
- package/.agent/scripts/security_scan.js +303 -303
- package/.agent/scripts/session_manager.js +276 -276
- package/.agent/scripts/skill_evolution.js +644 -644
- package/.agent/scripts/skill_integrator.js +313 -313
- package/.agent/scripts/strengthen_skills.js +193 -193
- package/.agent/scripts/strip_tribunal.js +47 -47
- package/.agent/scripts/swarm_dispatcher.js +360 -360
- package/.agent/scripts/test_runner.js +193 -193
- package/.agent/scripts/utils.js +32 -32
- package/.agent/scripts/verify_all.js +257 -256
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +1 -1
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +1 -1
- package/.agent/skills/doc.md +1 -1
- package/.agent/skills/knowledge-graph/SKILL.md +52 -52
- package/.agent/skills/ui-ux-pro-max/SKILL.md +562 -562
- package/.agent/workflows/generate.md +183 -183
- package/.agent/workflows/tribunal-speed.md +183 -183
- package/README.md +1 -1
- package/bin/tribunal-kit.js +76 -87
- package/package.json +6 -3
- package/scripts/changelog.js +167 -167
- package/scripts/sync-version.js +81 -81
- package/.agent/history/architecture-explorer.html +0 -352
- package/.agent/history/architecture-graph.yaml +0 -109
- package/.agent/history/graph-cache.json +0 -215
- package/.agent/history/snapshots/migrate_refs.js.json +0 -11
- package/.agent/history/snapshots/scripts__changelog.js.json +0 -12
- package/.agent/history/snapshots/scripts__sync-version.js.json +0 -11
- package/.agent/history/snapshots/scripts__validate-payload.js.json +0 -11
- package/.agent/history/snapshots/test__integration__bridges.test.js.json +0 -13
- package/.agent/history/snapshots/test__integration__init.test.js.json +0 -13
- package/.agent/history/snapshots/test__integration__routing.test.js.json +0 -11
- package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +0 -13
- package/.agent/history/snapshots/test__integration__wave2.test.js.json +0 -13
- package/.agent/history/snapshots/test__unit__args.test.js.json +0 -10
- package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +0 -10
- package/.agent/history/snapshots/test__unit__copyDir.test.js.json +0 -13
- package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +0 -11
- package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +0 -13
- package/.agent/history/snapshots/test__unit__semver.test.js.json +0 -10
- package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +0 -11
- package/.agent/scripts/__pycache__/_colors.cpython-311.pyc +0 -0
- package/.agent/scripts/__pycache__/_utils.cpython-311.pyc +0 -0
- 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,
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
}
|