tribunal-kit 5.8.4 → 5.8.5
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 +3 -3
- package/.agent/history/integrity_manifest.json +75 -7
- package/.agent/history/memory/.memory.idx +1065 -1
- package/.agent/history/memory/MEMORY.md +71 -1
- package/.agent/rules/GEMINI.md +1 -1
- package/.agent/scripts/_utils.js +28 -7
- package/.agent/scripts/context_broker.js +0 -19
- package/.agent/scripts/guardrail_engine.js +1 -1
- package/.agent/scripts/integrity_manifest.js +10 -4
- package/.agent/scripts/marathon_harness.js +10 -1
- package/.agent/scripts/skill_evolution.js +36 -21
- package/.agent/scripts/swarm_dispatcher.js +65 -6
- package/.agent/skills/api-patterns/scripts/__pycache__/api_validator.cpython-311.pyc +0 -0
- package/.agent/skills/better-colors/SKILL.md +12 -4
- package/.agent/skills/database-design/scripts/__pycache__/schema_validator.cpython-311.pyc +0 -0
- package/.agent/skills/fixing-accessibility/SKILL.md +6 -4
- package/.agent/skills/frontend-design/SKILL.md +80 -81
- package/.agent/skills/frontend-design/scripts/__pycache__/accessibility_checker.cpython-311.pyc +0 -0
- package/.agent/skills/frontend-design/scripts/__pycache__/ux_audit.cpython-311.pyc +0 -0
- package/.agent/skills/geo-fundamentals/scripts/__pycache__/geo_checker.cpython-311.pyc +0 -0
- package/.agent/skills/i18n-localization/scripts/__pycache__/i18n_checker.cpython-311.pyc +0 -0
- package/.agent/skills/impeccable/SKILL.md +2 -0
- package/.agent/skills/lint-and-validate/scripts/__pycache__/lint_runner.cpython-311.pyc +0 -0
- package/.agent/skills/lint-and-validate/scripts/__pycache__/type_coverage.cpython-311.pyc +0 -0
- package/.agent/skills/mobile-design/scripts/__pycache__/mobile_audit.cpython-311.pyc +0 -0
- package/.agent/skills/motion-engineering/SKILL.md +66 -164
- package/.agent/skills/nextjs-react-expert/scripts/__pycache__/convert_rules.cpython-311.pyc +0 -0
- package/.agent/skills/nextjs-react-expert/scripts/__pycache__/react_performance_checker.cpython-311.pyc +0 -0
- package/.agent/skills/performance-profiling/scripts/__pycache__/lighthouse_audit.cpython-311.pyc +0 -0
- package/.agent/skills/seo-fundamentals/scripts/__pycache__/seo_checker.cpython-311.pyc +0 -0
- package/.agent/skills/testing-patterns/scripts/__pycache__/test_runner.cpython-311.pyc +0 -0
- package/.agent/skills/vulnerability-scanner/scripts/__pycache__/security_scan.cpython-311.pyc +0 -0
- package/.agent/skills/webapp-testing/scripts/__pycache__/playwright_runner.cpython-311.pyc +0 -0
- package/.agent/workflows/tribunal-full.md +5 -5
- package/CONTRIBUTING.md +1 -1
- package/README.md +4 -4
- package/bin/mcp-server.js +45 -45
- package/bin/wrapper.js +50 -25
- package/dist/cli.js +30 -0
- package/dist/commands/init.js +25 -1
- package/dist/commands/native.js +228 -0
- package/dist/commands/validate.js +67 -0
- package/dist/mcp/server.js +4 -138
- package/package.json +11 -10
- package/scripts/benchmark.js +42 -0
- package/scripts/sync-version.js +129 -76
package/bin/mcp-server.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* over standard I/O, allowing AI clients (Cursor, Windsurf, Claude) to natively
|
|
8
8
|
* invoke tribunal checks.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* In-process tools load reusable modules directly. Commands that depend on a
|
|
11
|
+
* standalone CLI process remain isolated deliberately.
|
|
12
12
|
*
|
|
13
13
|
* Protocol: MCP 2024-11-05 over JSON-RPC 2.0 / stdio
|
|
14
14
|
*/
|
|
@@ -18,7 +18,7 @@ const { spawnSync } = require("child_process");
|
|
|
18
18
|
|
|
19
19
|
const PKG = require(path.resolve(__dirname, "../package.json"));
|
|
20
20
|
|
|
21
|
-
// Timeout for
|
|
21
|
+
// Timeout for intentionally isolated child processes (30 seconds).
|
|
22
22
|
const SPAWN_TIMEOUT_MS = 30000;
|
|
23
23
|
|
|
24
24
|
// Minimal JSON-RPC 2.0 over stdio
|
|
@@ -31,61 +31,60 @@ const rl = readline.createInterface({
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Run the
|
|
35
|
-
*
|
|
34
|
+
* Run the workspace integrity audit without invoking the CLI schema validator.
|
|
35
|
+
* The MCP audit is about Tribunal assets, while `tk validate` validates one
|
|
36
|
+
* explicitly supplied payload and schema.
|
|
36
37
|
*/
|
|
37
|
-
function
|
|
38
|
-
const os = require("os");
|
|
38
|
+
function runTribunalAudit() {
|
|
39
39
|
const fs = require("fs");
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let binPath = null;
|
|
48
|
-
try {
|
|
49
|
-
const pkgPath = require.resolve(`${pkgName}/package.json`);
|
|
50
|
-
const candidatePath = path.resolve(
|
|
51
|
-
path.dirname(pkgPath),
|
|
52
|
-
`bin/tribunal-core${ext}`,
|
|
53
|
-
);
|
|
54
|
-
if (fs.existsSync(candidatePath)) binPath = candidatePath;
|
|
55
|
-
} catch (_) {}
|
|
56
|
-
if (!binPath) {
|
|
57
|
-
const devPath = path.resolve(
|
|
58
|
-
__dirname,
|
|
59
|
-
"..",
|
|
60
|
-
"target",
|
|
61
|
-
"release",
|
|
62
|
-
`tribunal-core${ext}`,
|
|
63
|
-
);
|
|
64
|
-
if (fs.existsSync(devPath)) binPath = devPath;
|
|
65
|
-
}
|
|
40
|
+
const projectRoot = process.cwd();
|
|
41
|
+
const manifestScript = path.join(
|
|
42
|
+
projectRoot,
|
|
43
|
+
".agent",
|
|
44
|
+
"scripts",
|
|
45
|
+
"integrity_manifest.js",
|
|
46
|
+
);
|
|
66
47
|
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
encoding: "utf8",
|
|
70
|
-
timeout: SPAWN_TIMEOUT_MS,
|
|
71
|
-
});
|
|
72
|
-
return result.stdout || result.stderr || "No output";
|
|
48
|
+
if (!fs.existsSync(manifestScript)) {
|
|
49
|
+
return "Error: .agent/scripts/integrity_manifest.js was not found. Run `tk init` first.";
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
|
|
76
|
-
|
|
52
|
+
try {
|
|
53
|
+
const { generateManifest } = require(manifestScript);
|
|
54
|
+
const manifest = generateManifest(projectRoot);
|
|
55
|
+
if (manifest.error) return `Error: ${manifest.error}`;
|
|
56
|
+
|
|
57
|
+
const { integrity } = manifest;
|
|
58
|
+
const lines = [
|
|
59
|
+
"Tribunal audit complete.",
|
|
60
|
+
`Agents: ${manifest.agents.total} (${manifest.agents.reviewer_count} reviewers)`,
|
|
61
|
+
`Skills: ${manifest.skills.total}`,
|
|
62
|
+
`Scripts: ${manifest.scripts.total}`,
|
|
63
|
+
`Workflows: ${manifest.workflows.total}`,
|
|
64
|
+
`References: ${integrity.total_references}; phantom references: ${integrity.phantom_references}`,
|
|
65
|
+
`Count claims: ${integrity.total_claims}; invalid claims: ${integrity.invalid_claims}`,
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
if (integrity.phantom_references > 0 || integrity.invalid_claims > 0) {
|
|
69
|
+
lines.push("Audit found integrity issues; run `tk guardrail --scan` for remediation details.");
|
|
70
|
+
} else {
|
|
71
|
+
lines.push("All discovered references and global asset-count claims are valid.");
|
|
72
|
+
}
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return `Audit failed: ${error.message}`;
|
|
76
|
+
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
* Search case law
|
|
80
|
+
* Search case law in an isolated process because its CLI owns persistent state.
|
|
81
81
|
*/
|
|
82
82
|
function searchCaseLaw(query) {
|
|
83
83
|
const caseLawScript = path.resolve(
|
|
84
84
|
__dirname,
|
|
85
85
|
"../.agent/scripts/case_law_manager.js",
|
|
86
86
|
);
|
|
87
|
-
//
|
|
88
|
-
// that modifies global state, but we use spawn with minimal overhead
|
|
87
|
+
// case_law_manager is a standalone stateful CLI, so retain its process boundary.
|
|
89
88
|
const result = spawnSync(
|
|
90
89
|
process.execPath,
|
|
91
90
|
[caseLawScript, "search-cases", "--query", query],
|
|
@@ -301,7 +300,7 @@ function handleRequest(req) {
|
|
|
301
300
|
}
|
|
302
301
|
|
|
303
302
|
if (toolName === "run_tribunal_audit") {
|
|
304
|
-
const text =
|
|
303
|
+
const text = runTribunalAudit();
|
|
305
304
|
return { content: [{ type: "text", text }] };
|
|
306
305
|
}
|
|
307
306
|
|
|
@@ -548,5 +547,6 @@ if (process.env.NODE_ENV === "test") {
|
|
|
548
547
|
module.exports = {
|
|
549
548
|
handleRequest,
|
|
550
549
|
stripBoilerplate,
|
|
550
|
+
runTribunalAudit,
|
|
551
551
|
};
|
|
552
552
|
}
|
package/bin/wrapper.js
CHANGED
|
@@ -21,8 +21,13 @@ const RUST_COMMANDS = new Set([
|
|
|
21
21
|
"hook",
|
|
22
22
|
"uninstall",
|
|
23
23
|
"memory",
|
|
24
|
+
"min-context",
|
|
25
|
+
"dag-schedule",
|
|
26
|
+
"context-compress",
|
|
27
|
+
"optimize-step",
|
|
24
28
|
]);
|
|
25
29
|
|
|
30
|
+
|
|
26
31
|
// Determine the path to the compiled Rust binary
|
|
27
32
|
// In a full production release, this checks optionalDependencies in node_modules
|
|
28
33
|
// For development, it checks the local target/release folder
|
|
@@ -30,6 +35,11 @@ function getBinaryPath() {
|
|
|
30
35
|
if (process.env.TRIBUNAL_FORCE_JS === "1") {
|
|
31
36
|
return null;
|
|
32
37
|
}
|
|
38
|
+
|
|
39
|
+
if (process.env.TRIBUNAL_CORE_PATH && fs.existsSync(process.env.TRIBUNAL_CORE_PATH)) {
|
|
40
|
+
return process.env.TRIBUNAL_CORE_PATH;
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
const isWindows = os.platform() === "win32";
|
|
34
44
|
const ext = isWindows ? ".exe" : "";
|
|
35
45
|
const platform = os.platform();
|
|
@@ -38,46 +48,61 @@ function getBinaryPath() {
|
|
|
38
48
|
// First, try production resolution (from optionalDependencies)
|
|
39
49
|
const pkgName = `@tribunal-kit/core-${platform}-${arch}`;
|
|
40
50
|
try {
|
|
41
|
-
// Try to resolve the binary from the optional dependency package
|
|
42
51
|
const pkgPath = require.resolve(`${pkgName}/package.json`);
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
`bin/tribunal-core${ext}`,
|
|
46
|
-
);
|
|
52
|
+
const pkgDir = path.dirname(pkgPath);
|
|
53
|
+
const binPath = path.resolve(pkgDir, `bin/tribunal-core${ext}`);
|
|
47
54
|
if (fs.existsSync(binPath)) {
|
|
48
55
|
return binPath;
|
|
49
56
|
}
|
|
57
|
+
const rootBinPath = path.resolve(pkgDir, `tribunal-core${ext}`);
|
|
58
|
+
if (fs.existsSync(rootBinPath)) {
|
|
59
|
+
return rootBinPath;
|
|
60
|
+
}
|
|
50
61
|
} catch {
|
|
51
62
|
// Package not found, ignore and fall back to local dev targets
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
// Second, try to find the binary
|
|
55
|
-
const
|
|
56
|
-
__dirname,
|
|
57
|
-
"..",
|
|
58
|
-
"target",
|
|
59
|
-
"
|
|
60
|
-
`tribunal-core${ext}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
// Second, try to find the binary in local dev target directories
|
|
66
|
+
const candidatePaths = [
|
|
67
|
+
path.resolve(__dirname, "..", "target", "release", `tribunal-core${ext}`),
|
|
68
|
+
path.resolve(__dirname, "..", "target", "debug", `tribunal-core${ext}`),
|
|
69
|
+
path.resolve(__dirname, "..", "..", "target", "release", `tribunal-core${ext}`),
|
|
70
|
+
path.resolve(__dirname, "..", "..", "target", "debug", `tribunal-core${ext}`),
|
|
71
|
+
path.resolve(process.cwd(), "target", "release", `tribunal-core${ext}`),
|
|
72
|
+
path.resolve(process.cwd(), "target", "debug", `tribunal-core${ext}`),
|
|
73
|
+
path.resolve(process.cwd(), "tribunal-kit", "target", "release", `tribunal-core${ext}`),
|
|
74
|
+
path.resolve(process.cwd(), "tribunal-kit", "target", "debug", `tribunal-core${ext}`),
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const candidate of candidatePaths) {
|
|
78
|
+
if (fs.existsSync(candidate)) {
|
|
79
|
+
return candidate;
|
|
80
|
+
}
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
// Third,
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
// Third, attempt on-demand compilation if Cargo.toml exists locally and cargo is installed
|
|
84
|
+
const cargoTomlPath = path.resolve(__dirname, "..", "Cargo.toml");
|
|
85
|
+
if (fs.existsSync(cargoTomlPath)) {
|
|
86
|
+
try {
|
|
87
|
+
const buildResult = spawnSync("cargo", ["build", "--release"], {
|
|
88
|
+
cwd: path.resolve(__dirname, ".."),
|
|
89
|
+
stdio: "ignore",
|
|
90
|
+
});
|
|
91
|
+
if (buildResult.status === 0) {
|
|
92
|
+
const releasePath = candidatePaths[0];
|
|
93
|
+
if (fs.existsSync(releasePath)) {
|
|
94
|
+
return releasePath;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// cargo not available or build failed, fallback gracefully
|
|
99
|
+
}
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
return null;
|
|
79
103
|
}
|
|
80
104
|
|
|
105
|
+
|
|
81
106
|
function runRustBinary(binPath, args) {
|
|
82
107
|
const stdio = [
|
|
83
108
|
"inherit",
|
package/dist/cli.js
CHANGED
|
@@ -106,6 +106,11 @@ function cmdHelp(quiet = false) {
|
|
|
106
106
|
(0, logger_1.log)(cmd('compile', 'Compile rules into a static instruction file for terminal agents'));
|
|
107
107
|
(0, logger_1.log)(cmd('memory', '4-Type Taxonomy Persistent Memory Engine (store, recall, gc, stats, export)'));
|
|
108
108
|
(0, logger_1.log)(cmd('optimize-skill', 'Optimize project skills actively using SkillOpt validation gates'));
|
|
109
|
+
(0, logger_1.log)(cmd('validate', 'Validate a JSON payload against a Tribunal schema'));
|
|
110
|
+
(0, logger_1.log)(cmd('min-context', 'Remove empty lines from a context file'));
|
|
111
|
+
(0, logger_1.log)(cmd('dag-schedule', 'Compute parallel execution waves from task dependencies'));
|
|
112
|
+
(0, logger_1.log)(cmd('context-compress', 'Compress a context file while retaining VERIFY comments'));
|
|
113
|
+
(0, logger_1.log)(cmd('optimize-step', 'Apply bounded SkillOpt edits from a JSON payload'));
|
|
109
114
|
(0, logger_1.log)(cmd('guardrail', 'Validate .agent/ integrity (phantom refs, count mismatches, drift)'));
|
|
110
115
|
(0, logger_1.log)(cmd('uninstall', 'Remove .agent/ folder from project'));
|
|
111
116
|
console.log();
|
|
@@ -267,6 +272,31 @@ async function runWithUpdateCheck(command, flags) {
|
|
|
267
272
|
await cmdGuardrail(flags, process.argv, quiet);
|
|
268
273
|
break;
|
|
269
274
|
}
|
|
275
|
+
case 'validate': {
|
|
276
|
+
const cmdValidate = loadCmd('./commands/validate', 'cmdValidate');
|
|
277
|
+
cmdValidate(flags, process.argv, quiet);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case 'min-context': {
|
|
281
|
+
const cmdMinContext = loadCmd('./commands/native', 'cmdMinContext');
|
|
282
|
+
cmdMinContext(process.argv, quiet);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case 'dag-schedule': {
|
|
286
|
+
const cmdDagSchedule = loadCmd('./commands/native', 'cmdDagSchedule');
|
|
287
|
+
cmdDagSchedule(process.argv, quiet);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 'context-compress': {
|
|
291
|
+
const cmdContextCompress = loadCmd('./commands/native', 'cmdContextCompress');
|
|
292
|
+
cmdContextCompress(process.argv, quiet);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
case 'optimize-step': {
|
|
296
|
+
const cmdOptimizeStep = loadCmd('./commands/native', 'cmdOptimizeStep');
|
|
297
|
+
cmdOptimizeStep(process.argv, quiet);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
270
300
|
case 'help':
|
|
271
301
|
case '--help':
|
|
272
302
|
case '-h':
|
package/dist/commands/init.js
CHANGED
|
@@ -267,12 +267,13 @@ async function cmdInit(flags, quiet = false) {
|
|
|
267
267
|
console.log(drawRow(' Next Steps:', (0, logger_1.c)('gray', ' Next Steps:')));
|
|
268
268
|
console.log(stepRow('/generate', 'Generate code with reviews'));
|
|
269
269
|
console.log(stepRow('/review', 'Audit existing code for issues'));
|
|
270
|
-
console.log(stepRow('/tribunal-full', 'Run all
|
|
270
|
+
console.log(stepRow('/tribunal-full', 'Run all 19 reviewers in parallel'));
|
|
271
271
|
console.log(drawRow('', ''));
|
|
272
272
|
console.log(` ${(0, logger_1.c)(borderCol, '└' + '─'.repeat(W) + '┘')}`);
|
|
273
273
|
console.log();
|
|
274
274
|
(0, logger_1.log)(` ${(0, logger_1.c)('gray', '✦ Generating IDE bridge files...')}`);
|
|
275
275
|
await generateIDEBridges(targetDir, agentDest, dryRun, isMinimal);
|
|
276
|
+
await scaffoldDesignSystem(targetDir, agentSrc, dryRun);
|
|
276
277
|
}
|
|
277
278
|
console.log();
|
|
278
279
|
}
|
|
@@ -375,4 +376,27 @@ ${rulesContent}
|
|
|
375
376
|
await Promise.all(bridges.map(b => writeBridge(b.path, b.content, b.label)));
|
|
376
377
|
console.log();
|
|
377
378
|
}
|
|
379
|
+
async function scaffoldDesignSystem(targetDir, agentSrc, dryRun = false) {
|
|
380
|
+
const designMdSrc = path_1.default.join(agentSrc, 'templates', 'DESIGN.md');
|
|
381
|
+
const designTokensSrc = path_1.default.join(agentSrc, 'templates', 'design-tokens.json');
|
|
382
|
+
const designMdDest = path_1.default.join(targetDir, 'DESIGN.md');
|
|
383
|
+
const designTokensDest = path_1.default.join(targetDir, 'design-tokens.json');
|
|
384
|
+
|
|
385
|
+
const copyIfMissing = async (src, dest, label) => {
|
|
386
|
+
if (!fs_1.default.existsSync(src)) return;
|
|
387
|
+
if (fs_1.default.existsSync(dest)) {
|
|
388
|
+
(0, logger_1.dbg)(` skip (exists): ${path_1.default.basename(dest)}`);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (dryRun) {
|
|
392
|
+
(0, logger_1.dbg)(` would create: ${dest}`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
await fs_1.default.promises.copyFile(src, dest);
|
|
396
|
+
(0, logger_1.ok)(`${label} → ${(0, logger_1.c)('gray', path_1.default.relative(targetDir, dest))}`);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
await copyIfMissing(designMdSrc, designMdDest, 'DESIGN.md System');
|
|
400
|
+
await copyIfMissing(designTokensSrc, designTokensDest, 'Design Tokens');
|
|
401
|
+
}
|
|
378
402
|
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
function getOption(args, names) {
|
|
6
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
7
|
+
const arg = args[index];
|
|
8
|
+
for (const name of names) {
|
|
9
|
+
if (arg === name) return args[index + 1] || null;
|
|
10
|
+
if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function fail(message) {
|
|
17
|
+
console.error(message);
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseMaxLines(value) {
|
|
23
|
+
if (value === null) return null;
|
|
24
|
+
const parsed = Number.parseInt(value, 10);
|
|
25
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function cmdMinContext(processArgs, quiet = false) {
|
|
29
|
+
const args = processArgs.slice(3);
|
|
30
|
+
const file = getOption(args, ["--file", "-f"]);
|
|
31
|
+
const maxLines = parseMaxLines(getOption(args, ["--max-lines"]));
|
|
32
|
+
if (!file) return fail("Usage: tk min-context --file <path> [--max-lines <count>]");
|
|
33
|
+
if (maxLines === undefined) return fail("--max-lines must be a non-negative integer.");
|
|
34
|
+
|
|
35
|
+
let content;
|
|
36
|
+
try {
|
|
37
|
+
content = fs.readFileSync(file, "utf8");
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return fail(`Failed to read file: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const originalLines = content.split(/\r?\n/).length;
|
|
43
|
+
let lines = content.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.trim());
|
|
44
|
+
if (maxLines !== null) lines = lines.slice(0, maxLines);
|
|
45
|
+
const result = {
|
|
46
|
+
file,
|
|
47
|
+
original_lines: originalLines,
|
|
48
|
+
minified_lines: lines.length,
|
|
49
|
+
lines_reduced: Math.max(0, originalLines - lines.length),
|
|
50
|
+
minified: lines.join("\n"),
|
|
51
|
+
};
|
|
52
|
+
if (!quiet) console.error(`✓ Minified ${file}`);
|
|
53
|
+
console.log(JSON.stringify(result));
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function cmdDagSchedule(processArgs, quiet = false) {
|
|
58
|
+
const args = processArgs.slice(3);
|
|
59
|
+
const rawTasks = getOption(args, ["--tasks", "-t"]);
|
|
60
|
+
if (!rawTasks) return fail("Usage: tk dag-schedule --tasks '<json-array>'");
|
|
61
|
+
|
|
62
|
+
let tasks;
|
|
63
|
+
try {
|
|
64
|
+
tasks = JSON.parse(rawTasks);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return fail(`Failed to parse task JSON: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(tasks) || tasks.some((task) => !task || typeof task.id !== "string" || !task.id)) {
|
|
69
|
+
return fail("Each task must be an object with a non-empty id.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const ids = new Set(tasks.map((task) => task.id));
|
|
73
|
+
const inDegree = new Map(tasks.map((task) => [task.id, 0]));
|
|
74
|
+
const dependents = new Map(tasks.map((task) => [task.id, []]));
|
|
75
|
+
for (const task of tasks) {
|
|
76
|
+
const dependencies = Array.isArray(task.dependencies) ? task.dependencies : [];
|
|
77
|
+
for (const dependency of dependencies) {
|
|
78
|
+
if (ids.has(dependency)) {
|
|
79
|
+
dependents.get(dependency).push(task.id);
|
|
80
|
+
inDegree.set(task.id, inDegree.get(task.id) + 1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const waves = [];
|
|
86
|
+
let wave = [...inDegree.entries()].filter(([, degree]) => degree === 0).map(([id]) => id).sort();
|
|
87
|
+
let processed = 0;
|
|
88
|
+
while (wave.length > 0) {
|
|
89
|
+
waves.push(wave);
|
|
90
|
+
processed += wave.length;
|
|
91
|
+
const next = [];
|
|
92
|
+
for (const id of wave) {
|
|
93
|
+
for (const dependent of dependents.get(id)) {
|
|
94
|
+
const nextDegree = inDegree.get(dependent) - 1;
|
|
95
|
+
inDegree.set(dependent, nextDegree);
|
|
96
|
+
if (nextDegree === 0) next.push(dependent);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
wave = [...new Set(next)].sort();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const result = {
|
|
103
|
+
success: processed === tasks.length,
|
|
104
|
+
total_tasks: tasks.length,
|
|
105
|
+
total_waves: waves.length,
|
|
106
|
+
waves,
|
|
107
|
+
is_cyclic: processed < tasks.length,
|
|
108
|
+
};
|
|
109
|
+
if (!quiet && result.is_cyclic) console.error("⚠ Dependency cycle detected.");
|
|
110
|
+
console.log(JSON.stringify(result));
|
|
111
|
+
return result.success;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function cmdContextCompress(processArgs, quiet = false) {
|
|
115
|
+
const args = processArgs.slice(3);
|
|
116
|
+
const file = getOption(args, ["--file", "-f"]);
|
|
117
|
+
const maxLines = parseMaxLines(getOption(args, ["--max-lines"]));
|
|
118
|
+
if (!file) return fail("Usage: tk context-compress --file <path> [--max-lines <count>]");
|
|
119
|
+
if (maxLines === undefined) return fail("--max-lines must be a non-negative integer.");
|
|
120
|
+
|
|
121
|
+
let content;
|
|
122
|
+
try {
|
|
123
|
+
content = fs.readFileSync(file, "utf8");
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return fail(`Failed to read file: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const codeFile = /\.(?:js|ts|rs|json)$/i.test(file);
|
|
129
|
+
let lines = content.split(/\r?\n/).filter((line) => {
|
|
130
|
+
const trimmed = line.trim();
|
|
131
|
+
return trimmed && (!codeFile || !trimmed.startsWith("//") || trimmed.includes("// VERIFY"));
|
|
132
|
+
});
|
|
133
|
+
if (maxLines !== null && lines.length > maxLines) {
|
|
134
|
+
const omitted = lines.length - maxLines;
|
|
135
|
+
lines = lines.slice(0, maxLines);
|
|
136
|
+
lines.push(`// ... [Truncated ${omitted} lines for agent context optimization]`);
|
|
137
|
+
}
|
|
138
|
+
const compressedContent = lines.join("\n");
|
|
139
|
+
const originalBytes = Buffer.byteLength(content);
|
|
140
|
+
const compressedBytes = Buffer.byteLength(compressedContent);
|
|
141
|
+
const result = {
|
|
142
|
+
success: true,
|
|
143
|
+
original_bytes: originalBytes,
|
|
144
|
+
compressed_bytes: compressedBytes,
|
|
145
|
+
compression_ratio: originalBytes === 0 ? 1 : 1 - compressedBytes / originalBytes,
|
|
146
|
+
compressed_content: compressedContent,
|
|
147
|
+
};
|
|
148
|
+
if (!quiet) console.error(`✓ Compressed ${file}`);
|
|
149
|
+
console.log(JSON.stringify(result));
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function cmdOptimizeStep(processArgs, quiet = false) {
|
|
154
|
+
const args = processArgs.slice(3);
|
|
155
|
+
const skillPath = getOption(args, ["--skill-path"]);
|
|
156
|
+
const rawEdits = getOption(args, ["--edits-json"]);
|
|
157
|
+
const parsedBudget = parseMaxLines(getOption(args, ["--budget"]));
|
|
158
|
+
const budget = parsedBudget ?? 4;
|
|
159
|
+
if (!skillPath || !rawEdits) return fail("Usage: tk optimize-step --skill-path <path> --edits-json '<json-array>' [--budget <count>]");
|
|
160
|
+
if (parsedBudget === undefined) return fail("--budget must be a non-negative integer.");
|
|
161
|
+
|
|
162
|
+
let edits;
|
|
163
|
+
try {
|
|
164
|
+
edits = JSON.parse(rawEdits);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return fail(`Failed to parse edits JSON: ${error.message}`);
|
|
167
|
+
}
|
|
168
|
+
if (!Array.isArray(edits)) return fail("--edits-json must be a JSON array.");
|
|
169
|
+
|
|
170
|
+
let text = fs.existsSync(skillPath) ? fs.readFileSync(skillPath, "utf8") : "";
|
|
171
|
+
const protectedStart = text.indexOf("<!-- SLOW_UPDATE_START -->");
|
|
172
|
+
const protectedEnd = text.indexOf("<!-- SLOW_UPDATE_END -->");
|
|
173
|
+
const isProtected = (position) => protectedStart !== -1 && protectedEnd !== -1 && position >= protectedStart && position < protectedEnd;
|
|
174
|
+
const reports = [];
|
|
175
|
+
let appliedCount = 0;
|
|
176
|
+
const ranked = [...edits].sort((left, right) => {
|
|
177
|
+
const leftFailure = left.source_type === "failure" ? 1 : 0;
|
|
178
|
+
const rightFailure = right.source_type === "failure" ? 1 : 0;
|
|
179
|
+
return rightFailure - leftFailure || (right.support_count || 1) - (left.support_count || 1);
|
|
180
|
+
}).slice(0, budget);
|
|
181
|
+
|
|
182
|
+
for (const edit of ranked) {
|
|
183
|
+
const operation = edit && edit.op;
|
|
184
|
+
const target = edit && edit.target;
|
|
185
|
+
const replacement = edit && edit.content;
|
|
186
|
+
if (operation === "append" && typeof replacement === "string") {
|
|
187
|
+
if (text.includes(replacement.trim())) reports.push("skip: append duplicate content");
|
|
188
|
+
else {
|
|
189
|
+
text = `${text}${text && !text.endsWith("\n") ? "\n" : ""}${replacement}\n`;
|
|
190
|
+
appliedCount += 1;
|
|
191
|
+
reports.push("applied: append content");
|
|
192
|
+
}
|
|
193
|
+
} else if (["delete", "replace", "insert_after"].includes(operation) && typeof target === "string") {
|
|
194
|
+
const position = text.indexOf(target);
|
|
195
|
+
if (position === -1) reports.push(`skip: ${operation} target not found`);
|
|
196
|
+
else if (isProtected(position)) reports.push(`skip: ${operation} target is inside protected region`);
|
|
197
|
+
else if (operation === "delete") {
|
|
198
|
+
text = text.replace(target, "");
|
|
199
|
+
appliedCount += 1;
|
|
200
|
+
reports.push("applied: deleted target");
|
|
201
|
+
} else if (typeof replacement !== "string") reports.push(`skip: ${operation} content missing`);
|
|
202
|
+
else if (operation === "replace") {
|
|
203
|
+
text = text.replace(target, replacement);
|
|
204
|
+
appliedCount += 1;
|
|
205
|
+
reports.push("applied: replaced target");
|
|
206
|
+
} else {
|
|
207
|
+
const before = text.slice(0, position + target.length);
|
|
208
|
+
const after = text.slice(position + target.length);
|
|
209
|
+
text = `${before}${replacement.startsWith("\n") ? "" : "\n"}${replacement}${replacement.endsWith("\n") ? "" : "\n"}${after}`;
|
|
210
|
+
appliedCount += 1;
|
|
211
|
+
reports.push("applied: inserted content after target");
|
|
212
|
+
}
|
|
213
|
+
} else reports.push(`skip: unknown or invalid operation ${String(operation)}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (appliedCount > 0) fs.writeFileSync(skillPath, text, "utf8");
|
|
217
|
+
const result = { success: true, applied_count: appliedCount, reports };
|
|
218
|
+
if (!quiet) console.error(`✓ Applied ${appliedCount} bounded SkillOpt edit(s)`);
|
|
219
|
+
console.log(JSON.stringify(result));
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
cmdMinContext,
|
|
225
|
+
cmdDagSchedule,
|
|
226
|
+
cmdContextCompress,
|
|
227
|
+
cmdOptimizeStep,
|
|
228
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* validate.js — CLI command handler for `tk validate` (JS Fallback)
|
|
4
|
+
*
|
|
5
|
+
* Validates JSON payloads or .agent/ structure against strict schemas.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.cmdValidate = cmdValidate;
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const logger_1 = require("../utils/logger");
|
|
13
|
+
|
|
14
|
+
async function cmdValidate(flags, quiet = false) {
|
|
15
|
+
const projectRoot = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
16
|
+
const agentDir = path.join(projectRoot, ".agent");
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(agentDir)) {
|
|
19
|
+
(0, logger_1.err)("No .agent/ directory found. Run `tk init` first.");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const fileToValidate = flags.file || flags.target;
|
|
24
|
+
|
|
25
|
+
if (fileToValidate) {
|
|
26
|
+
const fullPath = path.resolve(fileToValidate);
|
|
27
|
+
if (!fs.existsSync(fullPath)) {
|
|
28
|
+
(0, logger_1.err)(`File not found for validation: ${fileToValidate}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
33
|
+
if (fullPath.endsWith(".json")) {
|
|
34
|
+
JSON.parse(content);
|
|
35
|
+
if (!quiet) {
|
|
36
|
+
(0, logger_1.log)(` ${(0, logger_1.c)("green", "✔")} ${(0, logger_1.bold)("Valid JSON payload:")} ${fullPath}`);
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
if (!quiet) {
|
|
40
|
+
(0, logger_1.log)(` ${(0, logger_1.c)("green", "✔")} ${(0, logger_1.bold)("File validated successfully:")} ${fullPath}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
(0, logger_1.err)(`Validation failed for ${fileToValidate}: ${err.message}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// General .agent validation
|
|
51
|
+
const scriptPath = path.join(agentDir, "scripts", "guardrail_engine.js");
|
|
52
|
+
if (fs.existsSync(scriptPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const { runScan } = require(scriptPath);
|
|
55
|
+
if (typeof runScan === "function") {
|
|
56
|
+
await runScan({ projectRoot, quiet });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
} catch (_e) {
|
|
60
|
+
// Fallback below
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!quiet) {
|
|
65
|
+
(0, logger_1.log)(` ${(0, logger_1.c)("green", "✔")} ${(0, logger_1.bold)(".agent payload structure validated successfully")}`);
|
|
66
|
+
}
|
|
67
|
+
}
|