@skill-harness/cli 0.32.4 → 0.33.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/assets/report.template.html +3 -2
- package/dist/cli.js +22 -12
- package/dist/diagnose-format.js +5 -0
- package/package.json +48 -48
|
@@ -177,6 +177,7 @@ function render() {
|
|
|
177
177
|
const errorNote = g.errors > 0 ? ` — ${g.errors} infrastructure error${g.errors === 1 ? "" : "s"}` : "";
|
|
178
178
|
gradeHtml = `${g.letter} (${g.pct}%) · ${g.passed}/${g.total}${suspectNote}${errorNote} ${badge}`;
|
|
179
179
|
}
|
|
180
|
+
const summaryHtml = col.summaryLines ? `<details><summary>Evidence, profiles and usage</summary><div class="meta">${col.summaryLines.map(line => escapeHtml(line)).join("<br>")}</div></details>` : "";
|
|
180
181
|
const m = col.metrics;
|
|
181
182
|
const metricHtml = m && m.total_reps > 0
|
|
182
183
|
? `<div class='grade' title='subject metrics reported by ${m.subject_metrics_reps}/${m.total_reps} reps'>` +
|
|
@@ -200,7 +201,7 @@ function render() {
|
|
|
200
201
|
: `lift 0 · no effect`;
|
|
201
202
|
const inc = ls.inconclusive > 0 ? ` · ${ls.inconclusive} inconclusive` : "";
|
|
202
203
|
const part = col.lift.partial ? " · partial" : "";
|
|
203
|
-
html += `<th><div>${escapeHtml(col.label)}</div><div class='grade'>${gradeHtml}</div>${metricHtml}` +
|
|
204
|
+
html += `<th><div>${escapeHtml(col.label)}</div><div class='grade'>${gradeHtml}</div>${metricHtml}${summaryHtml}` +
|
|
204
205
|
`<div class='lift ${cls}' title='vs red baseline ${escapeHtml(col.lift.redTimestamp)} — ${ls.kept} passed without the skill too'>${body}${inc}${part}</div></th>`;
|
|
205
206
|
return;
|
|
206
207
|
}
|
|
@@ -210,7 +211,7 @@ function render() {
|
|
|
210
211
|
if (none) {
|
|
211
212
|
liftHtml = `<div class='lift none' title='${escapeHtml(none.title)}'>${escapeHtml(none.text)}</div>`;
|
|
212
213
|
}
|
|
213
|
-
html += `<th><div>${escapeHtml(col.label)}</div><div class='grade'>${gradeHtml}</div>${metricHtml}${liftHtml}</th>`;
|
|
214
|
+
html += `<th><div>${escapeHtml(col.label)}</div><div class='grade'>${gradeHtml}</div>${metricHtml}${summaryHtml}${liftHtml}</th>`;
|
|
214
215
|
});
|
|
215
216
|
html += "</tr></thead><tbody>";
|
|
216
217
|
for (const scn of DATA.scenarios) {
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { withThinking, modelProfileLabel } from "@skill-harness/core";
|
|
2
3
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
3
4
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
5
|
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, planCalibration, forkSavedRun, withProcessScope, } from "@skill-harness/core";
|
|
6
|
+
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, requestedSubject, loadCalibration, runCalibration, planCalibration, forkSavedRun, withProcessScope, } from "@skill-harness/core";
|
|
6
7
|
import { getAdapter } from "@skill-harness/adapters";
|
|
7
8
|
import { serveReview } from "./serve.js";
|
|
8
9
|
import { cmdDecision } from "./decision.js";
|
|
@@ -173,7 +174,7 @@ export async function cmdRun(args, adapterOverride) {
|
|
|
173
174
|
// any subject tokens are spent: a refusal that arrives after the model has been
|
|
174
175
|
// paid for is a worse version of the problem it exists to prevent.
|
|
175
176
|
const judgeFlagRun = flagStr(args, "judge");
|
|
176
|
-
const judge = parseModelRef(judgeFlagRun ?? defaultJudge());
|
|
177
|
+
const judge = withThinking(parseModelRef(judgeFlagRun ?? defaultJudge()), flagStr(args, "judge-thinking"));
|
|
177
178
|
assertJudgeAllowed(judge, {
|
|
178
179
|
source: judgeFlagRun ? "--judge" : "the default judge (SKILL_HARNESS_JUDGE or the baked value)",
|
|
179
180
|
allowMetered: flagBool(args, "allow-metered-judge"),
|
|
@@ -185,6 +186,7 @@ export async function cmdRun(args, adapterOverride) {
|
|
|
185
186
|
// that was never the problem. Free, offline validation first — it is also why
|
|
186
187
|
// CI, which has no `pi`, could not test this refusal at all.
|
|
187
188
|
const only = selectedIds(args);
|
|
189
|
+
const models = resolveModels(args).map(token => ({ token, model: withThinking(parseModelRef(token), flagStr(args, "thinking")) }));
|
|
188
190
|
const harnessName = flagStr(args, "harness", "pi");
|
|
189
191
|
const adapter = adapterOverride ?? getAdapter(harnessName);
|
|
190
192
|
if (!(await adapter.available()))
|
|
@@ -195,7 +197,6 @@ export async function cmdRun(args, adapterOverride) {
|
|
|
195
197
|
const label = flagStr(args, "label") || null;
|
|
196
198
|
const parallel = Math.max(1, Number(flagStr(args, "parallel", "1")) || 1);
|
|
197
199
|
const { reps, passThreshold } = parseRunTuning(args);
|
|
198
|
-
const modelTokens = resolveModels(args);
|
|
199
200
|
// Resolved ONCE, before the skill loop: a typo'd arm name fails before any
|
|
200
201
|
// token is spent on any skill.
|
|
201
202
|
const armName = flagStr(args, "arm") || null;
|
|
@@ -213,12 +214,11 @@ export async function cmdRun(args, adapterOverride) {
|
|
|
213
214
|
// that look comparable and are not. Checked per skill, before its first token.
|
|
214
215
|
assertNotDowngraded(dirname(skill.testsDir), "run");
|
|
215
216
|
const spec = loadSpec(skill.specPath);
|
|
216
|
-
for (const token of
|
|
217
|
-
const model = parseModelRef(token);
|
|
217
|
+
for (const { token, model } of models) {
|
|
218
218
|
// The version is on the banner because a stale global install is otherwise
|
|
219
219
|
// invisible: a 0.1.0 binary grades a 0.3.x corpus, produces plausible
|
|
220
220
|
// numbers, and nothing on screen says which tool made them.
|
|
221
|
-
console.log(`\n▶ ${spec.skill} · ${harnessName}:${token} · mode=${mode} ·
|
|
221
|
+
console.log(`\n▶ ${spec.skill} · ${harnessName}:${token} · mode=${mode} · subject=${modelProfileLabel(model)} · judge=${modelProfileLabel(judge)} · skill-harness ${HARNESS_VERSION}`);
|
|
222
222
|
const summary = await runSkillModel({
|
|
223
223
|
spec,
|
|
224
224
|
skillDir: skill.dir,
|
|
@@ -280,10 +280,10 @@ export async function cmdGrade(args, adapterOverride) {
|
|
|
280
280
|
// the current Pi default.
|
|
281
281
|
const judgeFlag = flagStr(args, "judge");
|
|
282
282
|
const onlyUnparsed = flagBool(args, "unparsed-only");
|
|
283
|
-
if (onlyUnparsed && judgeFlag !== undefined)
|
|
283
|
+
if (onlyUnparsed && (judgeFlag !== undefined || flagStr(args, "judge-thinking") !== undefined))
|
|
284
284
|
throw new Error("--unparsed-only uses the recorded judge; --judge cannot be combined with it");
|
|
285
285
|
const resolvedRecordedJudge = judgeFlag ? undefined : recordedJudgeOrDefault(prev?.judge);
|
|
286
|
-
const judge = judgeFlag ? parseModelRef(judgeFlag) : resolvedRecordedJudge.judge;
|
|
286
|
+
const judge = withThinking(judgeFlag ? parseModelRef(judgeFlag) : resolvedRecordedJudge.judge, flagStr(args, "judge-thinking"));
|
|
287
287
|
if (onlyUnparsed && resolvedRecordedJudge?.migratedFrom)
|
|
288
288
|
throw new Error("--unparsed-only cannot use this retired recorded judge; use grade to migrate it explicitly");
|
|
289
289
|
if (resolvedRecordedJudge?.migratedFrom) {
|
|
@@ -369,7 +369,7 @@ export async function cmdRegate(args, adapterOverride) {
|
|
|
369
369
|
const spec = loadSpec(specPath);
|
|
370
370
|
const prev = readResults(runDir);
|
|
371
371
|
const resolvedRecordedJudge = judgeFlag ? undefined : recordedJudgeOrDefault(prev.judge);
|
|
372
|
-
const judge = judgeFlag ? parseModelRef(judgeFlag) : resolvedRecordedJudge.judge;
|
|
372
|
+
const judge = withThinking(judgeFlag ? parseModelRef(judgeFlag) : resolvedRecordedJudge.judge, flagStr(args, "judge-thinking"));
|
|
373
373
|
if (resolvedRecordedJudge?.migratedFrom) {
|
|
374
374
|
console.error(`skill-harness: recorded judge ${resolvedRecordedJudge.migratedFrom.provider}:${resolvedRecordedJudge.migratedFrom.model} was removed; re-judging through Pi with ${judge.provider}:${judge.model}`);
|
|
375
375
|
}
|
|
@@ -751,6 +751,7 @@ export function help() {
|
|
|
751
751
|
return `skill-harness ${HARNESS_VERSION} — test/optimize loop for agent skills (pi harness)
|
|
752
752
|
|
|
753
753
|
run <skill|all> --skills <root> [--model prov:model ...] [--models file] [--only A1,D2]
|
|
754
|
+
[--thinking LEVEL] [--judge-thinking LEVEL]
|
|
754
755
|
[--mode red|green|force] [--judge prov:model] [--harness pi] [--label name] [--parallel N] [--reps N] [--pass-threshold T]
|
|
755
756
|
[--canary] green only: one instructed-loading probe; autonomous selection stays untested
|
|
756
757
|
[--structured] request structured capture (Pi uses it by default for subject usage)
|
|
@@ -816,7 +817,16 @@ export async function cmdDiagnose(args) {
|
|
|
816
817
|
const recorded = readResults(runDir);
|
|
817
818
|
const { skills, specs } = roots(args);
|
|
818
819
|
const selected = resolveSkill(skills, recorded.skill, specs);
|
|
819
|
-
|
|
820
|
+
if ((args.multi.model?.length ?? 0) > 1)
|
|
821
|
+
throw new Error("diagnose previews one subject profile at a time");
|
|
822
|
+
const modelToken = args.multi.model?.[0] ?? flagStr(args, "model"), judgeToken = flagStr(args, "judge");
|
|
823
|
+
if (modelToken?.includes(","))
|
|
824
|
+
throw new Error("diagnose previews one subject profile at a time");
|
|
825
|
+
const profiles = {
|
|
826
|
+
subject: modelToken !== undefined || Object.hasOwn(args.flags, "thinking") ? withThinking(modelToken !== undefined ? parseModelRef(modelToken) : requestedSubject(recorded), flagStr(args, "thinking")) : undefined,
|
|
827
|
+
judge: judgeToken !== undefined || Object.hasOwn(args.flags, "judge-thinking") ? withThinking(judgeToken !== undefined ? parseModelRef(judgeToken) : recorded.judge, flagStr(args, "judge-thinking")) : undefined,
|
|
828
|
+
};
|
|
829
|
+
const report = diagnoseRun(runDir, selected.dir, selected.specPath, profiles);
|
|
820
830
|
console.log(format === "text" ? formatDiagnosis(report) : JSON.stringify(report, null, 2));
|
|
821
831
|
}
|
|
822
832
|
export async function cmdCalibrate(args, adapterOverride) {
|
|
@@ -830,7 +840,7 @@ export async function cmdCalibrate(args, adapterOverride) {
|
|
|
830
840
|
if (flagBool(args, "plan")) {
|
|
831
841
|
if (!judgeToken)
|
|
832
842
|
throw new Error("Calibration --plan needs an explicit --judge; it makes zero provider calls.");
|
|
833
|
-
const plan = planCalibration(calibration, { judge: parseModelRef(judgeToken), only, reuse });
|
|
843
|
+
const plan = planCalibration(calibration, { judge: withThinking(parseModelRef(judgeToken), flagStr(args, "judge-thinking")), only, reuse });
|
|
834
844
|
console.log(JSON.stringify({ ...plan, providerCalls: 0 }, null, 2));
|
|
835
845
|
if (!plan.ready)
|
|
836
846
|
process.exitCode = 1;
|
|
@@ -848,7 +858,7 @@ export async function cmdCalibrate(args, adapterOverride) {
|
|
|
848
858
|
const adapter = adapterOverride ?? getAdapter("pi");
|
|
849
859
|
if (!await adapter.available())
|
|
850
860
|
throw new Error("Pi is required for judge calibration. Configure the selected model in Pi first.");
|
|
851
|
-
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}`) });
|
|
861
|
+
const result = await runCalibration(calibration, { adapter, judge: withThinking(parseModelRef(judgeToken), flagStr(args, "judge-thinking")), only, reuse, output: resolve(output), allowMeteredJudge: flagBool(args, "allow-metered-judge"), onProgress: p => console.log(`Judging control ${p.index + 1}/${p.total}: ${p.id}`) });
|
|
852
862
|
console.log(JSON.stringify(result, null, 2));
|
|
853
863
|
if (result.status !== "matched")
|
|
854
864
|
process.exitCode = 1;
|
package/dist/diagnose-format.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { modelProfileLabel } from "@skill-harness/core";
|
|
1
2
|
/** Human view of the same offline report; never a second scoring policy. */
|
|
2
3
|
export function formatDiagnosis(report) {
|
|
3
4
|
const grouped = report.criterionDimensions;
|
|
@@ -9,6 +10,10 @@ export function formatDiagnosis(report) {
|
|
|
9
10
|
`Coverage: ${grouped.coverage.availableVotes}/${grouped.coverage.expectedVotes} known-size votes available; ${grouped.coverage.unknownScenarioSizes} scenario sizes unknown.`,
|
|
10
11
|
`Mapping: ${grouped.metadataSha256}`,
|
|
11
12
|
];
|
|
13
|
+
if (report.summary) {
|
|
14
|
+
const summary = report.summary;
|
|
15
|
+
lines.push(`Subject requested: ${modelProfileLabel(summary.subject.requested)}; effective thinking: unknown.`, `Judge requested: ${modelProfileLabel(summary.judge.requested)}; effective thinking: unknown.`, `Retained token observations: ${summary.usage.subject.recordedInvocations} subject invocations (total unknown); ${summary.usage.judge.recordedInvocations}/${summary.usage.judge.expectedInvocations ?? "unknown"} judge attempts. Full per-counter coverage: --format json. Costs are separate.`, summary.limits);
|
|
16
|
+
}
|
|
12
17
|
for (const scenario of grouped.scenarios) {
|
|
13
18
|
lines.push("", `${scenario.id}: recorded ${scenario.original.judge_verdict}; override ${scenario.original.override ?? "none"}; objective ${scenario.original.objective?.status ?? "not recorded"}`);
|
|
14
19
|
for (const [dimension, counts] of Object.entries(scenario.counts)) {
|
package/package.json
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
2
|
+
"name": "@skill-harness/cli",
|
|
3
|
+
"version": "0.33.0",
|
|
4
|
+
"description": "skill-harness CLI \u2014 run, grade, review, and lint agent-skill scenarios on the pi harness",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/cli.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"skill-harness": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./dist/cli.js",
|
|
13
|
+
"./serve": "./dist/serve.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"agent-skills",
|
|
17
|
+
"skill",
|
|
18
|
+
"llm",
|
|
19
|
+
"testing",
|
|
20
|
+
"eval",
|
|
21
|
+
"grading",
|
|
22
|
+
"pi",
|
|
23
|
+
"ci",
|
|
24
|
+
"harness"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/**/*.js",
|
|
28
|
+
"dist/**/*.d.ts",
|
|
29
|
+
"assets",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/mojomanyana/skill-harness.git"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"prepack": "node ../../scripts/release-pack.mjs --guard"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@skill-harness/core": "0.33.0",
|
|
48
|
+
"@skill-harness/adapters": "0.33.0"
|
|
49
|
+
}
|
|
50
50
|
}
|