@skill-harness/cli 0.29.0 → 0.30.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 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 requires **exactly Pi 1.0.4**. Different or unavailable
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, } 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";
@@ -747,6 +747,10 @@ export function help() {
747
747
  review <skill> --skills <root> [--port N] serve the interactive review UI
748
748
  add-test <skill> --skills <root> --id ID --title T --turn ... --check ... [--critical] [--mode seeded --fixture path]
749
749
  init <skill> --skills <root> [--force] scaffold a commented template spec (${free("init")})
750
+ preflight <skill|all> --skills <root> [--specs <root>] eval checks (${free("preflight")})
751
+ calibrate <manifest.json> [--run --judge p:m --out NEW_DIR] validate or judge reviewed controls
752
+ diagnose <run-dir> --skills <root> [--specs <root>] inspect saved evidence (${free("diagnose")})
753
+ In Pi: /skill-harness create <idea> guided skill + optional agent + eval creation
750
754
  suggest <skill> --skills <root> [--model prov:model] [--force] LLM-draft a spec from SKILL.md (spends tokens)
751
755
  list --skills <root> discovered skills + spec status (${free("list")})
752
756
  lint <skill|all> --skills <root> validate specs/fixtures + results-consistency (${free("lint")}; CI gate; exits non-zero on findings)
@@ -766,6 +770,51 @@ defaults: model=${DEFAULT_MODEL} judge=${defaultJudge()} mode=green harness=p
766
770
  the judge default runs through Pi on your ChatGPT subscription (\`openai-codex\`), not a metered
767
771
  API key. Set SKILL_HARNESS_JUDGE to change it for a repo or a shell; --judge wins over both.`;
768
772
  }
773
+ export async function cmdPreflight(args) {
774
+ const { skills, specs } = roots(args);
775
+ const target = args._[0];
776
+ if (!target)
777
+ throw new Error("usage: skill-harness preflight <skill|all> --skills <root> [--specs <root>]");
778
+ const selected = target === "all" ? discoverUsable(skills, specs).filter(s => s.hasSpec) : [resolveSkill(skills, target, specs)];
779
+ if (!selected.length)
780
+ throw new Error("No specifications found; in Pi use /skill-harness create to author a skill and its evals.");
781
+ const findings = selected.flatMap(s => lintSkill(s.dir, s.testsDir));
782
+ const errors = findings.filter(failsGate);
783
+ console.log(JSON.stringify({ status: errors.length ? "blocked" : "checked", providerCalls: 0, semanticValidity: "requires-reviewed-controls", findings }, null, 2));
784
+ if (errors.length)
785
+ process.exitCode = 1;
786
+ }
787
+ export async function cmdDiagnose(args) {
788
+ if (!args._[0])
789
+ throw new Error("usage: skill-harness diagnose <run-dir> --skills <root> [--specs <root>]");
790
+ const runDir = resolve(args._[0]);
791
+ const recorded = readResults(runDir);
792
+ const { skills, specs } = roots(args);
793
+ const selected = resolveSkill(skills, recorded.skill, specs);
794
+ console.log(JSON.stringify(diagnoseRun(runDir, selected.dir, selected.specPath), null, 2));
795
+ }
796
+ export async function cmdCalibrate(args, adapterOverride) {
797
+ if (!args._[0])
798
+ throw new Error("usage: skill-harness calibrate <manifest.json> [--run --judge provider:model --out NEW_DIR]");
799
+ const calibration = loadCalibration(resolve(args._[0]));
800
+ 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 }));
802
+ if (!calibration.ready)
803
+ process.exitCode = 1;
804
+ return;
805
+ }
806
+ const judgeToken = flagStr(args, "judge");
807
+ const output = flagStr(args, "out");
808
+ if (!judgeToken || !output)
809
+ throw new Error("Calibration --run needs an explicitly selected --judge and a new --out directory.");
810
+ const adapter = adapterOverride ?? getAdapter("pi");
811
+ if (!await adapter.available())
812
+ 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}`) });
814
+ console.log(JSON.stringify(result, null, 2));
815
+ if (result.status !== "matched")
816
+ process.exitCode = 1;
817
+ }
769
818
  export async function main(argv) {
770
819
  const cmd = argv[0];
771
820
  if (cmd === "decision")
@@ -773,6 +822,11 @@ export async function main(argv) {
773
822
  const args = parseArgs(argv.slice(1));
774
823
  assertNoRetiredFlags(cmd, args);
775
824
  switch (cmd) {
825
+ case "create":
826
+ 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.");
827
+ case "preflight": return cmdPreflight(args);
828
+ case "diagnose": return cmdDiagnose(args);
829
+ case "calibrate": return cmdCalibrate(args);
776
830
  case "run": return cmdRun(args);
777
831
  case "grade":
778
832
  case "regrade": return cmdGrade(args);
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: dirname(specPath), threshold, mode: results.mode,
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.29.0",
3
+ "version": "0.30.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.29.0",
48
- "@skill-harness/adapters": "0.29.0"
47
+ "@skill-harness/core": "0.30.0",
48
+ "@skill-harness/adapters": "0.30.0"
49
49
  }
50
50
  }