tribunal-kit 5.7.0 → 5.8.0
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/ARCHITECTURE.md +6 -7
- package/.agent/agents/frontend-reviewer.md +13 -0
- package/.agent/agents/frontend-specialist.md +14 -0
- package/.agent/agents/logic-reviewer.md +11 -0
- package/.agent/agents/orchestrator.md +15 -0
- package/.agent/agents/security-auditor.md +13 -0
- package/.agent/agents/ui-ux-auditor.md +7 -31
- package/.agent/history/memory/.memory.idx +766 -0
- package/.agent/history/memory/MEMORY.md +62 -0
- package/.agent/routing_index.json +694 -714
- package/.agent/rules/GEMINI.md +58 -8
- package/.agent/scripts/_colors.js +131 -89
- package/.agent/scripts/_utils.js +163 -128
- package/.agent/scripts/auto_preview.js +207 -197
- package/.agent/scripts/bundle_analyzer.js +227 -192
- package/.agent/scripts/case_law_manager.js +991 -689
- package/.agent/scripts/checklist.js +233 -190
- package/.agent/scripts/context_broker.js +930 -605
- package/.agent/scripts/dependency_analyzer.js +275 -184
- package/.agent/scripts/graph_builder.js +412 -341
- package/.agent/scripts/graph_visualizer.js +392 -390
- package/.agent/scripts/graph_zoom.js +198 -156
- package/.agent/scripts/inner_loop_validator.js +523 -445
- package/.agent/scripts/lint_runner.js +199 -157
- package/.agent/scripts/marathon_harness.js +819 -661
- package/.agent/scripts/minify_context.js +115 -100
- package/.agent/scripts/mutation_runner.js +321 -280
- package/.agent/scripts/prompt_compiler.js +62 -42
- package/.agent/scripts/schema_validator.js +373 -280
- package/.agent/scripts/security_scan.js +333 -190
- package/.agent/scripts/session_manager.js +306 -270
- package/.agent/scripts/skill_evolution.js +810 -637
- package/.agent/scripts/skill_integrator.js +327 -307
- package/.agent/scripts/strengthen_skills.js +203 -193
- package/.agent/scripts/swarm_dispatcher.js +558 -457
- package/.agent/scripts/test_runner.js +178 -152
- package/.agent/scripts/verify_all.js +200 -168
- package/.agent/skills/fabel-protocol/SKILL.md +235 -0
- package/.agent/skills/thinking-protocol/SKILL.md +27 -0
- package/.agent/workflows/generate.md +1 -1
- package/.agent/workflows/tribunal-speed.md +1 -1
- package/README.md +53 -53
- package/bin/mcp-server.js +460 -175
- package/bin/tribunal-kit.js +1245 -987
- package/bin/wrapper.js +104 -74
- package/dist/cli.js +31 -0
- package/dist/commands/case.js +23 -0
- package/dist/commands/compile.js +84 -0
- package/dist/commands/init.js +42 -0
- package/dist/commands/learn.js +57 -0
- package/dist/commands/memory.js +456 -0
- package/package.json +2 -2
- package/scripts/benchmark.js +162 -125
- package/scripts/changelog.js +196 -168
- package/scripts/sync-version.js +94 -81
- package/scripts/validate-payload.js +85 -78
package/bin/tribunal-kit.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* tribunal-kit CLI (alias: tk)
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Commands:
|
|
6
6
|
* init — Install .agent/ into target project
|
|
7
7
|
* update — Re-install to get latest changes
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* case — Manage Case Law precedents
|
|
11
11
|
* hook — Install pre-push git hook
|
|
12
12
|
* uninstall — Remove .agent/ from project
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* Usage:
|
|
15
15
|
* npx tribunal-kit init
|
|
16
16
|
* npx tribunal-kit init --force
|
|
@@ -22,182 +22,225 @@
|
|
|
22
22
|
* tribunal-kit uninstall
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
const fs = require(
|
|
26
|
-
const path = require(
|
|
27
|
-
const https = require(
|
|
28
|
-
const { execSync, spawn } = require(
|
|
29
|
-
|
|
30
|
-
function runShellAsync(command, options) {
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
const child = spawn(command, [], { ...options, shell: true });
|
|
33
|
-
child.on('close', code => {
|
|
34
|
-
if (code !== 0) reject(new Error(`Command failed with exit code ${code}`));
|
|
35
|
-
else resolve();
|
|
36
|
-
});
|
|
37
|
-
child.on('error', reject);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
25
|
+
const fs = require("fs");
|
|
26
|
+
const path = require("path");
|
|
27
|
+
const https = require("https");
|
|
28
|
+
const { execSync, spawn } = require("child_process");
|
|
40
29
|
|
|
41
30
|
/**
|
|
42
31
|
* Safely run a Node.js script with arguments as an array.
|
|
43
32
|
* No shell interpolation — immune to injection.
|
|
44
33
|
*/
|
|
45
34
|
function runScriptAsync(scriptPath, args = [], options = {}) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
51
|
-
child.on('close', code => {
|
|
52
|
-
if (code !== 0) reject(new Error(`Script failed with exit code ${code}`));
|
|
53
|
-
else resolve();
|
|
54
|
-
});
|
|
55
|
-
child.on('error', reject);
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const child = spawn(process.execPath, [scriptPath, ...args], {
|
|
37
|
+
stdio: "inherit",
|
|
38
|
+
...options,
|
|
56
39
|
});
|
|
40
|
+
child.on("close", (code) => {
|
|
41
|
+
if (code !== 0) reject(new Error(`Script failed with exit code ${code}`));
|
|
42
|
+
else resolve();
|
|
43
|
+
});
|
|
44
|
+
child.on("error", reject);
|
|
45
|
+
});
|
|
57
46
|
}
|
|
58
47
|
|
|
59
|
-
const PKG = require(path.resolve(__dirname,
|
|
48
|
+
const PKG = require(path.resolve(__dirname, "..", "package.json"));
|
|
60
49
|
const CURRENT_VERSION = PKG.version;
|
|
61
50
|
|
|
62
51
|
// ── Colors ───────────────────────────────────────────────
|
|
63
52
|
const C = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
53
|
+
reset: "\x1b[0m",
|
|
54
|
+
bold: "\x1b[1m",
|
|
55
|
+
dim: "\x1b[2m",
|
|
56
|
+
red: "\x1b[91m",
|
|
57
|
+
green: "\x1b[92m",
|
|
58
|
+
yellow: "\x1b[93m",
|
|
59
|
+
blue: "\x1b[94m",
|
|
60
|
+
magenta: "\x1b[95m",
|
|
61
|
+
cyan: "\x1b[96m",
|
|
62
|
+
white: "\x1b[97m",
|
|
63
|
+
gray: "\x1b[90m",
|
|
64
|
+
bgCyan: "\x1b[46m",
|
|
76
65
|
};
|
|
77
66
|
|
|
78
67
|
function colorize(color, text) {
|
|
79
|
-
|
|
68
|
+
return `${C[color]}${text}${C.reset}`;
|
|
80
69
|
}
|
|
81
70
|
|
|
82
|
-
function c(color, text) {
|
|
83
|
-
|
|
71
|
+
function c(color, text) {
|
|
72
|
+
return `${C[color]}${text}${C.reset}`;
|
|
73
|
+
}
|
|
74
|
+
function bold(text) {
|
|
75
|
+
return `${C.bold}${text}${C.reset}`;
|
|
76
|
+
}
|
|
84
77
|
|
|
85
78
|
// ── Logging ──────────────────────────────────────────────
|
|
86
79
|
let quiet = false;
|
|
87
80
|
let verbose = false;
|
|
88
81
|
|
|
89
|
-
function log(msg)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
82
|
+
function log(msg) {
|
|
83
|
+
if (!quiet) console.log(msg);
|
|
84
|
+
}
|
|
85
|
+
function ok(msg) {
|
|
86
|
+
if (!quiet) console.log(` ${c("green", "✔")} ${msg}`);
|
|
87
|
+
}
|
|
88
|
+
function warn(msg) {
|
|
89
|
+
if (!quiet) console.log(` ${c("yellow", "⚠")} ${msg}`);
|
|
90
|
+
}
|
|
91
|
+
function err(msg) {
|
|
92
|
+
console.error(` ${c("red", "✖")} ${msg}`);
|
|
93
|
+
}
|
|
94
|
+
function dim(msg) {
|
|
95
|
+
if (!quiet) console.log(` ${c("gray", msg)}`);
|
|
96
|
+
}
|
|
97
|
+
function dbg(msg) {
|
|
98
|
+
if (verbose) console.log(` ${c("gray", "⊡")} ${c("gray", msg)}`);
|
|
99
|
+
}
|
|
95
100
|
|
|
96
101
|
// ── Arg Parser ───────────────────────────────────────────
|
|
97
102
|
function parseArgs(argv) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (arg === '--dry-run') { args.flags.dryRun = true; continue; }
|
|
111
|
-
if (arg === '--minimal') { args.flags.minimal = true; continue; }
|
|
112
|
-
if (arg === '--skip-update-check') { args.flags.skipUpdateCheck = true; continue; }
|
|
113
|
-
if (arg === '--head') { args.flags.head = true; continue; }
|
|
114
|
-
if (arg.startsWith('--path=')) {
|
|
115
|
-
args.flags.path = arg.split('=').slice(1).join('=');
|
|
116
|
-
}
|
|
117
|
-
if (arg === '--path') {
|
|
118
|
-
const idx = raw.indexOf('--path');
|
|
119
|
-
const nextVal = raw[idx + 1];
|
|
120
|
-
if (!nextVal || nextVal.startsWith('--')) {
|
|
121
|
-
console.error(` \x1b[91m✖ --path requires a directory argument\x1b[0m`);
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
args.flags.path = nextVal;
|
|
125
|
-
}
|
|
126
|
-
if (arg.startsWith('--branch=')) {
|
|
127
|
-
args.flags.branch = arg.split('=').slice(1).join('=');
|
|
128
|
-
}
|
|
103
|
+
const args = { command: null, flags: {} };
|
|
104
|
+
const raw = argv.slice(2);
|
|
105
|
+
|
|
106
|
+
// First non-flag arg is the command
|
|
107
|
+
for (const arg of raw) {
|
|
108
|
+
if (!arg.startsWith("--") && !args.command) {
|
|
109
|
+
args.command = arg;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (arg === "--force") {
|
|
113
|
+
args.flags.force = true;
|
|
114
|
+
continue;
|
|
129
115
|
}
|
|
116
|
+
if (arg === "--quiet") {
|
|
117
|
+
args.flags.quiet = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (arg === "--verbose") {
|
|
121
|
+
args.flags.verbose = true;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (arg === "--dry-run") {
|
|
125
|
+
args.flags.dryRun = true;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (arg === "--minimal") {
|
|
129
|
+
args.flags.minimal = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (arg === "--token-optimized") {
|
|
133
|
+
args.flags.tokenOptimized = true;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (arg === "--skip-update-check") {
|
|
137
|
+
args.flags.skipUpdateCheck = true;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (arg === "--head") {
|
|
141
|
+
args.flags.head = true;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (arg.startsWith("--path=")) {
|
|
145
|
+
args.flags.path = arg.split("=").slice(1).join("=");
|
|
146
|
+
}
|
|
147
|
+
if (arg === "--path") {
|
|
148
|
+
const idx = raw.indexOf("--path");
|
|
149
|
+
const nextVal = raw[idx + 1];
|
|
150
|
+
if (!nextVal || nextVal.startsWith("--")) {
|
|
151
|
+
console.error(
|
|
152
|
+
` \x1b[91m✖ --path requires a directory argument\x1b[0m`,
|
|
153
|
+
);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
args.flags.path = nextVal;
|
|
157
|
+
}
|
|
158
|
+
if (arg.startsWith("--branch=")) {
|
|
159
|
+
args.flags.branch = arg.split("=").slice(1).join("=");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
130
162
|
|
|
131
|
-
|
|
163
|
+
return args;
|
|
132
164
|
}
|
|
133
165
|
|
|
134
166
|
// ── File Utilities ────────────────────────────────────────
|
|
135
167
|
|
|
136
168
|
// Core agents to install in --minimal mode
|
|
137
169
|
const CORE_AGENTS = new Set([
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
170
|
+
"backend-specialist.md",
|
|
171
|
+
"frontend-specialist.md",
|
|
172
|
+
"database-architect.md",
|
|
173
|
+
"debugger.md",
|
|
174
|
+
"security-auditor.md",
|
|
175
|
+
"logic-reviewer.md",
|
|
176
|
+
"dependency-reviewer.md",
|
|
177
|
+
"type-safety-reviewer.md",
|
|
178
|
+
"performance-reviewer.md",
|
|
179
|
+
"orchestrator.md",
|
|
180
|
+
"explorer-agent.md",
|
|
181
|
+
"project-planner.md",
|
|
182
|
+
"test-engineer.md",
|
|
151
183
|
]);
|
|
152
184
|
|
|
153
185
|
// Core skills to install in --minimal mode
|
|
154
186
|
const CORE_SKILLS = new Set([
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
187
|
+
"clean-code",
|
|
188
|
+
"architecture",
|
|
189
|
+
"testing-patterns",
|
|
190
|
+
"systematic-debugging",
|
|
191
|
+
"frontend-design",
|
|
192
|
+
"database-design",
|
|
193
|
+
"api-patterns",
|
|
194
|
+
"nodejs-best-practices",
|
|
195
|
+
"vulnerability-scanner",
|
|
196
|
+
"typescript-advanced",
|
|
197
|
+
"python-pro",
|
|
198
|
+
"nextjs-react-expert",
|
|
199
|
+
"react-specialist",
|
|
200
|
+
"performance-profiling",
|
|
201
|
+
"lint-and-validate",
|
|
159
202
|
]);
|
|
160
203
|
|
|
161
204
|
async function copyDir(src, dest, dryRun = false, filter = null) {
|
|
162
|
-
|
|
163
|
-
|
|
205
|
+
if (!dryRun) {
|
|
206
|
+
await fs.promises.mkdir(dest, { recursive: true });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const entries = await fs.promises.readdir(src, { withFileTypes: true });
|
|
210
|
+
let count = 0;
|
|
211
|
+
|
|
212
|
+
for (const entry of entries) {
|
|
213
|
+
// Apply filter if provided (for --minimal mode)
|
|
214
|
+
if (filter && !filter(entry.name, src)) {
|
|
215
|
+
dbg(` skip: ${entry.name}`);
|
|
216
|
+
continue;
|
|
164
217
|
}
|
|
165
218
|
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
for (const entry of entries) {
|
|
170
|
-
// Apply filter if provided (for --minimal mode)
|
|
171
|
-
if (filter && !filter(entry.name, src)) {
|
|
172
|
-
dbg(` skip: ${entry.name}`);
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
219
|
+
const srcPath = path.join(src, entry.name);
|
|
220
|
+
const destPath = path.join(dest, entry.name);
|
|
175
221
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
dbg(` copy: ${entry.name}`);
|
|
186
|
-
count++;
|
|
187
|
-
}
|
|
222
|
+
if (entry.isDirectory()) {
|
|
223
|
+
count += await copyDir(srcPath, destPath, dryRun, filter);
|
|
224
|
+
} else {
|
|
225
|
+
if (!dryRun) {
|
|
226
|
+
await fs.promises.copyFile(srcPath, destPath);
|
|
227
|
+
}
|
|
228
|
+
dbg(` copy: ${entry.name}`);
|
|
229
|
+
count++;
|
|
188
230
|
}
|
|
231
|
+
}
|
|
189
232
|
|
|
190
|
-
|
|
233
|
+
return count;
|
|
191
234
|
}
|
|
192
235
|
|
|
193
236
|
async function countDir(dir) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
237
|
+
let count = 0;
|
|
238
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
239
|
+
for (const e of entries) {
|
|
240
|
+
if (e.isDirectory()) count += await countDir(path.join(dir, e.name));
|
|
241
|
+
else count++;
|
|
242
|
+
}
|
|
243
|
+
return count;
|
|
201
244
|
}
|
|
202
245
|
|
|
203
246
|
// ── Version Check & Auto-Update ──────────────────────────
|
|
@@ -207,15 +250,15 @@ async function countDir(dir) {
|
|
|
207
250
|
* 1 if a > b, -1 if a < b, 0 if equal.
|
|
208
251
|
*/
|
|
209
252
|
function compareSemver(a, b) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
253
|
+
const pa = a.replace(/^v/, "").split(".").map(Number);
|
|
254
|
+
const pb = b.replace(/^v/, "").split(".").map(Number);
|
|
255
|
+
for (let i = 0; i < 3; i++) {
|
|
256
|
+
const na = pa[i] || 0;
|
|
257
|
+
const nb = pb[i] || 0;
|
|
258
|
+
if (na > nb) return 1;
|
|
259
|
+
if (na < nb) return -1;
|
|
260
|
+
}
|
|
261
|
+
return 0;
|
|
219
262
|
}
|
|
220
263
|
|
|
221
264
|
/**
|
|
@@ -223,33 +266,38 @@ function compareSemver(a, b) {
|
|
|
223
266
|
* Returns the version string (e.g. '4.0.0') or null on failure.
|
|
224
267
|
*/
|
|
225
268
|
function fetchLatestVersion() {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
|
|
269
|
+
return new Promise((resolve) => {
|
|
270
|
+
const req = https.get(
|
|
271
|
+
"https://registry.npmjs.org/tribunal-kit/latest",
|
|
272
|
+
{
|
|
273
|
+
headers: {
|
|
274
|
+
Accept: "application/json",
|
|
275
|
+
"User-Agent": `tribunal-kit/${CURRENT_VERSION}`,
|
|
276
|
+
},
|
|
277
|
+
timeout: 5000,
|
|
278
|
+
},
|
|
279
|
+
(res) => {
|
|
280
|
+
let data = "";
|
|
281
|
+
res.on("data", (chunk) => {
|
|
282
|
+
data += chunk;
|
|
283
|
+
});
|
|
284
|
+
res.on("end", () => {
|
|
285
|
+
try {
|
|
286
|
+
const json = JSON.parse(data);
|
|
287
|
+
const version = json.version || null;
|
|
288
|
+
resolve(version);
|
|
289
|
+
} catch {
|
|
290
|
+
resolve(null);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
req.on("error", () => resolve(null));
|
|
296
|
+
req.on("timeout", () => {
|
|
297
|
+
req.destroy();
|
|
298
|
+
resolve(null);
|
|
252
299
|
});
|
|
300
|
+
});
|
|
253
301
|
}
|
|
254
302
|
|
|
255
303
|
/**
|
|
@@ -258,61 +306,66 @@ function fetchLatestVersion() {
|
|
|
258
306
|
* Returns true if a re-invoke happened (caller should exit), false otherwise.
|
|
259
307
|
*/
|
|
260
308
|
async function autoUpdateCheck(originalArgs) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
log(' Checking for updates...');
|
|
267
|
-
const latestVersion = await fetchLatestVersion();
|
|
268
|
-
|
|
269
|
-
if (!latestVersion) {
|
|
270
|
-
// Network fail — proceed silently with current version
|
|
271
|
-
return false;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (compareSemver(latestVersion, CURRENT_VERSION) <= 0) {
|
|
275
|
-
// Already up to date
|
|
276
|
-
dim(`Version ${CURRENT_VERSION} is up to date.`);
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
309
|
+
// Recursion guard: if we're already a re-invoked process, skip
|
|
310
|
+
if (process.env.TK_SKIP_UPDATE_CHECK === "1") {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
279
313
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
log(colorize('cyan', ` ⬆ New version available: ${colorize('bold', CURRENT_VERSION)} → ${colorize('bold', latestVersion)}`));
|
|
283
|
-
log(colorize('gray', ' Re-invoking with latest version...'));
|
|
284
|
-
log('');
|
|
314
|
+
log(" Checking for updates...");
|
|
315
|
+
const latestVersion = await fetchLatestVersion();
|
|
285
316
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
317
|
+
if (!latestVersion) {
|
|
318
|
+
// Network fail — proceed silently with current version
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
290
321
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
322
|
+
if (compareSemver(latestVersion, CURRENT_VERSION) <= 0) {
|
|
323
|
+
// Already up to date
|
|
324
|
+
dim(`Version ${CURRENT_VERSION} is up to date.`);
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Newer version available — re-invoke
|
|
329
|
+
log("");
|
|
330
|
+
log(
|
|
331
|
+
colorize(
|
|
332
|
+
"cyan",
|
|
333
|
+
` ⬆ New version available: ${colorize("bold", CURRENT_VERSION)} → ${colorize("bold", latestVersion)}`,
|
|
334
|
+
),
|
|
335
|
+
);
|
|
336
|
+
log(colorize("gray", " Re-invoking with latest version..."));
|
|
337
|
+
log("");
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
// Build the command pulling from npm registry
|
|
341
|
+
const args = originalArgs.join(" ");
|
|
342
|
+
const cmd = `npx -y tribunal-kit@${latestVersion} ${args}`;
|
|
343
|
+
|
|
344
|
+
execSync(cmd, {
|
|
345
|
+
stdio: "inherit",
|
|
346
|
+
env: { ...process.env, TK_SKIP_UPDATE_CHECK: "1" },
|
|
347
|
+
});
|
|
348
|
+
return true; // Re-invoke succeeded, caller should exit
|
|
349
|
+
} catch (e) {
|
|
350
|
+
warn(`Auto-update failed: ${e.message}`);
|
|
351
|
+
warn("Continuing with current version...");
|
|
352
|
+
return false; // Fall through to current version
|
|
353
|
+
}
|
|
301
354
|
}
|
|
302
355
|
|
|
303
356
|
// ── Kit Source Location ───────────────────────────────────
|
|
304
357
|
function getKitAgent() {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
358
|
+
// When installed via npm, the .agent/ folder is next to this script's package
|
|
359
|
+
const kitRoot = path.resolve(__dirname, "..");
|
|
360
|
+
const agentDir = path.join(kitRoot, ".agent");
|
|
308
361
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
362
|
+
if (!fs.existsSync(agentDir)) {
|
|
363
|
+
err(`Kit .agent/ folder not found at: ${agentDir}`);
|
|
364
|
+
err("The package may be corrupted. Try: npm install -g tribunal-kit");
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
314
367
|
|
|
315
|
-
|
|
368
|
+
return agentDir;
|
|
316
369
|
}
|
|
317
370
|
|
|
318
371
|
// ── Self-Install Guard ────────────────────────────────────
|
|
@@ -322,351 +375,432 @@ function getKitAgent() {
|
|
|
322
375
|
* when run from inside the project directory.
|
|
323
376
|
*/
|
|
324
377
|
function isSelfInstall(targetDir) {
|
|
325
|
-
|
|
326
|
-
|
|
378
|
+
const kitRoot = path.resolve(__dirname, "..");
|
|
379
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
327
380
|
|
|
328
|
-
|
|
329
|
-
|
|
381
|
+
// Direct path match
|
|
382
|
+
if (resolvedTarget === kitRoot) return true;
|
|
330
383
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
384
|
+
// Check if the target's package.json is this package
|
|
385
|
+
const targetPkg = path.join(resolvedTarget, "package.json");
|
|
386
|
+
if (fs.existsSync(targetPkg)) {
|
|
387
|
+
try {
|
|
388
|
+
const targetName = JSON.parse(fs.readFileSync(targetPkg, "utf8")).name;
|
|
389
|
+
if (targetName === PKG.name) return true;
|
|
390
|
+
} catch {
|
|
391
|
+
// Unreadable package.json — not a match
|
|
340
392
|
}
|
|
393
|
+
}
|
|
341
394
|
|
|
342
|
-
|
|
395
|
+
return false;
|
|
343
396
|
}
|
|
344
397
|
|
|
345
398
|
// ── Banner ────────────────────────────────────────────────
|
|
346
399
|
function banner() {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
400
|
+
if (quiet) return;
|
|
401
|
+
// Big ASCII art (TRIBUNAL-KIT)
|
|
402
|
+
const art = String.raw`
|
|
350
403
|
████████╗██████╗ ██╗██████╗ ██╗ ██╗███╗ ██╗ █████╗ ██╗ ██╗ ██╗██╗████████╗
|
|
351
404
|
╚══██╔══╝██╔══██╗██║██╔══██╗██║ ██║████╗ ██║██╔══██╗██║ ██║ ██╔╝██║╚══██╔══╝
|
|
352
405
|
██║ ██████╔╝██║██████╔╝██║ ██║██╔██╗ ██║███████║██║█████╗█████╔╝ ██║ ██║
|
|
353
406
|
██║ ██╔══██╗██║██╔══██╗██║ ██║██║╚██╗██║██╔══██║██║╚════╝██╔═██╗ ██║ ██║
|
|
354
407
|
██║ ██║ ██║██║██████╔╝╚██████╔╝██║ ╚████║██║ ██║███████╗ ██║ ██╗██║ ██║
|
|
355
|
-
╚═╝ ╚═╝ ╚═╝╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
log(gradientLine);
|
|
408
|
+
╚═╝ ╚═╝ ╚═╝╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ `
|
|
409
|
+
.split("\n")
|
|
410
|
+
.filter(Boolean);
|
|
411
|
+
console.log();
|
|
412
|
+
const _maxLen = Math.max(...art.map((line) => line.length));
|
|
413
|
+
for (const line of art) {
|
|
414
|
+
let gradientLine = " " + C.bold;
|
|
415
|
+
for (let i = 0; i < line.length; i++) {
|
|
416
|
+
gradientLine += `\x1b[38;2;255;22;55m${line[i]}`;
|
|
365
417
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
418
|
+
gradientLine += C.reset;
|
|
419
|
+
log(gradientLine);
|
|
420
|
+
}
|
|
421
|
+
console.log();
|
|
422
|
+
// Subtitle strip
|
|
423
|
+
const W = 84;
|
|
424
|
+
const sub = "Anti-Hallucination Agent System";
|
|
425
|
+
const sp = Math.max(0, W - sub.length);
|
|
426
|
+
const centred =
|
|
427
|
+
" ".repeat(Math.floor(sp / 2)) + sub + " ".repeat(Math.ceil(sp / 2));
|
|
428
|
+
const RED_ANSI = "\x1b[38;2;255;22;55m";
|
|
429
|
+
console.log(` ${RED_ANSI}╔${"═".repeat(W)}╗${C.reset}`);
|
|
430
|
+
console.log(
|
|
431
|
+
` ${RED_ANSI}║${C.reset}${c("gray", centred)}${RED_ANSI}║${C.reset}`,
|
|
432
|
+
);
|
|
433
|
+
console.log(` ${RED_ANSI}╚${"═".repeat(W)}╝${C.reset}`);
|
|
434
|
+
console.log();
|
|
377
435
|
}
|
|
378
436
|
|
|
379
437
|
// ── Commands ──────────────────────────────────────────────
|
|
380
438
|
async function cmdInit(flags) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
439
|
+
const agentSrc = getKitAgent();
|
|
440
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
441
|
+
const agentDest = path.join(targetDir, ".agent");
|
|
442
|
+
const dryRun = flags.dryRun || false;
|
|
443
|
+
|
|
444
|
+
// ── Self-install guard ──────────────────────────────────
|
|
445
|
+
if (isSelfInstall(targetDir)) {
|
|
446
|
+
err("Cannot run init/update inside the tribunal-kit package itself.");
|
|
447
|
+
err(`Target: ${targetDir}`);
|
|
448
|
+
err(`Package: ${path.resolve(__dirname, "..")}`);
|
|
449
|
+
console.log();
|
|
450
|
+
dim("This command is designed to install .agent/ into OTHER projects.");
|
|
451
|
+
dim("Run it from the root of the project you want to set up:");
|
|
452
|
+
dim(" cd /path/to/your-project");
|
|
453
|
+
dim(" npx tribunal-kit init");
|
|
454
|
+
console.log();
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
// ────────────────────────────────────────────────────────
|
|
458
|
+
|
|
459
|
+
// ── Backup / Cleanup ────────────────────────────────────
|
|
460
|
+
if (!dryRun && fs.existsSync(agentDest) && flags.force) {
|
|
461
|
+
// Backup the existing subdirectories before overwriting
|
|
462
|
+
const backupDir = path.join(agentDest, ".backups", `backup-${Date.now()}`);
|
|
463
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
464
|
+
|
|
465
|
+
const subdirs = [
|
|
466
|
+
"agents",
|
|
467
|
+
"workflows",
|
|
468
|
+
"skills",
|
|
469
|
+
"scripts",
|
|
470
|
+
".shared",
|
|
471
|
+
"rules",
|
|
472
|
+
];
|
|
473
|
+
for (const sub of subdirs) {
|
|
474
|
+
const subPath = path.join(agentDest, sub);
|
|
475
|
+
if (fs.existsSync(subPath)) {
|
|
476
|
+
// Copy to backup dir
|
|
477
|
+
await copyDir(subPath, path.join(backupDir, sub), false);
|
|
478
|
+
// Removed aggressive deletion so user custom files persist
|
|
479
|
+
}
|
|
398
480
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const backupDir = path.join(agentDest, '.backups', `backup-${Date.now()}`);
|
|
405
|
-
fs.mkdirSync(backupDir, { recursive: true });
|
|
406
|
-
|
|
407
|
-
const subdirs = ['agents', 'workflows', 'skills', 'scripts', '.shared', 'rules'];
|
|
408
|
-
for (const sub of subdirs) {
|
|
409
|
-
const subPath = path.join(agentDest, sub);
|
|
410
|
-
if (fs.existsSync(subPath)) {
|
|
411
|
-
// Copy to backup dir
|
|
412
|
-
await copyDir(subPath, path.join(backupDir, sub), false);
|
|
413
|
-
// Removed aggressive deletion so user custom files persist
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
log(` ${c('gray', '✦ Backed up existing configurations to .agent/.backups/')}`);
|
|
481
|
+
log(
|
|
482
|
+
` ${c("gray", "✦ Backed up existing configurations to .agent/.backups/")}`,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
// ────────────────────────────────────────────────────────
|
|
417
486
|
|
|
487
|
+
banner();
|
|
418
488
|
|
|
419
|
-
|
|
420
|
-
|
|
489
|
+
if (dryRun) {
|
|
490
|
+
log(colorize("yellow", " DRY RUN — no files will be written"));
|
|
491
|
+
console.log();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Check target exists
|
|
495
|
+
if (!fs.existsSync(targetDir)) {
|
|
496
|
+
err(`Target directory not found: ${targetDir}`);
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Check if .agent already exists
|
|
501
|
+
if (fs.existsSync(agentDest) && !flags.force) {
|
|
502
|
+
warn(".agent/ already exists in this project.");
|
|
503
|
+
log(
|
|
504
|
+
` ${c("gray", "▸")} To refresh or update it, run: ${colorize("white", "tribunal-kit init --force")}`,
|
|
505
|
+
);
|
|
506
|
+
log(
|
|
507
|
+
` ${c("gray", "▸")} Or check status with: ${colorize("cyan", "tribunal-kit status")}`,
|
|
508
|
+
);
|
|
509
|
+
console.log();
|
|
510
|
+
process.exit(0);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Ensure history dirs exist (Case Law + Skill Evolution)
|
|
514
|
+
if (!dryRun) {
|
|
515
|
+
const caseDir = path.join(agentDest, "history", "case-law", "cases");
|
|
516
|
+
const evoDir = path.join(agentDest, "history", "skill-evolution");
|
|
517
|
+
fs.mkdirSync(caseDir, { recursive: true });
|
|
518
|
+
fs.mkdirSync(evoDir, { recursive: true });
|
|
519
|
+
const gkCase = path.join(caseDir, ".gitkeep");
|
|
520
|
+
const gkEvo = path.join(evoDir, ".gitkeep");
|
|
521
|
+
if (!fs.existsSync(gkCase)) fs.writeFileSync(gkCase, "");
|
|
522
|
+
if (!fs.existsSync(gkEvo)) fs.writeFileSync(gkEvo, "");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Count what we're installing
|
|
526
|
+
const isMinimal = flags.minimal || false;
|
|
527
|
+
if (isMinimal) {
|
|
528
|
+
log(
|
|
529
|
+
` ${c("yellow", "⚡")} ${bold("Minimal mode")} — installing core agents and skills only`,
|
|
530
|
+
);
|
|
531
|
+
console.log();
|
|
532
|
+
}
|
|
533
|
+
const totalFiles = await countDir(agentSrc);
|
|
534
|
+
dbg(`Source: ${agentSrc}`);
|
|
535
|
+
dbg(`Target: ${agentDest}`);
|
|
536
|
+
dbg(`Total source files: ${totalFiles}`);
|
|
537
|
+
log(
|
|
538
|
+
` ${c("gray", "▸")} Scanning ${c("white", String(totalFiles))} files ${c("gray", "→")} ${c("gray", agentDest)}`,
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
// Build filter for --minimal mode
|
|
543
|
+
const minimalFilter = isMinimal
|
|
544
|
+
? (name, parentDir) => {
|
|
545
|
+
const parentName = path.basename(parentDir);
|
|
546
|
+
if (parentName === "agents") return CORE_AGENTS.has(name);
|
|
547
|
+
if (parentName === "skills") return CORE_SKILLS.has(name);
|
|
548
|
+
return true; // everything else passes
|
|
549
|
+
}
|
|
550
|
+
: null;
|
|
421
551
|
|
|
422
|
-
|
|
552
|
+
const copied = await copyDir(agentSrc, agentDest, dryRun, minimalFilter);
|
|
423
553
|
|
|
554
|
+
console.log();
|
|
424
555
|
if (dryRun) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
556
|
+
ok(
|
|
557
|
+
`${bold("DRY RUN")} complete — would install ${c("cyan", String(copied))} files`,
|
|
558
|
+
);
|
|
559
|
+
dim(`Target: ${agentDest}`);
|
|
560
|
+
} else {
|
|
561
|
+
// ── Success card — W=62, rows padded by plain-text length ──
|
|
562
|
+
const W = 62;
|
|
563
|
+
const agentsCount = fs.readdirSync(path.join(agentDest, "agents")).length;
|
|
564
|
+
const workflowsCount = fs.readdirSync(
|
|
565
|
+
path.join(agentDest, "workflows"),
|
|
566
|
+
).length;
|
|
567
|
+
const skillsCount = fs.readdirSync(path.join(agentDest, "skills")).length;
|
|
568
|
+
const scriptsCount = fs.readdirSync(
|
|
569
|
+
path.join(agentDest, "scripts"),
|
|
570
|
+
).length;
|
|
571
|
+
|
|
572
|
+
// Stat rows: compute trailing spaces from plain text so right ║ aligns
|
|
573
|
+
const statRow = (icon, label, val, col) => {
|
|
574
|
+
// emoji JS .length===2 == terminal display width 2 ✓
|
|
575
|
+
const plain = ` ${icon} ${label.padEnd(10)}${String(val).padStart(3)} installed`;
|
|
576
|
+
const trail = " ".repeat(Math.max(0, W - plain.length));
|
|
577
|
+
return ` ${c("cyan", "║")} ${icon} ${c("white", label.padEnd(10))}${c(col, String(val).padStart(3))} ${c("gray", "installed")}${trail}${c("cyan", "║")}`;
|
|
578
|
+
};
|
|
579
|
+
// Plain-text rows (header / blank)
|
|
580
|
+
const plainRow = (text, wrapFn) => {
|
|
581
|
+
const trail = " ".repeat(Math.max(0, W - text.length));
|
|
582
|
+
return ` ${c("cyan", "║")}${wrapFn(text)}${trail}${c("cyan", "║")}`;
|
|
583
|
+
};
|
|
584
|
+
// Next-step rows: fixed cmd column + description
|
|
585
|
+
const stepRow = (cmd, desc) => {
|
|
586
|
+
const plain = ` ${cmd.padEnd(16)}${desc}`;
|
|
587
|
+
const trail = " ".repeat(Math.max(0, W - plain.length));
|
|
588
|
+
return ` ${c("cyan", "║")} ${c("white", cmd.padEnd(16))}${c("gray", desc)}${trail}${c("cyan", "║")}`;
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
console.log(
|
|
592
|
+
` ${c("green", "✔")} ${bold(c("green", "Installation complete"))} ${c("gray", "—")} ${c("white", String(copied))} files`,
|
|
593
|
+
);
|
|
594
|
+
console.log(` ${c("gray", " ╰─")} ${c("gray", agentDest)}`);
|
|
595
|
+
console.log();
|
|
596
|
+
console.log(` ${c("cyan", "╔" + "═".repeat(W) + "╗")}`);
|
|
597
|
+
console.log(
|
|
598
|
+
plainRow(` What's inside:`, (s) => c("bold", c("white", s))),
|
|
599
|
+
);
|
|
600
|
+
console.log(` ${c("cyan", "╠" + "═".repeat(W) + "╣")}`);
|
|
601
|
+
console.log(statRow("🤖", "Agents", agentsCount, "magenta"));
|
|
602
|
+
console.log(statRow("⚡", "Workflows", workflowsCount, "yellow"));
|
|
603
|
+
console.log(statRow("🧠", "Skills", skillsCount, "blue"));
|
|
604
|
+
console.log(statRow("🔧", "Scripts", scriptsCount, "green"));
|
|
605
|
+
console.log(` ${c("cyan", "╠" + "═".repeat(W) + "╣")}`);
|
|
606
|
+
console.log(plainRow("", () => ""));
|
|
607
|
+
console.log(plainRow(` Next steps:`, (s) => c("gray", s)));
|
|
608
|
+
console.log(
|
|
609
|
+
stepRow("/generate", "Generate code with anti-hallucination"),
|
|
610
|
+
);
|
|
611
|
+
console.log(stepRow("/review", "Audit existing code for issues"));
|
|
612
|
+
console.log(
|
|
613
|
+
stepRow("/tribunal-full", "Run all 16 reviewers in parallel"),
|
|
614
|
+
);
|
|
615
|
+
console.log(plainRow("", () => ""));
|
|
616
|
+
console.log(` ${c("cyan", "╚" + "═".repeat(W) + "╝")}`);
|
|
617
|
+
console.log();
|
|
618
|
+
log(` ${c("gray", "✦ Updating .gitignore...")}`);
|
|
619
|
+
await updateGitignore(targetDir, dryRun);
|
|
620
|
+
log(` ${c("gray", "✦ Generating IDE bridge files...")}`);
|
|
621
|
+
await generateIDEBridges(targetDir, agentDest, dryRun, flags.tokenOptimized || false);
|
|
461
622
|
}
|
|
462
|
-
const totalFiles = await countDir(agentSrc);
|
|
463
|
-
dbg(`Source: ${agentSrc}`);
|
|
464
|
-
dbg(`Target: ${agentDest}`);
|
|
465
|
-
dbg(`Total source files: ${totalFiles}`);
|
|
466
|
-
log(` ${c('gray','▸')} Scanning ${c('white', String(totalFiles))} files ${c('gray','→')} ${c('gray', agentDest)}`);
|
|
467
623
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (parentName === 'skills') return CORE_SKILLS.has(name);
|
|
474
|
-
return true; // everything else passes
|
|
475
|
-
} : null;
|
|
476
|
-
|
|
477
|
-
const copied = await copyDir(agentSrc, agentDest, dryRun, minimalFilter);
|
|
478
|
-
|
|
479
|
-
console.log();
|
|
480
|
-
if (dryRun) {
|
|
481
|
-
ok(`${bold('DRY RUN')} complete — would install ${c('cyan', String(copied))} files`);
|
|
482
|
-
dim(`Target: ${agentDest}`);
|
|
483
|
-
} else {
|
|
484
|
-
// ── Success card — W=62, rows padded by plain-text length ──
|
|
485
|
-
const W = 62;
|
|
486
|
-
const agentsCount = fs.readdirSync(path.join(agentDest, 'agents')).length;
|
|
487
|
-
const workflowsCount = fs.readdirSync(path.join(agentDest, 'workflows')).length;
|
|
488
|
-
const skillsCount = fs.readdirSync(path.join(agentDest, 'skills')).length;
|
|
489
|
-
const scriptsCount = fs.readdirSync(path.join(agentDest, 'scripts')).length;
|
|
490
|
-
|
|
491
|
-
// Stat rows: compute trailing spaces from plain text so right ║ aligns
|
|
492
|
-
const statRow = (icon, label, val, col) => {
|
|
493
|
-
// emoji JS .length===2 == terminal display width 2 ✓
|
|
494
|
-
const plain = ` ${icon} ${label.padEnd(10)}${String(val).padStart(3)} installed`;
|
|
495
|
-
const trail = ' '.repeat(Math.max(0, W - plain.length));
|
|
496
|
-
return ` ${c('cyan','║')} ${icon} ${c('white',label.padEnd(10))}${c(col,String(val).padStart(3))} ${c('gray','installed')}${trail}${c('cyan','║')}`;
|
|
497
|
-
};
|
|
498
|
-
// Plain-text rows (header / blank)
|
|
499
|
-
const plainRow = (text, wrapFn) => {
|
|
500
|
-
const trail = ' '.repeat(Math.max(0, W - text.length));
|
|
501
|
-
return ` ${c('cyan','║')}${wrapFn(text)}${trail}${c('cyan','║')}`;
|
|
502
|
-
};
|
|
503
|
-
// Next-step rows: fixed cmd column + description
|
|
504
|
-
const stepRow = (cmd, desc) => {
|
|
505
|
-
const plain = ` ${cmd.padEnd(16)}${desc}`;
|
|
506
|
-
const trail = ' '.repeat(Math.max(0, W - plain.length));
|
|
507
|
-
return ` ${c('cyan','║')} ${c('white',cmd.padEnd(16))}${c('gray',desc)}${trail}${c('cyan','║')}`;
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
console.log(` ${c('green','✔')} ${bold(c('green','Installation complete'))} ${c('gray','—')} ${c('white',String(copied))} files`);
|
|
511
|
-
console.log(` ${c('gray',' ╰─')} ${c('gray', agentDest)}`);
|
|
512
|
-
console.log();
|
|
513
|
-
console.log(` ${c('cyan', '╔' + '═'.repeat(W) + '╗')}`);
|
|
514
|
-
console.log(plainRow(` What's inside:`, s => c('bold', c('white', s))));
|
|
515
|
-
console.log(` ${c('cyan', '╠' + '═'.repeat(W) + '╣')}`);
|
|
516
|
-
console.log(statRow('🤖', 'Agents', agentsCount, 'magenta'));
|
|
517
|
-
console.log(statRow('⚡', 'Workflows', workflowsCount, 'yellow'));
|
|
518
|
-
console.log(statRow('🧠', 'Skills', skillsCount, 'blue'));
|
|
519
|
-
console.log(statRow('🔧', 'Scripts', scriptsCount, 'green'));
|
|
520
|
-
console.log(` ${c('cyan', '╠' + '═'.repeat(W) + '╣')}`);
|
|
521
|
-
console.log(plainRow('', () => ''));
|
|
522
|
-
console.log(plainRow(` Next steps:`, s => c('gray', s)));
|
|
523
|
-
console.log(stepRow('/generate', 'Generate code with anti-hallucination'));
|
|
524
|
-
console.log(stepRow('/review', 'Audit existing code for issues'));
|
|
525
|
-
console.log(stepRow('/tribunal-full', 'Run all 16 reviewers in parallel'));
|
|
526
|
-
console.log(plainRow('', () => ''));
|
|
527
|
-
console.log(` ${c('cyan', '╚' + '═'.repeat(W) + '╝')}`);
|
|
528
|
-
console.log();
|
|
529
|
-
log(` ${c('gray', '✦ Updating .gitignore...')}`);
|
|
530
|
-
await updateGitignore(targetDir, dryRun);
|
|
531
|
-
log(` ${c('gray', '✦ Generating IDE bridge files...')}`);
|
|
532
|
-
await generateIDEBridges(targetDir, agentDest, dryRun);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
console.log();
|
|
536
|
-
} catch (e) {
|
|
537
|
-
err(`Failed to install: ${e.message}`);
|
|
538
|
-
process.exit(1);
|
|
539
|
-
}
|
|
624
|
+
console.log();
|
|
625
|
+
} catch (e) {
|
|
626
|
+
err(`Failed to install: ${e.message}`);
|
|
627
|
+
process.exit(1);
|
|
628
|
+
}
|
|
540
629
|
}
|
|
541
630
|
|
|
542
631
|
// ── Gitignore Management ──────────────────────────────────
|
|
543
632
|
async function updateGitignore(targetDir, dryRun = false) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
await fs.promises.writeFile(gitignorePath, content, 'utf8');
|
|
562
|
-
dbg(' Updated .gitignore');
|
|
633
|
+
if (dryRun) return;
|
|
634
|
+
const gitignorePath = path.join(targetDir, ".gitignore");
|
|
635
|
+
const entries = [".agent/.backups/", ".agent/history/"];
|
|
636
|
+
let content = "";
|
|
637
|
+
try {
|
|
638
|
+
content = await fs.promises.readFile(gitignorePath, "utf8");
|
|
639
|
+
} catch (err) {
|
|
640
|
+
if (err.code !== "ENOENT") throw err;
|
|
641
|
+
}
|
|
642
|
+
let appended = false;
|
|
643
|
+
for (const entry of entries) {
|
|
644
|
+
if (!content.includes(entry)) {
|
|
645
|
+
content +=
|
|
646
|
+
(content.length > 0 && !content.endsWith("\n") ? "\n" : "") +
|
|
647
|
+
entry +
|
|
648
|
+
"\n";
|
|
649
|
+
appended = true;
|
|
563
650
|
}
|
|
651
|
+
}
|
|
652
|
+
if (appended) {
|
|
653
|
+
await fs.promises.writeFile(gitignorePath, content, "utf8");
|
|
654
|
+
dbg(" Updated .gitignore");
|
|
655
|
+
}
|
|
564
656
|
}
|
|
565
657
|
|
|
566
658
|
// ── IDE Bridge Files ──────────────────────────────────────
|
|
567
659
|
// Each AI IDE reads rules from a different location.
|
|
568
660
|
// We generate bridge files that point each IDE at .agent/
|
|
569
|
-
async function generateIDEBridges(targetDir, agentDest, dryRun = false) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
661
|
+
async function generateIDEBridges(targetDir, agentDest, dryRun = false, tokenOptimized = false) {
|
|
662
|
+
const rulesFile = path.join(agentDest, "rules", "GEMINI.md");
|
|
663
|
+
let rulesContent = "";
|
|
664
|
+
try {
|
|
665
|
+
rulesContent = await fs.promises.readFile(rulesFile, "utf8");
|
|
666
|
+
} catch {
|
|
667
|
+
// rules file doesn't exist
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
let rulesToInject = rulesContent;
|
|
671
|
+
if (tokenOptimized) {
|
|
672
|
+
rulesToInject = `---
|
|
673
|
+
trigger: always_on
|
|
674
|
+
---
|
|
577
675
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
676
|
+
# Tribunal Kit — Token-Optimized Mode
|
|
677
|
+
|
|
678
|
+
You are running under Tribunal Kit in token-optimized mode. To minimize prompt token usage by 85% and avoid context poisoning:
|
|
679
|
+
|
|
680
|
+
1. **Get Sparse Context**: Call the \`get_sparse_context\` tool on the \`tribunal-kit\` MCP server at the start of any feature build, bug fix, or refactor. Pass the active task description and files to retrieve a dynamically tailored, minified ruleset.
|
|
681
|
+
2. **Dynamic Rule Enforcements**: Follow all rules returned by the MCP context tool. Do NOT load full raw markdown files from \`.agent/\` to prevent context bloat.
|
|
682
|
+
3. **Verify Precedents**: Search historical rejections using \`search_case_law\` before editing code.
|
|
683
|
+
|
|
684
|
+
## Critical Core Constraints
|
|
685
|
+
- Parameterize all SQL queries.
|
|
686
|
+
- Keep secrets in environment variables.
|
|
687
|
+
- Run tests with \`npm test\` after any changes.
|
|
688
|
+
`;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Helper: write a bridge file or merge it if it exists
|
|
692
|
+
const writeBridge = async (filePath, content, label, isJson = false) => {
|
|
693
|
+
if (dryRun) {
|
|
694
|
+
dbg(` would create/update: ${filePath}`);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const dir = path.dirname(filePath);
|
|
698
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
586
699
|
|
|
700
|
+
try {
|
|
701
|
+
const existingContent = await fs.promises.readFile(filePath, "utf8");
|
|
702
|
+
if (isJson) {
|
|
587
703
|
try {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
704
|
+
const existingData = JSON.parse(existingContent);
|
|
705
|
+
const newData = JSON.parse(content);
|
|
706
|
+
|
|
707
|
+
if (!existingData.rules) existingData.rules = [];
|
|
708
|
+
const rulePath = newData.rules[0].path;
|
|
709
|
+
|
|
710
|
+
// Clear both standard and optimized paths to prevent duplicates/stale paths
|
|
711
|
+
existingData.rules = existingData.rules.filter(
|
|
712
|
+
(r) => r.path !== "GEMINI.md" && r.path !== "../.agent/rules/GEMINI.md"
|
|
713
|
+
);
|
|
714
|
+
existingData.rules.push(newData.rules[0]);
|
|
715
|
+
|
|
716
|
+
existingData.agents = { ...existingData.agents, ...newData.agents };
|
|
717
|
+
existingData.skills = { ...existingData.skills, ...newData.skills };
|
|
718
|
+
existingData.workflows = {
|
|
719
|
+
...existingData.workflows,
|
|
720
|
+
...newData.workflows,
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
await fs.promises.writeFile(
|
|
724
|
+
filePath,
|
|
725
|
+
JSON.stringify(existingData, null, 2) + "\n",
|
|
726
|
+
"utf8",
|
|
727
|
+
);
|
|
728
|
+
ok(
|
|
729
|
+
`${label} (merged) → ${c("gray", path.relative(targetDir, filePath))}`,
|
|
730
|
+
);
|
|
731
|
+
} catch (e) {
|
|
732
|
+
warn(`Failed to merge ${label}: ${e.message}`);
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
if (
|
|
736
|
+
!existingContent.includes("Tribunal Kit") &&
|
|
737
|
+
(!rulesToInject ||
|
|
738
|
+
!existingContent.includes(rulesToInject.slice(0, 50)))
|
|
739
|
+
) {
|
|
740
|
+
await fs.promises.appendFile(filePath, "\n" + content, "utf8");
|
|
741
|
+
ok(
|
|
742
|
+
`${label} (appended) → ${c("gray", path.relative(targetDir, filePath))}`,
|
|
743
|
+
);
|
|
744
|
+
} else {
|
|
745
|
+
dbg(` skip (rules exist): ${path.basename(filePath)}`);
|
|
623
746
|
}
|
|
624
|
-
|
|
747
|
+
}
|
|
748
|
+
} catch (err) {
|
|
749
|
+
if (err.code === "ENOENT") {
|
|
750
|
+
await fs.promises.writeFile(filePath, content, "utf8");
|
|
751
|
+
ok(`${label} → ${c("gray", path.relative(targetDir, filePath))}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
};
|
|
625
755
|
|
|
626
|
-
|
|
627
|
-
|
|
756
|
+
// ── 1. Cursor (.cursorrules) ──────────────────────────
|
|
757
|
+
const cursorRules = `# Tribunal Kit — Cursor Bridge
|
|
628
758
|
# Auto-generated by tribunal-kit init. Do not edit manually.
|
|
629
|
-
# Source: .agent/rules/GEMINI.md
|
|
759
|
+
# Source: .agent/rules/GEMINI.md (Optimized)
|
|
630
760
|
|
|
631
|
-
${
|
|
761
|
+
${rulesToInject}
|
|
632
762
|
`;
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
763
|
+
await writeBridge(
|
|
764
|
+
path.join(targetDir, ".cursorrules"),
|
|
765
|
+
cursorRules,
|
|
766
|
+
"Cursor",
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
// ── 2. Windsurf (.windsurfrules) ─────────────────────
|
|
770
|
+
const windsurfRules = `# Tribunal Kit — Windsurf Bridge
|
|
641
771
|
# Auto-generated by tribunal-kit init. Do not edit manually.
|
|
642
|
-
# Source: .agent/rules/GEMINI.md
|
|
772
|
+
# Source: .agent/rules/GEMINI.md (Optimized)
|
|
643
773
|
|
|
644
|
-
${
|
|
774
|
+
${rulesToInject}
|
|
645
775
|
`;
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
)
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
776
|
+
await writeBridge(
|
|
777
|
+
path.join(targetDir, ".windsurfrules"),
|
|
778
|
+
windsurfRules,
|
|
779
|
+
"Windsurf",
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
// ── 3. Gemini / Antigravity (.gemini/settings.json) ──
|
|
783
|
+
const geminiRulesPath = tokenOptimized ? "GEMINI.md" : "../.agent/rules/GEMINI.md";
|
|
784
|
+
const geminiSettings =
|
|
785
|
+
JSON.stringify(
|
|
786
|
+
{
|
|
787
|
+
rules: [{ path: geminiRulesPath, trigger: "always_on" }],
|
|
788
|
+
agents: { directory: "../.agent/agents" },
|
|
789
|
+
skills: { directory: "../.agent/skills" },
|
|
790
|
+
workflows: { directory: "../.agent/workflows" },
|
|
791
|
+
},
|
|
792
|
+
null,
|
|
793
|
+
2,
|
|
794
|
+
) + "\n";
|
|
795
|
+
await writeBridge(
|
|
796
|
+
path.join(targetDir, ".gemini", "settings.json"),
|
|
797
|
+
geminiSettings,
|
|
798
|
+
"Gemini/Antigravity",
|
|
799
|
+
true,
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
// ── Also create .gemini/GEMINI.md as a direct rules file ──
|
|
803
|
+
const geminiRulesBridge = `---
|
|
670
804
|
trigger: always_on
|
|
671
805
|
---
|
|
672
806
|
|
|
@@ -674,557 +808,681 @@ trigger: always_on
|
|
|
674
808
|
# Auto-generated by tribunal-kit init.
|
|
675
809
|
# Full rules: .agent/rules/GEMINI.md
|
|
676
810
|
|
|
677
|
-
${
|
|
811
|
+
${rulesToInject}
|
|
678
812
|
`;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
813
|
+
await writeBridge(
|
|
814
|
+
path.join(targetDir, ".gemini", "GEMINI.md"),
|
|
815
|
+
geminiRulesBridge,
|
|
816
|
+
"Gemini rules",
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
// ── 4. GitHub Copilot (.github/copilot-instructions.md) ──
|
|
820
|
+
const copilotInstructions = `# Tribunal Kit — Copilot Bridge
|
|
687
821
|
# Auto-generated by tribunal-kit init. Do not edit manually.
|
|
688
|
-
# Source: .agent/rules/GEMINI.md
|
|
822
|
+
# Source: .agent/rules/GEMINI.md (Optimized)
|
|
689
823
|
|
|
690
|
-
${
|
|
824
|
+
${rulesToInject}
|
|
691
825
|
`;
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
826
|
+
await writeBridge(
|
|
827
|
+
path.join(targetDir, ".github", "copilot-instructions.md"),
|
|
828
|
+
copilotInstructions,
|
|
829
|
+
"GitHub Copilot",
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
// ── 5. Claude (.claude/CLAUDE.md) ─────────────────────
|
|
833
|
+
const claudeRules = `# Tribunal Kit — Claude Bridge
|
|
700
834
|
# Auto-generated by tribunal-kit init. Do not edit manually.
|
|
701
|
-
# Source: .agent/rules/GEMINI.md
|
|
835
|
+
# Source: .agent/rules/GEMINI.md (Optimized)
|
|
702
836
|
|
|
703
|
-
${
|
|
837
|
+
${rulesToInject}
|
|
704
838
|
`;
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
839
|
+
await writeBridge(
|
|
840
|
+
path.join(targetDir, ".claude", "CLAUDE.md"),
|
|
841
|
+
claudeRules,
|
|
842
|
+
"Claude",
|
|
843
|
+
);
|
|
710
844
|
|
|
711
|
-
|
|
845
|
+
console.log();
|
|
712
846
|
}
|
|
713
847
|
|
|
714
|
-
async function cmdSync(
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
848
|
+
async function cmdSync() {
|
|
849
|
+
console.log(`\n╭─ ${c("bold", "Tribunal IDE Sync")} ──────────────────`);
|
|
850
|
+
console.log("│");
|
|
851
|
+
console.log(`│ ${c("gray", "✦ Regenerating IDE bridge files...")}`);
|
|
852
|
+
const cwd = process.cwd();
|
|
853
|
+
const agentDest = path.join(cwd, ".agent");
|
|
854
|
+
if (!fs.existsSync(agentDest)) {
|
|
855
|
+
console.error(`│ ${c("red", "✖ Error: .agent/ directory not found.")}`);
|
|
856
|
+
console.error(`│ ${c("gray", "Run `tk init` first.")}`);
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
859
|
+
await generateIDEBridges(cwd, agentDest, false);
|
|
860
|
+
console.log(`│ ${c("green", "✔ Sync complete.")}`);
|
|
861
|
+
console.log("╰────────────────────────────────────────\n");
|
|
728
862
|
}
|
|
729
863
|
|
|
730
864
|
async function cmdUpdate(flags) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
865
|
+
// ── Self-install guard (early, before banner) ───────────
|
|
866
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
867
|
+
if (isSelfInstall(targetDir)) {
|
|
868
|
+
err("Cannot run update inside the tribunal-kit package itself.");
|
|
869
|
+
err(`Target: ${targetDir}`);
|
|
870
|
+
console.log();
|
|
871
|
+
dim("This command is designed to update .agent/ in OTHER projects.");
|
|
872
|
+
dim("Run it from the root of the project you want to update:");
|
|
873
|
+
dim(" cd /path/to/your-project");
|
|
874
|
+
dim(" npx tribunal-kit update");
|
|
875
|
+
console.log();
|
|
876
|
+
process.exit(1);
|
|
877
|
+
}
|
|
878
|
+
// ────────────────────────────────────────────────────────
|
|
879
|
+
|
|
880
|
+
// Update = init with --force
|
|
881
|
+
flags.force = true;
|
|
882
|
+
if (!quiet) {
|
|
883
|
+
log(
|
|
884
|
+
` ${c("cyan", "↻")} ${bold("Updating")} ${c("white", ".agent/")} to latest version...`,
|
|
885
|
+
);
|
|
886
|
+
console.log();
|
|
887
|
+
}
|
|
888
|
+
await cmdInit(flags);
|
|
753
889
|
}
|
|
754
890
|
|
|
755
|
-
|
|
756
891
|
async function cmdLearn(flags) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
892
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
893
|
+
const agentDest = path.join(targetDir, ".agent");
|
|
894
|
+
|
|
895
|
+
if (!fs.existsSync(agentDest)) {
|
|
896
|
+
err(".agent/ not found. Run: npx tribunal-kit init");
|
|
897
|
+
process.exit(1);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
banner();
|
|
901
|
+
|
|
902
|
+
const W = 62;
|
|
903
|
+
const title = " Tribunal Learn — Supreme Court Mode";
|
|
904
|
+
const trail = " ".repeat(Math.max(0, W - title.length));
|
|
905
|
+
console.log(` ${c("cyan", "\u2554" + "\u2550".repeat(W) + "\u2557")}`);
|
|
906
|
+
console.log(
|
|
907
|
+
` ${c("cyan", "\u2551")}${c("bold", c("white", title))}${trail}${c("cyan", "\u2551")}`,
|
|
908
|
+
);
|
|
909
|
+
console.log(` ${c("cyan", "\u255a" + "\u2550".repeat(W) + "\u255d")}`);
|
|
910
|
+
console.log();
|
|
911
|
+
|
|
912
|
+
const evoArgs = ["digest"];
|
|
913
|
+
if (flags.dryRun) evoArgs.push("--dry-run");
|
|
914
|
+
if (flags.head) evoArgs.push("--head");
|
|
915
|
+
|
|
916
|
+
// Phase 1: Skill Evolution
|
|
917
|
+
log(
|
|
918
|
+
` ${c("cyan", "\u229b")} ${bold("Phase 1")} \u2014 Skill Evolution Forge (auto-generating project idioms)`,
|
|
919
|
+
);
|
|
920
|
+
const evoScript = path.join(agentDest, "scripts", "skill_evolution.js");
|
|
921
|
+
if (!fs.existsSync(evoScript)) {
|
|
922
|
+
warn("skill_evolution.js not found \u2014 run: npx tribunal-kit update");
|
|
923
|
+
} else {
|
|
924
|
+
try {
|
|
925
|
+
await runScriptAsync(evoScript, evoArgs, { cwd: targetDir });
|
|
926
|
+
} catch (e) {
|
|
927
|
+
warn(`Skill Evolution error: ${e.message}`);
|
|
791
928
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
console.log();
|
|
932
|
+
|
|
933
|
+
// Phase 2: Case Law prompt
|
|
934
|
+
log(
|
|
935
|
+
` ${c("cyan", "\u229b")} ${bold("Phase 2")} \u2014 Case Law Engine (building precedence record)`,
|
|
936
|
+
);
|
|
937
|
+
console.log();
|
|
938
|
+
log(` ${c("gray", "\u25b8")} Record a new rejection precedent:`);
|
|
939
|
+
log(` ${c("white", "npx tribunal-kit case add")}`);
|
|
940
|
+
console.log();
|
|
941
|
+
log(` ${c("gray", "\u25b8")} Search existing case law:`);
|
|
942
|
+
log(` ${c("white", 'npx tribunal-kit case search "your query"')}`);
|
|
943
|
+
console.log();
|
|
944
|
+
log(
|
|
945
|
+
` ${c("green", "\u2714")} ${bold("Learn cycle complete.")} Your Tribunal grows smarter with every commit.`,
|
|
946
|
+
);
|
|
947
|
+
console.log();
|
|
806
948
|
}
|
|
807
949
|
|
|
808
950
|
// ── Async Main Wrapper ───────────────────────────────────
|
|
809
951
|
async function runWithUpdateCheck(command, flags) {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
// Proceed with current version
|
|
822
|
-
switch (command) {
|
|
823
|
-
case 'init':
|
|
824
|
-
await cmdInit(flags);
|
|
825
|
-
break;
|
|
826
|
-
case 'update':
|
|
827
|
-
await cmdUpdate(flags);
|
|
828
|
-
break;
|
|
829
|
-
case 'status':
|
|
830
|
-
cmdStatus(flags);
|
|
831
|
-
break;
|
|
832
|
-
case 'learn':
|
|
833
|
-
await cmdLearn(flags);
|
|
834
|
-
break;
|
|
835
|
-
case 'case':
|
|
836
|
-
await cmdCase(flags);
|
|
837
|
-
break;
|
|
838
|
-
case 'hook':
|
|
839
|
-
cmdHook(flags);
|
|
840
|
-
break;
|
|
841
|
-
case 'graph':
|
|
842
|
-
await cmdGraph(flags);
|
|
843
|
-
break;
|
|
844
|
-
case 'mutate':
|
|
845
|
-
await cmdMutate(flags);
|
|
846
|
-
break;
|
|
847
|
-
case 'context':
|
|
848
|
-
cmdContext(flags);
|
|
849
|
-
break;
|
|
850
|
-
case 'sync':
|
|
851
|
-
await cmdSync();
|
|
852
|
-
break;
|
|
853
|
-
case 'marathon':
|
|
854
|
-
await cmdMarathon(flags);
|
|
855
|
-
break;
|
|
856
|
-
case 'uninstall':
|
|
857
|
-
cmdUninstall(flags);
|
|
858
|
-
break;
|
|
859
|
-
case 'help':
|
|
860
|
-
case '--help':
|
|
861
|
-
case '-h':
|
|
862
|
-
case null:
|
|
863
|
-
cmdHelp();
|
|
864
|
-
break;
|
|
865
|
-
default:
|
|
866
|
-
err(`Unknown command: "${command}"`);
|
|
867
|
-
console.log();
|
|
868
|
-
dim('Run tribunal-kit --help for usage');
|
|
869
|
-
process.exit(1);
|
|
952
|
+
const shouldSkip =
|
|
953
|
+
flags.skipUpdateCheck || process.env.TK_SKIP_UPDATE_CHECK === "1";
|
|
954
|
+
|
|
955
|
+
if (!shouldSkip && (command === "init" || command === "update")) {
|
|
956
|
+
// Pass through the original args (minus the node/script path)
|
|
957
|
+
const originalArgs = process.argv.slice(2);
|
|
958
|
+
const didReInvoke = await autoUpdateCheck(originalArgs);
|
|
959
|
+
if (didReInvoke) {
|
|
960
|
+
process.exit(0); // Latest version handled it
|
|
870
961
|
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Proceed with current version
|
|
965
|
+
switch (command) {
|
|
966
|
+
case "init":
|
|
967
|
+
await cmdInit(flags);
|
|
968
|
+
break;
|
|
969
|
+
case "update":
|
|
970
|
+
await cmdUpdate(flags);
|
|
971
|
+
break;
|
|
972
|
+
case "status":
|
|
973
|
+
cmdStatus(flags);
|
|
974
|
+
break;
|
|
975
|
+
case "learn":
|
|
976
|
+
await cmdLearn(flags);
|
|
977
|
+
break;
|
|
978
|
+
case "case":
|
|
979
|
+
await cmdCase(flags);
|
|
980
|
+
break;
|
|
981
|
+
case "hook":
|
|
982
|
+
cmdHook(flags);
|
|
983
|
+
break;
|
|
984
|
+
case "graph":
|
|
985
|
+
await cmdGraph(flags);
|
|
986
|
+
break;
|
|
987
|
+
case "mutate":
|
|
988
|
+
await cmdMutate(flags);
|
|
989
|
+
break;
|
|
990
|
+
case "context":
|
|
991
|
+
cmdContext(flags);
|
|
992
|
+
break;
|
|
993
|
+
case "sync":
|
|
994
|
+
await cmdSync();
|
|
995
|
+
break;
|
|
996
|
+
case "marathon":
|
|
997
|
+
await cmdMarathon(flags);
|
|
998
|
+
break;
|
|
999
|
+
case "uninstall":
|
|
1000
|
+
cmdUninstall(flags);
|
|
1001
|
+
break;
|
|
1002
|
+
case "help":
|
|
1003
|
+
case "--help":
|
|
1004
|
+
case "-h":
|
|
1005
|
+
case null:
|
|
1006
|
+
cmdHelp();
|
|
1007
|
+
break;
|
|
1008
|
+
default:
|
|
1009
|
+
err(`Unknown command: "${command}"`);
|
|
1010
|
+
console.log();
|
|
1011
|
+
dim("Run tribunal-kit --help for usage");
|
|
1012
|
+
process.exit(1);
|
|
1013
|
+
}
|
|
871
1014
|
}
|
|
872
1015
|
|
|
873
1016
|
async function cmdCase(flags) {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1017
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
1018
|
+
const agentDest = path.join(targetDir, ".agent");
|
|
1019
|
+
|
|
1020
|
+
if (!fs.existsSync(agentDest)) {
|
|
1021
|
+
err(".agent/ not found. Run: npx tribunal-kit init");
|
|
1022
|
+
process.exit(1);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const args = process.argv.slice(3);
|
|
1026
|
+
if (
|
|
1027
|
+
args.length === 0 ||
|
|
1028
|
+
args[0] === "help" ||
|
|
1029
|
+
args[0] === "--help" ||
|
|
1030
|
+
args[0] === "-h"
|
|
1031
|
+
) {
|
|
1032
|
+
banner();
|
|
1033
|
+
log(` ${c("cyan", "\u2554" + "\u2550".repeat(60) + "\u2557")}`);
|
|
1034
|
+
log(
|
|
1035
|
+
` ${c("cyan", "\u2551")}${c("bold", c("white", " Tribunal Case Law Engine \u2014 Supreme Court "))}${c("cyan", "\u2551")}`,
|
|
1036
|
+
);
|
|
1037
|
+
log(` ${c("cyan", "\u255a" + "\u2550".repeat(60) + "\u255d")}`);
|
|
1038
|
+
console.log();
|
|
1039
|
+
log(
|
|
1040
|
+
` ${c("cyan", "add".padEnd(10))} ${c("gray", "Record a new Case Law rejection pattern")}`,
|
|
1041
|
+
);
|
|
1042
|
+
log(
|
|
1043
|
+
` ${c("cyan", "search".padEnd(10))} ${c("gray", 'Search existing cases (e.g., search "query")')}`,
|
|
1044
|
+
);
|
|
1045
|
+
log(
|
|
1046
|
+
` ${c("cyan", "list".padEnd(10))} ${c("gray", "List all recorded case law")}`,
|
|
1047
|
+
);
|
|
1048
|
+
log(
|
|
1049
|
+
` ${c("cyan", "show".padEnd(10))} ${c("gray", "Show full diff for a case (e.g., show --id 1)")}`,
|
|
1050
|
+
);
|
|
1051
|
+
log(
|
|
1052
|
+
` ${c("cyan", "stats".padEnd(10))} ${c("gray", "Show case law stats by domain/verdict")}`,
|
|
1053
|
+
);
|
|
1054
|
+
log(
|
|
1055
|
+
` ${c("cyan", "export".padEnd(10))} ${c("gray", "Export all cases to Markdown")}`,
|
|
1056
|
+
);
|
|
1057
|
+
log(
|
|
1058
|
+
` ${c("cyan", "overrule".padEnd(10))} ${c("gray", "Overrule a past precedent (e.g., overrule --id 1)")}`,
|
|
1059
|
+
);
|
|
1060
|
+
console.log();
|
|
1061
|
+
process.exit(1);
|
|
1062
|
+
}
|
|
899
1063
|
|
|
900
|
-
|
|
1064
|
+
const caseLawScript = path.join(agentDest, "scripts", "case_law_manager.js");
|
|
901
1065
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1066
|
+
// Make shorthand aliases for the subcommand (first arg only)
|
|
1067
|
+
const caseArgs = [...args];
|
|
1068
|
+
if (caseArgs[0] === "add") caseArgs[0] = "add-case";
|
|
1069
|
+
if (caseArgs[0] === "search") caseArgs[0] = "search-cases";
|
|
906
1070
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1071
|
+
try {
|
|
1072
|
+
await runScriptAsync(caseLawScript, caseArgs, { cwd: targetDir });
|
|
1073
|
+
} catch {
|
|
1074
|
+
process.exit(1); // Script already prints errors
|
|
1075
|
+
}
|
|
912
1076
|
}
|
|
913
1077
|
|
|
914
1078
|
async function cmdGraph(flags) {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1079
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
1080
|
+
const agentDest = path.join(targetDir, ".agent");
|
|
1081
|
+
|
|
1082
|
+
if (!fs.existsSync(agentDest)) {
|
|
1083
|
+
err(".agent/ not found. Run: npx tribunal-kit init");
|
|
1084
|
+
process.exit(1);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
banner();
|
|
1088
|
+
const builderScript = path.join(agentDest, "scripts", "graph_builder.js");
|
|
1089
|
+
const visualizerScript = path.join(
|
|
1090
|
+
agentDest,
|
|
1091
|
+
"scripts",
|
|
1092
|
+
"graph_visualizer.js",
|
|
1093
|
+
);
|
|
1094
|
+
const htmlFile = path.join(
|
|
1095
|
+
agentDest,
|
|
1096
|
+
"history",
|
|
1097
|
+
"architecture-explorer.html",
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
try {
|
|
1101
|
+
await runScriptAsync(builderScript, [], { cwd: targetDir });
|
|
1102
|
+
await runScriptAsync(visualizerScript, [], { cwd: targetDir });
|
|
1103
|
+
|
|
1104
|
+
log(` ${c("cyan", "▸")} Opening visualizer in browser...`);
|
|
1105
|
+
// Open browser safely without shell interpolation
|
|
1106
|
+
const { opener, openerArgs } = (() => {
|
|
1107
|
+
if (process.platform === "win32")
|
|
1108
|
+
return { opener: "cmd", openerArgs: ["/c", "start", "", htmlFile] };
|
|
1109
|
+
if (process.platform === "darwin")
|
|
1110
|
+
return { opener: "open", openerArgs: [htmlFile] };
|
|
1111
|
+
return { opener: "xdg-open", openerArgs: [htmlFile] };
|
|
1112
|
+
})();
|
|
1113
|
+
spawn(opener, openerArgs, { stdio: "ignore", detached: true }).unref();
|
|
1114
|
+
} catch (e) {
|
|
1115
|
+
err(`Graph generation failed: ${e.message}`);
|
|
1116
|
+
process.exit(1);
|
|
1117
|
+
}
|
|
944
1118
|
}
|
|
945
1119
|
|
|
946
1120
|
function cmdHook(flags) {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1121
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
1122
|
+
const gitDir = path.join(targetDir, ".git");
|
|
1123
|
+
|
|
1124
|
+
if (!fs.existsSync(gitDir)) {
|
|
1125
|
+
err("Not a git repository. Cannot install git hooks here.");
|
|
1126
|
+
process.exit(1);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const hooksDir = path.join(gitDir, "hooks");
|
|
1130
|
+
if (!fs.existsSync(hooksDir)) {
|
|
1131
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const prePushPath = path.join(hooksDir, "pre-push");
|
|
1135
|
+
const hookScript = `#!/bin/sh\n# Supreme Court - Auto Learn on Push\necho "⚖️ Tribunal Supreme Court: Evolving Skills..."\nnpx tribunal-kit learn --head\necho "✦ Synchronizing IDE bridges..."\nnpx tribunal-kit sync\n`;
|
|
1136
|
+
|
|
1137
|
+
fs.writeFileSync(prePushPath, hookScript, { mode: 0o755 });
|
|
1138
|
+
|
|
1139
|
+
console.log();
|
|
1140
|
+
log(` ${c("green", "✔")} Installed pre-push git hook.`);
|
|
1141
|
+
log(
|
|
1142
|
+
` ${c("gray", "▸")} Skill Evolution and IDE Sync will now run automatically every time you git push.`,
|
|
1143
|
+
);
|
|
1144
|
+
console.log();
|
|
969
1145
|
}
|
|
970
1146
|
|
|
971
1147
|
async function cmdMutate(flags) {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1148
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
1149
|
+
const agentDest = path.join(targetDir, ".agent");
|
|
1150
|
+
|
|
1151
|
+
if (!fs.existsSync(agentDest)) {
|
|
1152
|
+
err(".agent/ not found. Run: npx tribunal-kit init");
|
|
1153
|
+
process.exit(1);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const args = process.argv.slice(3);
|
|
1157
|
+
if (args.length < 2) {
|
|
1158
|
+
err("Usage: npx tribunal-kit mutate <target_file> <test_command>");
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const mutateScript = path.join(agentDest, "scripts", "mutation_runner.js");
|
|
1163
|
+
try {
|
|
1164
|
+
await runScriptAsync(mutateScript, args, { cwd: targetDir });
|
|
1165
|
+
} catch {
|
|
1166
|
+
process.exit(1);
|
|
1167
|
+
}
|
|
992
1168
|
}
|
|
993
1169
|
|
|
994
1170
|
function cmdUninstall(flags) {
|
|
995
|
-
|
|
996
|
-
|
|
1171
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
1172
|
+
const agentDest = path.join(targetDir, ".agent");
|
|
997
1173
|
|
|
998
|
-
|
|
1174
|
+
banner();
|
|
999
1175
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1176
|
+
if (!fs.existsSync(agentDest)) {
|
|
1177
|
+
log(
|
|
1178
|
+
` ${c("yellow", "⚠")} ${bold(".agent/")} is not installed in this project.`,
|
|
1179
|
+
);
|
|
1180
|
+
console.log();
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1005
1183
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1184
|
+
if (flags.dryRun) {
|
|
1185
|
+
log(colorize("yellow", " DRY RUN — would remove:"));
|
|
1186
|
+
log(` ${c("gray", " ╰─")} ${agentDest}`);
|
|
1187
|
+
console.log();
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1012
1190
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1191
|
+
try {
|
|
1192
|
+
fs.rmSync(agentDest, { recursive: true, force: true });
|
|
1193
|
+
log(
|
|
1194
|
+
` ${c("green", "✔")} ${bold(".agent/")} has been removed from this project.`,
|
|
1195
|
+
);
|
|
1196
|
+
console.log();
|
|
1197
|
+
log(
|
|
1198
|
+
` ${c("gray", "▸")} To reinstall: ${c("cyan", "npx tribunal-kit init")}`,
|
|
1199
|
+
);
|
|
1200
|
+
console.log();
|
|
1201
|
+
} catch (e) {
|
|
1202
|
+
err(`Failed to remove .agent/: ${e.message}`);
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1023
1205
|
}
|
|
1024
1206
|
|
|
1025
1207
|
function cmdStatus(flags) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
banner();
|
|
1208
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
1209
|
+
const agentDest = path.join(targetDir, ".agent");
|
|
1030
1210
|
|
|
1031
|
-
|
|
1032
|
-
log(` ${c('red','✖')} ${bold('Not installed')} in this project`);
|
|
1033
|
-
console.log();
|
|
1034
|
-
log(` ${c('gray','Run:')} ${c('cyan','npx tribunal-kit init')}`);
|
|
1035
|
-
console.log();
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
1211
|
+
banner();
|
|
1038
1212
|
|
|
1039
|
-
|
|
1213
|
+
if (!fs.existsSync(agentDest)) {
|
|
1214
|
+
log(` ${c("red", "✖")} ${bold("Not installed")} in this project`);
|
|
1040
1215
|
console.log();
|
|
1041
|
-
|
|
1042
|
-
const icons = { agents: '🤖', workflows: '⚡', skills: '🧠', scripts: '🔧' };
|
|
1043
|
-
const colors = { agents: 'magenta', workflows: 'yellow', skills: 'blue', scripts: 'green' };
|
|
1044
|
-
const subdirs = ['agents', 'workflows', 'skills', 'scripts'];
|
|
1045
|
-
for (const sub of subdirs) {
|
|
1046
|
-
const subPath = path.join(agentDest, sub);
|
|
1047
|
-
if (fs.existsSync(subPath)) {
|
|
1048
|
-
const count = fs.readdirSync(subPath).filter(f => !fs.statSync(path.join(subPath, f)).isDirectory()).length;
|
|
1049
|
-
log(` ${icons[sub]} ${c(colors[sub], sub.padEnd(12))}${c('white', String(count).padStart(3))} files`);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1216
|
+
log(` ${c("gray", "Run:")} ${c("cyan", "npx tribunal-kit init")}`);
|
|
1052
1217
|
console.log();
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
log(
|
|
1222
|
+
` ${c("green", "✔")} ${bold(c("green", "Installed"))} ${c("gray", "→")} ${c("gray", agentDest)}`,
|
|
1223
|
+
);
|
|
1224
|
+
console.log();
|
|
1225
|
+
|
|
1226
|
+
const icons = { agents: "🤖", workflows: "⚡", skills: "🧠", scripts: "🔧" };
|
|
1227
|
+
const colors = {
|
|
1228
|
+
agents: "magenta",
|
|
1229
|
+
workflows: "yellow",
|
|
1230
|
+
skills: "blue",
|
|
1231
|
+
scripts: "green",
|
|
1232
|
+
};
|
|
1233
|
+
const subdirs = ["agents", "workflows", "skills", "scripts"];
|
|
1234
|
+
for (const sub of subdirs) {
|
|
1235
|
+
const subPath = path.join(agentDest, sub);
|
|
1236
|
+
if (fs.existsSync(subPath)) {
|
|
1237
|
+
const count = fs
|
|
1238
|
+
.readdirSync(subPath)
|
|
1239
|
+
.filter(
|
|
1240
|
+
(f) => !fs.statSync(path.join(subPath, f)).isDirectory(),
|
|
1241
|
+
).length;
|
|
1242
|
+
log(
|
|
1243
|
+
` ${icons[sub]} ${c(colors[sub], sub.padEnd(12))}${c("white", String(count).padStart(3))} files`,
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
console.log();
|
|
1053
1248
|
}
|
|
1054
1249
|
|
|
1055
1250
|
function cmdHelp() {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1251
|
+
banner();
|
|
1252
|
+
const cmd = (name, desc) =>
|
|
1253
|
+
` ${c("cyan", name.padEnd(10))} ${c("gray", desc)}`;
|
|
1254
|
+
const opt = (flag, desc) =>
|
|
1255
|
+
` ${c("yellow", flag.padEnd(22))} ${c("gray", desc)}`;
|
|
1256
|
+
const ex = (s) => ` ${c("gray", "▸")} ${c("white", s)}`;
|
|
1257
|
+
|
|
1258
|
+
log(bold(" Commands"));
|
|
1259
|
+
log(` ${c("gray", "─".repeat(40))}`);
|
|
1260
|
+
log(cmd("init", "Install .agent/ into current project"));
|
|
1261
|
+
log(cmd("update", "Re-install to get latest version"));
|
|
1262
|
+
log(cmd("status", "Check if .agent/ is installed"));
|
|
1263
|
+
log(cmd("learn", "Evolve project idioms based on git diffs"));
|
|
1264
|
+
log(
|
|
1265
|
+
cmd(
|
|
1266
|
+
"case",
|
|
1267
|
+
"Manage Case Law precedents (add, search, list, show, stats, overrule)",
|
|
1268
|
+
),
|
|
1269
|
+
);
|
|
1270
|
+
log(cmd("graph", "Build and visualize the architecture graph"));
|
|
1271
|
+
log(cmd("mutate", "Run the Mutation Engine to test test-suite reliability"));
|
|
1272
|
+
log(
|
|
1273
|
+
cmd("context", "Retrieve a highly-optimized Context Snapshot for a file"),
|
|
1274
|
+
);
|
|
1275
|
+
log(cmd("sync", "Synchronize IDE bridge files with current rules"));
|
|
1276
|
+
log(cmd("marathon", "Long-running agent harness (init, status, next, mark)"));
|
|
1277
|
+
log(cmd("hook", "Install pre-push git hook for auto-learning"));
|
|
1278
|
+
log(cmd("uninstall", "Remove .agent/ folder from project"));
|
|
1279
|
+
console.log();
|
|
1280
|
+
log(bold(" Options"));
|
|
1281
|
+
log(` ${c("gray", "─".repeat(40))}`);
|
|
1282
|
+
log(opt("--force", "Overwrite existing .agent/ folder"));
|
|
1283
|
+
log(opt("--path <dir>", "Install in specific directory"));
|
|
1284
|
+
log(opt("--quiet", "Suppress all output"));
|
|
1285
|
+
log(opt("--verbose", "Show detailed debug logging"));
|
|
1286
|
+
log(opt("--dry-run", "Preview actions without executing"));
|
|
1287
|
+
log(opt("--minimal", "Install core agents/skills only (~13 agents)"));
|
|
1288
|
+
log(opt("--skip-update-check", "Skip auto-update version check"));
|
|
1289
|
+
log(opt("--head", "(learn) Diff against last commit instead of staged"));
|
|
1290
|
+
console.log();
|
|
1291
|
+
log(bold(" Aliases"));
|
|
1292
|
+
log(` ${c("gray", "─".repeat(40))}`);
|
|
1293
|
+
log(
|
|
1294
|
+
` ${c("cyan", "tk")} ${c("gray", "Shorthand for tribunal-kit (e.g., tk init, tk status)")}`,
|
|
1295
|
+
);
|
|
1296
|
+
console.log();
|
|
1297
|
+
log(bold(" Examples"));
|
|
1298
|
+
log(` ${c("gray", "─".repeat(40))}`);
|
|
1299
|
+
log(ex("npx tribunal-kit init"));
|
|
1300
|
+
log(ex("tk init --force"));
|
|
1301
|
+
log(ex("tk init --path ./my-app"));
|
|
1302
|
+
log(ex("npx tribunal-kit init --dry-run"));
|
|
1303
|
+
log(ex("tk update"));
|
|
1304
|
+
log(ex("tk status"));
|
|
1305
|
+
log(ex("tk learn"));
|
|
1306
|
+
log(ex("tk learn --dry-run"));
|
|
1307
|
+
log(ex("tk learn --head"));
|
|
1308
|
+
log(ex("tk case add"));
|
|
1309
|
+
log(ex('tk case search "useEffect"'));
|
|
1310
|
+
log(ex("tk case list"));
|
|
1311
|
+
log(ex("tk case show --id 1"));
|
|
1312
|
+
log(ex("tk case stats"));
|
|
1313
|
+
log(ex("tk case export"));
|
|
1314
|
+
log(ex("tk case overrule --id 1"));
|
|
1315
|
+
log(ex("tk graph"));
|
|
1316
|
+
log(ex('tk mutate src/utils.js "npm test"'));
|
|
1317
|
+
log(ex('tk marathon init "Build a todo app"'));
|
|
1318
|
+
log(ex("tk marathon status"));
|
|
1319
|
+
log(ex("tk marathon next"));
|
|
1320
|
+
log(ex("tk marathon mark 5 pass"));
|
|
1321
|
+
log(ex("tk hook"));
|
|
1322
|
+
log(ex("tk uninstall"));
|
|
1323
|
+
console.log();
|
|
1118
1324
|
}
|
|
1119
1325
|
|
|
1120
|
-
|
|
1121
1326
|
async function cmdMarathon(flags) {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1327
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
1328
|
+
const agentDest = path.join(targetDir, ".agent");
|
|
1329
|
+
|
|
1330
|
+
if (!fs.existsSync(agentDest)) {
|
|
1331
|
+
err(".agent/ not found. Run: npx tribunal-kit init");
|
|
1332
|
+
process.exit(1);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
const args = process.argv.slice(3);
|
|
1336
|
+
if (
|
|
1337
|
+
args.length === 0 ||
|
|
1338
|
+
args[0] === "help" ||
|
|
1339
|
+
args[0] === "--help" ||
|
|
1340
|
+
args[0] === "-h"
|
|
1341
|
+
) {
|
|
1342
|
+
banner();
|
|
1343
|
+
log(` ${c("cyan", "╔" + "═".repeat(60) + "╗")}`);
|
|
1344
|
+
log(
|
|
1345
|
+
` ${c("cyan", "║")}${c("bold", c("white", " Marathon — Long-Running Agent Harness "))}${c("cyan", "║")}`,
|
|
1346
|
+
);
|
|
1347
|
+
log(` ${c("cyan", "╚" + "═".repeat(60) + "╝")}`);
|
|
1348
|
+
console.log();
|
|
1349
|
+
log(
|
|
1350
|
+
` ${c("cyan", "init".padEnd(16))} ${c("gray", 'Start a new marathon (init "spec")')}`,
|
|
1351
|
+
);
|
|
1352
|
+
log(
|
|
1353
|
+
` ${c("cyan", "status".padEnd(16))} ${c("gray", "Show progress dashboard")}`,
|
|
1354
|
+
);
|
|
1355
|
+
log(
|
|
1356
|
+
` ${c("cyan", "next".padEnd(16))} ${c("gray", "Show next unfinished feature")}`,
|
|
1357
|
+
);
|
|
1358
|
+
log(
|
|
1359
|
+
` ${c("cyan", "mark".padEnd(16))} ${c("gray", "Mark feature pass/fail (mark <id> pass)")}`,
|
|
1360
|
+
);
|
|
1361
|
+
log(
|
|
1362
|
+
` ${c("cyan", "log".padEnd(16))} ${c("gray", "Add a progress note")}`,
|
|
1363
|
+
);
|
|
1364
|
+
log(
|
|
1365
|
+
` ${c("cyan", "session-start".padEnd(16))} ${c("gray", "Begin a new work session")}`,
|
|
1366
|
+
);
|
|
1367
|
+
log(
|
|
1368
|
+
` ${c("cyan", "session-end".padEnd(16))} ${c("gray", "End session with summary")}`,
|
|
1369
|
+
);
|
|
1370
|
+
log(
|
|
1371
|
+
` ${c("cyan", "add-feature".padEnd(16))} ${c("gray", 'Add feature: "category" "desc" "step1" ...')}`,
|
|
1372
|
+
);
|
|
1373
|
+
log(
|
|
1374
|
+
` ${c("cyan", "reset".padEnd(16))} ${c("gray", "Archive and start fresh")}`,
|
|
1375
|
+
);
|
|
1376
|
+
console.log();
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const marathonScript = path.join(agentDest, "scripts", "marathon_harness.js");
|
|
1381
|
+
try {
|
|
1382
|
+
await runScriptAsync(marathonScript, args, { cwd: targetDir });
|
|
1383
|
+
} catch {
|
|
1384
|
+
process.exit(1);
|
|
1385
|
+
}
|
|
1156
1386
|
}
|
|
1157
1387
|
|
|
1158
1388
|
function cmdContext(flags) {
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1389
|
+
const targetDir = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
1390
|
+
const agentDest = path.join(targetDir, ".agent");
|
|
1391
|
+
|
|
1392
|
+
if (!fs.existsSync(agentDest)) {
|
|
1393
|
+
err(".agent/ not found. Run: npx tribunal-kit init");
|
|
1394
|
+
process.exit(1);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
const args = process.argv.slice(3);
|
|
1398
|
+
if (args.length === 0 || args[0] === "help" || args[0] === "--help") {
|
|
1399
|
+
console.error("Usage: npx tribunal-kit context <target_file>");
|
|
1400
|
+
process.exit(1);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
const targetFile = args[0].replace(/\\/g, "/");
|
|
1404
|
+
const snapshotName = targetFile.replace(/[\\\/]/g, "__") + ".json";
|
|
1405
|
+
const snapshotPath = require("path").join(
|
|
1406
|
+
agentDest,
|
|
1407
|
+
"history",
|
|
1408
|
+
"snapshots",
|
|
1409
|
+
snapshotName,
|
|
1410
|
+
);
|
|
1411
|
+
|
|
1412
|
+
if (!require("fs").existsSync(snapshotPath)) {
|
|
1413
|
+
console.error(
|
|
1414
|
+
" \x1b[91m✖\x1b[0m Context Snapshot not found for: " + targetFile,
|
|
1415
|
+
);
|
|
1416
|
+
console.log(" Run: npx tribunal-kit graph (to generate snapshots)");
|
|
1417
|
+
process.exit(1);
|
|
1418
|
+
}
|
|
1176
1419
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
}
|
|
1420
|
+
try {
|
|
1421
|
+
const snapshot = JSON.parse(
|
|
1422
|
+
require("fs").readFileSync(snapshotPath, "utf8"),
|
|
1423
|
+
);
|
|
1182
1424
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
console.log('- `' + imp + '` (exports: ' + exports.join(', ') + ')');
|
|
1195
|
-
} else {
|
|
1196
|
-
console.log('- `' + imp + '`');
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
console.log();
|
|
1200
|
-
}
|
|
1425
|
+
console.log("\n# Context Snapshot: " + snapshot.file);
|
|
1426
|
+
process.stdout.write(
|
|
1427
|
+
"> Size Estimate: " + (snapshot["estimatedTokens"] || "Unknown") + "\n",
|
|
1428
|
+
);
|
|
1429
|
+
console.log(
|
|
1430
|
+
"> Risk Score: " +
|
|
1431
|
+
snapshot.riskScore +
|
|
1432
|
+
" (Blast Radius: " +
|
|
1433
|
+
snapshot.blastRadius +
|
|
1434
|
+
")\n",
|
|
1435
|
+
);
|
|
1201
1436
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1437
|
+
if (Object.keys(snapshot.imports).length > 0) {
|
|
1438
|
+
console.log("## Imports");
|
|
1439
|
+
for (const [imp, exports] of Object.entries(snapshot.imports)) {
|
|
1440
|
+
if (exports && exports.length > 0) {
|
|
1441
|
+
console.log("- `" + imp + "` (exports: " + exports.join(", ") + ")");
|
|
1442
|
+
} else {
|
|
1443
|
+
console.log("- `" + imp + "`");
|
|
1208
1444
|
}
|
|
1445
|
+
}
|
|
1446
|
+
console.log();
|
|
1447
|
+
}
|
|
1209
1448
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1449
|
+
if (snapshot.dependents && snapshot.dependents.length > 0) {
|
|
1450
|
+
console.log("## Dependents");
|
|
1451
|
+
for (const dep of snapshot.dependents) {
|
|
1452
|
+
console.log("- `" + dep + "`");
|
|
1453
|
+
}
|
|
1454
|
+
console.log();
|
|
1216
1455
|
}
|
|
1456
|
+
|
|
1457
|
+
console.log("## Source Code");
|
|
1458
|
+
console.log("```javascript\n" + snapshot.content + "\n```\n");
|
|
1459
|
+
} catch (e) {
|
|
1460
|
+
console.error("Failed to read snapshot: " + e.message);
|
|
1461
|
+
process.exit(1);
|
|
1462
|
+
}
|
|
1217
1463
|
}
|
|
1218
1464
|
|
|
1219
1465
|
// ── Main ──────────────────────────────────────────────────
|
|
1220
|
-
|
|
1466
|
+
if (require.main === module) {
|
|
1467
|
+
const { command, flags } = parseArgs(process.argv);
|
|
1221
1468
|
|
|
1222
|
-
if (flags.quiet) quiet = true;
|
|
1223
|
-
if (flags.verbose) verbose = true;
|
|
1469
|
+
if (flags.quiet) quiet = true;
|
|
1470
|
+
if (flags.verbose) verbose = true;
|
|
1224
1471
|
|
|
1225
|
-
runWithUpdateCheck(command, flags);
|
|
1472
|
+
runWithUpdateCheck(command, flags);
|
|
1473
|
+
}
|
|
1226
1474
|
|
|
1227
1475
|
// -- Exports (for testing) -- do not remove
|
|
1228
1476
|
if (require.main !== module) {
|
|
1229
|
-
module.exports = {
|
|
1477
|
+
module.exports = {
|
|
1478
|
+
parseArgs,
|
|
1479
|
+
compareSemver,
|
|
1480
|
+
copyDir,
|
|
1481
|
+
countDir,
|
|
1482
|
+
isSelfInstall,
|
|
1483
|
+
CORE_AGENTS,
|
|
1484
|
+
CORE_SKILLS,
|
|
1485
|
+
generateIDEBridges,
|
|
1486
|
+
cmdMarathon,
|
|
1487
|
+
};
|
|
1230
1488
|
}
|