@cydm/pie 2.1.0 → 2.1.1
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/README.md +6 -3
- package/dist/builtin/extensions/ask-user/index.js +1 -1
- package/dist/builtin/extensions/computer-use/index.js +3993 -107
- package/dist/builtin/extensions/plan-mode/index.js +1 -1
- package/dist/builtin/extensions/subagent/index.js +2 -2
- package/dist/builtin/extensions/todo/index.js +1 -1
- package/dist/builtin/skills/chrome-devtools-axi/LICENSE +21 -0
- package/dist/builtin/skills/chrome-devtools-axi/SKILL.md +20 -1
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-bridge.js +20034 -0
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-cli.js +8 -2
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +26 -0
- package/dist/builtin/skills/chrome-devtools-axi/chunk-42SGITKJ.js +11 -0
- package/dist/builtin/skills/chrome-devtools-axi/chunk-OJ6N23UZ.js +47 -0
- package/dist/builtin/skills/chrome-devtools-axi/chunk-UPATXALW.js +215 -0
- package/dist/builtin/skills/chrome-devtools-axi/cli-F2VCSVL4.js +4424 -0
- package/dist/builtin/skills/skill-creator/SKILL.md +5 -2
- package/dist/builtin/skills/skill-creator/eval-viewer/generate_review.mjs +7 -20
- package/dist/builtin/skills/skill-creator/eval-viewer/viewer.html +15 -15
- package/dist/builtin/skills/skill-creator/scripts/aggregate_benchmark.mjs +20 -19
- package/dist/builtin/skills/skill-creator/scripts/generate_report.mjs +3 -3
- package/dist/builtin/skills/skill-creator/scripts/improve_description.mjs +12 -8
- package/dist/builtin/skills/skill-creator/scripts/package_skill.mjs +1 -0
- package/dist/builtin/skills/skill-creator/scripts/pie_runner.mjs +77 -31
- package/dist/builtin/skills/skill-creator/scripts/run_eval.mjs +38 -11
- package/dist/builtin/skills/skill-creator/scripts/run_loop.mjs +12 -6
- package/dist/builtin/skills/skill-creator/scripts/skill_metadata.mjs +12 -1
- package/dist/builtin/skills/tui-use/LICENSE +21 -0
- package/dist/builtin/skills/tui-use/README.md +13 -3
- package/dist/builtin/skills/tui-use/SKILL.md +87 -39
- package/dist/builtin/skills/tui-use/tui-use-cli.cjs +10608 -0
- package/dist/builtin/skills/tui-use/tui-use-cli.js +39 -0
- package/dist/builtin/skills/tui-use/tui-use-daemon.cjs +7916 -0
- package/dist/chunks/{chunk-PMPD56N2.js → chunk-LU7RKALH.js} +82 -24
- package/dist/chunks/{chunk-G2POWIA5.js → chunk-TJ2F4FL5.js} +64 -16
- package/dist/cli.js +7503 -87
- package/package.json +26 -18
- package/scripts/repair-node-pty.mjs +45 -0
|
@@ -382,12 +382,15 @@ Save the eval set to the workspace, then run in the background:
|
|
|
382
382
|
node <skill-creator-path>/scripts/run_loop.mjs \
|
|
383
383
|
--eval-set <path-to-trigger-eval.json> \
|
|
384
384
|
--skill-path <path-to-skill> \
|
|
385
|
-
--model <
|
|
385
|
+
--model <profile/modelId> \
|
|
386
386
|
--max-iterations 5 \
|
|
387
387
|
--verbose
|
|
388
388
|
```
|
|
389
389
|
|
|
390
|
-
Use
|
|
390
|
+
Use a configured Pie `profile/modelId` so the triggering test matches what the
|
|
391
|
+
user actually experiences. The runner applies selection through JSON-events
|
|
392
|
+
metadata and verifies the actual session model. Unknown models and runtime
|
|
393
|
+
errors fail explicitly; they are not counted as successful negative examples.
|
|
391
394
|
|
|
392
395
|
While it runs, periodically tail the output to give the user updates on which iteration it's on and what the scores look like.
|
|
393
396
|
|
|
@@ -4,15 +4,14 @@ import path from "node:path";
|
|
|
4
4
|
import { createServer } from "node:http";
|
|
5
5
|
import { readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { execFile } from "node:child_process";
|
|
8
|
-
import { promisify } from "node:util";
|
|
9
7
|
import { openInBrowser } from "../scripts/pie_runner.mjs";
|
|
8
|
+
import { readJsonFile } from "../scripts/skill_metadata.mjs";
|
|
10
9
|
|
|
11
|
-
const execFileAsync = promisify(execFile);
|
|
12
10
|
const METADATA_FILES = new Set(["transcript.md", "user_notes.md", "metrics.json"]);
|
|
13
11
|
const TEXT_EXTENSIONS = new Set([".txt", ".md", ".json", ".csv", ".py", ".js", ".ts", ".tsx", ".jsx", ".yaml", ".yml", ".xml", ".html", ".css", ".sh", ".rb", ".go", ".rs", ".java", ".c", ".cpp", ".h", ".hpp", ".sql", ".r", ".toml", ".mjs"]);
|
|
14
12
|
const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"]);
|
|
15
13
|
const MIME_OVERRIDES = {
|
|
14
|
+
".pdf": "application/pdf",
|
|
16
15
|
".svg": "image/svg+xml",
|
|
17
16
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
18
17
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
@@ -36,7 +35,7 @@ async function pathExists(targetPath) {
|
|
|
36
35
|
async function readJsonIfExists(targetPath) {
|
|
37
36
|
if (!(await pathExists(targetPath))) return null;
|
|
38
37
|
try {
|
|
39
|
-
return
|
|
38
|
+
return await readJsonFile(targetPath);
|
|
40
39
|
} catch {
|
|
41
40
|
return null;
|
|
42
41
|
}
|
|
@@ -175,18 +174,7 @@ export async function generateHtml({ workspace, skillName, previous = null, benc
|
|
|
175
174
|
if (benchmark) {
|
|
176
175
|
embedded.benchmark = benchmark;
|
|
177
176
|
}
|
|
178
|
-
return template.replace("/*__EMBEDDED_DATA__*/", `const EMBEDDED_DATA = ${JSON.stringify(embedded)};`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async function killPort(port) {
|
|
182
|
-
try {
|
|
183
|
-
const { stdout } = await execFileAsync("lsof", ["-ti", `:${port}`], { timeout: 5000 });
|
|
184
|
-
for (const pid of stdout.split("\n").map((value) => value.trim()).filter(Boolean)) {
|
|
185
|
-
try {
|
|
186
|
-
process.kill(Number(pid), "SIGTERM");
|
|
187
|
-
} catch {}
|
|
188
|
-
}
|
|
189
|
-
} catch {}
|
|
177
|
+
return template.replace("/*__EMBEDDED_DATA__*/", () => `const EMBEDDED_DATA = ${JSON.stringify(embedded).replaceAll("<", "\\u003c")};`);
|
|
190
178
|
}
|
|
191
179
|
|
|
192
180
|
function parseArgs(args) {
|
|
@@ -211,10 +199,10 @@ function parseArgs(args) {
|
|
|
211
199
|
|
|
212
200
|
async function main(argv = process.argv) {
|
|
213
201
|
const args = parseArgs(argv.slice(2));
|
|
214
|
-
|
|
215
|
-
if (!workspace) {
|
|
202
|
+
if (!args._[0]) {
|
|
216
203
|
throw new Error("Usage: node generate_review.mjs <workspace-path> [--port PORT] [--skill-name NAME] [--static output.html]");
|
|
217
204
|
}
|
|
205
|
+
const workspace = path.resolve(args._[0]);
|
|
218
206
|
const skillName = args["skill-name"] ?? path.basename(workspace).replace(/-workspace$/, "");
|
|
219
207
|
const previous = args["previous-workspace"] ? await loadPreviousIteration(path.resolve(args["previous-workspace"])) : {};
|
|
220
208
|
const benchmark = args.benchmark ? await readJsonIfExists(path.resolve(args.benchmark)) : null;
|
|
@@ -225,7 +213,6 @@ async function main(argv = process.argv) {
|
|
|
225
213
|
return;
|
|
226
214
|
}
|
|
227
215
|
let port = Number(args.port ?? args.p ?? 3117);
|
|
228
|
-
await killPort(port);
|
|
229
216
|
const feedbackPath = path.join(workspace, "feedback.json");
|
|
230
217
|
const server = createServer(async (req, res) => {
|
|
231
218
|
try {
|
|
@@ -273,7 +260,7 @@ async function main(argv = process.argv) {
|
|
|
273
260
|
port = typeof address === "object" && address ? address.port : port;
|
|
274
261
|
const url = `http://localhost:${port}`;
|
|
275
262
|
process.stdout.write(`Eval Viewer\nURL: ${url}\nWorkspace: ${workspace}\nFeedback: ${feedbackPath}\n`);
|
|
276
|
-
await openInBrowser(url);
|
|
263
|
+
if (!args["no-open"]) await openInBrowser(url);
|
|
277
264
|
}
|
|
278
265
|
|
|
279
266
|
const entryPath = fileURLToPath(import.meta.url);
|
|
@@ -882,7 +882,7 @@
|
|
|
882
882
|
const badgeClass = summary.pass_rate >= 0.8 ? "grade-pass" : summary.pass_rate >= 0.5 ? "" : "grade-fail";
|
|
883
883
|
html += '<div class="grades-summary">';
|
|
884
884
|
html += '<span class="grade-badge ' + badgeClass + '">' + passRate + '</span>';
|
|
885
|
-
html += '<span>' + (summary.passed || 0) + ' passed, ' + (summary.failed || 0) + ' failed of ' + (summary.total || 0) + '</span>';
|
|
885
|
+
html += '<span>' + escapeHtml(summary.passed || 0) + ' passed, ' + escapeHtml(summary.failed || 0) + ' failed of ' + escapeHtml(summary.total || 0) + '</span>';
|
|
886
886
|
html += '</div>';
|
|
887
887
|
|
|
888
888
|
// Assertions list
|
|
@@ -1099,7 +1099,7 @@
|
|
|
1099
1099
|
function escapeHtml(text) {
|
|
1100
1100
|
const div = document.createElement("div");
|
|
1101
1101
|
div.textContent = text;
|
|
1102
|
-
return div.innerHTML;
|
|
1102
|
+
return div.innerHTML.replaceAll('"', """).replaceAll("'", "'");
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
1105
|
// ---- View switching ----
|
|
@@ -1129,16 +1129,16 @@
|
|
|
1129
1129
|
html += "<h2 style='font-family: Poppins, sans-serif; margin-bottom: 0.5rem;'>Benchmark Results</h2>";
|
|
1130
1130
|
html += "<p style='color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1.25rem;'>";
|
|
1131
1131
|
if (metadata.skill_name) html += "<strong>" + escapeHtml(metadata.skill_name) + "</strong> — ";
|
|
1132
|
-
if (metadata.timestamp) html += metadata.timestamp + " — ";
|
|
1133
|
-
if (metadata.evals_run) html += "Evals: " + metadata.evals_run.join(", ") + " — ";
|
|
1134
|
-
html += (metadata.runs_per_configuration || "?") + " runs per configuration";
|
|
1132
|
+
if (metadata.timestamp) html += escapeHtml(metadata.timestamp) + " — ";
|
|
1133
|
+
if (metadata.evals_run) html += "Evals: " + escapeHtml(metadata.evals_run.join(", ")) + " — ";
|
|
1134
|
+
html += escapeHtml(metadata.runs_per_configuration || "?") + " runs per configuration";
|
|
1135
1135
|
html += "</p>";
|
|
1136
1136
|
|
|
1137
1137
|
// Summary table
|
|
1138
1138
|
html += '<table class="benchmark-table">';
|
|
1139
1139
|
|
|
1140
1140
|
function fmtStat(stat, pct) {
|
|
1141
|
-
if (!stat) return "—";
|
|
1141
|
+
if (!stat || !Number.isFinite(stat.mean) || !Number.isFinite(stat.stddev)) return "—";
|
|
1142
1142
|
const suffix = pct ? "%" : "";
|
|
1143
1143
|
const m = pct ? (stat.mean * 100).toFixed(0) : stat.mean.toFixed(1);
|
|
1144
1144
|
const s = pct ? (stat.stddev * 100).toFixed(0) : stat.stddev.toFixed(1);
|
|
@@ -1169,14 +1169,14 @@
|
|
|
1169
1169
|
html += "<tr><td><strong>Pass Rate</strong></td>";
|
|
1170
1170
|
html += "<td>" + fmtStat(a.pass_rate, true) + "</td>";
|
|
1171
1171
|
html += "<td>" + fmtStat(b.pass_rate, true) + "</td>";
|
|
1172
|
-
html += '<td class="' + deltaClass(delta.pass_rate) + '">' + (delta.pass_rate || "—") + "</td></tr>";
|
|
1172
|
+
html += '<td class="' + deltaClass(delta.pass_rate) + '">' + escapeHtml(delta.pass_rate || "—") + "</td></tr>";
|
|
1173
1173
|
|
|
1174
1174
|
// Time (only show row if data exists)
|
|
1175
1175
|
if (a.time_seconds || b.time_seconds) {
|
|
1176
1176
|
html += "<tr><td><strong>Time (s)</strong></td>";
|
|
1177
1177
|
html += "<td>" + fmtStat(a.time_seconds, false) + "</td>";
|
|
1178
1178
|
html += "<td>" + fmtStat(b.time_seconds, false) + "</td>";
|
|
1179
|
-
html += '<td class="' + deltaClass(delta.time_seconds) + '">' + (delta.time_seconds ? delta.time_seconds + "s" : "—") + "</td></tr>";
|
|
1179
|
+
html += '<td class="' + deltaClass(delta.time_seconds) + '">' + escapeHtml(delta.time_seconds ? delta.time_seconds + "s" : "—") + "</td></tr>";
|
|
1180
1180
|
}
|
|
1181
1181
|
|
|
1182
1182
|
// Tokens (only show row if data exists)
|
|
@@ -1184,7 +1184,7 @@
|
|
|
1184
1184
|
html += "<tr><td><strong>Tokens</strong></td>";
|
|
1185
1185
|
html += "<td>" + fmtStat(a.tokens, false) + "</td>";
|
|
1186
1186
|
html += "<td>" + fmtStat(b.tokens, false) + "</td>";
|
|
1187
|
-
html += '<td class="' + deltaClass(delta.tokens) + '">' + (delta.tokens || "—") + "</td></tr>";
|
|
1187
|
+
html += '<td class="' + deltaClass(delta.tokens) + '">' + escapeHtml(delta.tokens || "—") + "</td></tr>";
|
|
1188
1188
|
}
|
|
1189
1189
|
|
|
1190
1190
|
html += "</tbody></table>";
|
|
@@ -1225,11 +1225,11 @@
|
|
|
1225
1225
|
const r = run.result || {};
|
|
1226
1226
|
const prClass = r.pass_rate >= 0.8 ? "benchmark-delta-positive" : r.pass_rate < 0.5 ? "benchmark-delta-negative" : "";
|
|
1227
1227
|
html += '<tr class="' + rowClass + '">';
|
|
1228
|
-
html += "<td>" + configLabel + "</td>";
|
|
1229
|
-
html += "<td>" + run.run_number + "</td>";
|
|
1230
|
-
html += '<td class="' + prClass + '">' + ((r.pass_rate || 0) * 100).toFixed(0) + "% (" + (r.passed || 0) + "/" + (r.total || 0) + ")</td>";
|
|
1228
|
+
html += "<td>" + escapeHtml(configLabel) + "</td>";
|
|
1229
|
+
html += "<td>" + escapeHtml(run.run_number) + "</td>";
|
|
1230
|
+
html += '<td class="' + prClass + '">' + ((r.pass_rate || 0) * 100).toFixed(0) + "% (" + escapeHtml(r.passed || 0) + "/" + escapeHtml(r.total || 0) + ")</td>";
|
|
1231
1231
|
if (hasTime) html += "<td>" + (r.time_seconds != null ? r.time_seconds.toFixed(1) : "—") + "</td>";
|
|
1232
|
-
if (hasErrors) html += "<td>" + (r.errors || 0) + "</td>";
|
|
1232
|
+
if (hasErrors) html += "<td>" + escapeHtml(r.errors || 0) + "</td>";
|
|
1233
1233
|
html += "</tr>";
|
|
1234
1234
|
}
|
|
1235
1235
|
|
|
@@ -1238,7 +1238,7 @@
|
|
|
1238
1238
|
const avgRate = rates.reduce((a, b) => a + b, 0) / rates.length;
|
|
1239
1239
|
const avgPrClass = avgRate >= 0.8 ? "benchmark-delta-positive" : avgRate < 0.5 ? "benchmark-delta-negative" : "";
|
|
1240
1240
|
html += '<tr class="benchmark-row-avg ' + rowClass + '">';
|
|
1241
|
-
html += "<td>" + configLabel + "</td>";
|
|
1241
|
+
html += "<td>" + escapeHtml(configLabel) + "</td>";
|
|
1242
1242
|
html += "<td>Avg</td>";
|
|
1243
1243
|
html += '<td class="' + avgPrClass + '">' + (avgRate * 100).toFixed(0) + "%</td>";
|
|
1244
1244
|
if (hasTime) {
|
|
@@ -1289,7 +1289,7 @@
|
|
|
1289
1289
|
if (exp) {
|
|
1290
1290
|
const cls = exp.passed ? "benchmark-delta-positive" : "benchmark-delta-negative";
|
|
1291
1291
|
const icon = exp.passed ? "\u2713" : "\u2717";
|
|
1292
|
-
html += '<span class="' + cls + '" title="Run ' + run.run_number + ': ' + escapeHtml(exp.evidence || "") + '">' + icon + "</span> ";
|
|
1292
|
+
html += '<span class="' + cls + '" title="Run ' + escapeHtml(run.run_number) + ': ' + escapeHtml(exp.evidence || "") + '">' + icon + "</span> ";
|
|
1293
1293
|
} else {
|
|
1294
1294
|
html += "— ";
|
|
1295
1295
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { readdir,
|
|
4
|
+
import { readdir, stat, writeFile } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { readJsonFile } from "./skill_metadata.mjs";
|
|
6
7
|
|
|
7
8
|
function round(value, places = 4) {
|
|
8
9
|
const factor = 10 ** places;
|
|
@@ -41,7 +42,7 @@ async function readJsonIfExists(targetPath) {
|
|
|
41
42
|
return null;
|
|
42
43
|
}
|
|
43
44
|
try {
|
|
44
|
-
return
|
|
45
|
+
return await readJsonFile(targetPath);
|
|
45
46
|
} catch {
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
@@ -76,10 +77,8 @@ export async function loadRunResults(benchmarkDir) {
|
|
|
76
77
|
for (const configName of await listDirectories(evalDir)) {
|
|
77
78
|
const configDir = path.join(evalDir, configName);
|
|
78
79
|
const childDirs = await listDirectories(configDir);
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
80
|
+
const nestedRuns = childDirs.filter((name) => name.startsWith("run-"));
|
|
81
|
+
const runDirs = nestedRuns.length > 0 ? nestedRuns : [""];
|
|
83
82
|
if (!results[configName]) {
|
|
84
83
|
results[configName] = [];
|
|
85
84
|
}
|
|
@@ -91,19 +90,16 @@ export async function loadRunResults(benchmarkDir) {
|
|
|
91
90
|
}
|
|
92
91
|
const timing = grading.timing ?? {};
|
|
93
92
|
let timeSeconds = Number(timing.total_duration_seconds ?? 0);
|
|
94
|
-
let tokens =
|
|
93
|
+
let tokens = null;
|
|
95
94
|
const timingJson = await readJsonIfExists(path.join(runDir, "timing.json"));
|
|
96
95
|
if (timeSeconds === 0 && timingJson) {
|
|
97
96
|
timeSeconds = Number(timingJson.total_duration_seconds ?? 0);
|
|
98
|
-
tokens = Number(timingJson.total_tokens ?? 0);
|
|
99
97
|
}
|
|
98
|
+
if (Number.isFinite(timingJson?.total_tokens)) tokens = timingJson.total_tokens;
|
|
100
99
|
const executionMetrics = grading.execution_metrics ?? {};
|
|
101
|
-
if (tokens === 0) {
|
|
102
|
-
tokens = Number(executionMetrics.output_chars ?? 0);
|
|
103
|
-
}
|
|
104
100
|
results[configName].push({
|
|
105
101
|
eval_id: evalId,
|
|
106
|
-
run_number: Number.parseInt(runDirName.split("-")[1], 10) ||
|
|
102
|
+
run_number: Number.parseInt(runDirName.split("-")[1], 10) || 1,
|
|
107
103
|
pass_rate: Number(grading.summary?.pass_rate ?? 0),
|
|
108
104
|
passed: Number(grading.summary?.passed ?? 0),
|
|
109
105
|
failed: Number(grading.summary?.failed ?? 0),
|
|
@@ -127,13 +123,15 @@ export async function loadRunResults(benchmarkDir) {
|
|
|
127
123
|
|
|
128
124
|
export function aggregateResults(results) {
|
|
129
125
|
const runSummary = {};
|
|
130
|
-
const configs = Object.keys(results);
|
|
126
|
+
const configs = Object.keys(results).sort((a, b) => Number(b === "with_skill") - Number(a === "with_skill"));
|
|
131
127
|
for (const config of configs) {
|
|
132
128
|
const runs = results[config] ?? [];
|
|
133
129
|
runSummary[config] = {
|
|
134
130
|
pass_rate: calculateStats(runs.map((run) => Number(run.pass_rate ?? 0))),
|
|
135
131
|
time_seconds: calculateStats(runs.map((run) => Number(run.time_seconds ?? 0))),
|
|
136
|
-
tokens:
|
|
132
|
+
tokens: runs.some((run) => Number.isFinite(run.tokens))
|
|
133
|
+
? calculateStats(runs.map((run) => run.tokens).filter(Number.isFinite))
|
|
134
|
+
: { mean: null, stddev: null, min: null, max: null },
|
|
137
135
|
};
|
|
138
136
|
}
|
|
139
137
|
const primaryConfig = configs[0];
|
|
@@ -143,7 +141,8 @@ export function aggregateResults(results) {
|
|
|
143
141
|
runSummary.delta = {
|
|
144
142
|
pass_rate: `${(primary.pass_rate?.mean ?? 0) - (baseline.pass_rate?.mean ?? 0) >= 0 ? "+" : ""}${round((primary.pass_rate?.mean ?? 0) - (baseline.pass_rate?.mean ?? 0), 2).toFixed(2)}`,
|
|
145
143
|
time_seconds: `${(primary.time_seconds?.mean ?? 0) - (baseline.time_seconds?.mean ?? 0) >= 0 ? "+" : ""}${round((primary.time_seconds?.mean ?? 0) - (baseline.time_seconds?.mean ?? 0), 1).toFixed(1)}`,
|
|
146
|
-
tokens:
|
|
144
|
+
tokens: primary.tokens?.mean != null && baseline.tokens?.mean != null
|
|
145
|
+
? `${primary.tokens.mean - baseline.tokens.mean >= 0 ? "+" : ""}${Math.round(primary.tokens.mean - baseline.tokens.mean)}` : "—",
|
|
147
146
|
};
|
|
148
147
|
return runSummary;
|
|
149
148
|
}
|
|
@@ -152,7 +151,8 @@ export async function generateBenchmark(benchmarkDir, { skillName = "", skillPat
|
|
|
152
151
|
const results = await loadRunResults(benchmarkDir);
|
|
153
152
|
const runSummary = aggregateResults(results);
|
|
154
153
|
const runs = [];
|
|
155
|
-
for (const
|
|
154
|
+
for (const configuration of Object.keys(runSummary).filter((key) => key !== "delta")) {
|
|
155
|
+
const entries = results[configuration];
|
|
156
156
|
for (const result of entries) {
|
|
157
157
|
runs.push({
|
|
158
158
|
eval_id: result.eval_id,
|
|
@@ -174,6 +174,7 @@ export async function generateBenchmark(benchmarkDir, { skillName = "", skillPat
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
const evalIds = [...new Set(runs.map((run) => run.eval_id))].sort((a, b) => a - b);
|
|
177
|
+
const counts = new Set(Object.values(results).flatMap((entries) => evalIds.map((id) => entries.filter((run) => run.eval_id === id).length)));
|
|
177
178
|
return {
|
|
178
179
|
metadata: {
|
|
179
180
|
skill_name: skillName || "<skill-name>",
|
|
@@ -182,7 +183,7 @@ export async function generateBenchmark(benchmarkDir, { skillName = "", skillPat
|
|
|
182
183
|
analyzer_model: "<model-name>",
|
|
183
184
|
timestamp: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
184
185
|
evals_run: evalIds,
|
|
185
|
-
runs_per_configuration:
|
|
186
|
+
runs_per_configuration: counts.size === 1 ? [...counts][0] : null,
|
|
186
187
|
},
|
|
187
188
|
runs,
|
|
188
189
|
run_summary: runSummary,
|
|
@@ -206,7 +207,7 @@ export function generateMarkdown(benchmark) {
|
|
|
206
207
|
"",
|
|
207
208
|
`**Model**: ${metadata.executor_model ?? "<model-name>"}`,
|
|
208
209
|
`**Date**: ${metadata.timestamp ?? ""}`,
|
|
209
|
-
`**Evals**: ${(metadata.evals_run ?? []).join(", ")} (${metadata.runs_per_configuration ??
|
|
210
|
+
`**Evals**: ${(metadata.evals_run ?? []).join(", ")} (${metadata.runs_per_configuration ?? "variable"} runs each per configuration)`,
|
|
210
211
|
"",
|
|
211
212
|
"## Summary",
|
|
212
213
|
"",
|
|
@@ -214,7 +215,7 @@ export function generateMarkdown(benchmark) {
|
|
|
214
215
|
"|--------|------------|---------------|-------|",
|
|
215
216
|
`| Pass Rate | ${(aSummary.pass_rate?.mean ?? 0) * 100}% ± ${(aSummary.pass_rate?.stddev ?? 0) * 100}% | ${(bSummary.pass_rate?.mean ?? 0) * 100}% ± ${(bSummary.pass_rate?.stddev ?? 0) * 100}% | ${delta.pass_rate ?? "—"} |`,
|
|
216
217
|
`| Time | ${(aSummary.time_seconds?.mean ?? 0).toFixed?.(1) ?? aSummary.time_seconds?.mean ?? 0}s ± ${(aSummary.time_seconds?.stddev ?? 0).toFixed?.(1) ?? aSummary.time_seconds?.stddev ?? 0}s | ${(bSummary.time_seconds?.mean ?? 0).toFixed?.(1) ?? bSummary.time_seconds?.mean ?? 0}s ± ${(bSummary.time_seconds?.stddev ?? 0).toFixed?.(1) ?? bSummary.time_seconds?.stddev ?? 0}s | ${delta.time_seconds ?? "—"}s |`,
|
|
217
|
-
`| Tokens | ${Math.round(aSummary.tokens
|
|
218
|
+
`| Tokens | ${aSummary.tokens?.mean == null ? "—" : `${Math.round(aSummary.tokens.mean)} ± ${Math.round(aSummary.tokens.stddev)}`} | ${bSummary.tokens?.mean == null ? "—" : `${Math.round(bSummary.tokens.mean)} ± ${Math.round(bSummary.tokens.stddev)}`} | ${delta.tokens ?? "—"} |`,
|
|
218
219
|
];
|
|
219
220
|
if (Array.isArray(benchmark.notes) && benchmark.notes.length > 0) {
|
|
220
221
|
lines.push("", "## Notes", "");
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { writeFile } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { readJsonFile } from "./skill_metadata.mjs";
|
|
6
7
|
|
|
7
8
|
function escapeHtml(value) {
|
|
8
9
|
return String(value ?? "")
|
|
@@ -176,8 +177,7 @@ async function main(argv = process.argv) {
|
|
|
176
177
|
if (!inputPath) {
|
|
177
178
|
throw new Error("Usage: node generate_report.mjs <input.json> [-o output.html] [--skill-name name]");
|
|
178
179
|
}
|
|
179
|
-
const
|
|
180
|
-
const data = JSON.parse(raw);
|
|
180
|
+
const data = inputPath === "-" ? JSON.parse((await readStdin()).replace(/^\uFEFF/, "")) : await readJsonFile(inputPath);
|
|
181
181
|
const output = generateHtml(data, { skillName: args["skill-name"] ?? "", autoRefresh: false });
|
|
182
182
|
if (args.output || args.o) {
|
|
183
183
|
await writeFile(args.output || args.o, output);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { mkdir,
|
|
4
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { callPieText } from "./pie_runner.mjs";
|
|
7
|
-
import { parseSkillFile } from "./skill_metadata.mjs";
|
|
7
|
+
import { parseSkillFile, readJsonFile, validateFrontmatter } from "./skill_metadata.mjs";
|
|
8
8
|
|
|
9
9
|
export async function improveDescription({
|
|
10
10
|
skillName,
|
|
@@ -100,8 +100,11 @@ I'd encourage you to be creative and mix up the style in different iterations si
|
|
|
100
100
|
|
|
101
101
|
Please respond with only the new description text in <new_description> tags, nothing else.`;
|
|
102
102
|
|
|
103
|
-
const transcript = { iteration, prompt };
|
|
104
|
-
const
|
|
103
|
+
const transcript = { iteration, prompt, requested_model: model, actual_models: [] };
|
|
104
|
+
const onModelSelection = ({ actualModel }) => {
|
|
105
|
+
if (actualModel && !transcript.actual_models.includes(actualModel)) transcript.actual_models.push(actualModel);
|
|
106
|
+
};
|
|
107
|
+
const text = await callPieText(prompt, { model, onModelSelection });
|
|
105
108
|
transcript.response = text;
|
|
106
109
|
|
|
107
110
|
let description = parseDescription(text);
|
|
@@ -119,7 +122,7 @@ A previous attempt produced this description, which at ${description.length} cha
|
|
|
119
122
|
"${description}"
|
|
120
123
|
|
|
121
124
|
Rewrite it to be under 1024 characters while keeping the most important trigger words and intent coverage. Respond with only the new description in <new_description> tags.`;
|
|
122
|
-
const shortenText = await callPieText(shortenPrompt);
|
|
125
|
+
const shortenText = await callPieText(shortenPrompt, { model, onModelSelection });
|
|
123
126
|
const shortened = parseDescription(shortenText);
|
|
124
127
|
transcript.rewrite_prompt = shortenPrompt;
|
|
125
128
|
transcript.rewrite_response = shortenText;
|
|
@@ -133,7 +136,8 @@ Rewrite it to be under 1024 characters while keeping the most important trigger
|
|
|
133
136
|
await mkdir(logDir, { recursive: true });
|
|
134
137
|
await writeFile(path.join(logDir, `improve_iter_${iteration ?? "unknown"}.json`), JSON.stringify(transcript, null, 2));
|
|
135
138
|
}
|
|
136
|
-
|
|
139
|
+
const validation = validateFrontmatter({ name: skillName, description });
|
|
140
|
+
if (!validation.valid) throw new Error(`Improved description is invalid: ${validation.message}`);
|
|
137
141
|
return description;
|
|
138
142
|
}
|
|
139
143
|
|
|
@@ -145,8 +149,8 @@ function parseDescription(text) {
|
|
|
145
149
|
async function main(argv = process.argv) {
|
|
146
150
|
const args = parseArgs(argv.slice(2));
|
|
147
151
|
const skillPath = path.resolve(args["skill-path"]);
|
|
148
|
-
const evalResults =
|
|
149
|
-
const history = args.history ?
|
|
152
|
+
const evalResults = await readJsonFile(args["eval-results"]);
|
|
153
|
+
const history = args.history ? await readJsonFile(args.history) : [];
|
|
150
154
|
const { name, content } = await parseSkillFile(skillPath);
|
|
151
155
|
const currentDescription = evalResults.description;
|
|
152
156
|
|
|
@@ -81,6 +81,7 @@ export async function packageSkill(skillPath, outputDir) {
|
|
|
81
81
|
const files = await collectFiles(resolvedSkillPath, baseDir);
|
|
82
82
|
|
|
83
83
|
for (const file of files) {
|
|
84
|
+
if (path.resolve(file.fullPath) === archivePath) continue;
|
|
84
85
|
if (shouldExclude(file.arcname)) {
|
|
85
86
|
process.stdout.write(` Skipped: ${file.arcname}\n`);
|
|
86
87
|
continue;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import fs from "node:fs";
|
|
6
|
+
import os from "node:os";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
|
|
@@ -61,21 +62,29 @@ export function getPieCliPath() {
|
|
|
61
62
|
);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
export async function runPieJsonPrompt(prompt, { cwd = process.cwd(), timeout = 300_000, sessionId = null } = {}) {
|
|
65
|
+
export async function runPieJsonPrompt(prompt, { cwd = process.cwd(), timeout = 300_000, sessionId = null, model = null } = {}) {
|
|
66
|
+
if (model && (typeof model !== "string" || !/^[^/\s]+\/\S+$/.test(model))) {
|
|
67
|
+
throw new Error("Model must use profile/modelId format.");
|
|
68
|
+
}
|
|
65
69
|
return new Promise((resolve, reject) => {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
+
const id = sessionId || `skill-run-${uuidFragment()}`;
|
|
71
|
+
const turnId = `skill-turn-${uuidFragment()}`;
|
|
72
|
+
const args = [getPieCliPath(), "--json-events", "--event-detail", "full", "--session-id", id];
|
|
70
73
|
|
|
71
|
-
const child = spawn(
|
|
74
|
+
const child = spawn(process.execPath, args, {
|
|
72
75
|
cwd,
|
|
73
76
|
env: { ...process.env },
|
|
74
|
-
stdio: ["
|
|
77
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
78
|
+
windowsHide: true,
|
|
75
79
|
});
|
|
80
|
+
child.stdout.setEncoding("utf8");
|
|
81
|
+
child.stderr.setEncoding("utf8");
|
|
76
82
|
|
|
77
|
-
let
|
|
83
|
+
let buffer = "";
|
|
78
84
|
let stderr = "";
|
|
85
|
+
let text = "";
|
|
86
|
+
let response;
|
|
87
|
+
let turnSettled = false;
|
|
79
88
|
let settled = false;
|
|
80
89
|
|
|
81
90
|
const cleanup = () => {
|
|
@@ -89,55 +98,92 @@ export async function runPieJsonPrompt(prompt, { cwd = process.cwd(), timeout =
|
|
|
89
98
|
if (settled) return;
|
|
90
99
|
settled = true;
|
|
91
100
|
cleanup();
|
|
92
|
-
try {
|
|
93
|
-
child.kill("SIGKILL");
|
|
94
|
-
} catch {}
|
|
95
101
|
cb();
|
|
96
102
|
};
|
|
97
103
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
const terminate = () => {
|
|
105
|
+
child.kill();
|
|
106
|
+
child.stdin.destroy();
|
|
107
|
+
child.stdout.destroy();
|
|
108
|
+
child.stderr.destroy();
|
|
109
|
+
child.unref();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const consume = (line) => {
|
|
113
|
+
let event;
|
|
114
|
+
try { event = JSON.parse(line); } catch { return; }
|
|
115
|
+
if (event.turnId && event.turnId !== turnId) return;
|
|
116
|
+
if (event.type === "display.output" && event.stream === "terminal") text += event.chunk || "";
|
|
117
|
+
if (event.type === "turn.completed") response = { ok: true, text: event.summary ?? text };
|
|
118
|
+
if (event.type === "turn.failed" || event.type === "turn.cancelled") {
|
|
119
|
+
const error = event.error || event.reason || event.type;
|
|
120
|
+
response = { ok: false, error: typeof error === "string" ? error : error.message || JSON.stringify(error) };
|
|
110
121
|
}
|
|
111
|
-
|
|
122
|
+
if (event.type === "turn.settled") turnSettled = true;
|
|
112
123
|
};
|
|
113
124
|
|
|
114
125
|
const timer = setTimeout(() => {
|
|
126
|
+
terminate();
|
|
115
127
|
finish(() => reject(new Error(`pie prompt timed out after ${Math.round(timeout / 1000)}s`)));
|
|
116
128
|
}, timeout);
|
|
117
129
|
|
|
118
130
|
child.stdout.on("data", (chunk) => {
|
|
119
|
-
|
|
120
|
-
|
|
131
|
+
buffer += String(chunk);
|
|
132
|
+
let newline;
|
|
133
|
+
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
134
|
+
consume(buffer.slice(0, newline).trim());
|
|
135
|
+
buffer = buffer.slice(newline + 1);
|
|
136
|
+
}
|
|
121
137
|
});
|
|
122
138
|
child.stderr.on("data", (chunk) => {
|
|
123
|
-
stderr
|
|
139
|
+
stderr = (stderr + String(chunk)).slice(-8_000);
|
|
124
140
|
});
|
|
125
141
|
child.on("error", (error) => {
|
|
126
142
|
finish(() => reject(error));
|
|
127
143
|
});
|
|
128
144
|
child.on("close", (code) => {
|
|
129
|
-
if (
|
|
145
|
+
if (buffer.trim()) consume(buffer.trim());
|
|
146
|
+
if (code !== 0 || !turnSettled || !response) {
|
|
147
|
+
finish(() => reject(new Error(`pie prompt exited ${code} without a settled response\nstderr: ${stderr}`)));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const configuredDir = process.env.PIE_CODING_AGENT_DIR || path.join(os.homedir(), ".pie");
|
|
151
|
+
const configDir = configuredDir === "~" ? os.homedir()
|
|
152
|
+
: configuredDir.startsWith("~/") ? path.join(os.homedir(), configuredDir.slice(2)) : configuredDir;
|
|
153
|
+
let actualModel = null;
|
|
154
|
+
try {
|
|
155
|
+
const session = JSON.parse(fs.readFileSync(path.join(configDir, "sessions", `${id}.json`), "utf8"));
|
|
156
|
+
const metadata = session.metadata || {};
|
|
157
|
+
if (metadata.provider && metadata.modelId) actualModel = `${metadata.provider}/${metadata.modelId}`;
|
|
158
|
+
const messages = session.messages || (session.entries || []).map((entry) => entry.message).filter(Boolean);
|
|
159
|
+
const lastAssistant = messages.filter((message) => message.role === "assistant").at(-1);
|
|
160
|
+
if (response.ok && lastAssistant) {
|
|
161
|
+
response.text = typeof lastAssistant.content === "string" ? lastAssistant.content
|
|
162
|
+
: (lastAssistant.content || []).filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
163
|
+
}
|
|
164
|
+
} catch {}
|
|
165
|
+
if (model && response.ok && actualModel !== model) {
|
|
166
|
+
finish(() => reject(new Error(`Requested model ${model}, but session reported ${actualModel || "no model"}.`)));
|
|
130
167
|
return;
|
|
131
168
|
}
|
|
132
|
-
finish(() =>
|
|
169
|
+
finish(() => resolve({ ...response, sessionId: id, requestedModel: model, actualModel }));
|
|
133
170
|
});
|
|
171
|
+
child.stdin.end(`${JSON.stringify({
|
|
172
|
+
type: "turn.request",
|
|
173
|
+
sessionId: id,
|
|
174
|
+
turnId,
|
|
175
|
+
prompt,
|
|
176
|
+
cwd,
|
|
177
|
+
...(model ? { metadata: { model } } : {}),
|
|
178
|
+
})}\n`);
|
|
134
179
|
});
|
|
135
180
|
}
|
|
136
181
|
|
|
137
|
-
export async function callPieText(prompt, {
|
|
138
|
-
const response = await runPieJsonPrompt(prompt,
|
|
182
|
+
export async function callPieText(prompt, { onModelSelection, ...options } = {}) {
|
|
183
|
+
const response = await runPieJsonPrompt(prompt, options);
|
|
139
184
|
if (!response?.ok) {
|
|
140
185
|
throw new Error(response?.error || "Pie prompt failed");
|
|
141
186
|
}
|
|
187
|
+
onModelSelection?.({ requestedModel: response.requestedModel, actualModel: response.actualModel });
|
|
142
188
|
return String(response.text || "");
|
|
143
189
|
}
|