tarsk 0.5.42 → 0.5.44
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/dist/bundled-skills/skill-creator/LICENSE.txt +202 -0
- package/dist/bundled-skills/skill-creator/SKILL.md +510 -0
- package/dist/bundled-skills/skill-creator/agents/analyzer.md +283 -0
- package/dist/bundled-skills/skill-creator/agents/comparator.md +203 -0
- package/dist/bundled-skills/skill-creator/agents/grader.md +227 -0
- package/dist/bundled-skills/skill-creator/assets/eval_review.html +292 -0
- package/dist/bundled-skills/skill-creator/eval-viewer/generate_review.js +544 -0
- package/dist/bundled-skills/skill-creator/eval-viewer/viewer.html +1478 -0
- package/dist/bundled-skills/skill-creator/package.json +3 -0
- package/dist/bundled-skills/skill-creator/references/schemas.md +423 -0
- package/dist/bundled-skills/skill-creator/scripts/aggregate_benchmark.js +377 -0
- package/dist/bundled-skills/skill-creator/scripts/generate_report.js +345 -0
- package/dist/bundled-skills/skill-creator/scripts/improve_description.js +263 -0
- package/dist/bundled-skills/skill-creator/scripts/package_skill.js +147 -0
- package/dist/bundled-skills/skill-creator/scripts/quick_validate.js +132 -0
- package/dist/bundled-skills/skill-creator/scripts/run_eval.js +345 -0
- package/dist/bundled-skills/skill-creator/scripts/run_loop.js +411 -0
- package/dist/bundled-skills/skill-creator/scripts/utils.js +60 -0
- package/dist/index.js +3779 -2332
- package/dist/public/assets/{account-view-BAm4Az95.js → account-view-CTizS7Yb.js} +1 -1
- package/dist/public/assets/{api-C44iPrsZ.js → api-Cuj9iSVv.js} +1 -1
- package/dist/public/assets/browser-tab-DSpOIu84.js +1 -0
- package/dist/public/assets/commit-dialog-kTaKOsNO.js +1 -0
- package/dist/public/assets/context-menu-DRXV-Suu.js +1 -0
- package/dist/public/assets/create-repo-dialog-gIZLk6LZ.js +1 -0
- package/dist/public/assets/dialogs-config-CxRnlUcQ.js +51 -0
- package/dist/public/assets/diff-view-B2OdJMRe.js +3 -0
- package/dist/public/assets/explorer-tab-view-fejcb7N1.js +2 -0
- package/dist/public/assets/explorer-tree-C2kLAwI0.js +1 -0
- package/dist/public/assets/explorer-view-BmKKHyrH.js +1 -0
- package/dist/public/assets/git-history-dialog-B2ABiCXZ.js +1 -0
- package/dist/public/assets/git-ops-button-Bxx_jyqR.js +2 -0
- package/dist/public/assets/history-view-DebwP9D7.js +9 -0
- package/dist/public/assets/index-C2_gO3dh.css +1 -0
- package/dist/public/assets/index-D9aY24hf.js +89 -0
- package/dist/public/assets/mcp-server-card-B6iwOybN.js +1 -0
- package/dist/public/assets/merged-pr-dialog-CObeSEaB.js +1 -0
- package/dist/public/assets/model-star-rating-BgdOyTTO.js +1 -0
- package/dist/public/assets/onboarding-BN_K08ma.js +1 -0
- package/dist/public/assets/project-settings-view-BDvHOmF8.js +1 -0
- package/dist/public/assets/providers-list-view-DydWz8z3.js +1 -0
- package/dist/public/assets/pull-request-dialog-LJhZpT8C.js +1 -0
- package/dist/public/assets/pull-with-changes-dialog-BPIj9wIk.js +1 -0
- package/dist/public/assets/push-before-pr-dialog-DzBp93KT.js +1 -0
- package/dist/public/assets/{radio-group-DkDRaQdF.js → radio-group-B9xMCZ1X.js} +1 -1
- package/dist/public/assets/react-vendor-CE5Dmjd0.js +16 -0
- package/dist/public/assets/settings-general-view-D_44BPVr.js +1 -0
- package/dist/public/assets/settings-instructions-view-Y6EK67-G.js +1 -0
- package/dist/public/assets/settings-list-BZDwK9bE.js +1 -0
- package/dist/public/assets/settings-mcp-servers-view-B8IqOOnN.js +5 -0
- package/dist/public/assets/{settings-models-skeleton-C5XrzM39.js → settings-models-skeleton-DAEnT4kC.js} +1 -1
- package/dist/public/assets/settings-models-view-CluPgRkw.js +1 -0
- package/dist/public/assets/settings-rules-view-CxHrCDwn.js +8 -0
- package/dist/public/assets/settings-skills-view-BE9u6FvJ.js +2 -0
- package/dist/public/assets/settings-slash-commands-view-CVOZ3GH3.js +1 -0
- package/dist/public/assets/settings-subagents-view-mguSf6zE.js +2 -0
- package/dist/public/assets/settings-system-prompt-view-0sXVxXh3.js +1 -0
- package/dist/public/assets/settings-view-BE8EW7s7.js +2 -0
- package/dist/public/assets/{skeleton-HsHVnOKO.js → skeleton-CVhaOA8k.js} +1 -1
- package/dist/public/assets/slug-utils-DGOwJkKI.js +1 -0
- package/dist/public/assets/terminal-panel-PjkgqbY6.js +1 -0
- package/dist/public/assets/{ui-components-CaeQlGTd.js → ui-components-DaffuSG7.js} +1 -1
- package/dist/public/assets/use-deferred-search-b843Ztu9.js +1 -0
- package/dist/public/assets/{utils-5mrXWXCs.js → utils-BIt9KCVx.js} +1 -1
- package/dist/public/assets/vosk-speech-CgILeNJR.js +2 -0
- package/dist/public/assets/{web-Gjw6klhQ.js → web-BBGaE-6B.js} +1 -1
- package/dist/public/assets/{web-NsuCXAqU.js → web-D6dBzWD1.js} +1 -1
- package/dist/public/index.html +8 -8
- package/package.json +4 -3
- package/dist/public/assets/browser-tab-DV6mFCbu.js +0 -1
- package/dist/public/assets/commit-dialog-DYdmiA4p.js +0 -1
- package/dist/public/assets/context-menu-BVP2PUoS.js +0 -1
- package/dist/public/assets/create-repo-dialog-DrGJUrGO.js +0 -1
- package/dist/public/assets/dialogs-config-BPzX42HH.js +0 -51
- package/dist/public/assets/diff-view-BnNtDcmG.js +0 -3
- package/dist/public/assets/explorer-tab-view-OGWG1dH9.js +0 -2
- package/dist/public/assets/explorer-tree--Ho2K8-i.js +0 -1
- package/dist/public/assets/explorer-view-Dw0Kyxb8.js +0 -1
- package/dist/public/assets/git-history-dialog-BpOpNbze.js +0 -1
- package/dist/public/assets/git-ops-button-DKOfNm4s.js +0 -2
- package/dist/public/assets/history-view-DwSqsTGI.js +0 -9
- package/dist/public/assets/index-BdLH7zyV.css +0 -1
- package/dist/public/assets/index-BqaiWs7x.js +0 -88
- package/dist/public/assets/mcp-server-card-BbAUiI-X.js +0 -1
- package/dist/public/assets/merged-pr-dialog-Cvo93bRH.js +0 -1
- package/dist/public/assets/onboarding-BmBbv0At.js +0 -1
- package/dist/public/assets/project-settings-view-D-8Ua1Tu.js +0 -1
- package/dist/public/assets/providers-list-view-Gm13Fagd.js +0 -1
- package/dist/public/assets/pull-request-dialog-BkzdP5F2.js +0 -1
- package/dist/public/assets/pull-with-changes-dialog-DQU7jHYL.js +0 -1
- package/dist/public/assets/push-before-pr-dialog-DII3gUVw.js +0 -1
- package/dist/public/assets/react-vendor-BmFalJ0W.js +0 -16
- package/dist/public/assets/settings-general-view-Br89Vrbv.js +0 -1
- package/dist/public/assets/settings-instructions-view-ByxlAb9U.js +0 -1
- package/dist/public/assets/settings-list-DSrSoTv7.js +0 -1
- package/dist/public/assets/settings-mcp-servers-view-U_la7C8z.js +0 -5
- package/dist/public/assets/settings-models-view-obQa4doG.js +0 -1
- package/dist/public/assets/settings-rules-view-Cd9WHEAE.js +0 -8
- package/dist/public/assets/settings-skills-view-B81njz98.js +0 -2
- package/dist/public/assets/settings-slash-commands-view-Ct8WC1Z0.js +0 -1
- package/dist/public/assets/settings-subagents-view-BwNTnGkz.js +0 -2
- package/dist/public/assets/settings-system-prompt-view-C4uLn68y.js +0 -1
- package/dist/public/assets/settings-view-DQW_9pnm.js +0 -2
- package/dist/public/assets/terminal-panel-wOltBu70.js +0 -2
- package/dist/public/assets/whisper-wasm-EGutPGND.js +0 -2
- package/dist/public/wasm/libmain-CWYJvMY5.js +0 -3318
- package/dist/public/wasm/libmain-D9-QM3iM.mjs +0 -3301
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Skill Packager - Creates a distributable .skill file of a skill folder. */
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
const { validateSkill } = require("./quick_validate.js");
|
|
9
|
+
|
|
10
|
+
const EXCLUDE_DIRS = new Set(["__pycache__", "node_modules"]);
|
|
11
|
+
const EXCLUDE_GLOBS = ["*.pyc"];
|
|
12
|
+
const EXCLUDE_FILES = new Set([".DS_Store"]);
|
|
13
|
+
const ROOT_EXCLUDE_DIRS = new Set(["evals"]);
|
|
14
|
+
|
|
15
|
+
function matchesGlob(name, pattern) {
|
|
16
|
+
const regex = new RegExp(
|
|
17
|
+
"^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
|
|
18
|
+
);
|
|
19
|
+
return regex.test(name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function shouldExclude(relPath) {
|
|
23
|
+
const parts = relPath.split(path.sep);
|
|
24
|
+
if (parts.some((part) => EXCLUDE_DIRS.has(part))) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (parts.length > 1 && ROOT_EXCLUDE_DIRS.has(parts[1])) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
const name = parts[parts.length - 1];
|
|
31
|
+
if (EXCLUDE_FILES.has(name)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return EXCLUDE_GLOBS.some((pattern) => matchesGlob(name, pattern));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function walkFiles(dir) {
|
|
38
|
+
const files = [];
|
|
39
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
40
|
+
const fullPath = path.join(dir, entry.name);
|
|
41
|
+
if (entry.isDirectory()) {
|
|
42
|
+
files.push(...walkFiles(fullPath));
|
|
43
|
+
} else if (entry.isFile()) {
|
|
44
|
+
files.push(fullPath);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function packageSkill(skillPath, outputDir = null) {
|
|
51
|
+
skillPath = path.resolve(skillPath);
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(skillPath)) {
|
|
54
|
+
console.log(`❌ Error: Skill folder not found: ${skillPath}`);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!fs.statSync(skillPath).isDirectory()) {
|
|
59
|
+
console.log(`❌ Error: Path is not a directory: ${skillPath}`);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const skillMd = path.join(skillPath, "SKILL.md");
|
|
64
|
+
if (!fs.existsSync(skillMd)) {
|
|
65
|
+
console.log(`❌ Error: SKILL.md not found in ${skillPath}`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log("🔍 Validating skill...");
|
|
70
|
+
const [valid, message] = validateSkill(skillPath);
|
|
71
|
+
if (!valid) {
|
|
72
|
+
console.log(`❌ Validation failed: ${message}`);
|
|
73
|
+
console.log(" Please fix the validation errors before packaging.");
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
console.log(`✅ ${message}\n`);
|
|
77
|
+
|
|
78
|
+
const skillName = path.basename(skillPath);
|
|
79
|
+
const outputPath = outputDir ? path.resolve(outputDir) : process.cwd();
|
|
80
|
+
if (outputDir) {
|
|
81
|
+
fs.mkdirSync(outputPath, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
const skillFilename = path.join(outputPath, `${skillName}.skill`);
|
|
84
|
+
|
|
85
|
+
const parentDir = path.dirname(skillPath);
|
|
86
|
+
const includedFiles = [];
|
|
87
|
+
for (const filePath of walkFiles(skillPath)) {
|
|
88
|
+
const arcname = path.relative(parentDir, filePath);
|
|
89
|
+
if (shouldExclude(arcname)) {
|
|
90
|
+
console.log(` Skipped: ${arcname}`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
includedFiles.push({ filePath, arcname });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (includedFiles.length === 0) {
|
|
97
|
+
console.log("❌ Error: No files to package");
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), "skill-pack-"));
|
|
102
|
+
try {
|
|
103
|
+
for (const { filePath, arcname } of includedFiles) {
|
|
104
|
+
const dest = path.join(stagingDir, arcname);
|
|
105
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
106
|
+
fs.copyFileSync(filePath, dest);
|
|
107
|
+
console.log(` Added: ${arcname}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (fs.existsSync(skillFilename)) {
|
|
111
|
+
fs.unlinkSync(skillFilename);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
execSync(`zip -r -q "${skillFilename}" "${skillName}"`, {
|
|
115
|
+
cwd: stagingDir,
|
|
116
|
+
stdio: "inherit",
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
console.log(`\n✅ Successfully packaged skill to: ${skillFilename}`);
|
|
120
|
+
return skillFilename;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.log(`❌ Error creating .skill file: ${error.message}`);
|
|
123
|
+
return null;
|
|
124
|
+
} finally {
|
|
125
|
+
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (require.main === module) {
|
|
130
|
+
if (process.argv.length < 3) {
|
|
131
|
+
console.log("Usage: node package_skill.js <path/to/skill-folder> [output-directory]");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const skillPath = process.argv[2];
|
|
136
|
+
const outputDir = process.argv[3] ?? null;
|
|
137
|
+
|
|
138
|
+
console.log(`📦 Packaging skill: ${skillPath}`);
|
|
139
|
+
if (outputDir) {
|
|
140
|
+
console.log(` Output directory: ${outputDir}`);
|
|
141
|
+
}
|
|
142
|
+
console.log();
|
|
143
|
+
|
|
144
|
+
process.exit(packageSkill(skillPath, outputDir) ? 0 : 1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { packageSkill };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Quick validation script for skills. */
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const ALLOWED_PROPERTIES = new Set([
|
|
8
|
+
"name",
|
|
9
|
+
"description",
|
|
10
|
+
"license",
|
|
11
|
+
"allowed-tools",
|
|
12
|
+
"metadata",
|
|
13
|
+
"compatibility",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
function parseSimpleFrontmatter(text) {
|
|
17
|
+
const frontmatter = {};
|
|
18
|
+
for (const line of text.split("\n")) {
|
|
19
|
+
const colonIndex = line.indexOf(":");
|
|
20
|
+
if (colonIndex === -1) continue;
|
|
21
|
+
const key = line.slice(0, colonIndex).trim();
|
|
22
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
23
|
+
if (
|
|
24
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
25
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
26
|
+
) {
|
|
27
|
+
value = value.slice(1, -1);
|
|
28
|
+
}
|
|
29
|
+
if (value === ">" || value === "|") {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
frontmatter[key] = value;
|
|
33
|
+
}
|
|
34
|
+
return frontmatter;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validateSkill(skillPath) {
|
|
38
|
+
skillPath = path.resolve(skillPath);
|
|
39
|
+
const skillMd = path.join(skillPath, "SKILL.md");
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(skillMd)) {
|
|
42
|
+
return [false, "SKILL.md not found"];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const content = fs.readFileSync(skillMd, "utf-8");
|
|
46
|
+
if (!content.startsWith("---")) {
|
|
47
|
+
return [false, "No YAML frontmatter found"];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
51
|
+
if (!match) {
|
|
52
|
+
return [false, "Invalid frontmatter format"];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const frontmatter = parseSimpleFrontmatter(match[1]);
|
|
56
|
+
if (!frontmatter || typeof frontmatter !== "object") {
|
|
57
|
+
return [false, "Frontmatter must be a YAML dictionary"];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const unexpectedKeys = Object.keys(frontmatter).filter((key) => !ALLOWED_PROPERTIES.has(key));
|
|
61
|
+
if (unexpectedKeys.length > 0) {
|
|
62
|
+
return [
|
|
63
|
+
false,
|
|
64
|
+
`Unexpected key(s) in SKILL.md frontmatter: ${unexpectedKeys.sort().join(", ")}. ` +
|
|
65
|
+
`Allowed properties are: ${[...ALLOWED_PROPERTIES].sort().join(", ")}`,
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!("name" in frontmatter)) {
|
|
70
|
+
return [false, "Missing 'name' in frontmatter"];
|
|
71
|
+
}
|
|
72
|
+
if (!("description" in frontmatter)) {
|
|
73
|
+
return [false, "Missing 'description' in frontmatter"];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const name = String(frontmatter.name ?? "").trim();
|
|
77
|
+
if (name) {
|
|
78
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
79
|
+
return [
|
|
80
|
+
false,
|
|
81
|
+
`Name '${name}' should be kebab-case (lowercase letters, digits, and hyphens only)`,
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
if (name.startsWith("-") || name.endsWith("-") || name.includes("--")) {
|
|
85
|
+
return [false, `Name '${name}' cannot start/end with hyphen or contain consecutive hyphens`];
|
|
86
|
+
}
|
|
87
|
+
if (name.length > 64) {
|
|
88
|
+
return [false, `Name is too long (${name.length} characters). Maximum is 64 characters.`];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const description = String(frontmatter.description ?? "").trim();
|
|
93
|
+
if (description) {
|
|
94
|
+
if (description.includes("<") || description.includes(">")) {
|
|
95
|
+
return [false, "Description cannot contain angle brackets (< or >)"];
|
|
96
|
+
}
|
|
97
|
+
if (description.length > 1024) {
|
|
98
|
+
return [
|
|
99
|
+
false,
|
|
100
|
+
`Description is too long (${description.length} characters). Maximum is 1024 characters.`,
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const compatibility = frontmatter.compatibility;
|
|
106
|
+
if (compatibility) {
|
|
107
|
+
if (typeof compatibility !== "string") {
|
|
108
|
+
return [false, `Compatibility must be a string, got ${typeof compatibility}`];
|
|
109
|
+
}
|
|
110
|
+
if (compatibility.length > 500) {
|
|
111
|
+
return [
|
|
112
|
+
false,
|
|
113
|
+
`Compatibility is too long (${compatibility.length} characters). Maximum is 500 characters.`,
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return [true, "Skill is valid!"];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (require.main === module) {
|
|
122
|
+
if (process.argv.length !== 3) {
|
|
123
|
+
console.log("Usage: node quick_validate.js <skill_directory>");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const [valid, message] = validateSkill(process.argv[2]);
|
|
128
|
+
console.log(message);
|
|
129
|
+
process.exit(valid ? 0 : 1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { validateSkill };
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Run trigger evaluation for a skill description. */
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
const { spawn } = require("child_process");
|
|
8
|
+
const { parseSkillMd } = require("./utils.js");
|
|
9
|
+
|
|
10
|
+
function findProjectRoot() {
|
|
11
|
+
let current = process.cwd();
|
|
12
|
+
while (true) {
|
|
13
|
+
if (
|
|
14
|
+
fs.existsSync(path.join(current, ".Tarsk")) &&
|
|
15
|
+
fs.statSync(path.join(current, ".Tarsk")).isDirectory()
|
|
16
|
+
) {
|
|
17
|
+
return current;
|
|
18
|
+
}
|
|
19
|
+
const parent = path.dirname(current);
|
|
20
|
+
if (parent === current) break;
|
|
21
|
+
current = parent;
|
|
22
|
+
}
|
|
23
|
+
return process.cwd();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildEnv() {
|
|
27
|
+
const env = { ...process.env };
|
|
28
|
+
delete env.TarskCODE;
|
|
29
|
+
return env;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function runSingleQuery(query, skillName, skillDescription, timeoutSec, projectRoot, model = null) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const uniqueId = crypto.randomBytes(4).toString("hex").slice(0, 8);
|
|
35
|
+
const cleanName = `${skillName}-skill-${uniqueId}`;
|
|
36
|
+
const projectCommandsDir = path.join(projectRoot, ".Tarsk", "commands");
|
|
37
|
+
const commandFile = path.join(projectCommandsDir, `${cleanName}.md`);
|
|
38
|
+
|
|
39
|
+
function cleanup() {
|
|
40
|
+
if (fs.existsSync(commandFile)) {
|
|
41
|
+
fs.unlinkSync(commandFile);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
fs.mkdirSync(projectCommandsDir, { recursive: true });
|
|
47
|
+
const indentedDesc = skillDescription.split("\n").join("\n ");
|
|
48
|
+
const commandContent =
|
|
49
|
+
"---\n" +
|
|
50
|
+
"description: |\n" +
|
|
51
|
+
` ${indentedDesc}\n` +
|
|
52
|
+
"---\n\n" +
|
|
53
|
+
`# ${skillName}\n\n` +
|
|
54
|
+
`This skill handles: ${skillDescription}\n`;
|
|
55
|
+
fs.writeFileSync(commandFile, commandContent);
|
|
56
|
+
|
|
57
|
+
const cmd = [
|
|
58
|
+
"tarsk",
|
|
59
|
+
"-p",
|
|
60
|
+
query,
|
|
61
|
+
"--output-format",
|
|
62
|
+
"stream-json",
|
|
63
|
+
"--verbose",
|
|
64
|
+
"--include-partial-messages",
|
|
65
|
+
];
|
|
66
|
+
if (model) {
|
|
67
|
+
cmd.push("--model", model);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
71
|
+
cwd: projectRoot,
|
|
72
|
+
env: buildEnv(),
|
|
73
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
let buffer = "";
|
|
77
|
+
let triggered = false;
|
|
78
|
+
let pendingToolName = null;
|
|
79
|
+
let accumulatedJson = "";
|
|
80
|
+
let finished = false;
|
|
81
|
+
|
|
82
|
+
function finish(value) {
|
|
83
|
+
if (finished) return;
|
|
84
|
+
finished = true;
|
|
85
|
+
clearTimeout(timeoutId);
|
|
86
|
+
if (proc.exitCode === null) {
|
|
87
|
+
proc.kill();
|
|
88
|
+
}
|
|
89
|
+
cleanup();
|
|
90
|
+
resolve(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const timeoutId = setTimeout(() => finish(triggered), timeoutSec * 1000);
|
|
94
|
+
|
|
95
|
+
proc.stdout.on("data", (chunk) => {
|
|
96
|
+
buffer += chunk.toString("utf-8");
|
|
97
|
+
while (true) {
|
|
98
|
+
const newlineIdx = buffer.indexOf("\n");
|
|
99
|
+
if (newlineIdx === -1) break;
|
|
100
|
+
const line = buffer.slice(0, newlineIdx).trim();
|
|
101
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
102
|
+
if (!line) continue;
|
|
103
|
+
|
|
104
|
+
let event;
|
|
105
|
+
try {
|
|
106
|
+
event = JSON.parse(line);
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (event.type === "stream_event") {
|
|
112
|
+
const se = event.event ?? {};
|
|
113
|
+
const seType = se.type ?? "";
|
|
114
|
+
|
|
115
|
+
if (seType === "content_block_start") {
|
|
116
|
+
const cb = se.content_block ?? {};
|
|
117
|
+
if (cb.type === "tool_use") {
|
|
118
|
+
const toolName = cb.name ?? "";
|
|
119
|
+
if (toolName === "Skill" || toolName === "Read") {
|
|
120
|
+
pendingToolName = toolName;
|
|
121
|
+
accumulatedJson = "";
|
|
122
|
+
} else {
|
|
123
|
+
finish(false);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else if (seType === "content_block_delta" && pendingToolName) {
|
|
128
|
+
const delta = se.delta ?? {};
|
|
129
|
+
if (delta.type === "input_json_delta") {
|
|
130
|
+
accumulatedJson += delta.partial_json ?? "";
|
|
131
|
+
if (accumulatedJson.includes(cleanName)) {
|
|
132
|
+
finish(true);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else if (seType === "content_block_stop" || seType === "message_stop") {
|
|
137
|
+
if (pendingToolName) {
|
|
138
|
+
finish(accumulatedJson.includes(cleanName));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (seType === "message_stop") {
|
|
142
|
+
finish(false);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else if (event.type === "assistant") {
|
|
147
|
+
const message = event.message ?? {};
|
|
148
|
+
for (const contentItem of message.content ?? []) {
|
|
149
|
+
if (contentItem.type !== "tool_use") continue;
|
|
150
|
+
const toolName = contentItem.name ?? "";
|
|
151
|
+
const toolInput = contentItem.input ?? {};
|
|
152
|
+
if (toolName === "Skill" && (toolInput.skill ?? "").includes(cleanName)) {
|
|
153
|
+
triggered = true;
|
|
154
|
+
} else if (toolName === "Read" && (toolInput.file_path ?? "").includes(cleanName)) {
|
|
155
|
+
triggered = true;
|
|
156
|
+
}
|
|
157
|
+
finish(triggered);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
} else if (event.type === "result") {
|
|
161
|
+
finish(triggered);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
proc.on("close", () => finish(triggered));
|
|
168
|
+
proc.on("error", () => finish(false));
|
|
169
|
+
} catch {
|
|
170
|
+
cleanup();
|
|
171
|
+
resolve(false);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function runEval(
|
|
177
|
+
evalSet,
|
|
178
|
+
skillName,
|
|
179
|
+
description,
|
|
180
|
+
numWorkers,
|
|
181
|
+
timeout,
|
|
182
|
+
projectRoot,
|
|
183
|
+
runsPerQuery = 1,
|
|
184
|
+
triggerThreshold = 0.5,
|
|
185
|
+
model = null,
|
|
186
|
+
) {
|
|
187
|
+
const tasks = [];
|
|
188
|
+
for (const item of evalSet) {
|
|
189
|
+
for (let runIdx = 0; runIdx < runsPerQuery; runIdx++) {
|
|
190
|
+
tasks.push({ item, runIdx });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const queryTriggers = {};
|
|
195
|
+
const queryItems = {};
|
|
196
|
+
let nextIndex = 0;
|
|
197
|
+
|
|
198
|
+
async function worker() {
|
|
199
|
+
while (nextIndex < tasks.length) {
|
|
200
|
+
const taskIndex = nextIndex++;
|
|
201
|
+
const { item } = tasks[taskIndex];
|
|
202
|
+
const query = item.query;
|
|
203
|
+
queryItems[query] = item;
|
|
204
|
+
if (!queryTriggers[query]) {
|
|
205
|
+
queryTriggers[query] = [];
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const triggered = await runSingleQuery(
|
|
209
|
+
query,
|
|
210
|
+
skillName,
|
|
211
|
+
description,
|
|
212
|
+
timeout,
|
|
213
|
+
projectRoot,
|
|
214
|
+
model,
|
|
215
|
+
);
|
|
216
|
+
queryTriggers[query].push(triggered);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.error(`Warning: query failed: ${error.message}`);
|
|
219
|
+
queryTriggers[query].push(false);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const workers = Math.max(1, numWorkers);
|
|
225
|
+
await Promise.all(Array.from({ length: workers }, () => worker()));
|
|
226
|
+
|
|
227
|
+
const results = [];
|
|
228
|
+
for (const [query, triggers] of Object.entries(queryTriggers)) {
|
|
229
|
+
const item = queryItems[query];
|
|
230
|
+
const triggerRate = triggers.reduce((sum, t) => sum + (t ? 1 : 0), 0) / triggers.length;
|
|
231
|
+
const shouldTrigger = item.should_trigger;
|
|
232
|
+
const didPass = shouldTrigger
|
|
233
|
+
? triggerRate >= triggerThreshold
|
|
234
|
+
: triggerRate < triggerThreshold;
|
|
235
|
+
results.push({
|
|
236
|
+
query,
|
|
237
|
+
should_trigger: shouldTrigger,
|
|
238
|
+
trigger_rate: triggerRate,
|
|
239
|
+
triggers: triggers.filter(Boolean).length,
|
|
240
|
+
runs: triggers.length,
|
|
241
|
+
pass: didPass,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const passed = results.filter((r) => r.pass).length;
|
|
246
|
+
const total = results.length;
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
skill_name: skillName,
|
|
250
|
+
description,
|
|
251
|
+
results,
|
|
252
|
+
summary: {
|
|
253
|
+
total,
|
|
254
|
+
passed,
|
|
255
|
+
failed: total - passed,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function parseArgs(argv) {
|
|
261
|
+
const args = {
|
|
262
|
+
evalSet: null,
|
|
263
|
+
skillPath: null,
|
|
264
|
+
description: null,
|
|
265
|
+
numWorkers: 10,
|
|
266
|
+
timeout: 30,
|
|
267
|
+
runsPerQuery: 3,
|
|
268
|
+
triggerThreshold: 0.5,
|
|
269
|
+
model: null,
|
|
270
|
+
verbose: false,
|
|
271
|
+
};
|
|
272
|
+
for (let i = 2; i < argv.length; i++) {
|
|
273
|
+
const arg = argv[i];
|
|
274
|
+
if (arg === "--eval-set") args.evalSet = argv[++i];
|
|
275
|
+
else if (arg === "--skill-path") args.skillPath = argv[++i];
|
|
276
|
+
else if (arg === "--description") args.description = argv[++i];
|
|
277
|
+
else if (arg === "--num-workers") args.numWorkers = parseInt(argv[++i], 10);
|
|
278
|
+
else if (arg === "--timeout") args.timeout = parseInt(argv[++i], 10);
|
|
279
|
+
else if (arg === "--runs-per-query") args.runsPerQuery = parseInt(argv[++i], 10);
|
|
280
|
+
else if (arg === "--trigger-threshold") args.triggerThreshold = parseFloat(argv[++i]);
|
|
281
|
+
else if (arg === "--model") args.model = argv[++i];
|
|
282
|
+
else if (arg === "--verbose") args.verbose = true;
|
|
283
|
+
}
|
|
284
|
+
return args;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function main() {
|
|
288
|
+
const args = parseArgs(process.argv);
|
|
289
|
+
if (!args.evalSet || !args.skillPath) {
|
|
290
|
+
console.error(
|
|
291
|
+
"Usage: node run_eval.js --eval-set PATH --skill-path PATH [--description TEXT] [--num-workers N] [--timeout SEC] [--runs-per-query N] [--trigger-threshold R] [--model MODEL] [--verbose]",
|
|
292
|
+
);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const evalSet = JSON.parse(fs.readFileSync(args.evalSet, "utf-8"));
|
|
297
|
+
const skillPath = path.resolve(args.skillPath);
|
|
298
|
+
if (!fs.existsSync(path.join(skillPath, "SKILL.md"))) {
|
|
299
|
+
console.error(`Error: No SKILL.md found at ${skillPath}`);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const { name, description: originalDescription } = parseSkillMd(skillPath);
|
|
304
|
+
const description = args.description ?? originalDescription;
|
|
305
|
+
const projectRoot = findProjectRoot();
|
|
306
|
+
|
|
307
|
+
if (args.verbose) {
|
|
308
|
+
console.error(`Evaluating: ${description}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const output = await runEval(
|
|
312
|
+
evalSet,
|
|
313
|
+
name,
|
|
314
|
+
description,
|
|
315
|
+
args.numWorkers,
|
|
316
|
+
args.timeout,
|
|
317
|
+
projectRoot,
|
|
318
|
+
args.runsPerQuery,
|
|
319
|
+
args.triggerThreshold,
|
|
320
|
+
args.model,
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
if (args.verbose) {
|
|
324
|
+
const summary = output.summary;
|
|
325
|
+
console.error(`Results: ${summary.passed}/${summary.total} passed`);
|
|
326
|
+
for (const r of output.results) {
|
|
327
|
+
const status = r.pass ? "PASS" : "FAIL";
|
|
328
|
+
const rateStr = `${r.triggers}/${r.runs}`;
|
|
329
|
+
console.error(
|
|
330
|
+
` [${status}] rate=${rateStr} expected=${r.should_trigger}: ${r.query.slice(0, 70)}`,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log(JSON.stringify(output, null, 2));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (require.main === module) {
|
|
339
|
+
main().catch((error) => {
|
|
340
|
+
console.error(error.message);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
module.exports = { findProjectRoot, runEval, runSingleQuery };
|