@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.
@@ -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 modelTokens) {
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} · judge=${judge.provider}:${judge.model} · skill-harness ${HARNESS_VERSION}`);
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
- const report = diagnoseRun(runDir, selected.dir, selected.specPath);
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;
@@ -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
- "name": "@skill-harness/cli",
3
- "version": "0.32.4",
4
- "description": "skill-harness CLI — 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.32.4",
48
- "@skill-harness/adapters": "0.32.4"
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
  }