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.
Files changed (46) hide show
  1. package/.agent/ARCHITECTURE.md +3 -3
  2. package/.agent/history/integrity_manifest.json +75 -7
  3. package/.agent/history/memory/.memory.idx +1065 -1
  4. package/.agent/history/memory/MEMORY.md +71 -1
  5. package/.agent/rules/GEMINI.md +1 -1
  6. package/.agent/scripts/_utils.js +28 -7
  7. package/.agent/scripts/context_broker.js +0 -19
  8. package/.agent/scripts/guardrail_engine.js +1 -1
  9. package/.agent/scripts/integrity_manifest.js +10 -4
  10. package/.agent/scripts/marathon_harness.js +10 -1
  11. package/.agent/scripts/skill_evolution.js +36 -21
  12. package/.agent/scripts/swarm_dispatcher.js +65 -6
  13. package/.agent/skills/api-patterns/scripts/__pycache__/api_validator.cpython-311.pyc +0 -0
  14. package/.agent/skills/better-colors/SKILL.md +12 -4
  15. package/.agent/skills/database-design/scripts/__pycache__/schema_validator.cpython-311.pyc +0 -0
  16. package/.agent/skills/fixing-accessibility/SKILL.md +6 -4
  17. package/.agent/skills/frontend-design/SKILL.md +80 -81
  18. package/.agent/skills/frontend-design/scripts/__pycache__/accessibility_checker.cpython-311.pyc +0 -0
  19. package/.agent/skills/frontend-design/scripts/__pycache__/ux_audit.cpython-311.pyc +0 -0
  20. package/.agent/skills/geo-fundamentals/scripts/__pycache__/geo_checker.cpython-311.pyc +0 -0
  21. package/.agent/skills/i18n-localization/scripts/__pycache__/i18n_checker.cpython-311.pyc +0 -0
  22. package/.agent/skills/impeccable/SKILL.md +2 -0
  23. package/.agent/skills/lint-and-validate/scripts/__pycache__/lint_runner.cpython-311.pyc +0 -0
  24. package/.agent/skills/lint-and-validate/scripts/__pycache__/type_coverage.cpython-311.pyc +0 -0
  25. package/.agent/skills/mobile-design/scripts/__pycache__/mobile_audit.cpython-311.pyc +0 -0
  26. package/.agent/skills/motion-engineering/SKILL.md +66 -164
  27. package/.agent/skills/nextjs-react-expert/scripts/__pycache__/convert_rules.cpython-311.pyc +0 -0
  28. package/.agent/skills/nextjs-react-expert/scripts/__pycache__/react_performance_checker.cpython-311.pyc +0 -0
  29. package/.agent/skills/performance-profiling/scripts/__pycache__/lighthouse_audit.cpython-311.pyc +0 -0
  30. package/.agent/skills/seo-fundamentals/scripts/__pycache__/seo_checker.cpython-311.pyc +0 -0
  31. package/.agent/skills/testing-patterns/scripts/__pycache__/test_runner.cpython-311.pyc +0 -0
  32. package/.agent/skills/vulnerability-scanner/scripts/__pycache__/security_scan.cpython-311.pyc +0 -0
  33. package/.agent/skills/webapp-testing/scripts/__pycache__/playwright_runner.cpython-311.pyc +0 -0
  34. package/.agent/workflows/tribunal-full.md +5 -5
  35. package/CONTRIBUTING.md +1 -1
  36. package/README.md +4 -4
  37. package/bin/mcp-server.js +45 -45
  38. package/bin/wrapper.js +50 -25
  39. package/dist/cli.js +30 -0
  40. package/dist/commands/init.js +25 -1
  41. package/dist/commands/native.js +228 -0
  42. package/dist/commands/validate.js +67 -0
  43. package/dist/mcp/server.js +4 -138
  44. package/package.json +11 -10
  45. package/scripts/benchmark.js +42 -0
  46. 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
- * PERF: Commands are loaded in-process via require() no child process spawn.
11
- * This eliminates ~200-500ms overhead per tool call that spawnSync introduced.
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 spawned processes (30 seconds) — only used for Rust binary calls
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 validate command via the Rust binary (if available) or JS fallback.
35
- * This is the only command that still benefits from process spawn (Rust speed).
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 runValidateCommand() {
38
- const os = require("os");
38
+ function runTribunalAudit() {
39
39
  const fs = require("fs");
40
- const isWindows = os.platform() === "win32";
41
- const ext = isWindows ? ".exe" : "";
42
- const platform = os.platform();
43
- const arch = os.arch();
44
-
45
- // Try Rust binary first
46
- const pkgName = `@tribunal-kit/core-${platform}-${arch}`;
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 (binPath) {
68
- const result = spawnSync(binPath, ["validate"], {
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
- // JS fallback — in-process
76
- return "Validate command requires the Rust binary. Run: cargo build --release";
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 loaded in-process for zero-spawn latency.
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
- // We still spawn for case_law_manager since it's a standalone script
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 = runValidateCommand();
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 binPath = path.resolve(
44
- path.dirname(pkgPath),
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 compiled from crates/core/Cargo.toml (Local dev)
55
- const devPath = path.resolve(
56
- __dirname,
57
- "..",
58
- "target",
59
- "release",
60
- `tribunal-core${ext}`,
61
- );
62
- if (fs.existsSync(devPath)) {
63
- return devPath;
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, try target/debug (if they ran `cargo build` instead of `--release`)
67
- const debugPath = path.resolve(
68
- __dirname,
69
- "..",
70
- "target",
71
- "debug",
72
- `tribunal-core${ext}`,
73
- );
74
- if (fs.existsSync(debugPath)) {
75
- return debugPath;
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':
@@ -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 20 reviewers in parallel'));
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
+ }