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.
- 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/history/architecture-graph.yaml +32 -1
- package/.agent/history/graph-cache.json +66 -19
- package/.agent/history/snapshots/bin__tribunal-kit.js.json +19 -0
- package/.agent/history/snapshots/eslint.config.js.json +9 -0
- package/.agent/history/snapshots/migrate_refs.js.json +3 -3
- package/.agent/history/snapshots/scripts__changelog.js.json +2 -1
- package/.agent/history/snapshots/scripts__sync-version.js.json +2 -1
- package/.agent/history/snapshots/scripts__validate-payload.js.json +1 -0
- package/.agent/history/snapshots/test__integration__bridges.test.js.json +2 -1
- package/.agent/history/snapshots/test__integration__init.test.js.json +1 -0
- package/.agent/history/snapshots/test__integration__routing.test.js.json +1 -0
- package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +2 -1
- package/.agent/history/snapshots/test__integration__wave2.test.js.json +2 -1
- package/.agent/history/snapshots/test__unit__args.test.js.json +11 -1
- package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +1 -0
- package/.agent/history/snapshots/test__unit__context_broker.test.js.json +11 -0
- package/.agent/history/snapshots/test__unit__copyDir.test.js.json +11 -1
- package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +1 -0
- package/.agent/history/snapshots/test__unit__inner_loop_validator.test.js.json +11 -0
- package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +11 -1
- package/.agent/history/snapshots/test__unit__semver.test.js.json +11 -1
- package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +1 -0
- package/.agent/scripts/_colors.js +154 -2
- package/.agent/scripts/_utils.js +205 -3
- package/.agent/scripts/append_flow.js +72 -72
- package/.agent/scripts/auto_preview.js +197 -197
- package/.agent/scripts/bundle_analyzer.js +90 -119
- package/.agent/scripts/case_law_manager.js +18 -13
- package/.agent/scripts/checklist.js +100 -88
- package/.agent/scripts/colors.js +7 -13
- package/.agent/scripts/compress_skills.js +141 -141
- package/.agent/scripts/consolidate_skills.js +149 -149
- package/.agent/scripts/context_broker.js +605 -609
- package/.agent/scripts/deep_compress.js +150 -150
- package/.agent/scripts/dependency_analyzer.js +68 -106
- package/.agent/scripts/graph_builder.js +341 -311
- package/.agent/scripts/graph_visualizer.js +390 -384
- package/.agent/scripts/graph_zoom.js +6 -4
- package/.agent/scripts/inner_loop_validator.js +445 -465
- package/.agent/scripts/lint_runner.js +27 -28
- 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 +280 -297
- package/.agent/scripts/security_scan.js +37 -64
- package/.agent/scripts/session_manager.js +270 -276
- package/.agent/scripts/skill_evolution.js +637 -644
- package/.agent/scripts/skill_integrator.js +307 -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 +32 -39
- package/.agent/scripts/utils.js +10 -25
- package/.agent/scripts/verify_all.js +84 -92
- 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/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,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bin/tribunal-kit.js": {
|
|
3
|
-
"mtimeMs":
|
|
3
|
+
"mtimeMs": 1777585239622.425,
|
|
4
|
+
"hash": "044b396a83a94d6d3787df1bebd6c58f6261f8b3",
|
|
4
5
|
"imports": [
|
|
5
6
|
"fs",
|
|
6
7
|
"path",
|
|
@@ -26,11 +27,20 @@
|
|
|
26
27
|
"riskScore": "Medium",
|
|
27
28
|
"blastRadius": 4
|
|
28
29
|
},
|
|
30
|
+
"eslint.config.js": {
|
|
31
|
+
"mtimeMs": 1777583796319.5537,
|
|
32
|
+
"hash": "d80785ac22194886311daa5c82c9765de8530876",
|
|
33
|
+
"imports": [],
|
|
34
|
+
"exports": [],
|
|
35
|
+
"dependents": [],
|
|
36
|
+
"riskScore": "Low",
|
|
37
|
+
"blastRadius": 0
|
|
38
|
+
},
|
|
29
39
|
"migrate_refs.js": {
|
|
30
|
-
"mtimeMs":
|
|
40
|
+
"mtimeMs": 1777584044503.6833,
|
|
41
|
+
"hash": "35b39052ff45b0b768fced3417c6245d9637ac18",
|
|
31
42
|
"imports": [
|
|
32
|
-
"fs"
|
|
33
|
-
"path"
|
|
43
|
+
"fs"
|
|
34
44
|
],
|
|
35
45
|
"exports": [],
|
|
36
46
|
"dependents": [],
|
|
@@ -38,7 +48,8 @@
|
|
|
38
48
|
"blastRadius": 0
|
|
39
49
|
},
|
|
40
50
|
"scripts/changelog.js": {
|
|
41
|
-
"mtimeMs":
|
|
51
|
+
"mtimeMs": 1777583387323.24,
|
|
52
|
+
"hash": "a078be5cb402a420593b4759afdae249cde78fdf",
|
|
42
53
|
"imports": [
|
|
43
54
|
"child_process",
|
|
44
55
|
"fs",
|
|
@@ -50,7 +61,8 @@
|
|
|
50
61
|
"blastRadius": 0
|
|
51
62
|
},
|
|
52
63
|
"scripts/sync-version.js": {
|
|
53
|
-
"mtimeMs":
|
|
64
|
+
"mtimeMs": 1777583387323.24,
|
|
65
|
+
"hash": "222ce95e5cbce0525359105d277688cfe61c7c6c",
|
|
54
66
|
"imports": [
|
|
55
67
|
"fs",
|
|
56
68
|
"path"
|
|
@@ -61,7 +73,8 @@
|
|
|
61
73
|
"blastRadius": 0
|
|
62
74
|
},
|
|
63
75
|
"scripts/validate-payload.js": {
|
|
64
|
-
"mtimeMs":
|
|
76
|
+
"mtimeMs": 1777583387324.2456,
|
|
77
|
+
"hash": "deefae3cc5c6807b56978fc47c36b85e7db03bc6",
|
|
65
78
|
"imports": [
|
|
66
79
|
"fs",
|
|
67
80
|
"path"
|
|
@@ -72,7 +85,8 @@
|
|
|
72
85
|
"blastRadius": 0
|
|
73
86
|
},
|
|
74
87
|
"test/integration/bridges.test.js": {
|
|
75
|
-
"mtimeMs":
|
|
88
|
+
"mtimeMs": 1777583387324.2456,
|
|
89
|
+
"hash": "9941a025cf46512d813660e4a09bf304f7fce64f",
|
|
76
90
|
"imports": [
|
|
77
91
|
"child_process",
|
|
78
92
|
"path",
|
|
@@ -85,7 +99,8 @@
|
|
|
85
99
|
"blastRadius": 0
|
|
86
100
|
},
|
|
87
101
|
"test/integration/init.test.js": {
|
|
88
|
-
"mtimeMs":
|
|
102
|
+
"mtimeMs": 1777583387326.36,
|
|
103
|
+
"hash": "43387fa138c7bdde2ee4d42d07ec3c82ed754275",
|
|
89
104
|
"imports": [
|
|
90
105
|
"child_process",
|
|
91
106
|
"path",
|
|
@@ -98,7 +113,8 @@
|
|
|
98
113
|
"blastRadius": 0
|
|
99
114
|
},
|
|
100
115
|
"test/integration/routing.test.js": {
|
|
101
|
-
"mtimeMs":
|
|
116
|
+
"mtimeMs": 1777583387327.369,
|
|
117
|
+
"hash": "aa8ff34289fee5be72e1d3f97083944f8f731dbd",
|
|
102
118
|
"imports": [
|
|
103
119
|
"path",
|
|
104
120
|
"fs"
|
|
@@ -109,7 +125,8 @@
|
|
|
109
125
|
"blastRadius": 0
|
|
110
126
|
},
|
|
111
127
|
"test/integration/swarm_dispatcher.test.js": {
|
|
112
|
-
"mtimeMs":
|
|
128
|
+
"mtimeMs": 1777584098830.7102,
|
|
129
|
+
"hash": "959cf59504544f624aea7ccad55fe2d9dc25ca4d",
|
|
113
130
|
"imports": [
|
|
114
131
|
"path",
|
|
115
132
|
"fs",
|
|
@@ -122,7 +139,8 @@
|
|
|
122
139
|
"blastRadius": 0
|
|
123
140
|
},
|
|
124
141
|
"test/integration/wave2.test.js": {
|
|
125
|
-
"mtimeMs":
|
|
142
|
+
"mtimeMs": 1777584104244.1404,
|
|
143
|
+
"hash": "703c635a4a0091e20785c3418b1ab294607a1635",
|
|
126
144
|
"imports": [
|
|
127
145
|
"fs",
|
|
128
146
|
"../../.agent/scripts/skill_integrator.js",
|
|
@@ -135,7 +153,8 @@
|
|
|
135
153
|
"blastRadius": 0
|
|
136
154
|
},
|
|
137
155
|
"test/unit/args.test.js": {
|
|
138
|
-
"mtimeMs":
|
|
156
|
+
"mtimeMs": 1777583387329.4229,
|
|
157
|
+
"hash": "b5863d3402957aae7315537ff9845c99899eeb50",
|
|
139
158
|
"imports": [
|
|
140
159
|
"../../bin/tribunal-kit"
|
|
141
160
|
],
|
|
@@ -145,7 +164,8 @@
|
|
|
145
164
|
"blastRadius": 0
|
|
146
165
|
},
|
|
147
166
|
"test/unit/case_law_manager.test.js": {
|
|
148
|
-
"mtimeMs":
|
|
167
|
+
"mtimeMs": 1777583387329.4229,
|
|
168
|
+
"hash": "172bdb2790ea2305d176f44b4d48826343e3fdc0",
|
|
149
169
|
"imports": [
|
|
150
170
|
"../../.agent/scripts/case_law_manager"
|
|
151
171
|
],
|
|
@@ -154,8 +174,20 @@
|
|
|
154
174
|
"riskScore": "Low",
|
|
155
175
|
"blastRadius": 0
|
|
156
176
|
},
|
|
177
|
+
"test/unit/context_broker.test.js": {
|
|
178
|
+
"mtimeMs": 1777585177453.438,
|
|
179
|
+
"hash": "5fa154b5885a89376a937bd4825b1ba5643c92f6",
|
|
180
|
+
"imports": [
|
|
181
|
+
"../../.agent/scripts/context_broker"
|
|
182
|
+
],
|
|
183
|
+
"exports": [],
|
|
184
|
+
"dependents": [],
|
|
185
|
+
"riskScore": "Low",
|
|
186
|
+
"blastRadius": 0
|
|
187
|
+
},
|
|
157
188
|
"test/unit/copyDir.test.js": {
|
|
158
|
-
"mtimeMs":
|
|
189
|
+
"mtimeMs": 1777583387330.4292,
|
|
190
|
+
"hash": "1f3111ef91650adcb45b728779000c03b9bdcb3f",
|
|
159
191
|
"imports": [
|
|
160
192
|
"fs",
|
|
161
193
|
"path",
|
|
@@ -168,7 +200,8 @@
|
|
|
168
200
|
"blastRadius": 0
|
|
169
201
|
},
|
|
170
202
|
"test/unit/graph_tools.test.js": {
|
|
171
|
-
"mtimeMs":
|
|
203
|
+
"mtimeMs": 1777583387330.4292,
|
|
204
|
+
"hash": "08b5b30f66169e34f86a9b8ad9aa842e0917f218",
|
|
172
205
|
"imports": [
|
|
173
206
|
"fs",
|
|
174
207
|
"path"
|
|
@@ -178,8 +211,20 @@
|
|
|
178
211
|
"riskScore": "Low",
|
|
179
212
|
"blastRadius": 0
|
|
180
213
|
},
|
|
214
|
+
"test/unit/inner_loop_validator.test.js": {
|
|
215
|
+
"mtimeMs": 1777585153973.2224,
|
|
216
|
+
"hash": "48487187be036a1448dcb2089cb17d01258f964f",
|
|
217
|
+
"imports": [
|
|
218
|
+
"../../.agent/scripts/inner_loop_validator"
|
|
219
|
+
],
|
|
220
|
+
"exports": [],
|
|
221
|
+
"dependents": [],
|
|
222
|
+
"riskScore": "Low",
|
|
223
|
+
"blastRadius": 0
|
|
224
|
+
},
|
|
181
225
|
"test/unit/selfInstall.test.js": {
|
|
182
|
-
"mtimeMs":
|
|
226
|
+
"mtimeMs": 1777583387331.756,
|
|
227
|
+
"hash": "65b099af8b884c6c1d174f7f70c6d836c27e02ea",
|
|
183
228
|
"imports": [
|
|
184
229
|
"path",
|
|
185
230
|
"fs",
|
|
@@ -192,7 +237,8 @@
|
|
|
192
237
|
"blastRadius": 0
|
|
193
238
|
},
|
|
194
239
|
"test/unit/semver.test.js": {
|
|
195
|
-
"mtimeMs":
|
|
240
|
+
"mtimeMs": 1777583387332.7576,
|
|
241
|
+
"hash": "d3b4e92656e66f496f034174abd53012ed00b33c",
|
|
196
242
|
"imports": [
|
|
197
243
|
"../../bin/tribunal-kit"
|
|
198
244
|
],
|
|
@@ -202,7 +248,8 @@
|
|
|
202
248
|
"blastRadius": 0
|
|
203
249
|
},
|
|
204
250
|
"test/unit/swarm_dispatcher.test.js": {
|
|
205
|
-
"mtimeMs":
|
|
251
|
+
"mtimeMs": 1777583387332.7576,
|
|
252
|
+
"hash": "59ea8d2f8e73dd3a384df1966e55002db2df0947",
|
|
206
253
|
"imports": [
|
|
207
254
|
"path",
|
|
208
255
|
"fs"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "bin/tribunal-kit.js",
|
|
3
|
+
"hash": "044b396a83a94d6d3787df1bebd6c58f6261f8b3",
|
|
4
|
+
"riskScore": "Medium",
|
|
5
|
+
"blastRadius": 4,
|
|
6
|
+
"imports": {
|
|
7
|
+
"fs": [],
|
|
8
|
+
"path": [],
|
|
9
|
+
"https": [],
|
|
10
|
+
"child_process": []
|
|
11
|
+
},
|
|
12
|
+
"dependents": [
|
|
13
|
+
"test/unit/args.test.js",
|
|
14
|
+
"test/unit/copyDir.test.js",
|
|
15
|
+
"test/unit/selfInstall.test.js",
|
|
16
|
+
"test/unit/semver.test.js"
|
|
17
|
+
],
|
|
18
|
+
"content": "#!/usr/bin/env node\r\n/**\r\n * tribunal-kit CLI (alias: tk)\r\n * \r\n * Commands:\r\n * init — Install .agent/ into target project\r\n * update — Re-install to get latest changes\r\n * status — Check if .agent/ is installed\r\n * learn — Evolve project idioms based on git diffs\r\n * case — Manage Case Law precedents\r\n * hook — Install pre-push git hook\r\n * uninstall — Remove .agent/ from project\r\n * \r\n * Usage:\r\n * npx tribunal-kit init\r\n * npx tribunal-kit init --force\r\n * npx tribunal-kit init --path ./myapp\r\n * npx tribunal-kit init --quiet\r\n * npx tribunal-kit init --dry-run\r\n * tribunal-kit update\r\n * tribunal-kit status\r\n * tribunal-kit uninstall\r\n */\r\n\r\nconst fs = require('fs');\r\nconst path = require('path');\r\nconst https = require('https');\r\nconst { execSync } = require('child_process');\r\n\r\nconst PKG = require(path.resolve(__dirname, '..', 'package.json'));\r\nconst CURRENT_VERSION = PKG.version;\r\n\r\n// ── Colors ───────────────────────────────────────────────\r\nconst C = {\r\n reset: '\\x1b[0m',\r\n bold: '\\x1b[1m',\r\n dim: '\\x1b[2m',\r\n red: '\\x1b[91m',\r\n green: '\\x1b[92m',\r\n yellow: '\\x1b[93m',\r\n blue: '\\x1b[94m',\r\n magenta: '\\x1b[95m',\r\n cyan: '\\x1b[96m',\r\n white: '\\x1b[97m',\r\n gray: '\\x1b[90m',\r\n bgCyan: '\\x1b[46m',\r\n};\r\n\r\nfunction colorize(color, text) {\r\n return `${C[color]}${text}${C.reset}`;\r\n}\r\n\r\nfunction c(color, text) { return `${C[color]}${text}${C.reset}`; }\r\nfunction bold(text) { return `${C.bold}${text}${C.reset}`; }\r\n\r\n// ── Logging ──────────────────────────────────────────────\r\nlet quiet = false;\r\nlet verbose = false;\r\n\r\nfunction log(msg) { if (!quiet) console.log(msg); }\r\nfunction ok(msg) { if (!quiet) console.log(` ${c('green', '✔')} ${msg}`); }\r\nfunction warn(msg) { if (!quiet) console.log(` ${c('yellow', '⚠')} ${msg}`); }\r\nfunction err(msg) { console.error(` ${c('red', '✖')} ${msg}`); }\r\nfunction dim(msg) { if (!quiet) console.log(` ${c('gray', msg)}`); }\r\nfunction dbg(msg) { if (verbose) console.log(` ${c('gray', '⊡')} ${c('gray', msg)}`); }\r\n\r\n// ── Arg Parser ───────────────────────────────────────────\r\nfunction parseArgs(argv) {\r\n const args = { command: null, flags: {} };\r\n const raw = argv.slice(2);\r\n\r\n // First non-flag arg is the command\r\n for (const arg of raw) {\r\n if (!arg.startsWith('--') && !args.command) {\r\n args.command = arg;\r\n continue;\r\n }\r\n if (arg === '--force') { args.flags.force = true; continue; }\r\n if (arg === '--quiet') { args.flags.quiet = true; continue; }\r\n if (arg === '--verbose') { args.flags.verbose = true; continue; }\r\n if (arg === '--dry-run') { args.flags.dryRun = true; continue; }\r\n if (arg === '--minimal') { args.flags.minimal = true; continue; }\r\n if (arg === '--skip-update-check') { args.flags.skipUpdateCheck = true; continue; }\r\n if (arg === '--head') { args.flags.head = true; continue; }\r\n if (arg.startsWith('--path=')) {\r\n args.flags.path = arg.split('=').slice(1).join('=');\r\n }\r\n if (arg === '--path') {\r\n const idx = raw.indexOf('--path');\r\n const nextVal = raw[idx + 1];\r\n if (!nextVal || nextVal.startsWith('--')) {\r\n console.error(` \\x1b[91m✖ --path requires a directory argument\\x1b[0m`);\r\n process.exit(1);\r\n }\r\n args.flags.path = nextVal;\r\n }\r\n if (arg.startsWith('--branch=')) {\r\n args.flags.branch = arg.split('=').slice(1).join('=');\r\n }\r\n }\r\n\r\n return args;\r\n}\r\n\r\n// ── File Utilities ────────────────────────────────────────\r\n\r\n// Core agents to install in --minimal mode\r\nconst CORE_AGENTS = new Set([\r\n 'backend-specialist.md',\r\n 'frontend-specialist.md',\r\n 'database-architect.md',\r\n 'debugger.md',\r\n 'security-auditor.md',\r\n 'logic-reviewer.md',\r\n 'dependency-reviewer.md',\r\n 'type-safety-reviewer.md',\r\n 'performance-reviewer.md',\r\n 'orchestrator.md',\r\n 'explorer-agent.md',\r\n 'project-planner.md',\r\n 'test-engineer.md',\r\n]);\r\n\r\n// Core skills to install in --minimal mode\r\nconst CORE_SKILLS = new Set([\r\n 'clean-code', 'architecture', 'testing-patterns', 'systematic-debugging',\r\n 'frontend-design', 'database-design', 'api-patterns', 'nodejs-best-practices',\r\n 'vulnerability-scanner', 'typescript-advanced', 'python-pro', 'nextjs-react-expert',\r\n 'react-specialist', 'performance-profiling', 'lint-and-validate',\r\n]);\r\n\r\nfunction copyDir(src, dest, dryRun = false, filter = null) {\r\n if (!dryRun) {\r\n fs.mkdirSync(dest, { recursive: true });\r\n }\r\n\r\n const entries = fs.readdirSync(src, { withFileTypes: true });\r\n let count = 0;\r\n\r\n for (const entry of entries) {\r\n // Apply filter if provided (for --minimal mode)\r\n if (filter && !filter(entry.name, src)) {\r\n dbg(` skip: ${entry.name}`);\r\n continue;\r\n }\r\n\r\n const srcPath = path.join(src, entry.name);\r\n const destPath = path.join(dest, entry.name);\r\n\r\n if (entry.isDirectory()) {\r\n count += copyDir(srcPath, destPath, dryRun, filter);\r\n } else {\r\n if (!dryRun) {\r\n fs.cpSync(srcPath, destPath, { force: true });\r\n }\r\n dbg(` copy: ${entry.name}`);\r\n count++;\r\n }\r\n }\r\n\r\n return count;\r\n}\r\n\r\nfunction countDir(dir) {\r\n let count = 0;\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n for (const e of entries) {\r\n if (e.isDirectory()) count += countDir(path.join(dir, e.name));\r\n else count++;\r\n }\r\n return count;\r\n}\r\n\r\n// ── Version Check & Auto-Update ──────────────────────────\r\n\r\n/**\r\n * Compare two semver strings. Returns:\r\n * 1 if a > b, -1 if a < b, 0 if equal.\r\n */\r\nfunction compareSemver(a, b) {\r\n const pa = a.replace(/^v/, '').split('.').map(Number);\r\n const pb = b.replace(/^v/, '').split('.').map(Number);\r\n for (let i = 0; i < 3; i++) {\r\n const na = pa[i] || 0;\r\n const nb = pb[i] || 0;\r\n if (na > nb) return 1;\r\n if (na < nb) return -1;\r\n }\r\n return 0;\r\n}\r\n\r\n/**\r\n * Fetch the latest version from npm registry.\r\n * Returns the version string (e.g. '4.0.0') or null on failure.\r\n */\r\nfunction fetchLatestVersion() {\r\n return new Promise((resolve) => {\r\n const req = https.get(\r\n 'https://registry.npmjs.org/tribunal-kit/latest',\r\n {\r\n headers: {\r\n 'Accept': 'application/json',\r\n 'User-Agent': `tribunal-kit/${CURRENT_VERSION}`\r\n },\r\n timeout: 5000\r\n },\r\n (res) => {\r\n let data = '';\r\n res.on('data', (chunk) => { data += chunk; });\r\n res.on('end', () => {\r\n try {\r\n const json = JSON.parse(data);\r\n const version = json.version || null;\r\n resolve(version);\r\n } catch {\r\n resolve(null);\r\n }\r\n });\r\n }\r\n );\r\n req.on('error', () => resolve(null));\r\n req.on('timeout', () => { req.destroy(); resolve(null); });\r\n });\r\n}\r\n\r\n/**\r\n * Check for a newer version and re-invoke with @latest if found.\r\n * Uses TK_SKIP_UPDATE_CHECK env var as recursion guard.\r\n * Returns true if a re-invoke happened (caller should exit), false otherwise.\r\n */\r\nasync function autoUpdateCheck(originalArgs) {\r\n // Recursion guard: if we're already a re-invoked process, skip\r\n if (process.env.TK_SKIP_UPDATE_CHECK === '1') {\r\n return false;\r\n }\r\n\r\n const latestVersion = await fetchLatestVersion();\r\n\r\n if (!latestVersion) {\r\n // Network fail — proceed silently with current version\r\n return false;\r\n }\r\n\r\n if (compareSemver(latestVersion, CURRENT_VERSION) <= 0) {\r\n // Already up to date\r\n dim(`Version ${CURRENT_VERSION} is up to date.`);\r\n return false;\r\n }\r\n\r\n // Newer version available — re-invoke\r\n log('');\r\n log(colorize('cyan', ` ⬆ New version available: ${colorize('bold', CURRENT_VERSION)} → ${colorize('bold', latestVersion)}`));\r\n log(colorize('gray', ' Re-invoking with latest version...'));\r\n log('');\r\n\r\n try {\r\n // Build the command pulling from npm registry\r\n const args = originalArgs.join(' ');\r\n const cmd = `npx -y tribunal-kit@${latestVersion} ${args}`;\r\n\r\n execSync(cmd, {\r\n stdio: 'inherit',\r\n env: { ...process.env, TK_SKIP_UPDATE_CHECK: '1' },\r\n });\r\n return true; // Re-invoke succeeded, caller should exit\r\n } catch (e) {\r\n warn(`Auto-update failed: ${e.message}`);\r\n warn('Continuing with current version...');\r\n return false; // Fall through to current version\r\n }\r\n}\r\n\r\n// ── Kit Source Location ───────────────────────────────────\r\nfunction getKitAgent() {\r\n // When installed via npm, the .agent/ folder is next to this script's package\r\n const kitRoot = path.resolve(__dirname, '..');\r\n const agentDir = path.join(kitRoot, '.agent');\r\n\r\n if (!fs.existsSync(agentDir)) {\r\n err(`Kit .agent/ folder not found at: ${agentDir}`);\r\n err('The package may be corrupted. Try: npm install -g tribunal-kit');\r\n process.exit(1);\r\n }\r\n\r\n return agentDir;\r\n}\r\n\r\n// ── Self-Install Guard ────────────────────────────────────\r\n/**\r\n * Returns true if the target directory IS the tribunal-kit package itself.\r\n * This prevents `init --force` / `update` from deleting the package's own files\r\n * when run from inside the project directory.\r\n */\r\nfunction isSelfInstall(targetDir) {\r\n const kitRoot = path.resolve(__dirname, '..');\r\n const resolvedTarget = path.resolve(targetDir);\r\n\r\n // Direct path match\r\n if (resolvedTarget === kitRoot) return true;\r\n\r\n // Check if the target's package.json is this package\r\n const targetPkg = path.join(resolvedTarget, 'package.json');\r\n if (fs.existsSync(targetPkg)) {\r\n try {\r\n const targetName = JSON.parse(fs.readFileSync(targetPkg, 'utf8')).name;\r\n if (targetName === PKG.name) return true;\r\n } catch {\r\n // Unreadable package.json — not a match\r\n }\r\n }\r\n\r\n return false;\r\n}\r\n\r\n// ── Banner ────────────────────────────────────────────────\r\nfunction banner() {\r\n if (quiet) return;\r\n // Big ASCII art (TRIBUNAL-KIT)\r\n const art = String.raw`\r\n████████╗██████╗ ██╗██████╗ ██╗ ██╗███╗ ██╗ █████╗ ██╗ ██╗ ██╗██╗████████╗\r\n╚══██╔══╝██╔══██╗██║██╔══██╗██║ ██║████╗ ██║██╔══██╗██║ ██║ ██╔╝██║╚══██╔══╝\r\n ██║ ██████╔╝██║██████╔╝██║ ██║██╔██╗ ██║███████║██║█████╗█████╔╝ ██║ ██║ \r\n ██║ ██╔══██╗██║██╔══██╗██║ ██║██║╚██╗██║██╔══██║██║╚════╝██╔═██╗ ██║ ██║ \r\n ██║ ██║ ██║██║██████╔╝╚██████╔╝██║ ╚████║██║ ██║███████╗ ██║ ██╗██║ ██║ \r\n ╚═╝ ╚═╝ ╚═╝╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ `.split('\\n').filter(Boolean);\r\n console.log();\r\n const _maxLen = Math.max(...art.map(line => line.length));\r\n for (const line of art) {\r\n let gradientLine = ' ' + C.bold;\r\n for (let i = 0; i < line.length; i++) {\r\n gradientLine += `\\x1b[38;2;255;22;55m${line[i]}`;\r\n }\r\n gradientLine += C.reset;\r\n log(gradientLine);\r\n }\r\n console.log();\r\n // Subtitle strip\r\n const W = 84;\r\n const sub = 'Anti-Hallucination Agent System';\r\n const sp = Math.max(0, W - sub.length);\r\n const centred = ' '.repeat(Math.floor(sp / 2)) + sub + ' '.repeat(Math.ceil(sp / 2));\r\n const RED_ANSI = '\\x1b[38;2;255;22;55m';\r\n console.log(` ${RED_ANSI}╔${'═'.repeat(W)}╗${C.reset}`);\r\n console.log(` ${RED_ANSI}║${C.reset}${c('gray', centred)}${RED_ANSI}║${C.reset}`);\r\n console.log(` ${RED_ANSI}╚${'═'.repeat(W)}╝${C.reset}`);\r\n console.log();\r\n}\r\n\r\n// ── Commands ──────────────────────────────────────────────\r\nfunction cmdInit(flags) {\r\n const agentSrc = getKitAgent();\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n const dryRun = flags.dryRun || false;\r\n\r\n // ── Self-install guard ──────────────────────────────────\r\n if (isSelfInstall(targetDir)) {\r\n err('Cannot run init/update inside the tribunal-kit package itself.');\r\n err(`Target: ${targetDir}`);\r\n err(`Package: ${path.resolve(__dirname, '..')}`);\r\n console.log();\r\n dim('This command is designed to install .agent/ into OTHER projects.');\r\n dim('Run it from the root of the project you want to set up:');\r\n dim(' cd /path/to/your-project');\r\n dim(' npx tribunal-kit init');\r\n console.log();\r\n process.exit(1);\r\n }\r\n // ────────────────────────────────────────────────────────\r\n\r\n // ── Backup / Cleanup ────────────────────────────────────\r\n if (!dryRun && fs.existsSync(agentDest) && flags.force) {\r\n // Backup the existing subdirectories before overwriting\r\n const backupDir = path.join(agentDest, '.backups', `backup-${Date.now()}`);\r\n fs.mkdirSync(backupDir, { recursive: true });\r\n\r\n // PRESERVE_DIRS: user-generated content that must survive updates\r\n const _PRESERVE_DIRS = ['history', 'patterns', 'mcp_config.json'];\r\n const subdirs = ['agents', 'workflows', 'skills', 'scripts', '.shared', 'rules'];\r\n for (const sub of subdirs) {\r\n const subPath = path.join(agentDest, sub);\r\n if (fs.existsSync(subPath)) {\r\n // Copy to backup dir\r\n copyDir(subPath, path.join(backupDir, sub), false);\r\n fs.rmSync(subPath, { recursive: true, force: true });\r\n }\r\n }\r\n log(` ${c('gray', '✦ Backed up existing configurations to .agent/.backups/')}`);\r\n\r\n\r\n }\r\n // ────────────────────────────────────────────────────────\r\n\r\n banner();\r\n\r\n if (dryRun) {\r\n log(colorize('yellow', ' DRY RUN — no files will be written'));\r\n console.log();\r\n }\r\n\r\n // Check target exists\r\n if (!fs.existsSync(targetDir)) {\r\n err(`Target directory not found: ${targetDir}`);\r\n process.exit(1);\r\n }\r\n\r\n // Check if .agent already exists\r\n if (fs.existsSync(agentDest) && !flags.force) {\r\n warn('.agent/ already exists in this project.');\r\n log(` ${c('gray', '▸')} To refresh or update it, run: ${colorize('white', 'tribunal-kit init --force')}`);\r\n log(` ${c('gray', '▸')} Or check status with: ${colorize('cyan', 'tribunal-kit status')}`);\r\n console.log();\r\n process.exit(0);\r\n }\r\n\r\n // Ensure history dirs exist (Case Law + Skill Evolution)\r\n if (!dryRun) {\r\n const caseDir = path.join(agentDest, 'history', 'case-law', 'cases');\r\n const evoDir = path.join(agentDest, 'history', 'skill-evolution');\r\n fs.mkdirSync(caseDir, { recursive: true });\r\n fs.mkdirSync(evoDir, { recursive: true });\r\n const gkCase = path.join(caseDir, '.gitkeep');\r\n const gkEvo = path.join(evoDir, '.gitkeep');\r\n if (!fs.existsSync(gkCase)) fs.writeFileSync(gkCase, '');\r\n if (!fs.existsSync(gkEvo)) fs.writeFileSync(gkEvo, '');\r\n }\r\n\r\n // Count what we're installing\r\n const isMinimal = flags.minimal || false;\r\n if (isMinimal) {\r\n log(` ${c('yellow','⚡')} ${bold('Minimal mode')} — installing core agents and skills only`);\r\n console.log();\r\n }\r\n const totalFiles = countDir(agentSrc);\r\n dbg(`Source: ${agentSrc}`);\r\n dbg(`Target: ${agentDest}`);\r\n dbg(`Total source files: ${totalFiles}`);\r\n log(` ${c('gray','▸')} Scanning ${c('white', String(totalFiles))} files ${c('gray','→')} ${c('gray', agentDest)}`);\r\n\r\n try {\r\n // Build filter for --minimal mode\r\n const minimalFilter = isMinimal ? (name, parentDir) => {\r\n const parentName = path.basename(parentDir);\r\n if (parentName === 'agents') return CORE_AGENTS.has(name);\r\n if (parentName === 'skills') return CORE_SKILLS.has(name);\r\n return true; // everything else passes\r\n } : null;\r\n\r\n const copied = copyDir(agentSrc, agentDest, dryRun, minimalFilter);\r\n\r\n console.log();\r\n if (dryRun) {\r\n ok(`${bold('DRY RUN')} complete — would install ${c('cyan', String(copied))} files`);\r\n dim(`Target: ${agentDest}`);\r\n } else {\r\n // ── Success card — W=62, rows padded by plain-text length ──\r\n const W = 62;\r\n const agentsCount = fs.readdirSync(path.join(agentDest, 'agents')).length;\r\n const workflowsCount = fs.readdirSync(path.join(agentDest, 'workflows')).length;\r\n const skillsCount = fs.readdirSync(path.join(agentDest, 'skills')).length;\r\n const scriptsCount = fs.readdirSync(path.join(agentDest, 'scripts')).length;\r\n\r\n // Stat rows: compute trailing spaces from plain text so right ║ aligns\r\n const statRow = (icon, label, val, col) => {\r\n // emoji JS .length===2 == terminal display width 2 ✓\r\n const plain = ` ${icon} ${label.padEnd(10)}${String(val).padStart(3)} installed`;\r\n const trail = ' '.repeat(Math.max(0, W - plain.length));\r\n return ` ${c('cyan','║')} ${icon} ${c('white',label.padEnd(10))}${c(col,String(val).padStart(3))} ${c('gray','installed')}${trail}${c('cyan','║')}`;\r\n };\r\n // Plain-text rows (header / blank)\r\n const plainRow = (text, wrapFn) => {\r\n const trail = ' '.repeat(Math.max(0, W - text.length));\r\n return ` ${c('cyan','║')}${wrapFn(text)}${trail}${c('cyan','║')}`;\r\n };\r\n // Next-step rows: fixed cmd column + description\r\n const stepRow = (cmd, desc) => {\r\n const plain = ` ${cmd.padEnd(16)}${desc}`;\r\n const trail = ' '.repeat(Math.max(0, W - plain.length));\r\n return ` ${c('cyan','║')} ${c('white',cmd.padEnd(16))}${c('gray',desc)}${trail}${c('cyan','║')}`;\r\n };\r\n\r\n console.log(` ${c('green','✔')} ${bold(c('green','Installation complete'))} ${c('gray','—')} ${c('white',String(copied))} files`);\r\n console.log(` ${c('gray',' ╰─')} ${c('gray', agentDest)}`);\r\n console.log();\r\n console.log(` ${c('cyan', '╔' + '═'.repeat(W) + '╗')}`);\r\n console.log(plainRow(` What's inside:`, s => c('bold', c('white', s))));\r\n console.log(` ${c('cyan', '╠' + '═'.repeat(W) + '╣')}`);\r\n console.log(statRow('🤖', 'Agents', agentsCount, 'magenta'));\r\n console.log(statRow('⚡', 'Workflows', workflowsCount, 'yellow'));\r\n console.log(statRow('🧠', 'Skills', skillsCount, 'blue'));\r\n console.log(statRow('🔧', 'Scripts', scriptsCount, 'green'));\r\n console.log(` ${c('cyan', '╠' + '═'.repeat(W) + '╣')}`);\r\n console.log(plainRow('', () => ''));\r\n console.log(plainRow(` Next steps:`, s => c('gray', s)));\r\n console.log(stepRow('/generate', 'Generate code with anti-hallucination'));\r\n console.log(stepRow('/review', 'Audit existing code for issues'));\r\n console.log(stepRow('/tribunal-full', 'Run all 16 reviewers in parallel'));\r\n console.log(plainRow('', () => ''));\r\n console.log(` ${c('cyan', '╚' + '═'.repeat(W) + '╝')}`);\r\n console.log();\r\n log(` ${c('gray', '✦ Generating IDE bridge files...')}`);\r\n generateIDEBridges(targetDir, agentDest, dryRun);\r\n }\r\n\r\n console.log();\r\n } catch (e) {\r\n err(`Failed to install: ${e.message}`);\r\n process.exit(1);\r\n }\r\n}\r\n\r\n// ── IDE Bridge Files ──────────────────────────────────────\r\n// Each AI IDE reads rules from a different location.\r\n// We generate bridge files that point each IDE at .agent/\r\nfunction generateIDEBridges(targetDir, agentDest, dryRun = false) {\r\n const rulesFile = path.join(agentDest, 'rules', 'GEMINI.md');\r\n let rulesContent = '';\r\n if (fs.existsSync(rulesFile)) {\r\n rulesContent = fs.readFileSync(rulesFile, 'utf8');\r\n }\r\n\r\n // Helper: write a bridge file only if it doesn't already exist\r\n const writeBridge = (filePath, content, label) => {\r\n if (dryRun) {\r\n dbg(` would create: ${filePath}`);\r\n return;\r\n }\r\n const dir = path.dirname(filePath);\r\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\r\n if (fs.existsSync(filePath)) {\r\n dbg(` skip (exists): ${path.basename(filePath)}`);\r\n return;\r\n }\r\n fs.writeFileSync(filePath, content, 'utf8');\r\n ok(`${label} → ${c('gray', path.relative(targetDir, filePath))}`);\r\n };\r\n\r\n // ── 1. Cursor (.cursorrules) ──────────────────────────\r\n const cursorRules = `# Tribunal Kit — Cursor Bridge\r\n# Auto-generated by tribunal-kit init. Do not edit manually.\r\n# Source: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.cursorrules'),\r\n cursorRules,\r\n 'Cursor'\r\n );\r\n\r\n // ── 2. Windsurf (.windsurfrules) ─────────────────────\r\n const windsurfRules = `# Tribunal Kit — Windsurf Bridge\r\n# Auto-generated by tribunal-kit init. Do not edit manually.\r\n# Source: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.windsurfrules'),\r\n windsurfRules,\r\n 'Windsurf'\r\n );\r\n\r\n // ── 3. Gemini / Antigravity (.gemini/settings.json) ──\r\n const geminiSettings = JSON.stringify({\r\n \"$schema\": \"https://raw.githubusercontent.com/anthropics/anthropic-cookbook/main/.gemini/settings.schema.json\",\r\n \"rules\": [\r\n { \"path\": \"../.agent/rules/GEMINI.md\", \"trigger\": \"always_on\" }\r\n ],\r\n \"agents\": { \"directory\": \"../.agent/agents\" },\r\n \"skills\": { \"directory\": \"../.agent/skills\" },\r\n \"workflows\": { \"directory\": \"../.agent/workflows\" }\r\n }, null, 2) + '\\n';\r\n writeBridge(\r\n path.join(targetDir, '.gemini', 'settings.json'),\r\n geminiSettings,\r\n 'Gemini/Antigravity'\r\n );\r\n\r\n // ── Also create .gemini/GEMINI.md as a direct rules file ──\r\n const geminiRulesBridge = `---\r\ntrigger: always_on\r\n---\r\n\r\n# Tribunal Kit — Gemini Bridge\r\n# Auto-generated by tribunal-kit init.\r\n# Full rules: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.gemini', 'GEMINI.md'),\r\n geminiRulesBridge,\r\n 'Gemini rules'\r\n );\r\n\r\n // ── 4. GitHub Copilot (.github/copilot-instructions.md) ──\r\n const copilotInstructions = `# Tribunal Kit — Copilot Bridge\r\n# Auto-generated by tribunal-kit init. Do not edit manually.\r\n# Source: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.github', 'copilot-instructions.md'),\r\n copilotInstructions,\r\n 'GitHub Copilot'\r\n );\r\n\r\n // ── 5. Claude (.claude/CLAUDE.md) ─────────────────────\r\n const claudeRules = `# Tribunal Kit — Claude Bridge\r\n# Auto-generated by tribunal-kit init. Do not edit manually.\r\n# Source: .agent/rules/GEMINI.md\r\n\r\n${rulesContent}\r\n`;\r\n writeBridge(\r\n path.join(targetDir, '.claude', 'CLAUDE.md'),\r\n claudeRules,\r\n 'Claude'\r\n );\r\n\r\n console.log();\r\n}\r\n\r\nfunction cmdUpdate(flags) {\r\n // ── Self-install guard (early, before banner) ───────────\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n if (isSelfInstall(targetDir)) {\r\n err('Cannot run update inside the tribunal-kit package itself.');\r\n err(`Target: ${targetDir}`);\r\n console.log();\r\n dim('This command is designed to update .agent/ in OTHER projects.');\r\n dim('Run it from the root of the project you want to update:');\r\n dim(' cd /path/to/your-project');\r\n dim(' npx tribunal-kit update');\r\n console.log();\r\n process.exit(1);\r\n }\r\n // ────────────────────────────────────────────────────────\r\n\r\n // Update = init with --force\r\n flags.force = true;\r\n if (!quiet) {\r\n log(` ${c('cyan','↻')} ${bold('Updating')} ${c('white','.agent/')} to latest version...`);\r\n console.log();\r\n }\r\n cmdInit(flags);\r\n}\r\n\r\n\r\nfunction cmdLearn(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n banner();\r\n\r\n const W = 62;\r\n const title = ' Tribunal Learn — Supreme Court Mode';\r\n const trail = ' '.repeat(Math.max(0, W - title.length));\r\n console.log(` ${c('cyan', '\\u2554' + '\\u2550'.repeat(W) + '\\u2557')}`);\r\n console.log(` ${c('cyan', '\\u2551')}${c('bold', c('white', title))}${trail}${c('cyan', '\\u2551')}`);\r\n console.log(` ${c('cyan', '\\u255a' + '\\u2550'.repeat(W) + '\\u255d')}`);\r\n console.log();\r\n\r\n const dryRun = flags.dryRun ? '--dry-run' : '';\r\n const useHead = flags.head ? '--head' : '';\r\n\r\n\r\n // Phase 1: Skill Evolution\r\n log(` ${c('cyan', '\\u229b')} ${bold('Phase 1')} \\u2014 Skill Evolution Forge (auto-generating project idioms)`);\r\n const evoScript = path.join(agentDest, 'scripts', 'skill_evolution.js');\r\n if (!fs.existsSync(evoScript)) {\r\n warn('skill_evolution.js not found \\u2014 run: npx tribunal-kit update');\r\n } else {\r\n try {\r\n const cmd = `node \"${evoScript}\" digest ${dryRun} ${useHead}`.trim();\r\n execSync(cmd, { stdio: 'inherit', cwd: targetDir });\r\n } catch (e) {\r\n warn(`Skill Evolution error: ${e.message}`);\r\n }\r\n }\r\n\r\n console.log();\r\n\r\n // Phase 2: Case Law prompt\r\n log(` ${c('cyan', '\\u229b')} ${bold('Phase 2')} \\u2014 Case Law Engine (building precedence record)`);\r\n console.log();\r\n log(` ${c('gray','\\u25b8')} Record a new rejection precedent:`);\r\n log(` ${c('white', 'npx tribunal-kit case add')}`);\r\n console.log();\r\n log(` ${c('gray','\\u25b8')} Search existing case law:`);\r\n log(` ${c('white', 'npx tribunal-kit case search \"your query\"')}`);\r\n console.log();\r\n log(` ${c('green', '\\u2714')} ${bold('Learn cycle complete.')} Your Tribunal grows smarter with every commit.`);\r\n console.log();\r\n}\r\n\r\n// ── Async Main Wrapper ───────────────────────────────────\r\nasync function runWithUpdateCheck(command, flags) {\r\n const shouldSkip = flags.skipUpdateCheck || process.env.TK_SKIP_UPDATE_CHECK === '1';\r\n\r\n if (!shouldSkip && (command === 'init' || command === 'update')) {\r\n // Pass through the original args (minus the node/script path)\r\n const originalArgs = process.argv.slice(2);\r\n const didReInvoke = await autoUpdateCheck(originalArgs);\r\n if (didReInvoke) {\r\n process.exit(0); // Latest version handled it\r\n }\r\n }\r\n\r\n // Proceed with current version\r\n switch (command) {\r\n case 'init':\r\n cmdInit(flags);\r\n break;\r\n case 'update':\r\n cmdUpdate(flags);\r\n break;\r\n case 'status':\r\n cmdStatus(flags);\r\n break;\r\n case 'learn':\r\n cmdLearn(flags);\r\n break;\r\n case 'case':\r\n cmdCase(flags);\r\n break;\r\n case 'hook':\r\n cmdHook(flags);\r\n break;\r\n case 'graph':\r\n cmdGraph(flags);\r\n break;\r\n case 'mutate':\r\n cmdMutate(flags);\r\n break;\r\n case 'context':\r\n cmdContext(flags);\r\n break;\r\n case 'uninstall':\r\n cmdUninstall(flags);\r\n break;\r\n case 'help':\r\n case '--help':\r\n case '-h':\r\n case null:\r\n cmdHelp();\r\n break;\r\n default:\r\n err(`Unknown command: \"${command}\"`);\r\n console.log();\r\n dim('Run tribunal-kit --help for usage');\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction cmdCase(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n const args = process.argv.slice(3).join(' ');\r\n if (!args || args === 'help' || args === '--help' || args === '-h') {\r\n banner();\r\n log(` ${c('cyan', '\\u2554' + '\\u2550'.repeat(60) + '\\u2557')}`);\r\n log(` ${c('cyan', '\\u2551')}${c('bold', c('white', ' Tribunal Case Law Engine \\u2014 Supreme Court '))}${c('cyan', '\\u2551')}`);\r\n log(` ${c('cyan', '\\u255a' + '\\u2550'.repeat(60) + '\\u255d')}`);\r\n console.log();\r\n log(` ${c('cyan', 'add'.padEnd(10))} ${c('gray', 'Record a new Case Law rejection pattern')}`);\r\n log(` ${c('cyan', 'search'.padEnd(10))} ${c('gray', 'Search existing cases (e.g., search \"query\")')}`);\r\n log(` ${c('cyan', 'list'.padEnd(10))} ${c('gray', 'List all recorded case law')}`);\r\n log(` ${c('cyan', 'show'.padEnd(10))} ${c('gray', 'Show full diff for a case (e.g., show --id 1)')}`);\r\n log(` ${c('cyan', 'stats'.padEnd(10))} ${c('gray', 'Show case law stats by domain/verdict')}`);\r\n log(` ${c('cyan', 'export'.padEnd(10))} ${c('gray', 'Export all cases to Markdown')}`);\r\n log(` ${c('cyan', 'overrule'.padEnd(10))} ${c('gray', 'Overrule a past precedent (e.g., overrule --id 1)')}`);\r\n console.log();\r\n process.exit(1);\r\n }\r\n\r\n const caseLawScript = path.join(agentDest, 'scripts', 'case_law_manager.js');\r\n\r\n // Make shorthand aliases\r\n let pyArgs = args;\r\n if (pyArgs.startsWith('add')) pyArgs = pyArgs.replace(/^add/, 'add-case');\r\n if (pyArgs.startsWith('search')) pyArgs = pyArgs.replace(/^search/, 'search-cases');\r\n\r\n try {\r\n const { execSync } = require('child_process');\r\n execSync(`node \"${caseLawScript}\" ${pyArgs}`, { stdio: 'inherit', cwd: targetDir });\r\n } catch {\r\n process.exit(1); // Script already prints errors\r\n }\r\n}\r\n\r\nfunction cmdGraph(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n banner();\r\n const { execSync } = require('child_process');\r\n const builderScript = path.join(agentDest, 'scripts', 'graph_builder.js');\r\n const visualizerScript = path.join(agentDest, 'scripts', 'graph_visualizer.js');\r\n const htmlFile = path.join(agentDest, 'history', 'architecture-explorer.html');\r\n\r\n try {\r\n execSync(`node \"${builderScript}\"`, { stdio: 'inherit', cwd: targetDir });\r\n execSync(`node \"${visualizerScript}\"`, { stdio: 'inherit', cwd: targetDir });\r\n \r\n log(` ${c('cyan', '▸')} Opening visualizer in browser...`);\r\n const opener = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';\r\n execSync(`${opener} \"${htmlFile}\"`);\r\n } catch (e) {\r\n err(`Graph generation failed: ${e.message}`);\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction cmdHook(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const gitDir = path.join(targetDir, '.git');\r\n \r\n if (!fs.existsSync(gitDir)) {\r\n err('Not a git repository. Cannot install git hooks here.');\r\n process.exit(1);\r\n }\r\n \r\n const hooksDir = path.join(gitDir, 'hooks');\r\n if (!fs.existsSync(hooksDir)) {\r\n fs.mkdirSync(hooksDir, { recursive: true });\r\n }\r\n \r\n const prePushPath = path.join(hooksDir, 'pre-push');\r\n const hookScript = `#!/bin/sh\\n# Supreme Court - Auto Learn on Push\\necho \"⚖️ Tribunal Supreme Court: Evolving Skills...\"\\nnpx tribunal-kit learn --head\\n`;\r\n \r\n fs.writeFileSync(prePushPath, hookScript, { mode: 0o755 });\r\n \r\n console.log();\r\n log(` ${c('green', '✔')} Installed pre-push git hook.`);\r\n log(` ${c('gray', '▸')} Skill Evolution will now run automatically every time you git push.`);\r\n console.log();\r\n}\r\n\r\nfunction cmdMutate(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n const args = process.argv.slice(3);\r\n if (args.length < 2) {\r\n err('Usage: npx tribunal-kit mutate <target_file> <test_command>');\r\n process.exit(1);\r\n }\r\n\r\n const mutateScript = path.join(agentDest, 'scripts', 'mutation_runner.js');\r\n const { execSync } = require('child_process');\r\n try {\r\n execSync(`node \"${mutateScript}\" ${args.join(' ')}`, { stdio: 'inherit', cwd: targetDir });\r\n } catch {\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction cmdUninstall(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n banner();\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n log(` ${c('yellow','⚠')} ${bold('.agent/')} is not installed in this project.`);\r\n console.log();\r\n return;\r\n }\r\n\r\n if (flags.dryRun) {\r\n log(colorize('yellow', ' DRY RUN — would remove:'));\r\n log(` ${c('gray',' ╰─')} ${agentDest}`);\r\n console.log();\r\n return;\r\n }\r\n\r\n try {\r\n fs.rmSync(agentDest, { recursive: true, force: true });\r\n log(` ${c('green','✔')} ${bold('.agent/')} has been removed from this project.`);\r\n console.log();\r\n log(` ${c('gray','▸')} To reinstall: ${c('cyan','npx tribunal-kit init')}`);\r\n console.log();\r\n } catch (e) {\r\n err(`Failed to remove .agent/: ${e.message}`);\r\n process.exit(1);\r\n }\r\n}\r\n\r\nfunction cmdStatus(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n\r\n banner();\r\n\r\n if (!fs.existsSync(agentDest)) {\r\n log(` ${c('red','✖')} ${bold('Not installed')} in this project`);\r\n console.log();\r\n log(` ${c('gray','Run:')} ${c('cyan','npx tribunal-kit init')}`);\r\n console.log();\r\n return;\r\n }\r\n\r\n log(` ${c('green','✔')} ${bold(c('green','Installed'))} ${c('gray','→')} ${c('gray', agentDest)}`);\r\n console.log();\r\n\r\n const icons = { agents: '🤖', workflows: '⚡', skills: '🧠', scripts: '🔧' };\r\n const colors = { agents: 'magenta', workflows: 'yellow', skills: 'blue', scripts: 'green' };\r\n const subdirs = ['agents', 'workflows', 'skills', 'scripts'];\r\n for (const sub of subdirs) {\r\n const subPath = path.join(agentDest, sub);\r\n if (fs.existsSync(subPath)) {\r\n const count = fs.readdirSync(subPath).filter(f => !fs.statSync(path.join(subPath, f)).isDirectory()).length;\r\n log(` ${icons[sub]} ${c(colors[sub], sub.padEnd(12))}${c('white', String(count).padStart(3))} files`);\r\n }\r\n }\r\n console.log();\r\n}\r\n\r\nfunction cmdHelp() {\r\n banner();\r\n const cmd = (name, desc) => ` ${c('cyan', name.padEnd(10))} ${c('gray', desc)}`;\r\n const opt = (flag, desc) => ` ${c('yellow', flag.padEnd(22))} ${c('gray', desc)}`;\r\n const ex = (s) => ` ${c('gray', '▸')} ${c('white', s)}`;\r\n\r\n log(bold(' Commands'));\r\n log(` ${c('gray','─'.repeat(40))}`);\r\n log(cmd('init', 'Install .agent/ into current project'));\r\n log(cmd('update', 'Re-install to get latest version'));\r\n log(cmd('status', 'Check if .agent/ is installed'));\r\n log(cmd('learn', 'Evolve project idioms based on git diffs'));\r\n log(cmd('case', 'Manage Case Law precedents (add, search, list, show, stats, overrule)'));\r\n log(cmd('graph', 'Build and visualize the architecture graph'));\r\n log(cmd('mutate', 'Run the Mutation Engine to test test-suite reliability'));\r\n log(cmd('context', 'Retrieve a highly-optimized Context Snapshot for a file'));\r\n log(cmd('hook', 'Install pre-push git hook for auto-learning'));\r\n log(cmd('uninstall','Remove .agent/ folder from project'));\r\n console.log();\r\n log(bold(' Options'));\r\n log(` ${c('gray','─'.repeat(40))}`);\r\n log(opt('--force', 'Overwrite existing .agent/ folder'));\r\n log(opt('--path <dir>', 'Install in specific directory'));\r\n log(opt('--quiet', 'Suppress all output'));\r\n log(opt('--verbose', 'Show detailed debug logging'));\r\n log(opt('--dry-run', 'Preview actions without executing'));\r\n log(opt('--minimal', 'Install core agents/skills only (~13 agents)'));\r\n log(opt('--skip-update-check', 'Skip auto-update version check'));\r\n log(opt('--head', '(learn) Diff against last commit instead of staged'));\r\n console.log();\r\n log(bold(' Aliases'));\r\n log(` ${c('gray','─'.repeat(40))}`);\r\n log(` ${c('cyan', 'tk')} ${c('gray', 'Shorthand for tribunal-kit (e.g., tk init, tk status)')}`);\r\n console.log();\r\n log(bold(' Examples'));\r\n log(` ${c('gray','─'.repeat(40))}`);\r\n log(ex('npx tribunal-kit init'));\r\n log(ex('tk init --force'));\r\n log(ex('tk init --path ./my-app'));\r\n log(ex('npx tribunal-kit init --dry-run'));\r\n log(ex('tk update'));\r\n log(ex('tk status'));\r\n log(ex('tk learn'));\r\n log(ex('tk learn --dry-run'));\r\n log(ex('tk learn --head'));\r\n log(ex('tk case add'));\r\n log(ex('tk case search \"useEffect\"'));\r\n log(ex('tk case list'));\r\n log(ex('tk case show --id 1'));\r\n log(ex('tk case stats'));\r\n log(ex('tk case export'));\r\n log(ex('tk case overrule --id 1'));\r\n log(ex('tk graph'));\r\n log(ex('tk mutate src/utils.js \"npm test\"'));\r\n log(ex('tk hook'));\r\n log(ex('tk uninstall'));\r\n console.log();\r\n}\r\n\r\n\r\nfunction cmdContext(flags) {\r\n const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();\r\n const agentDest = path.join(targetDir, '.agent');\r\n \r\n if (!fs.existsSync(agentDest)) {\r\n err('.agent/ not found. Run: npx tribunal-kit init');\r\n process.exit(1);\r\n }\r\n\r\n const args = process.argv.slice(3);\r\n if (args.length === 0 || args[0] === 'help' || args[0] === '--help') {\r\n console.error('Usage: npx tribunal-kit context <target_file>');\r\n process.exit(1);\r\n }\r\n\r\n const targetFile = args[0].replace(/\\\\/g, '/');\r\n const snapshotName = targetFile.replace(/[\\\\\\/]/g, '__') + '.json';\r\n const snapshotPath = require('path').join(agentDest, 'history', 'snapshots', snapshotName);\r\n\r\n if (!require('fs').existsSync(snapshotPath)) {\r\n console.error(' \\x1b[91m✖\\x1b[0m Context Snapshot not found for: ' + targetFile);\r\n console.log(' Run: npx tribunal-kit graph (to generate snapshots)');\r\n process.exit(1);\r\n }\r\n\r\n try {\r\n const snapshot = JSON.parse(require('fs').readFileSync(snapshotPath, 'utf8'));\r\n \r\n console.log('\\n# Context Snapshot: ' + snapshot.file);\r\n process.stdout.write('> Size Estimate: ' + (snapshot['estimatedTokens'] || 'Unknown') + '\\n');\r\n console.log('> Risk Score: ' + snapshot.riskScore + ' (Blast Radius: ' + snapshot.blastRadius + ')\\n');\r\n \r\n if (Object.keys(snapshot.imports).length > 0) {\r\n console.log('## Imports');\r\n for (const [imp, exports] of Object.entries(snapshot.imports)) {\r\n if (exports && exports.length > 0) {\r\n console.log('- `' + imp + '` (exports: ' + exports.join(', ') + ')');\r\n } else {\r\n console.log('- `' + imp + '`');\r\n }\r\n }\r\n console.log();\r\n }\r\n\r\n if (snapshot.dependents && snapshot.dependents.length > 0) {\r\n console.log('## Dependents');\r\n for (const dep of snapshot.dependents) {\r\n console.log('- `' + dep + '`');\r\n }\r\n console.log();\r\n }\r\n\r\n console.log('## Source Code');\r\n console.log('```javascript\\n' + snapshot.content + '\\n```\\n');\r\n \r\n } catch (e) {\r\n console.error('Failed to read snapshot: ' + e.message);\r\n process.exit(1);\r\n }\r\n}\r\n\r\n// ── Main ──────────────────────────────────────────────────\r\nconst { command, flags } = parseArgs(process.argv);\r\n\r\nif (flags.quiet) quiet = true;\r\nif (flags.verbose) verbose = true;\r\n\r\nrunWithUpdateCheck(command, flags);\r\n\r\n// -- Exports (for testing) -- do not remove\r\nif (require.main !== module) {\r\n module.exports = { parseArgs, compareSemver, copyDir, countDir, isSelfInstall, CORE_AGENTS, CORE_SKILLS, generateIDEBridges };\r\n}\r\n"
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": "eslint.config.js",
|
|
3
|
+
"hash": "d80785ac22194886311daa5c82c9765de8530876",
|
|
4
|
+
"riskScore": "Low",
|
|
5
|
+
"blastRadius": 0,
|
|
6
|
+
"imports": {},
|
|
7
|
+
"dependents": [],
|
|
8
|
+
"content": "module.exports = [\n {\n files: [\"**/*.js\"],\n languageOptions: {\n ecmaVersion: \"latest\",\n sourceType: \"commonjs\",\n globals: {\n require: \"readonly\",\n module: \"readonly\",\n process: \"readonly\",\n __dirname: \"readonly\",\n console: \"readonly\",\n exports: \"readonly\",\n setTimeout: \"readonly\",\n clearTimeout: \"readonly\",\n Buffer: \"readonly\",\n describe: \"readonly\",\n test: \"readonly\",\n it: \"readonly\",\n expect: \"readonly\",\n beforeEach: \"readonly\",\n afterEach: \"readonly\",\n beforeAll: \"readonly\",\n afterAll: \"readonly\",\n jest: \"readonly\"\n }\n },\n rules: {\n \"no-unused-vars\": [\"warn\", { \"argsIgnorePattern\": \"^_\", \"varsIgnorePattern\": \"^_\", \"caughtErrorsIgnorePattern\": \"^_\" }],\n \"no-undef\": \"error\"\n }\n }\n];\n"
|
|
9
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "migrate_refs.js",
|
|
3
|
+
"hash": "35b39052ff45b0b768fced3417c6245d9637ac18",
|
|
3
4
|
"riskScore": "Low",
|
|
4
5
|
"blastRadius": 0,
|
|
5
6
|
"imports": {
|
|
6
|
-
"fs": []
|
|
7
|
-
"path": []
|
|
7
|
+
"fs": []
|
|
8
8
|
},
|
|
9
9
|
"dependents": [],
|
|
10
|
-
"content": "const fs = require('fs');\
|
|
10
|
+
"content": "const fs = require('fs');\r\n\r\nconst filesToUpdate = [\r\n \".agent/workflows/swarm.md\",\r\n \".agent/workflows/session.md\",\r\n \".agent/rules/GEMINI.md\",\r\n \".agent/ARCHITECTURE.md\",\r\n \".agent/agents/swarm-worker-contracts.md\",\r\n \"AGENT_FLOW.md\",\r\n \"CHANGELOG.md\"\r\n];\r\n\r\nlet totalChanges = 0;\r\n\r\nfor (const filepath of filesToUpdate) {\r\n if (!fs.existsSync(filepath)) continue;\r\n let content = fs.readFileSync(filepath, 'utf8');\r\n \r\n // Explicit python calls\r\n content = content.replace(/python \\.agent\\/scripts\\/swarm_dispatcher\\.py/g, 'node .agent/scripts/swarm_dispatcher.js');\r\n content = content.replace(/python \\.agent\\/scripts\\/session_manager\\.py/g, 'node .agent/scripts/session_manager.js');\r\n content = content.replace(/python \\.agent\\/scripts\\/minify_context\\.py/g, 'node .agent/scripts/minify_context.js');\r\n content = content.replace(/python \\.agent\\/scripts\\/test_swarm_dispatcher\\.py/g, 'npx jest test/integration/swarm_dispatcher.test.js');\r\n \r\n // General filename references\r\n content = content.replace(/swarm_dispatcher\\.py/g, 'swarm_dispatcher.js');\r\n content = content.replace(/session_manager\\.py/g, 'session_manager.js');\r\n content = content.replace(/minify_context\\.py/g, 'minify_context.js');\r\n content = content.replace(/test_swarm_dispatcher\\.py/g, 'swarm_dispatcher.test.js');\r\n\r\n fs.writeFileSync(filepath, content, 'utf8');\r\n totalChanges++;\r\n}\r\nconsole.log(`Updated ${totalChanges} files.`);\r\n"
|
|
11
11
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "scripts/changelog.js",
|
|
3
|
+
"hash": "a078be5cb402a420593b4759afdae249cde78fdf",
|
|
3
4
|
"riskScore": "Low",
|
|
4
5
|
"blastRadius": 0,
|
|
5
6
|
"imports": {
|
|
@@ -8,5 +9,5 @@
|
|
|
8
9
|
"path": []
|
|
9
10
|
},
|
|
10
11
|
"dependents": [],
|
|
11
|
-
"content": "#!/usr/bin/env node\n/**\n * changelog.js — Auto-generate CHANGELOG from git history\n * \n * Categorizes commits by conventional commit type:\n * feat: → ✨ Features\n * fix: → 🐛 Bug Fixes\n * perf: → ⚡ Performance\n * docs: → 📝 Documentation\n * test: → ✅ Tests\n * refactor: → ♻️ Refactors\n * chore: → 🔧 Chores\n * BREAKING: → 💥 Breaking Changes\n * \n * Usage:\n * node scripts/changelog.js → Generate full changelog\n * node scripts/changelog.js --preview → Preview unreleased changes\n * node scripts/changelog.js --since v4.1.0 → Changes since a specific tag\n */\n\n'use strict';\n\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nconst path = require('path');\n\nconst PKG = require(path.resolve(__dirname, '..', 'package.json'));\nconst CHANGELOG_PATH = path.resolve(__dirname, '..', 'CHANGELOG.md');\n\n// ── Commit Categories ────────────────────────────────────\nconst CATEGORIES = {\n feat: { emoji: '✨', title: 'Features' },\n fix: { emoji: '🐛', title: 'Bug Fixes' },\n perf: { emoji: '⚡', title: 'Performance' },\n docs: { emoji: '📝', title: 'Documentation' },\n test: { emoji: '✅', title: 'Tests' },\n refactor: { emoji: '♻️', title: 'Refactors' },\n chore: { emoji: '🔧', title: 'Chores' },\n ci: { emoji: '🏗️', title: 'CI/CD' },\n style: { emoji: '🎨', title: 'Style' },\n breaking: { emoji: '💥', title: 'Breaking Changes' },\n};\n\n// ── Git Helpers ──────────────────────────────────────────\nfunction git(cmd) {\n try {\n return execSync(`git ${cmd}`, { encoding: 'utf8', timeout: 10000 }).trim();\n } catch {\n return '';\n }\n}\n\nfunction getLatestTag() {\n return git('describe --tags --abbrev=0 2>nul') || git('describe --tags --abbrev=0 2>/dev/null') || '';\n}\n\nfunction getCommits(since) {\n const range = since ? `${since}..HEAD` : 'HEAD';\n const format = '--format=\"%H||%s||%an||%ai\"';\n const raw = git(`log ${range} ${format} --no-merges`);\n if (!raw) return [];\n\n return raw.split('\\n').filter(Boolean).map(line => {\n const [hash, subject, author, date] = line.split('||');\n return { hash: hash?.slice(0, 7), subject, author, date: date?.slice(0, 10) };\n });\n}\n\nfunction categorize(subject) {\n const lower = subject.toLowerCase();\n\n // Check for BREAKING CHANGE\n if (lower.includes('breaking') || lower.includes('!:')) {\n return 'breaking';\n }\n\n // Match conventional commit prefix\n const match = subject.match(/^(\\w+)(?:\\(.+?\\))?:\\s*/);\n if (match) {\n const type = match[1].toLowerCase();\n if (CATEGORIES[type]) return type;\n }\n\n // Heuristic fallback\n if (lower.includes('fix') || lower.includes('bug')) return 'fix';\n if (lower.includes('add') || lower.includes('new') || lower.includes('feat')) return 'feat';\n if (lower.includes('doc') || lower.includes('readme')) return 'docs';\n if (lower.includes('test')) return 'test';\n if (lower.includes('refactor') || lower.includes('clean')) return 'refactor';\n if (lower.includes('perf') || lower.includes('optim')) return 'perf';\n if (lower.includes('ci') || lower.includes('workflow')) return 'ci';\n\n return 'chore';\n}\n\n// ── Changelog Generation ─────────────────────────────────\nfunction generateChangelog(commits, version, date) {\n const grouped = {};\n for (const commit of commits) {\n const cat = categorize(commit.subject);\n if (!grouped[cat]) grouped[cat] = [];\n // Strip conventional prefix for cleaner display\n const clean = commit.subject.replace(/^\\w+(\\(.+?\\))?:\\s*/, '');\n grouped[cat].push({ ...commit, clean });\n }\n\n let md = `## [${version}] — ${date}\\n\\n`;\n\n // Breaking changes first\n const order = ['breaking', 'feat', 'fix', 'perf', 'refactor', 'docs', 'test', 'ci', 'style', 'chore'];\n for (const cat of order) {\n if (!grouped[cat] || grouped[cat].length === 0) continue;\n const { emoji, title } = CATEGORIES[cat];\n md += `### ${emoji} ${title}\\n\\n`;\n for (const c of grouped[cat]) {\n md += `- ${c.clean} (\\`${c.hash}\\`)\\n`;\n }\n md += '\\n';\n }\n\n return md;\n}\n\n// ── Main ─────────────────────────────────────────────────\nfunction main() {\n const args = process.argv.slice(2);\n const isPreview = args.includes('--preview');\n const sinceIdx = args.indexOf('--since');\n const sinceTag = sinceIdx !== -1 ? args[sinceIdx + 1] : null;\n\n const since = sinceTag || getLatestTag();\n const commits = getCommits(since);\n\n if (commits.length === 0) {\n console.log(' ℹ️ No new commits found since', since || 'beginning');\n process.exit(0);\n }\n\n const today = new Date().toISOString().slice(0, 10);\n const version = isPreview ? 'Unreleased' : PKG.version;\n\n const changelog = generateChangelog(commits, version, today);\n\n if (isPreview) {\n console.log('\\n 📋 Changelog Preview\\n ' + '─'.repeat(40) + '\\n');\n console.log(changelog);\n console.log(` 📊 ${commits.length} commits since ${since || 'initial commit'}`);\n return;\n }\n\n // Write or prepend to CHANGELOG.md\n const header = `# Changelog\\n\\nAll notable changes to Tribunal Kit are documented here.\\nFormat follows [Keep a Changelog](https://keepachangelog.com/).\\n\\n`;\n\n let existing = '';\n if (fs.existsSync(CHANGELOG_PATH)) {\n existing = fs.readFileSync(CHANGELOG_PATH, 'utf8');\n // Remove existing header\n existing = existing.replace(/^# Changelog[\\s\\S]*?(?=## )/, '');\n }\n\n const full = header + changelog + existing;\n fs.writeFileSync(CHANGELOG_PATH, full, 'utf8');\n\n console.log(` ✔ CHANGELOG.md updated — v${version} (${commits.length} commits)`);\n}\n\nmain();\n"
|
|
12
|
+
"content": "#!/usr/bin/env node\r\n/**\r\n * changelog.js — Auto-generate CHANGELOG from git history\r\n * \r\n * Categorizes commits by conventional commit type:\r\n * feat: → ✨ Features\r\n * fix: → 🐛 Bug Fixes\r\n * perf: → ⚡ Performance\r\n * docs: → 📝 Documentation\r\n * test: → ✅ Tests\r\n * refactor: → ♻️ Refactors\r\n * chore: → 🔧 Chores\r\n * BREAKING: → 💥 Breaking Changes\r\n * \r\n * Usage:\r\n * node scripts/changelog.js → Generate full changelog\r\n * node scripts/changelog.js --preview → Preview unreleased changes\r\n * node scripts/changelog.js --since v4.1.0 → Changes since a specific tag\r\n */\r\n\r\n'use strict';\r\n\r\nconst { execSync } = require('child_process');\r\nconst fs = require('fs');\r\nconst path = require('path');\r\n\r\nconst PKG = require(path.resolve(__dirname, '..', 'package.json'));\r\nconst CHANGELOG_PATH = path.resolve(__dirname, '..', 'CHANGELOG.md');\r\n\r\n// ── Commit Categories ────────────────────────────────────\r\nconst CATEGORIES = {\r\n feat: { emoji: '✨', title: 'Features' },\r\n fix: { emoji: '🐛', title: 'Bug Fixes' },\r\n perf: { emoji: '⚡', title: 'Performance' },\r\n docs: { emoji: '📝', title: 'Documentation' },\r\n test: { emoji: '✅', title: 'Tests' },\r\n refactor: { emoji: '♻️', title: 'Refactors' },\r\n chore: { emoji: '🔧', title: 'Chores' },\r\n ci: { emoji: '🏗️', title: 'CI/CD' },\r\n style: { emoji: '🎨', title: 'Style' },\r\n breaking: { emoji: '💥', title: 'Breaking Changes' },\r\n};\r\n\r\n// ── Git Helpers ──────────────────────────────────────────\r\nfunction git(cmd) {\r\n try {\r\n return execSync(`git ${cmd}`, { encoding: 'utf8', timeout: 10000 }).trim();\r\n } catch {\r\n return '';\r\n }\r\n}\r\n\r\nfunction getLatestTag() {\r\n return git('describe --tags --abbrev=0 2>nul') || git('describe --tags --abbrev=0 2>/dev/null') || '';\r\n}\r\n\r\nfunction getCommits(since) {\r\n const range = since ? `${since}..HEAD` : 'HEAD';\r\n const format = '--format=\"%H||%s||%an||%ai\"';\r\n const raw = git(`log ${range} ${format} --no-merges`);\r\n if (!raw) return [];\r\n\r\n return raw.split('\\n').filter(Boolean).map(line => {\r\n const [hash, subject, author, date] = line.split('||');\r\n return { hash: hash?.slice(0, 7), subject, author, date: date?.slice(0, 10) };\r\n });\r\n}\r\n\r\nfunction categorize(subject) {\r\n const lower = subject.toLowerCase();\r\n\r\n // Check for BREAKING CHANGE\r\n if (lower.includes('breaking') || lower.includes('!:')) {\r\n return 'breaking';\r\n }\r\n\r\n // Match conventional commit prefix\r\n const match = subject.match(/^(\\w+)(?:\\(.+?\\))?:\\s*/);\r\n if (match) {\r\n const type = match[1].toLowerCase();\r\n if (CATEGORIES[type]) return type;\r\n }\r\n\r\n // Heuristic fallback\r\n if (lower.includes('fix') || lower.includes('bug')) return 'fix';\r\n if (lower.includes('add') || lower.includes('new') || lower.includes('feat')) return 'feat';\r\n if (lower.includes('doc') || lower.includes('readme')) return 'docs';\r\n if (lower.includes('test')) return 'test';\r\n if (lower.includes('refactor') || lower.includes('clean')) return 'refactor';\r\n if (lower.includes('perf') || lower.includes('optim')) return 'perf';\r\n if (lower.includes('ci') || lower.includes('workflow')) return 'ci';\r\n\r\n return 'chore';\r\n}\r\n\r\n// ── Changelog Generation ─────────────────────────────────\r\nfunction generateChangelog(commits, version, date) {\r\n const grouped = {};\r\n for (const commit of commits) {\r\n const cat = categorize(commit.subject);\r\n if (!grouped[cat]) grouped[cat] = [];\r\n // Strip conventional prefix for cleaner display\r\n const clean = commit.subject.replace(/^\\w+(\\(.+?\\))?:\\s*/, '');\r\n grouped[cat].push({ ...commit, clean });\r\n }\r\n\r\n let md = `## [${version}] — ${date}\\n\\n`;\r\n\r\n // Breaking changes first\r\n const order = ['breaking', 'feat', 'fix', 'perf', 'refactor', 'docs', 'test', 'ci', 'style', 'chore'];\r\n for (const cat of order) {\r\n if (!grouped[cat] || grouped[cat].length === 0) continue;\r\n const { emoji, title } = CATEGORIES[cat];\r\n md += `### ${emoji} ${title}\\n\\n`;\r\n for (const c of grouped[cat]) {\r\n md += `- ${c.clean} (\\`${c.hash}\\`)\\n`;\r\n }\r\n md += '\\n';\r\n }\r\n\r\n return md;\r\n}\r\n\r\n// ── Main ─────────────────────────────────────────────────\r\nfunction main() {\r\n const args = process.argv.slice(2);\r\n const isPreview = args.includes('--preview');\r\n const sinceIdx = args.indexOf('--since');\r\n const sinceTag = sinceIdx !== -1 ? args[sinceIdx + 1] : null;\r\n\r\n const since = sinceTag || getLatestTag();\r\n const commits = getCommits(since);\r\n\r\n if (commits.length === 0) {\r\n console.log(' ℹ️ No new commits found since', since || 'beginning');\r\n process.exit(0);\r\n }\r\n\r\n const today = new Date().toISOString().slice(0, 10);\r\n const version = isPreview ? 'Unreleased' : PKG.version;\r\n\r\n const changelog = generateChangelog(commits, version, today);\r\n\r\n if (isPreview) {\r\n console.log('\\n 📋 Changelog Preview\\n ' + '─'.repeat(40) + '\\n');\r\n console.log(changelog);\r\n console.log(` 📊 ${commits.length} commits since ${since || 'initial commit'}`);\r\n return;\r\n }\r\n\r\n // Write or prepend to CHANGELOG.md\r\n const header = `# Changelog\\n\\nAll notable changes to Tribunal Kit are documented here.\\nFormat follows [Keep a Changelog](https://keepachangelog.com/).\\n\\n`;\r\n\r\n let existing = '';\r\n if (fs.existsSync(CHANGELOG_PATH)) {\r\n existing = fs.readFileSync(CHANGELOG_PATH, 'utf8');\r\n // Remove existing header\r\n existing = existing.replace(/^# Changelog[\\s\\S]*?(?=## )/, '');\r\n }\r\n\r\n const full = header + changelog + existing;\r\n fs.writeFileSync(CHANGELOG_PATH, full, 'utf8');\r\n\r\n console.log(` ✔ CHANGELOG.md updated — v${version} (${commits.length} commits)`);\r\n}\r\n\r\nmain();\r\n"
|
|
12
13
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "scripts/sync-version.js",
|
|
3
|
+
"hash": "222ce95e5cbce0525359105d277688cfe61c7c6c",
|
|
3
4
|
"riskScore": "Low",
|
|
4
5
|
"blastRadius": 0,
|
|
5
6
|
"imports": {
|
|
@@ -7,5 +8,5 @@
|
|
|
7
8
|
"path": []
|
|
8
9
|
},
|
|
9
10
|
"dependents": [],
|
|
10
|
-
"content": "#!/usr/bin/env node\n/**\n * sync-version.js — Version Sync for Tribunal Kit\n * \n * Reads the version and counts from package.json and the .agent/ directory,\n * then updates all stale references across documentation files.\n * \n * Run manually or as a preversion npm script:\n * node scripts/sync-version.js\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst ROOT = path.resolve(__dirname, '..');\nconst PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));\n\n// Count actual installed items\nfunction countItems(dir) {\n const fullPath = path.join(ROOT, '.agent', dir);\n if (!fs.existsSync(fullPath)) return '?';\n return fs.readdirSync(fullPath).filter(f => !f.startsWith('.')).length;\n}\n\nconst version = PKG.version;\nconst agents = countItems('agents');\nconst skills = countItems('skills');\nconst workflows = countItems('workflows');\nconst scripts = countItems('scripts');\n\nconsole.log(`\\n 📊 Tribunal Kit v${version} — Actual Counts`);\nconsole.log(` ──────────────────────────────────────`);\nconsole.log(` Agents: ${agents}`);\nconsole.log(` Skills: ${skills}`);\nconsole.log(` Workflows: ${workflows}`);\nconsole.log(` Scripts: ${scripts}`);\nconsole.log();\n\n// Files to check for stale numbers\nconst FILES_TO_CHECK = [\n 'README.md',\n 'AGENT_FLOW.md',\n '.agent/ARCHITECTURE.md',\n];\n\nlet staleFound = 0;\n\nfor (const relPath of FILES_TO_CHECK) {\n const filePath = path.join(ROOT, relPath);\n if (!fs.existsSync(filePath)) continue;\n\n const content = fs.readFileSync(filePath, 'utf8');\n \n // Check for common stale patterns\n const checks = [\n { regex: /(\\d+)\\s*(specialist\\s+)?agents/gi, expected: agents, label: 'agents' },\n { regex: /(\\d+)\\s*skill\\s*modules/gi, expected: skills, label: 'skills' },\n { regex: /(\\d+)\\s*slash\\s*command/gi, expected: workflows, label: 'workflows' },\n ];\n\n for (const check of checks) {\n let match;\n while ((match = check.regex.exec(content)) !== null) {\n const found = parseInt(match[1]);\n if (found !== check.expected && found > 5) { // ignore tiny numbers\n staleFound++;\n const line = content.substring(0, match.index).split('\\n').length;\n console.log(` ⚠️ ${relPath}:${line} — says ${found} ${check.label}, actual is ${check.expected}`);\n }\n }\n }\n}\n\nif (staleFound === 0) {\n console.log(` ✅ All counts are in sync across ${FILES_TO_CHECK.length} files.`);\n} else {\n console.log(`\\n ❌ Found ${staleFound} stale reference(s). Update manually or run the sync tool.`);\n process.exit(1);\n}\n\nconsole.log();\n"
|
|
11
|
+
"content": "#!/usr/bin/env node\r\n/**\r\n * sync-version.js — Version Sync for Tribunal Kit\r\n * \r\n * Reads the version and counts from package.json and the .agent/ directory,\r\n * then updates all stale references across documentation files.\r\n * \r\n * Run manually or as a preversion npm script:\r\n * node scripts/sync-version.js\r\n */\r\n\r\nconst fs = require('fs');\r\nconst path = require('path');\r\n\r\nconst ROOT = path.resolve(__dirname, '..');\r\nconst PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));\r\n\r\n// Count actual installed items\r\nfunction countItems(dir) {\r\n const fullPath = path.join(ROOT, '.agent', dir);\r\n if (!fs.existsSync(fullPath)) return '?';\r\n return fs.readdirSync(fullPath).filter(f => !f.startsWith('.')).length;\r\n}\r\n\r\nconst version = PKG.version;\r\nconst agents = countItems('agents');\r\nconst skills = countItems('skills');\r\nconst workflows = countItems('workflows');\r\nconst scripts = countItems('scripts');\r\n\r\nconsole.log(`\\n 📊 Tribunal Kit v${version} — Actual Counts`);\r\nconsole.log(` ──────────────────────────────────────`);\r\nconsole.log(` Agents: ${agents}`);\r\nconsole.log(` Skills: ${skills}`);\r\nconsole.log(` Workflows: ${workflows}`);\r\nconsole.log(` Scripts: ${scripts}`);\r\nconsole.log();\r\n\r\n// Files to check for stale numbers\r\nconst FILES_TO_CHECK = [\r\n 'README.md',\r\n 'AGENT_FLOW.md',\r\n '.agent/ARCHITECTURE.md',\r\n];\r\n\r\nlet staleFound = 0;\r\n\r\nfor (const relPath of FILES_TO_CHECK) {\r\n const filePath = path.join(ROOT, relPath);\r\n if (!fs.existsSync(filePath)) continue;\r\n\r\n const content = fs.readFileSync(filePath, 'utf8');\r\n \r\n // Check for common stale patterns\r\n const checks = [\r\n { regex: /(\\d+)\\s*(specialist\\s+)?agents/gi, expected: agents, label: 'agents' },\r\n { regex: /(\\d+)\\s*skill\\s*modules/gi, expected: skills, label: 'skills' },\r\n { regex: /(\\d+)\\s*slash\\s*command/gi, expected: workflows, label: 'workflows' },\r\n ];\r\n\r\n for (const check of checks) {\r\n let match;\r\n while ((match = check.regex.exec(content)) !== null) {\r\n const found = parseInt(match[1]);\r\n if (found !== check.expected && found > 5) { // ignore tiny numbers\r\n staleFound++;\r\n const line = content.substring(0, match.index).split('\\n').length;\r\n console.log(` ⚠️ ${relPath}:${line} — says ${found} ${check.label}, actual is ${check.expected}`);\r\n }\r\n }\r\n }\r\n}\r\n\r\nif (staleFound === 0) {\r\n console.log(` ✅ All counts are in sync across ${FILES_TO_CHECK.length} files.`);\r\n} else {\r\n console.log(`\\n ❌ Found ${staleFound} stale reference(s). Update manually or run the sync tool.`);\r\n process.exit(1);\r\n}\r\n\r\nconsole.log();\r\n"
|
|
11
12
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "test/integration/bridges.test.js",
|
|
3
|
+
"hash": "9941a025cf46512d813660e4a09bf304f7fce64f",
|
|
3
4
|
"riskScore": "Low",
|
|
4
5
|
"blastRadius": 0,
|
|
5
6
|
"imports": {
|
|
@@ -9,5 +10,5 @@
|
|
|
9
10
|
"os": []
|
|
10
11
|
},
|
|
11
12
|
"dependents": [],
|
|
12
|
-
"content": "'use strict';\n\nconst { spawnSync } = require('child_process');\nconst path = require('path');\nconst fs = require('fs');\nconst os = require('os');\n\nconst CLI = path.resolve(__dirname, '../../bin/tribunal-kit.js');\n\nfunction runCLI(args = [], opts = {}) {\n return spawnSync(process.execPath, [CLI, ...args], {\n encoding : 'utf8',\n timeout : 30000,\n env : { ...process.env, TK_SKIP_UPDATE_CHECK: '1' },\n ...opts,\n });\n}\n\ndescribe('IDE Bridge File Generation', () => {\n let tmpDir;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tk-bridge-'));\n });\n\n afterEach(() => {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n test('init creates .cursorrules', () => {\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\n const cursorRules = path.join(tmpDir, '.cursorrules');\n expect(fs.existsSync(cursorRules)).toBe(true);\n const content = fs.readFileSync(cursorRules, 'utf8');\n expect(content).toContain('Tribunal');\n expect(content).toContain('Anti-Hallucination');\n });\n\n test('init creates .windsurfrules', () => {\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\n const windsurfRules = path.join(tmpDir, '.windsurfrules');\n expect(fs.existsSync(windsurfRules)).toBe(true);\n const content = fs.readFileSync(windsurfRules, 'utf8');\n expect(content).toContain('Windsurf Bridge');\n expect(content).toContain('Tribunal');\n });\n\n test('init creates .gemini/settings.json', () => {\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\n const geminiSettings = path.join(tmpDir, '.gemini', 'settings.json');\n expect(fs.existsSync(geminiSettings)).toBe(true);\n const json = JSON.parse(fs.readFileSync(geminiSettings, 'utf8'));\n expect(json.rules).toBeDefined();\n expect(json.rules[0].path).toContain('.agent/rules/GEMINI.md');\n });\n\n test('init creates .github/copilot-instructions.md', () => {\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\n const copilot = path.join(tmpDir, '.github', 'copilot-instructions.md');\n expect(fs.existsSync(copilot)).toBe(true);\n const content = fs.readFileSync(copilot, 'utf8');\n expect(content).toContain('Copilot Bridge');\n });\n\n test('bridge files are NOT overwritten on re-init', () => {\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\n const cursorRules = path.join(tmpDir, '.cursorrules');\n fs.writeFileSync(cursorRules, '# My custom rules\\nDo not touch');\n runCLI(['init', '--path', tmpDir, '--force', '--skip-update-check']);\n const content = fs.readFileSync(cursorRules, 'utf8');\n expect(content).toBe('# My custom rules\\nDo not touch');\n });\n\n test('all bridge files contain the routing table', () => {\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\n const content = fs.readFileSync(path.join(tmpDir, '.cursorrules'), 'utf8');\n expect(content).toContain('backend-specialist');\n expect(content).toContain('frontend-specialist');\n });\n\n test('init --dry-run does NOT create bridge files', () => {\n runCLI(['init', '--path', tmpDir, '--dry-run', '--skip-update-check']);\n expect(fs.existsSync(path.join(tmpDir, '.cursorrules'))).toBe(false);\n });\n});\n"
|
|
13
|
+
"content": "'use strict';\r\n\r\nconst { spawnSync } = require('child_process');\r\nconst path = require('path');\r\nconst fs = require('fs');\r\nconst os = require('os');\r\n\r\nconst CLI = path.resolve(__dirname, '../../bin/tribunal-kit.js');\r\n\r\nfunction runCLI(args = [], opts = {}) {\r\n return spawnSync(process.execPath, [CLI, ...args], {\r\n encoding : 'utf8',\r\n timeout : 30000,\r\n env : { ...process.env, TK_SKIP_UPDATE_CHECK: '1' },\r\n ...opts,\r\n });\r\n}\r\n\r\ndescribe('IDE Bridge File Generation', () => {\r\n let tmpDir;\r\n\r\n beforeEach(() => {\r\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tk-bridge-'));\r\n });\r\n\r\n afterEach(() => {\r\n fs.rmSync(tmpDir, { recursive: true, force: true });\r\n });\r\n\r\n test('init creates .cursorrules', () => {\r\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\r\n const cursorRules = path.join(tmpDir, '.cursorrules');\r\n expect(fs.existsSync(cursorRules)).toBe(true);\r\n const content = fs.readFileSync(cursorRules, 'utf8');\r\n expect(content).toContain('Tribunal');\r\n expect(content).toContain('Anti-Hallucination');\r\n });\r\n\r\n test('init creates .windsurfrules', () => {\r\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\r\n const windsurfRules = path.join(tmpDir, '.windsurfrules');\r\n expect(fs.existsSync(windsurfRules)).toBe(true);\r\n const content = fs.readFileSync(windsurfRules, 'utf8');\r\n expect(content).toContain('Windsurf Bridge');\r\n expect(content).toContain('Tribunal');\r\n });\r\n\r\n test('init creates .gemini/settings.json', () => {\r\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\r\n const geminiSettings = path.join(tmpDir, '.gemini', 'settings.json');\r\n expect(fs.existsSync(geminiSettings)).toBe(true);\r\n const json = JSON.parse(fs.readFileSync(geminiSettings, 'utf8'));\r\n expect(json.rules).toBeDefined();\r\n expect(json.rules[0].path).toContain('.agent/rules/GEMINI.md');\r\n });\r\n\r\n test('init creates .github/copilot-instructions.md', () => {\r\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\r\n const copilot = path.join(tmpDir, '.github', 'copilot-instructions.md');\r\n expect(fs.existsSync(copilot)).toBe(true);\r\n const content = fs.readFileSync(copilot, 'utf8');\r\n expect(content).toContain('Copilot Bridge');\r\n });\r\n\r\n test('bridge files are NOT overwritten on re-init', () => {\r\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\r\n const cursorRules = path.join(tmpDir, '.cursorrules');\r\n fs.writeFileSync(cursorRules, '# My custom rules\\nDo not touch');\r\n runCLI(['init', '--path', tmpDir, '--force', '--skip-update-check']);\r\n const content = fs.readFileSync(cursorRules, 'utf8');\r\n expect(content).toBe('# My custom rules\\nDo not touch');\r\n });\r\n\r\n test('all bridge files contain the routing table', () => {\r\n runCLI(['init', '--path', tmpDir, '--skip-update-check']);\r\n const content = fs.readFileSync(path.join(tmpDir, '.cursorrules'), 'utf8');\r\n expect(content).toContain('backend-specialist');\r\n expect(content).toContain('frontend-specialist');\r\n });\r\n\r\n test('init --dry-run does NOT create bridge files', () => {\r\n runCLI(['init', '--path', tmpDir, '--dry-run', '--skip-update-check']);\r\n expect(fs.existsSync(path.join(tmpDir, '.cursorrules'))).toBe(false);\r\n });\r\n});\r\n"
|
|
13
14
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "test/integration/swarm_dispatcher.test.js",
|
|
3
|
+
"hash": "959cf59504544f624aea7ccad55fe2d9dc25ca4d",
|
|
3
4
|
"riskScore": "Low",
|
|
4
5
|
"blastRadius": 0,
|
|
5
6
|
"imports": {
|
|
@@ -9,5 +10,5 @@
|
|
|
9
10
|
"../../.agent/scripts/swarm_dispatcher.js": []
|
|
10
11
|
},
|
|
11
12
|
"dependents": [],
|
|
12
|
-
"content": "const path = require('path');\nconst fs = require('fs');\nconst os = require('os');\nconst {\n findAgentDir,\n validatePayload,\n
|
|
13
|
+
"content": "const path = require('path');\r\nconst fs = require('fs');\r\nconst os = require('os');\r\nconst {\r\n findAgentDir,\r\n validatePayload,\r\n validateWorkerRequest,\r\n validateSwarmPayload\r\n} = require('../../.agent/scripts/swarm_dispatcher.js');\r\n\r\ndescribe('swarm_dispatcher.js legacy mode', () => {\r\n let tmpDir;\r\n\r\n beforeEach(() => {\r\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarm-test-'));\r\n });\r\n\r\n afterEach(() => {\r\n fs.rmSync(tmpDir, { recursive: true, force: true });\r\n });\r\n\r\n test('findAgentDir finds .agent directory', () => {\r\n const agentDir = path.join(tmpDir, '.agent');\r\n fs.mkdirSync(agentDir);\r\n const subDir = path.join(tmpDir, 'src', 'deep', 'folder');\r\n fs.mkdirSync(subDir, { recursive: true });\r\n\r\n const result = findAgentDir(subDir);\r\n expect(result).not.toBeNull();\r\n expect(path.resolve(result)).toBe(path.resolve(agentDir));\r\n });\r\n\r\n test('findAgentDir returns null if not found', () => {\r\n const subDir = path.join(tmpDir, 'src', 'deep');\r\n fs.mkdirSync(subDir, { recursive: true });\r\n\r\n expect(findAgentDir(subDir)).toBeNull();\r\n });\r\n\r\n test('validatePayload valid', () => {\r\n const agentsDir = path.join(tmpDir, 'agents');\r\n fs.mkdirSync(agentsDir);\r\n fs.writeFileSync(path.join(agentsDir, 'test_agent.md'), '');\r\n\r\n const workspace = path.join(tmpDir, 'workspace');\r\n fs.mkdirSync(workspace);\r\n fs.writeFileSync(path.join(workspace, 'file1.txt'), '');\r\n\r\n const payload = {\r\n dispatch_micro_workers: [\r\n {\r\n target_agent: \"test_agent\",\r\n files_attached: [\"file1.txt\"]\r\n }\r\n ]\r\n };\r\n\r\n expect(validatePayload(payload, workspace, agentsDir)).toBe(true);\r\n });\r\n\r\n test('validatePayload missing workers', () => {\r\n expect(validatePayload({}, tmpDir, tmpDir)).toBe(false);\r\n });\r\n\r\n test('validatePayload files not a list', () => {\r\n const agentsDir = path.join(tmpDir, 'agents');\r\n fs.mkdirSync(agentsDir);\r\n fs.writeFileSync(path.join(agentsDir, 'test_agent.md'), '');\r\n\r\n const payload = {\r\n dispatch_micro_workers: [\r\n {\r\n target_agent: \"test_agent\",\r\n files_attached: \"a single file string\"\r\n }\r\n ]\r\n };\r\n\r\n expect(validatePayload(payload, tmpDir, agentsDir)).toBe(false);\r\n });\r\n\r\n test('validateSwarmPayload validates WorkerRequest correctly', () => {\r\n const agentsDir = path.join(tmpDir, 'agents');\r\n fs.mkdirSync(agentsDir);\r\n fs.writeFileSync(path.join(agentsDir, 'research.md'), '');\r\n\r\n const req = {\r\n task_id: \"t123\",\r\n type: \"research\",\r\n agent: \"research\",\r\n goal: \"Find the root cause\",\r\n context: \"We have a bug here.\",\r\n max_retries: 2\r\n };\r\n\r\n const errors = validateWorkerRequest(req, 0, agentsDir);\r\n expect(errors).toHaveLength(0);\r\n\r\n expect(validateSwarmPayload([req], agentsDir)).toBe(true);\r\n });\r\n});\r\n"
|
|
13
14
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "test/integration/wave2.test.js",
|
|
3
|
+
"hash": "703c635a4a0091e20785c3418b1ab294607a1635",
|
|
3
4
|
"riskScore": "Low",
|
|
4
5
|
"blastRadius": 0,
|
|
5
6
|
"imports": {
|
|
@@ -9,5 +10,5 @@
|
|
|
9
10
|
"../../.agent/scripts/case_law_manager.js": []
|
|
10
11
|
},
|
|
11
12
|
"dependents": [],
|
|
12
|
-
"content": "const { getAssociatedScript } = require('../../.agent/scripts/skill_integrator.js');\nconst { architecturalWeight, semanticDelta } = require('../../.agent/scripts/skill_evolution.js');\nconst {
|
|
13
|
+
"content": "const { getAssociatedScript } = require('../../.agent/scripts/skill_integrator.js');\r\nconst { architecturalWeight, semanticDelta } = require('../../.agent/scripts/skill_evolution.js');\r\nconst { findAgentDir } = require('../../.agent/scripts/case_law_manager.js');\r\n\r\ndescribe('Wave 2 Scripts (Evolving Brain)', () => {\r\n describe('skill_integrator.js', () => {\r\n it('should correctly identify implicit JS scripts', () => {\r\n // we mock fs internally if we wanted to true unit test, but for now just check exports\r\n expect(typeof getAssociatedScript).toBe('function');\r\n });\r\n });\r\n\r\n describe('skill_evolution.js', () => {\r\n it('should assign higher architectural weight to classes and interfaces', () => {\r\n expect(architecturalWeight('class User {')).toBeGreaterThanOrEqual(2);\r\n expect(architecturalWeight('interface Data {')).toBeGreaterThanOrEqual(2);\r\n });\r\n\r\n it('should assign 0 weight to noise patterns like comments and imports', () => {\r\n expect(architecturalWeight('// this is a comment')).toBe(0);\r\n expect(architecturalWeight('import { foo } from \"fs\";')).toBe(0);\r\n expect(architecturalWeight('')).toBe(0);\r\n });\r\n\r\n it('should successfully filter a semantic delta', () => {\r\n const rawDiff = `\r\n@@ -1,3 +1,4 @@\r\n import { foo } from 'fs';\r\n+import { baz } from 'fs';\r\n class Foo {\r\n- // old comment\r\n+ // new comment\r\n+ export class Baz {}\r\n }\r\n `.trim();\r\n\r\n const delta = semanticDelta(rawDiff, 2);\r\n // It gets kept because the hunk contains 'export class Baz {}'\r\n expect(delta).toContain('import { baz }');\r\n expect(delta).toContain('export class Baz {}');\r\n });\r\n });\r\n\r\n describe('case_law_manager.js', () => {\r\n it('should extract tags', () => {\r\n const { extractTags } = require('../../.agent/scripts/case_law_manager.js');\r\n expect(typeof extractTags).toBe('function');\r\n expect(extractTags('hello world and foo bar')).toContain('hello');\r\n expect(extractTags('hello world and foo bar')).not.toContain('and');\r\n });\r\n it('should find agent diff', () => {\r\n expect(typeof findAgentDir).toBe('function');\r\n });\r\n });\r\n});\r\n"
|
|
13
14
|
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"file": "test/unit/args.test.js",
|
|
3
|
+
"hash": "b5863d3402957aae7315537ff9845c99899eeb50",
|
|
3
4
|
"riskScore": "Low",
|
|
4
5
|
"blastRadius": 0,
|
|
5
6
|
"imports": {
|
|
6
|
-
"../../bin/tribunal-kit": [
|
|
7
|
+
"../../bin/tribunal-kit": [
|
|
8
|
+
"parseArgs",
|
|
9
|
+
"compareSemver",
|
|
10
|
+
"copyDir",
|
|
11
|
+
"countDir",
|
|
12
|
+
"isSelfInstall",
|
|
13
|
+
"CORE_AGENTS",
|
|
14
|
+
"CORE_SKILLS",
|
|
15
|
+
"generateIDEBridges"
|
|
16
|
+
]
|
|
7
17
|
},
|
|
8
18
|
"dependents": [],
|
|
9
19
|
"content": "'use strict';\r\n\r\nconst { parseArgs } = require('../../bin/tribunal-kit');\r\n\r\ndescribe('parseArgs', () => {\r\n test('parses \"init\" command', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'init']);\r\n expect(result.command).toBe('init');\r\n expect(result.flags).toEqual({});\r\n });\r\n\r\n test('parses \"update\" command', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'update']);\r\n expect(result.command).toBe('update');\r\n });\r\n\r\n test('parses \"status\" command', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'status']);\r\n expect(result.command).toBe('status');\r\n });\r\n\r\n test('returns null command when no args given', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js']);\r\n expect(result.command).toBeNull();\r\n });\r\n\r\n test('parses --force flag', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'init', '--force']);\r\n expect(result.flags.force).toBe(true);\r\n });\r\n\r\n test('parses --quiet flag', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'init', '--quiet']);\r\n expect(result.flags.quiet).toBe(true);\r\n });\r\n\r\n test('parses --dry-run flag', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'init', '--dry-run']);\r\n expect(result.flags.dryRun).toBe(true);\r\n });\r\n\r\n test('parses --skip-update-check flag', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'init', '--skip-update-check']);\r\n expect(result.flags.skipUpdateCheck).toBe(true);\r\n });\r\n\r\n test('parses --path=<value> flag', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'init', '--path=./my-app']);\r\n expect(result.flags.path).toBe('./my-app');\r\n });\r\n\r\n test('parses --path <value> flag (space separated)', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'init', '--path', './my-app']);\r\n expect(result.flags.path).toBe('./my-app');\r\n });\r\n\r\n test('ignores unknown flags gracefully', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'init', '--unknown-flag']);\r\n expect(result.command).toBe('init');\r\n // Unknown flags don't throw, just get silently ignored\r\n expect(result.flags.unknownFlag).toBeUndefined();\r\n });\r\n\r\n test('multiple flags together', () => {\r\n const result = parseArgs(['node', 'tribunal-kit.js', 'init', '--force', '--quiet', '--dry-run']);\r\n expect(result.flags.force).toBe(true);\r\n expect(result.flags.quiet).toBe(true);\r\n expect(result.flags.dryRun).toBe(true);\r\n });\r\n});\r\n"
|