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.
@@ -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 });
@@ -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": "4.6.1",
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",