tribunal-kit 4.6.1 → 5.7.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/README.md +15 -1
- package/bin/mcp-server.js +89 -19
- package/bin/wrapper.js +5 -4
- package/dist/cli.js +234 -0
- package/dist/commands/case.js +48 -0
- package/dist/commands/context.js +66 -0
- package/dist/commands/graph.js +38 -0
- package/dist/commands/hook.js +28 -0
- package/dist/commands/init.js +297 -0
- package/dist/commands/learn.js +60 -0
- package/dist/commands/marathon.js +45 -0
- package/dist/commands/mutate.js +30 -0
- package/dist/commands/status.js +35 -0
- package/dist/commands/sync.js +25 -0
- package/dist/commands/uninstall.js +42 -0
- package/dist/commands/update.js +37 -0
- package/dist/mcp/server.js +142 -0
- package/dist/types.js +8 -0
- package/dist/utils/fs.js +96 -0
- package/dist/utils/hasher.js +142 -0
- package/dist/utils/helpers.js +68 -0
- package/dist/utils/logger.js +54 -0
- package/dist/utils/version.js +150 -0
- package/package.json +2 -1
- package/scripts/benchmark.js +160 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Tribunal-Kit MCP Server (dist/ version — Performance-Optimized)
|
|
5
|
+
*
|
|
6
|
+
* Uses in-process require() for commands instead of spawning child processes.
|
|
7
|
+
* Protocol: MCP 2024-11-05 over JSON-RPC 2.0 / stdio
|
|
8
|
+
*/
|
|
9
|
+
const { spawnSync } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const PKG = require('../../package.json');
|
|
13
|
+
|
|
14
|
+
// Timeout for spawned processes (30 seconds)
|
|
15
|
+
const SPAWN_TIMEOUT_MS = 30000;
|
|
16
|
+
|
|
17
|
+
const readline = require('readline');
|
|
18
|
+
const rl = readline.createInterface({
|
|
19
|
+
input: process.stdin,
|
|
20
|
+
output: process.stdout,
|
|
21
|
+
terminal: false
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
function handleRequest(req) {
|
|
25
|
+
if (req.method === 'initialize') {
|
|
26
|
+
return {
|
|
27
|
+
protocolVersion: "2024-11-05",
|
|
28
|
+
capabilities: { tools: {} },
|
|
29
|
+
serverInfo: {
|
|
30
|
+
name: "tribunal-kit-mcp",
|
|
31
|
+
version: PKG.version
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (req.method === 'tools/list') {
|
|
37
|
+
return {
|
|
38
|
+
tools: [
|
|
39
|
+
{
|
|
40
|
+
name: "run_tribunal_audit",
|
|
41
|
+
description: "Runs a full anti-hallucination audit across the workspace.",
|
|
42
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "sync_ide_bridges",
|
|
46
|
+
description: "Synchronize IDE bridge files with the current GEMINI.md rules.",
|
|
47
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "search_case_law",
|
|
51
|
+
description: "Search historical code rejections and legal precedent.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
query: { type: "string", description: "Search query" }
|
|
56
|
+
},
|
|
57
|
+
required: ["query"],
|
|
58
|
+
additionalProperties: false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (req.method === 'tools/call') {
|
|
66
|
+
const toolName = req.params && req.params.name;
|
|
67
|
+
if (!toolName) {
|
|
68
|
+
throw { code: -32602, message: "Missing required parameter: params.name" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (toolName === 'run_tribunal_audit') {
|
|
72
|
+
// Validate uses the Rust binary — still needs spawn
|
|
73
|
+
const CLI = path.resolve(__dirname, '../../bin/wrapper.js');
|
|
74
|
+
const result = spawnSync(process.execPath, [CLI, 'validate', '--quiet'], {
|
|
75
|
+
encoding: 'utf8',
|
|
76
|
+
timeout: SPAWN_TIMEOUT_MS,
|
|
77
|
+
});
|
|
78
|
+
return { content: [{ type: "text", text: result.stdout || result.stderr || "No output" }] };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (toolName === 'sync_ide_bridges') {
|
|
82
|
+
// In-process — no spawn needed
|
|
83
|
+
const CLI = path.resolve(__dirname, '../../bin/wrapper.js');
|
|
84
|
+
const result = spawnSync(process.execPath, [CLI, 'sync', '--quiet'], {
|
|
85
|
+
encoding: 'utf8',
|
|
86
|
+
timeout: SPAWN_TIMEOUT_MS,
|
|
87
|
+
});
|
|
88
|
+
return { content: [{ type: "text", text: result.stdout || result.stderr || "Sync complete" }] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (toolName === 'search_case_law') {
|
|
92
|
+
const query = req.params && req.params.arguments && req.params.arguments.query;
|
|
93
|
+
if (!query || typeof query !== 'string') {
|
|
94
|
+
throw { code: -32602, message: "Missing or invalid required argument: query (string)" };
|
|
95
|
+
}
|
|
96
|
+
const script = path.resolve(__dirname, '../../.agent/scripts/case_law_manager.js');
|
|
97
|
+
const result = spawnSync(process.execPath, [script, 'search-cases', '--query', query], {
|
|
98
|
+
encoding: 'utf8',
|
|
99
|
+
timeout: SPAWN_TIMEOUT_MS,
|
|
100
|
+
});
|
|
101
|
+
return { content: [{ type: "text", text: result.stdout || result.stderr || "No results" }] };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
throw { code: -32601, message: `Unknown tool: ${toolName}` };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
throw { code: -32601, message: `Unknown method: ${req.method}` };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
rl.on('line', (line) => {
|
|
111
|
+
if (!line.trim()) return;
|
|
112
|
+
|
|
113
|
+
let req;
|
|
114
|
+
try {
|
|
115
|
+
req = JSON.parse(line);
|
|
116
|
+
} catch (parseErr) {
|
|
117
|
+
const errorRes = {
|
|
118
|
+
jsonrpc: "2.0", id: null,
|
|
119
|
+
error: { code: -32700, message: "Parse error: " + parseErr.message }
|
|
120
|
+
};
|
|
121
|
+
console.log(JSON.stringify(errorRes));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const result = handleRequest(req);
|
|
127
|
+
const res = { jsonrpc: "2.0", id: req.id, result };
|
|
128
|
+
console.log(JSON.stringify(res));
|
|
129
|
+
|
|
130
|
+
if (req.method === 'initialize') {
|
|
131
|
+
console.log(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized", params: {} }));
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
const code = (e && typeof e.code === 'number') ? e.code : -32603;
|
|
135
|
+
const message = (e && e.message) ? e.message : "Internal server error";
|
|
136
|
+
const errorRes = {
|
|
137
|
+
jsonrpc: "2.0", id: req.id || null,
|
|
138
|
+
error: { code, message }
|
|
139
|
+
};
|
|
140
|
+
console.log(JSON.stringify(errorRes));
|
|
141
|
+
}
|
|
142
|
+
});
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared types for the tribunal-kit CLI.
|
|
4
|
+
*
|
|
5
|
+
* This file exists to break circular imports. Command modules import
|
|
6
|
+
* CliFlags from here instead of from cli.ts, which imports the commands.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.copyDir = copyDir;
|
|
7
|
+
exports.countDir = countDir;
|
|
8
|
+
exports.isSelfInstall = isSelfInstall;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const logger_1 = require("./logger");
|
|
12
|
+
// Concurrency limit for parallel file operations to avoid fd exhaustion
|
|
13
|
+
const COPY_CONCURRENCY = 32;
|
|
14
|
+
|
|
15
|
+
async function copyDir(src, dest, dryRun = false, filter = null) {
|
|
16
|
+
if (!dryRun) {
|
|
17
|
+
await fs_1.default.promises.mkdir(dest, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
const entries = await fs_1.default.promises.readdir(src, { withFileTypes: true });
|
|
20
|
+
let count = 0;
|
|
21
|
+
const dirs = [];
|
|
22
|
+
const files = [];
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
// Apply filter if provided (for --minimal mode or incremental copy)
|
|
26
|
+
if (filter && !filter(entry.name, src, entry.isDirectory())) {
|
|
27
|
+
(0, logger_1.dbg)(` skip: ${entry.name}`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const srcPath = path_1.default.join(src, entry.name);
|
|
31
|
+
const destPath = path_1.default.join(dest, entry.name);
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
dirs.push({ srcPath, destPath });
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
files.push({ srcPath, destPath, name: entry.name });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Copy files in parallel batches
|
|
41
|
+
for (let i = 0; i < files.length; i += COPY_CONCURRENCY) {
|
|
42
|
+
const batch = files.slice(i, i + COPY_CONCURRENCY);
|
|
43
|
+
await Promise.all(batch.map(async ({ srcPath, destPath, name }) => {
|
|
44
|
+
if (!dryRun) {
|
|
45
|
+
await fs_1.default.promises.copyFile(srcPath, destPath);
|
|
46
|
+
}
|
|
47
|
+
(0, logger_1.dbg)(` copy: ${name}`);
|
|
48
|
+
}));
|
|
49
|
+
count += batch.length;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Recurse into directories in parallel (dirs are I/O-independent)
|
|
53
|
+
const dirResults = await Promise.all(
|
|
54
|
+
dirs.map(({ srcPath, destPath }) => copyDir(srcPath, destPath, dryRun, filter))
|
|
55
|
+
);
|
|
56
|
+
for (const dirCount of dirResults) {
|
|
57
|
+
count += dirCount;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return count;
|
|
61
|
+
}
|
|
62
|
+
async function countDir(dir) {
|
|
63
|
+
let count = 0;
|
|
64
|
+
const entries = await fs_1.default.promises.readdir(dir, { withFileTypes: true });
|
|
65
|
+
for (const e of entries) {
|
|
66
|
+
if (e.isDirectory())
|
|
67
|
+
count += await countDir(path_1.default.join(dir, e.name));
|
|
68
|
+
else
|
|
69
|
+
count++;
|
|
70
|
+
}
|
|
71
|
+
return count;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Returns true if the target directory IS the tribunal-kit package itself.
|
|
75
|
+
* This prevents `init --force` / `update` from deleting the package's own files
|
|
76
|
+
* when run from inside the project directory.
|
|
77
|
+
*/
|
|
78
|
+
function isSelfInstall(targetDir, pkgName, kitRoot) {
|
|
79
|
+
const resolvedTarget = path_1.default.resolve(targetDir);
|
|
80
|
+
// Direct path match
|
|
81
|
+
if (resolvedTarget === kitRoot)
|
|
82
|
+
return true;
|
|
83
|
+
// Check if the target's package.json is this package
|
|
84
|
+
const targetPkg = path_1.default.join(resolvedTarget, 'package.json');
|
|
85
|
+
if (fs_1.default.existsSync(targetPkg)) {
|
|
86
|
+
try {
|
|
87
|
+
const targetName = JSON.parse(fs_1.default.readFileSync(targetPkg, 'utf8')).name;
|
|
88
|
+
if (targetName === pkgName)
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Unreadable package.json — not a match
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateManifest = generateManifest;
|
|
7
|
+
exports.readManifest = readManifest;
|
|
8
|
+
exports.writeManifest = writeManifest;
|
|
9
|
+
exports.diffManifests = diffManifests;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
13
|
+
|
|
14
|
+
const MANIFEST_FILE = '.manifest.json';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compute SHA-256 hash of a file's contents.
|
|
18
|
+
* Uses streaming to handle large files without high memory usage.
|
|
19
|
+
*/
|
|
20
|
+
async function hashFile(filePath) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const hash = crypto_1.default.createHash('sha256');
|
|
23
|
+
const stream = fs_1.default.createReadStream(filePath);
|
|
24
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
25
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
26
|
+
stream.on('error', reject);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Walk a directory recursively and generate a hash manifest.
|
|
32
|
+
* Returns an object mapping relative file paths to their SHA-256 hashes.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} dir - The directory to walk
|
|
35
|
+
* @param {string} [baseDir] - The base directory for computing relative paths
|
|
36
|
+
* @returns {Promise<Record<string, string>>} Map of relative paths to SHA-256 hashes
|
|
37
|
+
*/
|
|
38
|
+
async function generateManifest(dir, baseDir) {
|
|
39
|
+
if (!baseDir) baseDir = dir;
|
|
40
|
+
const manifest = {};
|
|
41
|
+
|
|
42
|
+
if (!fs_1.default.existsSync(dir)) return manifest;
|
|
43
|
+
|
|
44
|
+
const entries = await fs_1.default.promises.readdir(dir, { withFileTypes: true });
|
|
45
|
+
|
|
46
|
+
// Process files in parallel batches
|
|
47
|
+
const BATCH_SIZE = 32;
|
|
48
|
+
const files = [];
|
|
49
|
+
const dirs = [];
|
|
50
|
+
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const fullPath = path_1.default.join(dir, entry.name);
|
|
53
|
+
if (entry.name === '.backups' || entry.name === '.manifest.json' || entry.name === 'history') {
|
|
54
|
+
continue; // Skip backup dirs, manifest, and history
|
|
55
|
+
}
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
dirs.push(fullPath);
|
|
58
|
+
} else {
|
|
59
|
+
files.push(fullPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Hash files in parallel batches
|
|
64
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
65
|
+
const batch = files.slice(i, i + BATCH_SIZE);
|
|
66
|
+
const results = await Promise.all(batch.map(async (filePath) => {
|
|
67
|
+
const relativePath = path_1.default.relative(baseDir, filePath).replace(/\\/g, '/');
|
|
68
|
+
const hash = await hashFile(filePath);
|
|
69
|
+
return { relativePath, hash };
|
|
70
|
+
}));
|
|
71
|
+
for (const { relativePath, hash } of results) {
|
|
72
|
+
manifest[relativePath] = hash;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Recurse into directories in parallel
|
|
77
|
+
const dirResults = await Promise.all(
|
|
78
|
+
dirs.map(d => generateManifest(d, baseDir))
|
|
79
|
+
);
|
|
80
|
+
for (const dirManifest of dirResults) {
|
|
81
|
+
Object.assign(manifest, dirManifest);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return manifest;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read an existing manifest from the .agent directory.
|
|
89
|
+
* Returns null if no manifest exists.
|
|
90
|
+
*/
|
|
91
|
+
function readManifest(agentDir) {
|
|
92
|
+
const manifestPath = path_1.default.join(agentDir, MANIFEST_FILE);
|
|
93
|
+
try {
|
|
94
|
+
if (!fs_1.default.existsSync(manifestPath)) return null;
|
|
95
|
+
const raw = fs_1.default.readFileSync(manifestPath, 'utf8');
|
|
96
|
+
return JSON.parse(raw);
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Write a manifest to the .agent directory.
|
|
104
|
+
*/
|
|
105
|
+
function writeManifest(agentDir, manifest) {
|
|
106
|
+
const manifestPath = path_1.default.join(agentDir, MANIFEST_FILE);
|
|
107
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Compare two manifests and return the diff.
|
|
112
|
+
*
|
|
113
|
+
* @param {Record<string, string>} oldManifest - Previously installed manifest
|
|
114
|
+
* @param {Record<string, string>} newManifest - Source manifest
|
|
115
|
+
* @returns {{ added: string[], changed: string[], removed: string[], unchanged: number }}
|
|
116
|
+
*/
|
|
117
|
+
function diffManifests(oldManifest, newManifest) {
|
|
118
|
+
const added = [];
|
|
119
|
+
const changed = [];
|
|
120
|
+
const removed = [];
|
|
121
|
+
let unchanged = 0;
|
|
122
|
+
|
|
123
|
+
// Find added and changed files
|
|
124
|
+
for (const [path, hash] of Object.entries(newManifest)) {
|
|
125
|
+
if (!(path in oldManifest)) {
|
|
126
|
+
added.push(path);
|
|
127
|
+
} else if (oldManifest[path] !== hash) {
|
|
128
|
+
changed.push(path);
|
|
129
|
+
} else {
|
|
130
|
+
unchanged++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Find removed files
|
|
135
|
+
for (const path of Object.keys(oldManifest)) {
|
|
136
|
+
if (!(path in newManifest)) {
|
|
137
|
+
removed.push(path);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { added, changed, removed, unchanged };
|
|
142
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runShellAsync = runShellAsync;
|
|
7
|
+
exports.getKitAgent = getKitAgent;
|
|
8
|
+
exports.banner = banner;
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const logger_1 = require("./logger");
|
|
13
|
+
function runShellAsync(command, options) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const child = (0, child_process_1.spawn)(command, [], { ...options, shell: true });
|
|
16
|
+
child.on('close', code => {
|
|
17
|
+
if (code !== 0)
|
|
18
|
+
reject(new Error(`Command failed with exit code ${code}`));
|
|
19
|
+
else
|
|
20
|
+
resolve();
|
|
21
|
+
});
|
|
22
|
+
child.on('error', reject);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function getKitAgent() {
|
|
26
|
+
// When installed via npm, the .agent/ folder is next to this script's package (two directories up from dist/commands)
|
|
27
|
+
// In src/utils, __dirname is .../src/utils. We go up to src, then to root. So path.resolve(__dirname, '../../.agent')
|
|
28
|
+
const kitRoot = path_1.default.resolve(__dirname, '../..');
|
|
29
|
+
const agentDir = path_1.default.join(kitRoot, '.agent');
|
|
30
|
+
if (!fs_1.default.existsSync(agentDir)) {
|
|
31
|
+
(0, logger_1.err)(`Kit .agent/ folder not found at: ${agentDir}`);
|
|
32
|
+
(0, logger_1.err)('The package may be corrupted. Try: npm install -g tribunal-kit');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
return agentDir;
|
|
36
|
+
}
|
|
37
|
+
function banner(quiet) {
|
|
38
|
+
if (quiet)
|
|
39
|
+
return;
|
|
40
|
+
// Big ASCII art (TRIBUNAL-KIT)
|
|
41
|
+
const art = String.raw `
|
|
42
|
+
████████╗██████╗ ██╗██████╗ ██╗ ██╗███╗ ██╗ █████╗ ██╗ ██╗ ██╗██╗████████╗
|
|
43
|
+
╚══██╔══╝██╔══██╗██║██╔══██╗██║ ██║████╗ ██║██╔══██╗██║ ██║ ██╔╝██║╚══██╔══╝
|
|
44
|
+
██║ ██████╔╝██║██████╔╝██║ ██║██╔██╗ ██║███████║██║█████╗█████╔╝ ██║ ██║
|
|
45
|
+
██║ ██╔══██╗██║██╔══██╗██║ ██║██║╚██╗██║██╔══██║██║╚════╝██╔═██╗ ██║ ██║
|
|
46
|
+
██║ ██║ ██║██║██████╔╝╚██████╔╝██║ ╚████║██║ ██║███████╗ ██║ ██╗██║ ██║
|
|
47
|
+
╚═╝ ╚═╝ ╚═╝╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ `.split('\n').filter(Boolean);
|
|
48
|
+
console.log();
|
|
49
|
+
for (const line of art) {
|
|
50
|
+
let gradientLine = ' \x1b[1m';
|
|
51
|
+
for (let i = 0; i < line.length; i++) {
|
|
52
|
+
gradientLine += `\x1b[38;2;255;22;55m${line[i]}`;
|
|
53
|
+
}
|
|
54
|
+
gradientLine += '\x1b[0m';
|
|
55
|
+
(0, logger_1.log)(gradientLine);
|
|
56
|
+
}
|
|
57
|
+
console.log();
|
|
58
|
+
// Subtitle strip
|
|
59
|
+
const W = 84;
|
|
60
|
+
const sub = 'Anti-Hallucination Agent System';
|
|
61
|
+
const sp = Math.max(0, W - sub.length);
|
|
62
|
+
const centred = ' '.repeat(Math.floor(sp / 2)) + sub + ' '.repeat(Math.ceil(sp / 2));
|
|
63
|
+
const RED_ANSI = '\x1b[38;2;255;22;55m';
|
|
64
|
+
console.log(` ${RED_ANSI}╔${'═'.repeat(W)}╗\x1b[0m`);
|
|
65
|
+
console.log(` ${RED_ANSI}║\x1b[0m${(0, logger_1.c)('gray', centred)}${RED_ANSI}║\x1b[0m`);
|
|
66
|
+
console.log(` ${RED_ANSI}╚${'═'.repeat(W)}╝\x1b[0m`);
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.C = void 0;
|
|
4
|
+
exports.colorize = colorize;
|
|
5
|
+
exports.c = c;
|
|
6
|
+
exports.bold = bold;
|
|
7
|
+
exports.setLogLevels = setLogLevels;
|
|
8
|
+
exports.log = log;
|
|
9
|
+
exports.ok = ok;
|
|
10
|
+
exports.warn = warn;
|
|
11
|
+
exports.err = err;
|
|
12
|
+
exports.dim = dim;
|
|
13
|
+
exports.dbg = dbg;
|
|
14
|
+
// src/utils/logger.ts
|
|
15
|
+
exports.C = {
|
|
16
|
+
reset: '\x1b[0m',
|
|
17
|
+
bold: '\x1b[1m',
|
|
18
|
+
dim: '\x1b[2m',
|
|
19
|
+
red: '\x1b[91m',
|
|
20
|
+
green: '\x1b[92m',
|
|
21
|
+
yellow: '\x1b[93m',
|
|
22
|
+
blue: '\x1b[94m',
|
|
23
|
+
magenta: '\x1b[95m',
|
|
24
|
+
cyan: '\x1b[96m',
|
|
25
|
+
white: '\x1b[97m',
|
|
26
|
+
gray: '\x1b[90m',
|
|
27
|
+
bgCyan: '\x1b[46m',
|
|
28
|
+
};
|
|
29
|
+
function colorize(color, text) {
|
|
30
|
+
return `${exports.C[color]}${text}${exports.C.reset}`;
|
|
31
|
+
}
|
|
32
|
+
function c(color, text) {
|
|
33
|
+
return `${exports.C[color]}${text}${exports.C.reset}`;
|
|
34
|
+
}
|
|
35
|
+
function bold(text) {
|
|
36
|
+
return `${exports.C.bold}${text}${exports.C.reset}`;
|
|
37
|
+
}
|
|
38
|
+
let quiet = false;
|
|
39
|
+
let verbose = false;
|
|
40
|
+
function setLogLevels(q, v) {
|
|
41
|
+
quiet = q;
|
|
42
|
+
verbose = v;
|
|
43
|
+
}
|
|
44
|
+
function log(msg) { if (!quiet)
|
|
45
|
+
console.log(msg); }
|
|
46
|
+
function ok(msg) { if (!quiet)
|
|
47
|
+
console.log(` ${c('green', '✔')} ${msg}`); }
|
|
48
|
+
function warn(msg) { if (!quiet)
|
|
49
|
+
console.log(` ${c('yellow', '⚠')} ${msg}`); }
|
|
50
|
+
function err(msg) { console.error(` ${c('red', '✖')} ${msg}`); }
|
|
51
|
+
function dim(msg) { if (!quiet)
|
|
52
|
+
console.log(` ${c('gray', msg)}`); }
|
|
53
|
+
function dbg(msg) { if (verbose)
|
|
54
|
+
console.log(` ${c('gray', '⊡')} ${c('gray', msg)}`); }
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.compareSemver = compareSemver;
|
|
7
|
+
exports.fetchLatestVersion = fetchLatestVersion;
|
|
8
|
+
exports.autoUpdateCheck = autoUpdateCheck;
|
|
9
|
+
const https_1 = __importDefault(require("https"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const os_1 = __importDefault(require("os"));
|
|
14
|
+
const logger_1 = require("./logger");
|
|
15
|
+
|
|
16
|
+
// Cache TTL: 1 hour (in milliseconds)
|
|
17
|
+
const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
18
|
+
|
|
19
|
+
function getCachePath() {
|
|
20
|
+
return path_1.default.join(os_1.default.homedir(), '.tribunal-kit-update-cache.json');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read cached version check result.
|
|
25
|
+
* Returns { version, timestamp } or null if cache is missing/expired/corrupt.
|
|
26
|
+
*/
|
|
27
|
+
function readCache() {
|
|
28
|
+
try {
|
|
29
|
+
const cachePath = getCachePath();
|
|
30
|
+
if (!fs_1.default.existsSync(cachePath)) return null;
|
|
31
|
+
const raw = fs_1.default.readFileSync(cachePath, 'utf8');
|
|
32
|
+
const data = JSON.parse(raw);
|
|
33
|
+
if (!data.version || !data.timestamp) return null;
|
|
34
|
+
// Check TTL
|
|
35
|
+
if (Date.now() - data.timestamp > CACHE_TTL_MS) return null;
|
|
36
|
+
return data;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Write version check result to cache.
|
|
44
|
+
*/
|
|
45
|
+
function writeCache(version) {
|
|
46
|
+
try {
|
|
47
|
+
const cachePath = getCachePath();
|
|
48
|
+
fs_1.default.writeFileSync(cachePath, JSON.stringify({
|
|
49
|
+
version,
|
|
50
|
+
timestamp: Date.now()
|
|
51
|
+
}), 'utf8');
|
|
52
|
+
} catch {
|
|
53
|
+
// Cache write failure is non-critical — silently ignore
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Compare two semver strings. Returns:
|
|
59
|
+
* 1 if a > b, -1 if a < b, 0 if equal.
|
|
60
|
+
*/
|
|
61
|
+
function compareSemver(a, b) {
|
|
62
|
+
const pa = a.replace(/^v/, '').split('.').map(Number);
|
|
63
|
+
const pb = b.replace(/^v/, '').split('.').map(Number);
|
|
64
|
+
for (let i = 0; i < 3; i++) {
|
|
65
|
+
const na = pa[i] || 0;
|
|
66
|
+
const nb = pb[i] || 0;
|
|
67
|
+
if (na > nb)
|
|
68
|
+
return 1;
|
|
69
|
+
if (na < nb)
|
|
70
|
+
return -1;
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Fetch the latest version from npm registry.
|
|
76
|
+
* Returns the version string (e.g. '4.0.0') or null on failure.
|
|
77
|
+
* Uses a 1-hour TTL cache to avoid redundant network calls.
|
|
78
|
+
*/
|
|
79
|
+
function fetchLatestVersion(currentVersion) {
|
|
80
|
+
// Check cache first
|
|
81
|
+
const cached = readCache();
|
|
82
|
+
if (cached) {
|
|
83
|
+
return Promise.resolve(cached.version);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
const req = https_1.default.get('https://registry.npmjs.org/tribunal-kit/latest', {
|
|
88
|
+
headers: {
|
|
89
|
+
'Accept': 'application/json',
|
|
90
|
+
'User-Agent': `tribunal-kit/${currentVersion}`
|
|
91
|
+
},
|
|
92
|
+
timeout: 3000 // Reduced from 5s to 3s for faster fallback
|
|
93
|
+
}, (res) => {
|
|
94
|
+
let data = '';
|
|
95
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
96
|
+
res.on('end', () => {
|
|
97
|
+
try {
|
|
98
|
+
const json = JSON.parse(data);
|
|
99
|
+
const version = json.version || null;
|
|
100
|
+
if (version) writeCache(version);
|
|
101
|
+
resolve(version);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
resolve(null);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
req.on('error', () => resolve(null));
|
|
109
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Non-blocking update check.
|
|
115
|
+
*
|
|
116
|
+
* PERF: Instead of blocking the command while checking for updates,
|
|
117
|
+
* this fires the HTTP request and shows the result AFTER the command completes.
|
|
118
|
+
* The check runs concurrently with command execution, adding zero latency.
|
|
119
|
+
*
|
|
120
|
+
* Returns true if a re-invoke happened (caller should exit), false otherwise.
|
|
121
|
+
*/
|
|
122
|
+
async function autoUpdateCheck(originalArgs, currentVersion) {
|
|
123
|
+
// Recursion guard: if we're already a re-invoked process, skip
|
|
124
|
+
if (process.env.TK_SKIP_UPDATE_CHECK === '1') {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Fire the fetch but DON'T await it immediately — let the command run first
|
|
129
|
+
const versionPromise = fetchLatestVersion(currentVersion);
|
|
130
|
+
|
|
131
|
+
// Register a process.on('beforeExit') hook to show the update notification
|
|
132
|
+
// AFTER the command has finished executing
|
|
133
|
+
process.on('beforeExit', async () => {
|
|
134
|
+
try {
|
|
135
|
+
const latestVersion = await versionPromise;
|
|
136
|
+
if (!latestVersion) return;
|
|
137
|
+
if (compareSemver(latestVersion, currentVersion) <= 0) return;
|
|
138
|
+
|
|
139
|
+
// Show a non-intrusive update notification
|
|
140
|
+
console.log();
|
|
141
|
+
console.log((0, logger_1.colorize)('cyan', ` ⬆ Update available: ${(0, logger_1.colorize)('bold', currentVersion)} → ${(0, logger_1.colorize)('bold', latestVersion)}`));
|
|
142
|
+
console.log((0, logger_1.colorize)('gray', ` Run: npx tribunal-kit@${latestVersion} init --force`));
|
|
143
|
+
console.log();
|
|
144
|
+
} catch {
|
|
145
|
+
// Silently ignore — update notification is non-critical
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return false; // Never block — always let the command proceed
|
|
150
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tribunal-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.7.0",
|
|
4
4
|
"description": "Anti-Hallucination AI Agent Kit — 43 specialist agents, 32 slash commands, 19 parallel Tribunal reviewers, Performance Swarm engine, Supreme Court case law pipeline, and long-running agent harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"bin/",
|
|
50
|
+
"dist/",
|
|
50
51
|
"scripts/",
|
|
51
52
|
".agent/",
|
|
52
53
|
"README.md",
|