@skill-harness/cli 0.30.0 → 0.31.0
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/cli.js +72 -13
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { discover, resolveSkill, loadSpec, parseSpec, appendScenario, parseModelRef, runSkillModel, formatScorecard, readResults, regradeRun, lintSkill, failsGate, renderTemplateSpec, isTemplateSpec, renderDraftSpec, buildSuggestPrompt, parseSuggestDraft, rescoreRun, regateRun, specPathForRunDir, collectLift, collectReport, collectStability, boundaryCells, stabilityNote, PATH_LEGEND, restampSkill, computeCoverage, formatCoverage, HARNESS_VERSION, defaultJudge, recordedJudgeOrDefault, assertJudgeAllowed, assertNotDowngraded, downgradeWarning, isScoredMode, resolveArm, isFreeOfflineCommand, diagnoseRun, loadCalibration, runCalibration, } from "@skill-harness/core";
|
|
5
|
+
import { discover, resolveSkill, loadSpec, parseSpec, appendScenario, parseModelRef, runSkillModel, formatScorecard, readResults, regradeRun, lintSkill, failsGate, renderTemplateSpec, isTemplateSpec, renderDraftSpec, buildSuggestPrompt, parseSuggestDraft, rescoreRun, regateRun, specPathForRunDir, collectLift, collectReport, collectStability, boundaryCells, stabilityNote, PATH_LEGEND, restampSkill, computeCoverage, formatCoverage, HARNESS_VERSION, defaultJudge, recordedJudgeOrDefault, assertJudgeAllowed, assertNotDowngraded, downgradeWarning, isScoredMode, resolveArm, isFreeOfflineCommand, diagnoseRun, loadCalibration, runCalibration, planCalibration, forkSavedRun, withProcessScope, } from "@skill-harness/core";
|
|
6
6
|
import { getAdapter } from "@skill-harness/adapters";
|
|
7
7
|
import { serveReview } from "./serve.js";
|
|
8
8
|
import { cmdDecision } from "./decision.js";
|
|
@@ -11,7 +11,7 @@ const DEFAULT_MODEL = "fireworks:accounts/fireworks/models/deepseek-v4-pro";
|
|
|
11
11
|
// SKILL_HARNESS_JUDGE over a baked value — it was duplicated in three places
|
|
12
12
|
// before, and the pi extension's copy could disagree with this one.
|
|
13
13
|
const DEFAULT_SUGGEST_MODEL = "openai-codex:gpt-5.6-sol";
|
|
14
|
-
const REPEATABLE = new Set(["model", "turn", "check"]);
|
|
14
|
+
const REPEATABLE = new Set(["model", "turn", "check", "reuse"]);
|
|
15
15
|
function parseArgs(argv) {
|
|
16
16
|
const _ = [];
|
|
17
17
|
const flags = {};
|
|
@@ -65,6 +65,15 @@ export function flagBool(args, key) {
|
|
|
65
65
|
const v = args.flags[key];
|
|
66
66
|
return v === true || v === "true" || v === "1";
|
|
67
67
|
}
|
|
68
|
+
function selectedIds(args) {
|
|
69
|
+
const raw = flagStr(args, "only");
|
|
70
|
+
if (raw === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
const ids = raw.split(",").map(s => s.trim());
|
|
73
|
+
if (ids.some(id => !id) || new Set(ids).size !== ids.length)
|
|
74
|
+
throw new Error("--only requires a nonempty, unique comma-separated ID list");
|
|
75
|
+
return ids;
|
|
76
|
+
}
|
|
68
77
|
function resolveModels(args) {
|
|
69
78
|
const models = [...(args.multi.model ?? [])];
|
|
70
79
|
const file = flagStr(args, "models");
|
|
@@ -174,8 +183,7 @@ export async function cmdRun(args, adapterOverride) {
|
|
|
174
183
|
// not on PATH" for a plain flag typo, sending the reader off to fix an install
|
|
175
184
|
// that was never the problem. Free, offline validation first — it is also why
|
|
176
185
|
// CI, which has no `pi`, could not test this refusal at all.
|
|
177
|
-
const
|
|
178
|
-
const only = onlyRaw ? onlyRaw.split(",").map((x) => x.trim()).filter(Boolean) : undefined;
|
|
186
|
+
const only = selectedIds(args);
|
|
179
187
|
const harnessName = flagStr(args, "harness", "pi");
|
|
180
188
|
const adapter = adapterOverride ?? getAdapter(harnessName);
|
|
181
189
|
if (!(await adapter.available()))
|
|
@@ -254,15 +262,16 @@ export async function cmdRun(args, adapterOverride) {
|
|
|
254
262
|
process.exitCode = 1;
|
|
255
263
|
}
|
|
256
264
|
export async function cmdGrade(args, adapterOverride) {
|
|
257
|
-
const
|
|
265
|
+
const only = selectedIds(args);
|
|
266
|
+
let runDir = args._[0];
|
|
258
267
|
if (!runDir)
|
|
259
268
|
throw new Error("usage: skill-harness grade <run-dir> [--judge prov:model] [--suspect-only | --unparsed-only]");
|
|
260
269
|
if (!existsSync(runDir)) {
|
|
261
270
|
throw new Error(`run dir not found: ${resolve(runDir)} (relative paths resolve against the cwd)`);
|
|
262
271
|
}
|
|
263
272
|
// spec lives at <runDir>/../../../specification.yaml (results/<tag>/<ts> -> tests/)
|
|
264
|
-
const
|
|
265
|
-
const
|
|
273
|
+
const specPath = flagStr(args, "spec") ? resolve(flagStr(args, "spec")) : join(dirname(dirname(dirname(runDir))), "specification.yaml");
|
|
274
|
+
const testsDir = dirname(specPath);
|
|
266
275
|
const spec = loadSpec(specPath);
|
|
267
276
|
const prev = existsSync(join(runDir, "results.yaml")) ? readResults(runDir) : null;
|
|
268
277
|
// Re-judge with the run's RECORDED judge + harness (parity with /rejudge) —
|
|
@@ -293,9 +302,17 @@ export async function cmdGrade(args, adapterOverride) {
|
|
|
293
302
|
const stale = downgradeWarning(dirname(testsDir));
|
|
294
303
|
if (stale)
|
|
295
304
|
console.error(stale);
|
|
305
|
+
const output = flagStr(args, "out");
|
|
306
|
+
if (output !== undefined) {
|
|
307
|
+
if (!output.trim())
|
|
308
|
+
throw new Error("--out requires a new directory");
|
|
309
|
+
runDir = forkSavedRun(runDir, output);
|
|
310
|
+
console.log(`Reusing saved answers in ${runDir}; original run preserved.`);
|
|
311
|
+
}
|
|
296
312
|
const results = await regradeRun({
|
|
297
313
|
runDir, spec, adapter, judge, specDir: testsDir, now: nowIso,
|
|
298
314
|
onlySuspect: flagBool(args, "suspect-only"), onlyUnparsed,
|
|
315
|
+
only,
|
|
299
316
|
});
|
|
300
317
|
for (const s of results.scenarios) {
|
|
301
318
|
console.log(` ${s.id} → ${s.judge_verdict}: ${s.judge_reason}`);
|
|
@@ -734,11 +751,12 @@ export function help() {
|
|
|
734
751
|
|
|
735
752
|
run <skill|all> --skills <root> [--model prov:model ...] [--models file] [--only A1,D2]
|
|
736
753
|
[--mode red|green|force] [--judge prov:model] [--harness pi] [--label name] [--parallel N] [--reps N] [--pass-threshold T]
|
|
737
|
-
[--canary] green only:
|
|
754
|
+
[--canary] green only: one instructed-loading probe; autonomous selection stays untested
|
|
738
755
|
[--structured] request structured capture (Pi uses it by default for subject usage)
|
|
739
756
|
[--arm <name>] measure under a named arm from <skills-root>/tests/arms.yaml
|
|
740
757
|
(loads its extensions, seeds pi-daddy definitions, tags the run dir)
|
|
741
758
|
grade <run-dir> [--judge prov:model] [--suspect-only] re-grade saved transcripts (neutral judge)
|
|
759
|
+
[--only A1,A2] [--out NEW_DIR] [--spec path] preserve originals and judge selected saved answers
|
|
742
760
|
regrade <run-dir> --unparsed-only re-judge ERROR criterion reps with recorded judge
|
|
743
761
|
rescore <run-dir>... re-score saved reps vs current spec thresholds (${free("rescore")})
|
|
744
762
|
regate <run-dir>... [--judge prov:model] re-evaluate saved gates (no subject call; judges fail→pass reps)
|
|
@@ -749,7 +767,10 @@ export function help() {
|
|
|
749
767
|
init <skill> --skills <root> [--force] scaffold a commented template spec (${free("init")})
|
|
750
768
|
preflight <skill|all> --skills <root> [--specs <root>] eval checks (${free("preflight")})
|
|
751
769
|
calibrate <manifest.json> [--run --judge p:m --out NEW_DIR] validate or judge reviewed controls
|
|
770
|
+
[--plan --judge p:m] [--only c1,c2] [--reuse PRIOR_DIR ...] exact historical reuse, no new subject calls
|
|
752
771
|
diagnose <run-dir> --skills <root> [--specs <root>] inspect saved evidence (${free("diagnose")})
|
|
772
|
+
Paid commands accept --max-memory-mb N / --max-write-mb N for sampled Linux process limits.
|
|
773
|
+
run/grade/calibrate save bounded private resources-*.json; unsupported host metrics stay unknown.
|
|
753
774
|
In Pi: /skill-harness create <idea> guided skill + optional agent + eval creation
|
|
754
775
|
suggest <skill> --skills <root> [--model prov:model] [--force] LLM-draft a spec from SKILL.md (spends tokens)
|
|
755
776
|
list --skills <root> discovered skills + spec status (${free("list")})
|
|
@@ -766,7 +787,7 @@ export function help() {
|
|
|
766
787
|
defaults: model=${DEFAULT_MODEL} judge=${defaultJudge()} mode=green harness=pi
|
|
767
788
|
green and force are both scored; red is the unscored baseline. green delivery depends on the
|
|
768
789
|
harness version (pi >= 0.83.0 discloses only the skill's description and loads the body on demand),
|
|
769
|
-
|
|
790
|
+
--mode force supplies the skill body; --canary checks instructed loading separately from autonomous selection.
|
|
770
791
|
the judge default runs through Pi on your ChatGPT subscription (\`openai-codex\`), not a metered
|
|
771
792
|
API key. Set SKILL_HARNESS_JUDGE to change it for a repo or a shell; --judge wins over both.`;
|
|
772
793
|
}
|
|
@@ -797,25 +818,63 @@ export async function cmdCalibrate(args, adapterOverride) {
|
|
|
797
818
|
if (!args._[0])
|
|
798
819
|
throw new Error("usage: skill-harness calibrate <manifest.json> [--run --judge provider:model --out NEW_DIR]");
|
|
799
820
|
const calibration = loadCalibration(resolve(args._[0]));
|
|
821
|
+
const judgeToken = flagStr(args, "judge");
|
|
822
|
+
const only = selectedIds(args);
|
|
823
|
+
const reuse = (args.multi.reuse ?? []).map(p => { if (!p.trim())
|
|
824
|
+
throw new Error("--reuse requires a prior calibration directory"); return resolve(p); });
|
|
825
|
+
if (flagBool(args, "plan")) {
|
|
826
|
+
if (!judgeToken)
|
|
827
|
+
throw new Error("Calibration --plan needs an explicit --judge; it makes zero provider calls.");
|
|
828
|
+
const plan = planCalibration(calibration, { judge: parseModelRef(judgeToken), only, reuse });
|
|
829
|
+
console.log(JSON.stringify({ ...plan, providerCalls: 0 }, null, 2));
|
|
830
|
+
if (!plan.ready)
|
|
831
|
+
process.exitCode = 1;
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
800
834
|
if (!flagBool(args, "run")) {
|
|
801
|
-
console.log(JSON.stringify({ status: calibration.ready ? "validated" : "needs-review", purpose: "judge-calibration-only", providerCalls: 0, cases: calibration.controls.length, issues: calibration.issues }));
|
|
835
|
+
console.log(JSON.stringify({ status: calibration.ready ? "validated" : "needs-review", purpose: "judge-calibration-only", executionGates: "not-evaluated", providerCalls: 0, cases: calibration.controls.length, issues: calibration.issues }));
|
|
802
836
|
if (!calibration.ready)
|
|
803
837
|
process.exitCode = 1;
|
|
804
838
|
return;
|
|
805
839
|
}
|
|
806
|
-
const judgeToken = flagStr(args, "judge");
|
|
807
840
|
const output = flagStr(args, "out");
|
|
808
841
|
if (!judgeToken || !output)
|
|
809
842
|
throw new Error("Calibration --run needs an explicitly selected --judge and a new --out directory.");
|
|
810
843
|
const adapter = adapterOverride ?? getAdapter("pi");
|
|
811
844
|
if (!await adapter.available())
|
|
812
845
|
throw new Error("Pi is required for judge calibration. Configure the selected model in Pi first.");
|
|
813
|
-
const result = await runCalibration(calibration, { adapter, judge: parseModelRef(judgeToken), output: resolve(output), allowMeteredJudge: flagBool(args, "allow-metered-judge"), onProgress: p => console.log(`Judging control ${p.index + 1}/${p.total}: ${p.id}`) });
|
|
846
|
+
const result = await runCalibration(calibration, { adapter, judge: parseModelRef(judgeToken), only, reuse, output: resolve(output), allowMeteredJudge: flagBool(args, "allow-metered-judge"), onProgress: p => console.log(`Judging control ${p.index + 1}/${p.total}: ${p.id}`) });
|
|
814
847
|
console.log(JSON.stringify(result, null, 2));
|
|
815
848
|
if (result.status !== "matched")
|
|
816
849
|
process.exitCode = 1;
|
|
817
850
|
}
|
|
818
851
|
export async function main(argv) {
|
|
852
|
+
if (!["run", "grade", "regrade", "calibrate", "regate", "suggest"].includes(argv[0]))
|
|
853
|
+
return dispatch(argv);
|
|
854
|
+
const args = parseArgs(argv.slice(1));
|
|
855
|
+
const limit = (name) => {
|
|
856
|
+
const raw = flagStr(args, name);
|
|
857
|
+
if (raw === undefined)
|
|
858
|
+
return undefined;
|
|
859
|
+
const number = Number(raw);
|
|
860
|
+
if (!Number.isFinite(number) || number <= 0 || !Number.isSafeInteger(number * 1048576))
|
|
861
|
+
throw new Error(`--${name} requires a positive MiB value`);
|
|
862
|
+
return number * 1048576;
|
|
863
|
+
};
|
|
864
|
+
const controller = new AbortController();
|
|
865
|
+
const interrupt = () => { process.exitCode = 130; controller.abort(new Error("SIGINT")); };
|
|
866
|
+
const terminate = () => { process.exitCode = 143; controller.abort(new Error("SIGTERM")); };
|
|
867
|
+
process.once("SIGINT", interrupt);
|
|
868
|
+
process.once("SIGTERM", terminate);
|
|
869
|
+
try {
|
|
870
|
+
await withProcessScope({ signal: controller.signal, maxRssBytes: limit("max-memory-mb"), maxWriteBytes: limit("max-write-mb") }, () => dispatch(argv));
|
|
871
|
+
}
|
|
872
|
+
finally {
|
|
873
|
+
process.removeListener("SIGINT", interrupt);
|
|
874
|
+
process.removeListener("SIGTERM", terminate);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async function dispatch(argv) {
|
|
819
878
|
const cmd = argv[0];
|
|
820
879
|
if (cmd === "decision")
|
|
821
880
|
return cmdDecision(argv.slice(1));
|
|
@@ -867,7 +926,7 @@ export async function main(argv) {
|
|
|
867
926
|
if (!process.env.VITEST) {
|
|
868
927
|
main(process.argv.slice(2)).catch((e) => {
|
|
869
928
|
console.error(`error: ${e instanceof Error ? e.message : e}`);
|
|
870
|
-
process.exitCode
|
|
929
|
+
process.exitCode ||= 1;
|
|
871
930
|
});
|
|
872
931
|
}
|
|
873
932
|
//# sourceMappingURL=cli.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-harness/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "skill-harness CLI — run, grade, review, and lint agent-skill scenarios on the pi harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"prepack": "node ../../scripts/release-pack.mjs --guard"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@skill-harness/core": "0.
|
|
48
|
-
"@skill-harness/adapters": "0.
|
|
47
|
+
"@skill-harness/core": "0.31.0",
|
|
48
|
+
"@skill-harness/adapters": "0.31.0"
|
|
49
49
|
}
|
|
50
50
|
}
|