@skill-harness/cli 0.29.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/README.md +19 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +123 -10
- package/dist/serve.js +9 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ through Pi.
|
|
|
9
9
|
Most users should install the `skill-harness` meta-package (`npm i -g
|
|
10
10
|
skill-harness`) instead of this package directly.
|
|
11
11
|
|
|
12
|
-
Fresh subject execution
|
|
12
|
+
Fresh subject execution supports **Pi 1.0.4 and 1.1.0**. Other or unavailable
|
|
13
13
|
versions produce execution ERROR before a subject call. Retained results remain
|
|
14
14
|
readable; the historical Pi 0.83.0 raw parser is not current qualification.
|
|
15
15
|
|
|
@@ -19,12 +19,30 @@ readable; the historical Pi 0.83.0 raw parser is not current qualification.
|
|
|
19
19
|
npm i -g @skill-harness/cli
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
## Create a skill in Pi
|
|
23
|
+
|
|
24
|
+
Install the `skill-harness` Pi package, select a model, then use
|
|
25
|
+
`/skill-harness create <your idea>`. Authoring runs in Pi and asks one necessary
|
|
26
|
+
question at a time; the standalone CLI does not substitute a direct model API.
|
|
27
|
+
The workflow previews a skill, optional pi-daddy wrapper, and evals in the same
|
|
28
|
+
or a separate repository before applying files. Generated skills need only Pi;
|
|
29
|
+
the wrapper is optional and principal-pi-skills is not required.
|
|
30
|
+
|
|
31
|
+
The CLI supports the subsequent offline preflight, reviewed judge calibration,
|
|
32
|
+
scenario execution and saved-result diagnosis. See
|
|
33
|
+
[EVAL-AUTHORING.md](https://github.com/mojomanyana/skill-harness/blob/main/docs/EVAL-AUTHORING.md)
|
|
34
|
+
for the complete workflow and its evidence limits.
|
|
35
|
+
|
|
22
36
|
## Commands
|
|
23
37
|
|
|
24
38
|
| command | does |
|
|
25
39
|
|---|---|
|
|
26
40
|
| `skill-harness list --skills <root>` | discovered skills + spec status |
|
|
27
41
|
| `skill-harness lint <skill\|all> --skills <root>` | validate specs/fixtures; CI gate |
|
|
42
|
+
| `skill-harness preflight <skill\|all> --skills <root> [--specs <root>]` | inspect eval configuration and discoverability offline |
|
|
43
|
+
| `skill-harness calibrate <manifest.json>` | validate selected calibration evidence offline |
|
|
44
|
+
| `skill-harness calibrate <manifest.json> --run --judge p:m --out NEW_DIR` | judge reviewed controls through Pi; preserve a new result directory |
|
|
45
|
+
| `skill-harness diagnose <run-dir> --skills <root> [--specs <root>]` | inspect saved evidence without changing historical scores |
|
|
28
46
|
| `skill-harness run <skill\|all> --skills <root> [--model p:m ...] [--judge p:m]` | run scenarios, grade, score |
|
|
29
47
|
| `skill-harness grade <run-dir> [--judge p:m]` | re-grade saved transcripts |
|
|
30
48
|
| `skill-harness rescore <run-dir>...` | re-apply current score thresholds offline |
|
package/dist/cli.d.ts
CHANGED
|
@@ -51,4 +51,7 @@ export declare function cmdLint(args: Args): Promise<void>;
|
|
|
51
51
|
* prints none.
|
|
52
52
|
*/
|
|
53
53
|
export declare function help(): string;
|
|
54
|
+
export declare function cmdPreflight(args: Args): Promise<void>;
|
|
55
|
+
export declare function cmdDiagnose(args: Args): Promise<void>;
|
|
56
|
+
export declare function cmdCalibrate(args: Args, adapterOverride?: HarnessAdapter): Promise<void>;
|
|
54
57
|
export declare function main(argv: string[]): Promise<void>;
|
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, } 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)
|
|
@@ -747,6 +765,13 @@ export function help() {
|
|
|
747
765
|
review <skill> --skills <root> [--port N] serve the interactive review UI
|
|
748
766
|
add-test <skill> --skills <root> --id ID --title T --turn ... --check ... [--critical] [--mode seeded --fixture path]
|
|
749
767
|
init <skill> --skills <root> [--force] scaffold a commented template spec (${free("init")})
|
|
768
|
+
preflight <skill|all> --skills <root> [--specs <root>] eval checks (${free("preflight")})
|
|
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
|
|
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.
|
|
774
|
+
In Pi: /skill-harness create <idea> guided skill + optional agent + eval creation
|
|
750
775
|
suggest <skill> --skills <root> [--model prov:model] [--force] LLM-draft a spec from SKILL.md (spends tokens)
|
|
751
776
|
list --skills <root> discovered skills + spec status (${free("list")})
|
|
752
777
|
lint <skill|all> --skills <root> validate specs/fixtures + results-consistency (${free("lint")}; CI gate; exits non-zero on findings)
|
|
@@ -762,17 +787,105 @@ export function help() {
|
|
|
762
787
|
defaults: model=${DEFAULT_MODEL} judge=${defaultJudge()} mode=green harness=pi
|
|
763
788
|
green and force are both scored; red is the unscored baseline. green delivery depends on the
|
|
764
789
|
harness version (pi >= 0.83.0 discloses only the skill's description and loads the body on demand),
|
|
765
|
-
|
|
790
|
+
--mode force supplies the skill body; --canary checks instructed loading separately from autonomous selection.
|
|
766
791
|
the judge default runs through Pi on your ChatGPT subscription (\`openai-codex\`), not a metered
|
|
767
792
|
API key. Set SKILL_HARNESS_JUDGE to change it for a repo or a shell; --judge wins over both.`;
|
|
768
793
|
}
|
|
794
|
+
export async function cmdPreflight(args) {
|
|
795
|
+
const { skills, specs } = roots(args);
|
|
796
|
+
const target = args._[0];
|
|
797
|
+
if (!target)
|
|
798
|
+
throw new Error("usage: skill-harness preflight <skill|all> --skills <root> [--specs <root>]");
|
|
799
|
+
const selected = target === "all" ? discoverUsable(skills, specs).filter(s => s.hasSpec) : [resolveSkill(skills, target, specs)];
|
|
800
|
+
if (!selected.length)
|
|
801
|
+
throw new Error("No specifications found; in Pi use /skill-harness create to author a skill and its evals.");
|
|
802
|
+
const findings = selected.flatMap(s => lintSkill(s.dir, s.testsDir));
|
|
803
|
+
const errors = findings.filter(failsGate);
|
|
804
|
+
console.log(JSON.stringify({ status: errors.length ? "blocked" : "checked", providerCalls: 0, semanticValidity: "requires-reviewed-controls", findings }, null, 2));
|
|
805
|
+
if (errors.length)
|
|
806
|
+
process.exitCode = 1;
|
|
807
|
+
}
|
|
808
|
+
export async function cmdDiagnose(args) {
|
|
809
|
+
if (!args._[0])
|
|
810
|
+
throw new Error("usage: skill-harness diagnose <run-dir> --skills <root> [--specs <root>]");
|
|
811
|
+
const runDir = resolve(args._[0]);
|
|
812
|
+
const recorded = readResults(runDir);
|
|
813
|
+
const { skills, specs } = roots(args);
|
|
814
|
+
const selected = resolveSkill(skills, recorded.skill, specs);
|
|
815
|
+
console.log(JSON.stringify(diagnoseRun(runDir, selected.dir, selected.specPath), null, 2));
|
|
816
|
+
}
|
|
817
|
+
export async function cmdCalibrate(args, adapterOverride) {
|
|
818
|
+
if (!args._[0])
|
|
819
|
+
throw new Error("usage: skill-harness calibrate <manifest.json> [--run --judge provider:model --out NEW_DIR]");
|
|
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
|
+
}
|
|
834
|
+
if (!flagBool(args, "run")) {
|
|
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 }));
|
|
836
|
+
if (!calibration.ready)
|
|
837
|
+
process.exitCode = 1;
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const output = flagStr(args, "out");
|
|
841
|
+
if (!judgeToken || !output)
|
|
842
|
+
throw new Error("Calibration --run needs an explicitly selected --judge and a new --out directory.");
|
|
843
|
+
const adapter = adapterOverride ?? getAdapter("pi");
|
|
844
|
+
if (!await adapter.available())
|
|
845
|
+
throw new Error("Pi is required for judge calibration. Configure the selected model in Pi first.");
|
|
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}`) });
|
|
847
|
+
console.log(JSON.stringify(result, null, 2));
|
|
848
|
+
if (result.status !== "matched")
|
|
849
|
+
process.exitCode = 1;
|
|
850
|
+
}
|
|
769
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) {
|
|
770
878
|
const cmd = argv[0];
|
|
771
879
|
if (cmd === "decision")
|
|
772
880
|
return cmdDecision(argv.slice(1));
|
|
773
881
|
const args = parseArgs(argv.slice(1));
|
|
774
882
|
assertNoRetiredFlags(cmd, args);
|
|
775
883
|
switch (cmd) {
|
|
884
|
+
case "create":
|
|
885
|
+
case "author": throw new Error("Skill creation uses your current Pi model. Start Pi with skill-harness installed, then use /skill-harness create followed by your idea; it asks one necessary question at a time.");
|
|
886
|
+
case "preflight": return cmdPreflight(args);
|
|
887
|
+
case "diagnose": return cmdDiagnose(args);
|
|
888
|
+
case "calibrate": return cmdCalibrate(args);
|
|
776
889
|
case "run": return cmdRun(args);
|
|
777
890
|
case "grade":
|
|
778
891
|
case "regrade": return cmdGrade(args);
|
|
@@ -813,7 +926,7 @@ export async function main(argv) {
|
|
|
813
926
|
if (!process.env.VITEST) {
|
|
814
927
|
main(process.argv.slice(2)).catch((e) => {
|
|
815
928
|
console.error(`error: ${e instanceof Error ? e.message : e}`);
|
|
816
|
-
process.exitCode
|
|
929
|
+
process.exitCode ||= 1;
|
|
817
930
|
});
|
|
818
931
|
}
|
|
819
932
|
//# sourceMappingURL=cli.js.map
|
package/dist/serve.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFileSync, existsSync } from "node:fs";
|
|
|
3
3
|
import { join, dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { collectReport, renderReport, collectTrends, readResults, writeResults, applyOverride, preserveTranscript, findTranscriptFiles, ensureResultsGitignore, appendJournal, loadSpec, regradeScenario, refreshRubricHashes, findJudgeRawFiles, effectiveThreshold, scoreContextFor, isScoredMode, rebuildScenarioResult, mergeScenarioMetrics, carryRepObjectives, envFlag, recordedJudgeOrDefault, assertJudgeAllowed, } from "@skill-harness/core";
|
|
6
|
+
import { collectReport, renderReport, collectTrends, readResults, writeResults, applyOverride, preserveTranscript, findTranscriptFiles, ensureResultsGitignore, appendJournal, loadSpec, regradeScenario, refreshRubricHashes, findJudgeRawFiles, effectiveThreshold, scoreContextFor, isScoredMode, rebuildScenarioResult, mergeScenarioMetrics, carryRepObjectives, envFlag, recordedJudgeOrDefault, assertJudgeAllowed, parseModelRef, resolveJudgeReferences, } from "@skill-harness/core";
|
|
7
7
|
import { getAdapter } from "@skill-harness/adapters";
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
/** Locate assets/report.template.html relative to dist/ or src/. */
|
|
@@ -145,10 +145,16 @@ export async function serveReview(opts) {
|
|
|
145
145
|
}
|
|
146
146
|
const threshold = effectiveThreshold(prev, scenario);
|
|
147
147
|
try {
|
|
148
|
+
// The prompt and refreshed rubric must name the same selected bytes,
|
|
149
|
+
// even when a reference is edited while a multi-repetition judge runs.
|
|
150
|
+
const specDir = dirname(specPath);
|
|
151
|
+
const references = resolveJudgeReferences(scenario, specDir);
|
|
148
152
|
const rr = await regradeScenario({
|
|
149
153
|
runDir: column.runDir, spec, scenario, adapter, judge: resolvedRecordedJudge.judge,
|
|
150
|
-
specDir
|
|
154
|
+
specDir, threshold, mode: results.mode, references,
|
|
151
155
|
expectedReps: prev.reps ?? 1,
|
|
156
|
+
prior: prev, piVersion: results.harness_cli_version,
|
|
157
|
+
...(results.model.includes(":") ? { subject: parseModelRef(results.model) } : {}),
|
|
152
158
|
});
|
|
153
159
|
const merged = results.scenarios.map((s) => {
|
|
154
160
|
if (s.id !== body.scenarioId)
|
|
@@ -174,7 +180,7 @@ export async function serveReview(opts) {
|
|
|
174
180
|
// retired the staleness gate for any run re-judged from the UI. Carried,
|
|
175
181
|
// with the one `rubric:` key this re-judge actually applied refreshed —
|
|
176
182
|
// the same doctrine `grade` follows (see refreshRubricHashes).
|
|
177
|
-
source_hashes: refreshRubricHashes(results.source_hashes, spec, [body.scenarioId]),
|
|
183
|
+
source_hashes: refreshRubricHashes(results.source_hashes, spec, [body.scenarioId], specDir, { [body.scenarioId]: references }),
|
|
178
184
|
source_hash_roots: results.source_hash_roots,
|
|
179
185
|
}, scoreContextFor(results, spec));
|
|
180
186
|
ensureResultsGitignore(join(opts.skillDir, "tests", "results"));
|
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
|
}
|