tribunal-kit 5.8.3 → 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 +5 -5
- package/.agent/history/integrity_manifest.json +1026 -0
- package/.agent/history/memory/.memory.idx +1217 -1
- package/.agent/history/memory/MEMORY.md +81 -1
- package/.agent/rules/GEMINI.md +1 -1
- package/.agent/scripts/_utils.js +28 -7
- package/.agent/scripts/case_law_manager.js +3 -15
- package/.agent/scripts/context_broker.js +3 -20
- package/.agent/scripts/guardrail_engine.js +643 -0
- package/.agent/scripts/integrity_manifest.js +492 -0
- package/.agent/scripts/marathon_harness.js +10 -1
- package/.agent/scripts/signal_detector.js +1 -1
- package/.agent/scripts/skill_evolution.js +39 -36
- package/.agent/scripts/skill_integrator.js +2 -12
- package/.agent/scripts/swarm_dispatcher.js +54 -5
- package/.agent/skills/12-principles-of-animation/SKILL.md +69 -0
- package/.agent/skills/60fps-animation/SKILL.md +78 -0
- package/.agent/skills/accessible-animation/SKILL.md +86 -0
- package/.agent/skills/adapt/SKILL.md +75 -0
- package/.agent/skills/animation-on-scroll/SKILL.md +110 -0
- package/.agent/skills/animation-systems/SKILL.md +69 -0
- package/.agent/skills/antfu-conventions/SKILL.md +74 -0
- package/.agent/skills/api-patterns/scripts/__pycache__/api_validator.cpython-311.pyc +0 -0
- package/.agent/skills/apple-design/SKILL.md +108 -0
- package/.agent/skills/audit-and-fix/SKILL.md +83 -0
- package/.agent/skills/baseline-ui/SKILL.md +74 -0
- package/.agent/skills/better-colors/SKILL.md +132 -0
- package/.agent/skills/better-typography/SKILL.md +111 -0
- package/.agent/skills/better-ui/SKILL.md +102 -0
- package/.agent/skills/bolder/SKILL.md +68 -0
- package/.agent/skills/build-primitive/SKILL.md +70 -0
- package/.agent/skills/clarify/SKILL.md +75 -0
- package/.agent/skills/cobejs/SKILL.md +96 -0
- package/.agent/skills/codebase-design/SKILL.md +84 -0
- package/.agent/skills/colorize/SKILL.md +77 -0
- package/.agent/skills/compact-landing/SKILL.md +70 -0
- package/.agent/skills/company-logos/SKILL.md +73 -0
- package/.agent/skills/create-design-md/SKILL.md +104 -0
- package/.agent/skills/critique/SKILL.md +95 -0
- package/.agent/skills/database-design/scripts/__pycache__/schema_validator.cpython-311.pyc +0 -0
- package/.agent/skills/delight/SKILL.md +65 -0
- package/.agent/skills/design-lab/SKILL.md +59 -0
- package/.agent/skills/diagnosing-bugs/SKILL.md +190 -0
- package/.agent/skills/distill/SKILL.md +65 -0
- package/.agent/skills/domain-modeling/SKILL.md +78 -0
- package/.agent/skills/fixing-accessibility/SKILL.md +100 -0
- package/.agent/skills/fixing-metadata/SKILL.md +97 -0
- package/.agent/skills/fixing-motion-performance/SKILL.md +96 -0
- 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/gpt-taste/SKILL.md +68 -0
- package/.agent/skills/harden/SKILL.md +74 -0
- package/.agent/skills/i18n-localization/scripts/__pycache__/i18n_checker.cpython-311.pyc +0 -0
- package/.agent/skills/impeccable/SKILL.md +74 -0
- package/.agent/skills/improve-codebase-architecture/SKILL.md +67 -0
- package/.agent/skills/improve-ui/SKILL.md +83 -0
- package/.agent/skills/landing-page/SKILL.md +88 -0
- package/.agent/skills/lint-and-validate/SKILL.md +0 -1
- 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/local-first-architecture/SKILL.md +66 -0
- package/.agent/skills/lottie-animation/SKILL.md +82 -0
- package/.agent/skills/marquee-loop/SKILL.md +96 -0
- package/.agent/skills/masked-reveal/SKILL.md +88 -0
- package/.agent/skills/micro-interaction/SKILL.md +100 -0
- package/.agent/skills/mobile-design/scripts/__pycache__/mobile_audit.cpython-311.pyc +0 -0
- package/.agent/skills/morphing-icons/SKILL.md +85 -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/page-transition-animation/SKILL.md +108 -0
- package/.agent/skills/parallel-agents/SKILL.md +5 -5
- package/.agent/skills/performance-profiling/scripts/__pycache__/lighthouse_audit.cpython-311.pyc +0 -0
- package/.agent/skills/polish/SKILL.md +63 -0
- package/.agent/skills/pricing-page/SKILL.md +68 -0
- package/.agent/skills/progressive-blur/SKILL.md +100 -0
- package/.agent/skills/quieter/SKILL.md +68 -0
- package/.agent/skills/react-doctor/SKILL.md +70 -0
- package/.agent/skills/redesign-skill/SKILL.md +65 -0
- package/.agent/skills/seo-fundamentals/scripts/__pycache__/seo_checker.cpython-311.pyc +0 -0
- package/.agent/skills/shape/SKILL.md +73 -0
- package/.agent/skills/soft-skill/SKILL.md +72 -0
- package/.agent/skills/sounds-on-the-web/SKILL.md +100 -0
- package/.agent/skills/svg-animation/SKILL.md +91 -0
- package/.agent/skills/swiss-design/SKILL.md +64 -0
- package/.agent/skills/taste-skill/SKILL.md +71 -0
- package/.agent/skills/tdd-workflow/SKILL.md +36 -165
- package/.agent/skills/testing-patterns/scripts/__pycache__/test_runner.cpython-311.pyc +0 -0
- package/.agent/skills/thermo-nuclear-code-quality-review/SKILL.md +82 -0
- package/.agent/skills/to-spring-or-not-to-spring/SKILL.md +66 -0
- package/.agent/skills/transitions-dev/SKILL.md +101 -0
- package/.agent/skills/typeset/SKILL.md +76 -0
- package/.agent/skills/ui-skills-root/SKILL.md +85 -0
- package/.agent/skills/vulnerability-scanner/scripts/__pycache__/security_scan.cpython-311.pyc +0 -0
- package/.agent/skills/web-quality-audit/SKILL.md +67 -0
- package/.agent/skills/webapp-testing/scripts/__pycache__/playwright_runner.cpython-311.pyc +0 -0
- package/.agent/workflows/generate.md +1 -1
- package/.agent/workflows/tribunal-full.md +2 -2
- package/.agent/workflows/tribunal-speed.md +2 -2
- package/CONTRIBUTING.md +1 -1
- package/README.md +349 -239
- package/bin/mcp-server.js +45 -45
- package/bin/tribunal-kit.js +62 -1482
- package/bin/wrapper.js +50 -25
- package/dist/cli.js +43 -0
- package/dist/commands/align.js +1 -1
- package/dist/commands/compile.js +1 -1
- package/dist/commands/guardrail.js +275 -0
- package/dist/commands/init.js +35 -10
- package/dist/commands/native.js +228 -0
- package/dist/commands/optimize.js +318 -0
- package/dist/commands/validate.js +67 -0
- package/dist/mcp/server.js +4 -138
- package/dist/utils/version.js +1 -1
- package/package.json +13 -10
- package/scripts/benchmark.js +42 -0
- package/scripts/fix-vbc.js +98 -0
- package/scripts/fix-vbc.ps1 +119 -0
- package/scripts/sync-version.js +129 -76
|
@@ -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,318 @@
|
|
|
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.cmdOptimizeSkill = cmdOptimizeSkill;
|
|
7
|
+
|
|
8
|
+
const fs = __importDefault(require("fs"));
|
|
9
|
+
const path = __importDefault(require("path"));
|
|
10
|
+
const os = __importDefault(require("os"));
|
|
11
|
+
const https = __importDefault(require("https"));
|
|
12
|
+
const { execSync } = require("child_process");
|
|
13
|
+
const logger = require("../utils/logger");
|
|
14
|
+
const helpers = require("../utils/helpers");
|
|
15
|
+
|
|
16
|
+
// ── LLM Helpers ──────────────────────────────────────────────────────────────
|
|
17
|
+
function detectLlmProvider() {
|
|
18
|
+
if (process.env.ANTHROPIC_API_KEY)
|
|
19
|
+
return { provider: "anthropic", key: process.env.ANTHROPIC_API_KEY };
|
|
20
|
+
if (process.env.OPENAI_API_KEY)
|
|
21
|
+
return { provider: "openai", key: process.env.OPENAI_API_KEY };
|
|
22
|
+
if (process.env.GEMINI_API_KEY)
|
|
23
|
+
return { provider: "gemini", key: process.env.GEMINI_API_KEY };
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function httpsPost(hostname, apiPath, headers, body) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const data = JSON.stringify(body);
|
|
30
|
+
const req = https.request(
|
|
31
|
+
{
|
|
32
|
+
method: "POST",
|
|
33
|
+
hostname,
|
|
34
|
+
path: apiPath,
|
|
35
|
+
headers: { ...headers, "Content-Length": Buffer.byteLength(data) },
|
|
36
|
+
},
|
|
37
|
+
(res) => {
|
|
38
|
+
let raw = "";
|
|
39
|
+
res.on("data", (c) => { raw += c; });
|
|
40
|
+
res.on("end", () => resolve(raw));
|
|
41
|
+
res.on("error", reject);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
req.on("error", reject);
|
|
45
|
+
req.setTimeout(30000, () => {
|
|
46
|
+
req.destroy(new Error("LLM API timeout"));
|
|
47
|
+
});
|
|
48
|
+
req.write(data);
|
|
49
|
+
req.end();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function callLlmApi(prompt, provider, apiKey) {
|
|
54
|
+
try {
|
|
55
|
+
if (provider === "anthropic") {
|
|
56
|
+
const raw = await httpsPost(
|
|
57
|
+
"api.anthropic.com",
|
|
58
|
+
"/v1/messages",
|
|
59
|
+
{
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
"x-api-key": apiKey,
|
|
62
|
+
"anthropic-version": "2023-06-01",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
model: "claude-3-5-sonnet-latest",
|
|
66
|
+
max_tokens: 1024,
|
|
67
|
+
messages: [{ role: "user", content: prompt }],
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
const json = JSON.parse(raw);
|
|
71
|
+
return json?.content?.[0]?.text ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (provider === "openai") {
|
|
75
|
+
const raw = await httpsPost(
|
|
76
|
+
"api.openai.com",
|
|
77
|
+
"/v1/chat/completions",
|
|
78
|
+
{
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
Authorization: `Bearer ${apiKey}`,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
model: "gpt-4o-mini",
|
|
84
|
+
max_tokens: 1024,
|
|
85
|
+
messages: [{ role: "user", content: prompt }],
|
|
86
|
+
temperature: 0.1,
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
const json = JSON.parse(raw);
|
|
90
|
+
return json?.choices?.[0]?.message?.content ?? null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (provider === "gemini") {
|
|
94
|
+
const raw = await httpsPost(
|
|
95
|
+
"generativelanguage.googleapis.com",
|
|
96
|
+
`/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`,
|
|
97
|
+
{ "Content-Type": "application/json" },
|
|
98
|
+
{
|
|
99
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
100
|
+
generationConfig: { maxOutputTokens: 1024, temperature: 0.1 },
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
const json = JSON.parse(raw);
|
|
104
|
+
return json?.candidates?.[0]?.content?.parts?.[0]?.text ?? null;
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
logger.err(`LLM API Call failed: ${e.message}`);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Core Binary Resolver ─────────────────────────────────────────────────────
|
|
114
|
+
function getCoreBinaryPath(targetDir) {
|
|
115
|
+
const isWindows = os.default.platform() === "win32";
|
|
116
|
+
const ext = isWindows ? ".exe" : "";
|
|
117
|
+
|
|
118
|
+
// Dev build target checks
|
|
119
|
+
const devPath = path.default.resolve(targetDir, 'target', 'release', `tribunal-core${ext}`);
|
|
120
|
+
if (fs.default.existsSync(devPath)) return devPath;
|
|
121
|
+
|
|
122
|
+
const debugPath = path.default.resolve(targetDir, 'target', 'debug', `tribunal-core${ext}`);
|
|
123
|
+
if (fs.default.existsSync(debugPath)) return debugPath;
|
|
124
|
+
|
|
125
|
+
// Production npm package checks
|
|
126
|
+
const pkgName = `@tribunal-kit/core-${os.default.platform()}-${os.default.arch()}`;
|
|
127
|
+
try {
|
|
128
|
+
const pkgPath = require.resolve(`${pkgName}/package.json`);
|
|
129
|
+
const binPath = path.default.resolve(path.default.dirname(pkgPath), `bin/tribunal-core${ext}`);
|
|
130
|
+
if (fs.default.existsSync(binPath)) return binPath;
|
|
131
|
+
} catch {}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Main Optimizer Command ───────────────────────────────────────────────────
|
|
137
|
+
async function cmdOptimizeSkill(flags, processArgs, quiet = false) {
|
|
138
|
+
const targetDir = flags.path ? path.default.resolve(flags.path) : process.cwd();
|
|
139
|
+
const agentDest = path.default.join(targetDir, '.agent');
|
|
140
|
+
|
|
141
|
+
if (!fs.default.existsSync(agentDest)) {
|
|
142
|
+
logger.err('.agent/ not found. Run: npx tribunal-kit init');
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
helpers.banner(quiet);
|
|
147
|
+
|
|
148
|
+
// Detect LLM Credentials
|
|
149
|
+
const llm = detectLlmProvider();
|
|
150
|
+
if (!llm) {
|
|
151
|
+
logger.err("No LLM API Key detected in environment. Please set GEMINI_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY.");
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Resolve Core Binary
|
|
156
|
+
const binPath = getCoreBinaryPath(targetDir);
|
|
157
|
+
if (!binPath) {
|
|
158
|
+
logger.err("tribunal-core binary not found. Run 'cargo build --release' or install the core package.");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Parse loop parameters
|
|
163
|
+
const skillName = flags.target || 'project-idioms';
|
|
164
|
+
const skillPath = path.default.join(agentDest, 'skills', skillName, 'SKILL.md');
|
|
165
|
+
|
|
166
|
+
if (!fs.default.existsSync(skillPath)) {
|
|
167
|
+
logger.err(`Target skill file not found: ${skillPath}`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Find the harness command
|
|
172
|
+
const positionalArgs = processArgs.slice(3).filter(arg => !arg.startsWith("--"));
|
|
173
|
+
if (positionalArgs.length === 0) {
|
|
174
|
+
logger.err("Harness command is required. Usage: tk optimize-skill --target <skill-name> \"<harness-command>\"");
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
const harnessCmd = positionalArgs[0];
|
|
178
|
+
|
|
179
|
+
const epochs = 4;
|
|
180
|
+
const editBudget = 4;
|
|
181
|
+
|
|
182
|
+
logger.log(` ${logger.c('cyan', '⊘')} Starting SkillOpt loop for skill: ${logger.bold(skillName)}`);
|
|
183
|
+
logger.log(` ${logger.c('cyan', '⊘')} Harness: "${logger.bold(harnessCmd)}"`);
|
|
184
|
+
logger.log(` ${logger.c('cyan', '⊘')} Provider: ${logger.bold(llm.provider)}`);
|
|
185
|
+
console.log();
|
|
186
|
+
|
|
187
|
+
let bestScore = 0.0;
|
|
188
|
+
let currentSkillText = fs.default.readFileSync(skillPath, 'utf8');
|
|
189
|
+
|
|
190
|
+
for (let epoch = 1; epoch <= epochs; epoch++) {
|
|
191
|
+
logger.log(` ${logger.c('cyan', '▶')} ${logger.bold(`Epoch ${epoch}/${epochs}`)}`);
|
|
192
|
+
|
|
193
|
+
// 1. Run Rollout Harness
|
|
194
|
+
logger.log(` ${logger.c('gray', '●')} Running rollout batch execution...`);
|
|
195
|
+
let rolloutLogs = "";
|
|
196
|
+
let rolloutSuccess = true;
|
|
197
|
+
try {
|
|
198
|
+
rolloutLogs = execSync(harnessCmd, { encoding: 'utf8', cwd: targetDir, stdio: 'pipe' });
|
|
199
|
+
} catch (e) {
|
|
200
|
+
rolloutLogs = e.stdout + "\n" + e.stderr;
|
|
201
|
+
rolloutSuccess = false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const _initialScore = rolloutSuccess ? 1.0 : 0.0;
|
|
205
|
+
logger.log(` ${logger.c('gray', '●')} Rollout score: ${rolloutSuccess ? logger.c('green', '1.0') : logger.c('red', '0.0')}`);
|
|
206
|
+
|
|
207
|
+
// 2. Call LLM for Patch Proposals
|
|
208
|
+
logger.log(` ${logger.c('gray', '●')} Requesting optimizer reflections from LLM...`);
|
|
209
|
+
const reflectionPrompt = `
|
|
210
|
+
You are a SkillOpt text-space optimizer coach.
|
|
211
|
+
Analyze the following rollout run logs and current skill document, then propose bounded add/delete/replace/insert_after edits.
|
|
212
|
+
The goal is to fix failure patterns and make the procedural memory rules clearer and more robust.
|
|
213
|
+
|
|
214
|
+
Current Skill Content:
|
|
215
|
+
"""
|
|
216
|
+
${currentSkillText}
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
Rollout Logs:
|
|
220
|
+
${rolloutLogs.slice(-4000)}
|
|
221
|
+
|
|
222
|
+
Output ONLY a JSON array containing proposed edits. No markdown formatting, no explanation.
|
|
223
|
+
Each edit must follow this schema:
|
|
224
|
+
[
|
|
225
|
+
{
|
|
226
|
+
"op": "append" | "insert_after" | "replace" | "delete",
|
|
227
|
+
"target": "<exact text match to replace/insert_after/delete, leave null for append>",
|
|
228
|
+
"content": "<exact markdown/rules to add, replace, or insert>",
|
|
229
|
+
"support_count": 1,
|
|
230
|
+
"source_type": "${rolloutSuccess ? "success" : "failure"}"
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
`;
|
|
234
|
+
|
|
235
|
+
const llmResponse = await callLlmApi(reflectionPrompt, llm.provider, llm.key);
|
|
236
|
+
if (!llmResponse) {
|
|
237
|
+
logger.warn(" ⚠️ LLM returned empty response. Skipping epoch step.");
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Clean LLM JSON response from markdown wrappers if present
|
|
242
|
+
let cleanedJson = llmResponse.trim();
|
|
243
|
+
if (cleanedJson.startsWith("```json")) {
|
|
244
|
+
cleanedJson = cleanedJson.replace(/^```json/, "").replace(/```$/, "").trim();
|
|
245
|
+
} else if (cleanedJson.startsWith("```")) {
|
|
246
|
+
cleanedJson = cleanedJson.replace(/^```/, "").replace(/```$/, "").trim();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 3. Invoke Rust binary for optimizing step
|
|
250
|
+
logger.log(` ${logger.c('gray', '●')} Merging and applying bounded edits via Rust Core...`);
|
|
251
|
+
let optResultStr = "";
|
|
252
|
+
try {
|
|
253
|
+
const tempFile = path.default.join(os.default.tmpdir(), `edits-${Date.now()}.json`);
|
|
254
|
+
fs.default.writeFileSync(tempFile, cleanedJson, 'utf8');
|
|
255
|
+
|
|
256
|
+
optResultStr = execSync(
|
|
257
|
+
`"${binPath}" optimize-step --skill-path "${skillPath}" --edits-json "${cleanedJson.replace(/"/g, '\\"')}" --budget ${editBudget}`,
|
|
258
|
+
{ encoding: 'utf8', cwd: targetDir, stdio: 'pipe' }
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
try { fs.default.unlinkSync(tempFile); } catch {}
|
|
262
|
+
} catch (e) {
|
|
263
|
+
logger.err(` ✖ Rust Core optimization failed: ${e.message}`);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let optReport;
|
|
268
|
+
try {
|
|
269
|
+
optReport = JSON.parse(optResultStr.trim());
|
|
270
|
+
} catch {
|
|
271
|
+
logger.err(" ✖ Failed to parse optimizer report from Rust Core.");
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (optReport.applied_count === 0) {
|
|
276
|
+
logger.log(` ${logger.c('yellow', '●')} No edits applied (deduplicated or target not found).`);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
logger.log(` ${logger.c('green', '●')} Applied ${optReport.applied_count} edits to skill.`);
|
|
281
|
+
|
|
282
|
+
// 4. Run Validation Harness
|
|
283
|
+
logger.log(` ${logger.c('gray', '●')} Evaluating candidate skill against validation split...`);
|
|
284
|
+
let valSuccess = true;
|
|
285
|
+
try {
|
|
286
|
+
execSync(harnessCmd, { encoding: 'utf8', cwd: targetDir, stdio: 'pipe' });
|
|
287
|
+
} catch {
|
|
288
|
+
valSuccess = false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const candidateScore = valSuccess ? 1.0 : 0.0;
|
|
292
|
+
|
|
293
|
+
// 5. Validation Gate
|
|
294
|
+
if (candidateScore > bestScore || (candidateScore === 1.0 && bestScore === 0.0)) {
|
|
295
|
+
logger.ok(` ✔ Gate Accepted! Score improved/stabilized to ${logger.c('green', String(candidateScore))}.`);
|
|
296
|
+
bestScore = candidateScore;
|
|
297
|
+
currentSkillText = fs.default.readFileSync(skillPath, 'utf8');
|
|
298
|
+
} else {
|
|
299
|
+
logger.warn(` ⚠️ Gate Rejected. Score (${candidateScore}) <= Best (${bestScore}). Reverting edits.`);
|
|
300
|
+
fs.default.writeFileSync(skillPath, currentSkillText, 'utf8');
|
|
301
|
+
|
|
302
|
+
// Save to rejected-edit buffer history
|
|
303
|
+
const rejectedPath = path.default.join(agentDest, 'history', 'skill-optimization');
|
|
304
|
+
fs.default.mkdirSync(rejectedPath, { recursive: true });
|
|
305
|
+
const bufFile = path.default.join(rejectedPath, 'rejected-buffer.json');
|
|
306
|
+
let buffer = [];
|
|
307
|
+
if (fs.default.existsSync(bufFile)) {
|
|
308
|
+
try { buffer = JSON.parse(fs.default.readFileSync(bufFile, 'utf8')); } catch {}
|
|
309
|
+
}
|
|
310
|
+
buffer.push({ epoch, timestamp: new Date().toISOString(), edits: optReport.reports });
|
|
311
|
+
fs.default.writeFileSync(bufFile, JSON.stringify(buffer, null, 2), 'utf8');
|
|
312
|
+
}
|
|
313
|
+
console.log();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
logger.ok(`🎉 SkillOpt training complete. Best validation score: ${logger.c('green', String(bestScore))}`);
|
|
317
|
+
console.log();
|
|
318
|
+
}
|
|
@@ -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
|
+
}
|