@skill-harness/cli 0.18.0 → 0.20.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 +9 -1
- package/dist/cli.js +20 -13
- package/dist/serve.js +21 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
The command surface for [skill-harness](https://github.com/mojomanyana/skill-harness) —
|
|
4
4
|
a test/optimize loop for agent skills: run spec'd scenarios on the `pi` harness,
|
|
5
5
|
LLM-judge each transcript, score against a ship bar, review, and re-run to
|
|
6
|
-
measure a `SKILL.md` edit.
|
|
6
|
+
measure a `SKILL.md` edit. Subject, judge, and spec-drafting model calls all run
|
|
7
|
+
through Pi.
|
|
7
8
|
|
|
8
9
|
Most users should install the `skill-harness` meta-package (`npm i -g
|
|
9
10
|
skill-harness`) instead of this package directly.
|
|
@@ -22,8 +23,15 @@ npm i -g @skill-harness/cli
|
|
|
22
23
|
| `skill-harness lint <skill\|all> --skills <root>` | validate specs/fixtures; CI gate |
|
|
23
24
|
| `skill-harness run <skill\|all> --skills <root> [--model p:m ...] [--judge p:m]` | run scenarios, grade, score |
|
|
24
25
|
| `skill-harness grade <run-dir> [--judge p:m]` | re-grade saved transcripts |
|
|
26
|
+
| `skill-harness rescore <run-dir>...` | re-apply current score thresholds offline |
|
|
27
|
+
| `skill-harness regate <run-dir>...` | re-evaluate saved objective gates |
|
|
28
|
+
| `skill-harness restamp <skill\|all> --skills <root> --from <ref>` | migrate matching model-visible digests |
|
|
29
|
+
| `skill-harness stability <skill\|all> --skills <root>` | report run-over-run verdict flips |
|
|
25
30
|
| `skill-harness review <skill> --skills <root> [--port N]` | serve the interactive review UI |
|
|
26
31
|
| `skill-harness add-test <skill> --skills <root> --id ID --title T ...` | scaffold a new scenario |
|
|
32
|
+
| `skill-harness init <skill> --skills <root>` | write an offline spec template |
|
|
33
|
+
| `skill-harness suggest <skill> --skills <root>` | draft a spec through Pi |
|
|
34
|
+
| `skill-harness coverage <skill\|all> --skills <root>` | report declared instruction coverage |
|
|
27
35
|
|
|
28
36
|
## More
|
|
29
37
|
|
package/dist/cli.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, resolve, relative } 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, collectStability, boundaryCells, stabilityNote, PATH_LEGEND, restampSkill, computeCoverage, formatCoverage, HARNESS_VERSION, defaultJudge, 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, collectStability, boundaryCells, stabilityNote, PATH_LEGEND, restampSkill, computeCoverage, formatCoverage, HARNESS_VERSION, defaultJudge, recordedJudgeOrDefault, assertJudgeAllowed, assertNotDowngraded, downgradeWarning, isScoredMode, resolveArm, isFreeOfflineCommand, } from "@skill-harness/core";
|
|
6
6
|
import { getAdapter } from "@skill-harness/adapters";
|
|
7
7
|
import { serveReview } from "./serve.js";
|
|
8
8
|
const DEFAULT_MODEL = "fireworks:accounts/fireworks/models/deepseek-v4-pro";
|
|
9
9
|
// The judge default lives in core (`defaultJudge()`), which resolves
|
|
10
10
|
// SKILL_HARNESS_JUDGE over a baked value — it was duplicated in three places
|
|
11
11
|
// before, and the pi extension's copy could disagree with this one.
|
|
12
|
-
const DEFAULT_SUGGEST_MODEL = "
|
|
12
|
+
const DEFAULT_SUGGEST_MODEL = "openai-codex:gpt-5.6-sol";
|
|
13
13
|
const REPEATABLE = new Set(["model", "turn", "check"]);
|
|
14
14
|
function parseArgs(argv) {
|
|
15
15
|
const _ = [];
|
|
@@ -236,14 +236,17 @@ export async function cmdGrade(args, adapterOverride) {
|
|
|
236
236
|
const spec = loadSpec(specPath);
|
|
237
237
|
const prev = existsSync(join(runDir, "results.yaml")) ? readResults(runDir) : null;
|
|
238
238
|
// Re-judge with the run's RECORDED judge + harness (parity with /rejudge) —
|
|
239
|
-
// an explicit --judge flag still wins;
|
|
240
|
-
// the
|
|
239
|
+
// an explicit --judge flag still wins; absent or retired recorded judges use
|
|
240
|
+
// the current Pi default.
|
|
241
241
|
const judgeFlag = flagStr(args, "judge");
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
242
|
+
const resolvedRecordedJudge = judgeFlag ? undefined : recordedJudgeOrDefault(prev?.judge);
|
|
243
|
+
const judge = judgeFlag ? parseModelRef(judgeFlag) : resolvedRecordedJudge.judge;
|
|
244
|
+
if (resolvedRecordedJudge?.migratedFrom) {
|
|
245
|
+
console.error(`skill-harness: recorded judge ${resolvedRecordedJudge.migratedFrom.provider}:${resolvedRecordedJudge.migratedFrom.model} was removed; re-judging through Pi with ${judge.provider}:${judge.model}`);
|
|
246
|
+
}
|
|
247
|
+
// A regrade normally reuses the judge the run RECORDED, so a run that names a
|
|
248
|
+
// metered judge bills on every later regrade with no flag typed anywhere. It is the one path
|
|
249
|
+
// where the cost decision was made by a file, not a person.
|
|
247
250
|
assertJudgeAllowed(judge, {
|
|
248
251
|
source: judgeFlag ? "--judge" : prev?.judge ? "the run's recorded judge" : "the default judge",
|
|
249
252
|
allowMetered: flagBool(args, "allow-metered-judge"),
|
|
@@ -312,7 +315,11 @@ export async function cmdRegate(args, adapterOverride) {
|
|
|
312
315
|
const specPath = specPathForRunDir(runDir);
|
|
313
316
|
const spec = loadSpec(specPath);
|
|
314
317
|
const prev = readResults(runDir);
|
|
315
|
-
const
|
|
318
|
+
const resolvedRecordedJudge = judgeFlag ? undefined : recordedJudgeOrDefault(prev.judge);
|
|
319
|
+
const judge = judgeFlag ? parseModelRef(judgeFlag) : resolvedRecordedJudge.judge;
|
|
320
|
+
if (resolvedRecordedJudge?.migratedFrom) {
|
|
321
|
+
console.error(`skill-harness: recorded judge ${resolvedRecordedJudge.migratedFrom.provider}:${resolvedRecordedJudge.migratedFrom.model} was removed; re-judging through Pi with ${judge.provider}:${judge.model}`);
|
|
322
|
+
}
|
|
316
323
|
assertJudgeAllowed(judge, {
|
|
317
324
|
source: judgeFlag ? "--judge" : "the run's recorded judge",
|
|
318
325
|
allowMetered: flagBool(args, "allow-metered-judge"),
|
|
@@ -692,7 +699,7 @@ export function help() {
|
|
|
692
699
|
run <skill|all> --skills <root> [--model prov:model ...] [--models file] [--only A1,D2]
|
|
693
700
|
[--mode red|green|force] [--judge prov:model] [--harness pi] [--label name] [--parallel N] [--reps N] [--pass-threshold T]
|
|
694
701
|
[--canary] green only: spend ONE probe proving the skill reached the model, and abort the run if it did not
|
|
695
|
-
[--structured]
|
|
702
|
+
[--structured] request structured capture (Pi uses it by default for subject usage)
|
|
696
703
|
[--arm <name>] measure under a named arm from <skills-root>/tests/arms.yaml
|
|
697
704
|
(loads its extensions, seeds pi-daddy definitions, tags the run dir)
|
|
698
705
|
grade <run-dir> [--judge prov:model] [--suspect-only] re-grade saved transcripts (neutral judge)
|
|
@@ -714,8 +721,8 @@ defaults: model=${DEFAULT_MODEL} judge=${defaultJudge()} mode=green harness=p
|
|
|
714
721
|
green and force are both scored; red is the unscored baseline. green delivery depends on the
|
|
715
722
|
harness version (pi >= 0.83.0 discloses only the skill's description and loads the body on demand),
|
|
716
723
|
so --mode force is the delivery that cannot silently degrade — and --canary proves green per run.
|
|
717
|
-
the judge default
|
|
718
|
-
|
|
724
|
+
the judge default runs through Pi on your ChatGPT subscription (\`openai-codex\`), not a metered
|
|
725
|
+
API key. Set SKILL_HARNESS_JUDGE to change it for a repo or a shell; --judge wins over both.`;
|
|
719
726
|
}
|
|
720
727
|
export async function main(argv) {
|
|
721
728
|
const cmd = argv[0];
|
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, } 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, } 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/. */
|
|
@@ -126,6 +126,17 @@ export async function serveReview(opts) {
|
|
|
126
126
|
res.end(JSON.stringify({ ok: false, error: `scenario ${body.scenarioId} is ${delivery?.status ?? "ERROR"}: delivery-gated evidence cannot be re-judged` }));
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
|
+
const resolvedRecordedJudge = recordedJudgeOrDefault(results.judge);
|
|
130
|
+
try {
|
|
131
|
+
assertJudgeAllowed(resolvedRecordedJudge.judge, {
|
|
132
|
+
source: resolvedRecordedJudge.migratedFrom ? "the current default judge" : "the run's recorded judge",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
137
|
+
res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
129
140
|
const adapter = opts.adapter ?? getAdapter(results.harness);
|
|
130
141
|
if (!(await adapter.available())) {
|
|
131
142
|
res.writeHead(400, { "content-type": "application/json" });
|
|
@@ -135,7 +146,7 @@ export async function serveReview(opts) {
|
|
|
135
146
|
const threshold = effectiveThreshold(prev, scenario);
|
|
136
147
|
try {
|
|
137
148
|
const rr = await regradeScenario({
|
|
138
|
-
runDir: column.runDir, spec, scenario, adapter, judge:
|
|
149
|
+
runDir: column.runDir, spec, scenario, adapter, judge: resolvedRecordedJudge.judge,
|
|
139
150
|
specDir: dirname(specPath), threshold, mode: results.mode,
|
|
140
151
|
expectedReps: prev.reps ?? 1,
|
|
141
152
|
});
|
|
@@ -147,7 +158,7 @@ export async function serveReview(opts) {
|
|
|
147
158
|
});
|
|
148
159
|
const written = writeResults(column.runDir, {
|
|
149
160
|
schema: results.schema, subject_invocations: results.subject_invocations,
|
|
150
|
-
skill: results.skill, harness: results.harness, model: results.model, judge:
|
|
161
|
+
skill: results.skill, harness: results.harness, model: results.model, judge: resolvedRecordedJudge.judge,
|
|
151
162
|
timestamp: results.timestamp, label: results.label, mode: results.mode, scenarios: merged,
|
|
152
163
|
partial: results.partial,
|
|
153
164
|
// Provenance survives a UI re-judge, same as it does through `grade`.
|
|
@@ -169,7 +180,13 @@ export async function serveReview(opts) {
|
|
|
169
180
|
const g = written.effective_grade;
|
|
170
181
|
appendJournal(column.runDir, { event: "score", ts: new Date().toISOString(), passed: g.passed, total: g.total, pct: g.pct, letter: g.letter, ship: g.ship, note: g.note });
|
|
171
182
|
res.writeHead(200, { "content-type": "application/json" });
|
|
172
|
-
res.end(JSON.stringify({
|
|
183
|
+
res.end(JSON.stringify({
|
|
184
|
+
ok: true,
|
|
185
|
+
grade: g,
|
|
186
|
+
judgeMigration: resolvedRecordedJudge.migratedFrom
|
|
187
|
+
? `${resolvedRecordedJudge.migratedFrom.provider}:${resolvedRecordedJudge.migratedFrom.model} → ${resolvedRecordedJudge.judge.provider}:${resolvedRecordedJudge.judge.model}`
|
|
188
|
+
: undefined,
|
|
189
|
+
}));
|
|
173
190
|
}
|
|
174
191
|
catch (e) {
|
|
175
192
|
// regradeScenario (or the write/journal that follows) failed — surface the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-harness/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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.20.0",
|
|
48
|
+
"@skill-harness/adapters": "0.20.0"
|
|
49
49
|
}
|
|
50
50
|
}
|