@skill-harness/core 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 +16 -2
- package/dist/adapters/types.d.ts +2 -0
- package/dist/authoring-preflight.d.ts +22 -0
- package/dist/authoring-preflight.js +111 -0
- package/dist/authoring.d.ts +96 -0
- package/dist/authoring.js +451 -0
- package/dist/calibration.d.ts +138 -0
- package/dist/calibration.js +352 -0
- package/dist/canary.d.ts +28 -26
- package/dist/canary.js +109 -92
- package/dist/command-cost.d.ts +1 -1
- package/dist/command-cost.js +2 -0
- package/dist/diagnose.d.ts +17 -0
- package/dist/diagnose.js +70 -0
- package/dist/execution-trace.d.ts +2 -1
- package/dist/execution-trace.js +4 -3
- package/dist/grade.d.ts +8 -0
- package/dist/grade.js +37 -8
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/journal.d.ts +3 -0
- package/dist/judge-evidence.d.ts +40 -0
- package/dist/judge-evidence.js +120 -0
- package/dist/judge-references.d.ts +8 -0
- package/dist/judge-references.js +34 -0
- package/dist/lint.d.ts +1 -1
- package/dist/lint.js +22 -0
- package/dist/regate.js +21 -3
- package/dist/regrade.d.ts +10 -1
- package/dist/regrade.js +146 -75
- package/dist/rescore.js +1 -0
- package/dist/resource-monitor.d.ts +3 -0
- package/dist/resource-monitor.js +72 -0
- package/dist/results.d.ts +16 -4
- package/dist/results.js +10 -0
- package/dist/run.d.ts +2 -2
- package/dist/run.js +147 -116
- package/dist/saved-run.d.ts +15 -0
- package/dist/saved-run.js +114 -0
- package/dist/scheduler.d.ts +3 -5
- package/dist/scheduler.js +18 -6
- package/dist/seeded.js +1 -0
- package/dist/sources.d.ts +4 -1
- package/dist/sources.js +44 -40
- package/dist/spec.d.ts +8 -0
- package/dist/spec.js +27 -0
- package/dist/trace-gates.d.ts +2 -0
- package/dist/trace-gates.js +13 -3
- package/dist/util/exec.d.ts +5 -0
- package/dist/util/exec.js +16 -32
- package/dist/util/fixture-inventory.d.ts +19 -0
- package/dist/util/fixture-inventory.js +68 -0
- package/dist/util/process.d.ts +78 -0
- package/dist/util/process.js +394 -0
- package/dist/workspace.js +13 -29
- package/package.json +1 -1
- package/schemas/results-v2.schema.json +10 -0
- package/schemas/results-v3.schema.json +10 -0
- package/schemas/specification-v1.schema.json +21 -0
package/README.md
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
# @skill-harness/core
|
|
2
2
|
|
|
3
3
|
The engine behind [skill-harness](https://github.com/mojomanyana/skill-harness):
|
|
4
|
-
|
|
4
|
+
skill-authoring drafts and previews, spec parsing, eval preflight, selected judge
|
|
5
|
+
references, calibration, run orchestration, grading, scoring, and result diagnosis.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
Most users should install `skill-harness` in Pi, select a model, and start with
|
|
8
|
+
`/skill-harness create <idea>`. Authoring requires Pi and its selected model;
|
|
9
|
+
generated skills require only Pi. A pi-daddy wrapper is optional. Evaluation
|
|
10
|
+
files can live with the skill or in a separate repository.
|
|
11
|
+
|
|
12
|
+
The CLI exposes `preflight`, offline `calibrate` validation, explicit
|
|
13
|
+
`calibrate --run --judge ...`, and `diagnose`. Core helpers preserve selected
|
|
14
|
+
references and reviewed calibration expectations without treating them as a
|
|
15
|
+
model-quality score. See
|
|
16
|
+
[EVAL-AUTHORING.md](https://github.com/mojomanyana/skill-harness/blob/main/docs/EVAL-AUTHORING.md)
|
|
17
|
+
and [JUDGE-EVIDENCE.md](https://github.com/mojomanyana/skill-harness/blob/main/docs/JUDGE-EVIDENCE.md).
|
|
18
|
+
|
|
19
|
+
Fresh Pi execution is qualified for exact versions **1.0.4 and 1.1.0**.
|
|
20
|
+
Other versions require separate transport qualification.
|
|
7
21
|
|
|
8
22
|
ESM, Node ≥ 20.
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -55,6 +55,8 @@ export interface RunReq {
|
|
|
55
55
|
* extension the developer happens to have installed cannot join the test.
|
|
56
56
|
*/
|
|
57
57
|
extensions?: string[];
|
|
58
|
+
/** Explicit Pi tool filter; an empty list disables tools. Not OS isolation. */
|
|
59
|
+
tools?: string[];
|
|
58
60
|
/**
|
|
59
61
|
* Extra env for the subject process, from the arm. Merged over `process.env` by
|
|
60
62
|
* the adapter — an arm must be able to add `PI_GRANTS_*` without the harness
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Spec } from "./spec.js";
|
|
2
|
+
export interface AuthoringFinding {
|
|
3
|
+
scenario: string;
|
|
4
|
+
code: "INPUT_DISCOVERY" | "READ_COUNT" | "PATH_PORTABILITY" | "COMMAND_SPELLING" | "TEST_SYNTAX" | "CITATION_FORMAT" | "UNAVAILABLE_FORBIDDEN_TOOL" | "UNAVAILABLE_REQUIRED_TOOL" | "JUDGE_REFERENCE_GAP" | "MISSING_COVERAGE_MAP";
|
|
5
|
+
severity?: "error";
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
/** Review hints only. A valid exact-format contract may intentionally trigger one. */
|
|
9
|
+
export declare function inspectEvalAuthoring(spec: Spec, profile?: {
|
|
10
|
+
tools?: readonly string[];
|
|
11
|
+
fixtureFiles?: (scenario: Spec["scenarios"][number]) => readonly string[];
|
|
12
|
+
/** Explicitly selected fixture/reference text only. No host search or automatic judge disclosure. */
|
|
13
|
+
fixtureText?: (scenario: Spec["scenarios"][number], file: string) => string | null;
|
|
14
|
+
referenceText?: (file: string) => string | null;
|
|
15
|
+
}): AuthoringFinding[];
|
|
16
|
+
/** Names only, never file contents. Do not follow symlinks or inspect private host directories. */
|
|
17
|
+
export declare function fixtureInventory(root: string): string[];
|
|
18
|
+
/** Optional text inventory for offline lint. Bounded; never expands the judge's input. */
|
|
19
|
+
export declare function authoringEvidenceProfile(specDir: string): {
|
|
20
|
+
fixtureText: (s: Spec["scenarios"][number], file: string) => string | null;
|
|
21
|
+
referenceText: (file: string) => string | null;
|
|
22
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { join, relative, isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { MARKERS } from "./workspace.js";
|
|
4
|
+
/** Review hints only. A valid exact-format contract may intentionally trigger one. */
|
|
5
|
+
export function inspectEvalAuthoring(spec, profile = {}) {
|
|
6
|
+
const findings = [];
|
|
7
|
+
for (const s of spec.scenarios) {
|
|
8
|
+
const tools = profile.tools ?? s.tools;
|
|
9
|
+
const add = (code, message) => findings.push({ scenario: s.id, code, message });
|
|
10
|
+
if (!s.covers?.length)
|
|
11
|
+
add("MISSING_COVERAGE_MAP", "No instruction-section coverage is declared. Map the behavior this scenario can detect; a map is not proof of coverage.");
|
|
12
|
+
if (s.judgeEvidence === "response" && profile.fixtureText && profile.referenceText) {
|
|
13
|
+
const references = (s.judgeReferences ?? []).map(profile.referenceText).filter((text) => text !== null);
|
|
14
|
+
const normalize = (text) => text.replace(/\r\n/g, "\n").trim();
|
|
15
|
+
const visible = [...references, s.judgeContext ?? ""].map(normalize);
|
|
16
|
+
for (const file of profile.fixtureFiles?.(s) ?? []) {
|
|
17
|
+
if (!/\.(?:md|txt|json|ya?ml|csv)$/i.test(file))
|
|
18
|
+
continue;
|
|
19
|
+
const source = profile.fixtureText(s, file);
|
|
20
|
+
if (source !== null && normalize(source) && !visible.some(ref => ref.includes(normalize(source)))) {
|
|
21
|
+
add("JUDGE_REFERENCE_GAP", `Fixture ${file} is not present verbatim in judge references/context. Verify whether its claims are needed to judge valid answers, including brand, goals and conflicting facts. Deliberately private sources may remain excluded; document the reason. This is an inventory warning, not a semantic verdict.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const call of s.traceAssert?.require_calls ?? []) {
|
|
26
|
+
const pattern = call.args?.path?.matches;
|
|
27
|
+
if (tools && !tools.includes(call.tool)) {
|
|
28
|
+
findings.push({ scenario: s.id, code: "UNAVAILABLE_REQUIRED_TOOL", severity: "error", message: `Required tool ${call.tool} is excluded by the explicit Pi tool filter.` });
|
|
29
|
+
}
|
|
30
|
+
if (tools?.length === 1 && tools[0] === "read" && call.tool === "read" && pattern) {
|
|
31
|
+
const matches = (profile.fixtureFiles?.(s) ?? []).filter(file => new RegExp(pattern).test(file));
|
|
32
|
+
if (matches.length && !matches.some(file => s.turns.join("\n").includes(file))) {
|
|
33
|
+
add("INPUT_DISCOVERY", "A required source is not named in the prompt and the supplied profile exposes only read. Provide an inventory or discovery capability.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (call.tool === "read" && (call.count?.min ?? 1) > 1) {
|
|
37
|
+
add("READ_COUNT", "Repeated reads do not prove content coverage. Calibrate a single complete read and repeated first chunks.");
|
|
38
|
+
}
|
|
39
|
+
if (pattern?.includes("(^|/)"))
|
|
40
|
+
add("PATH_PORTABILITY", "The path predicate rejects Windows separators. Check whether that restriction is intentional.");
|
|
41
|
+
if (call.tool === "bash" && call.args?.command?.matches === String.raw `\bnpm test\b`) {
|
|
42
|
+
add("COMMAND_SPELLING", "The command predicate rejects npm run test, which invokes the same package script.");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (s.assert?.diff_contains?.includes("expect("))
|
|
46
|
+
add("TEST_SYNTAX", "An assertion spelling does not establish that a regression test detects the defect.");
|
|
47
|
+
if (s.checklist.some(item => /\bCite\s+\S+\s+line\s+\d/i.test(item))) {
|
|
48
|
+
add("CITATION_FORMAT", "A mandatory line citation can reject an accurate attributed answer. Justify the format requirement or accept equivalent evidence.");
|
|
49
|
+
}
|
|
50
|
+
if (tools && s.traceAssert?.forbid_calls?.some(call => !tools.includes(call.tool))) {
|
|
51
|
+
add("UNAVAILABLE_FORBIDDEN_TOOL", "A forbidden tool is absent from the supplied profile. Non-use does not demonstrate refusal of an available capability.");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return findings;
|
|
55
|
+
}
|
|
56
|
+
/** Names only, never file contents. Do not follow symlinks or inspect private host directories. */
|
|
57
|
+
export function fixtureInventory(root) {
|
|
58
|
+
const files = [];
|
|
59
|
+
let entries = 0;
|
|
60
|
+
const visit = (directory, prefix, depth = 0) => {
|
|
61
|
+
if (depth > 32)
|
|
62
|
+
throw new Error("Fixture inventory exceeds 32 directory levels; select a smaller fixture");
|
|
63
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
64
|
+
if (++entries > 4096)
|
|
65
|
+
throw new Error("Fixture inventory exceeds 4096 entries; select a smaller fixture");
|
|
66
|
+
if (entry.name === ".git" || entry.isSymbolicLink())
|
|
67
|
+
continue;
|
|
68
|
+
const name = prefix + entry.name;
|
|
69
|
+
if (entry.isDirectory())
|
|
70
|
+
visit(join(directory, entry.name), name + "/", depth + 1);
|
|
71
|
+
else if (entry.isFile())
|
|
72
|
+
files.push(MARKERS.some(marker => name.startsWith(marker + "/")) ? name.slice(name.indexOf("/") + 1) : name);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
visit(root, "");
|
|
76
|
+
return [...new Set(files)].sort();
|
|
77
|
+
}
|
|
78
|
+
/** Optional text inventory for offline lint. Bounded; never expands the judge's input. */
|
|
79
|
+
export function authoringEvidenceProfile(specDir) {
|
|
80
|
+
let remaining = 2 * 1024 * 1024;
|
|
81
|
+
const cache = new Map();
|
|
82
|
+
const text = (file) => {
|
|
83
|
+
const path = resolve(specDir, file);
|
|
84
|
+
if (cache.has(path))
|
|
85
|
+
return cache.get(path);
|
|
86
|
+
let value = null;
|
|
87
|
+
try {
|
|
88
|
+
const rel = relative(realpathSync(specDir), realpathSync(path));
|
|
89
|
+
const stat = statSync(path), size = stat.size;
|
|
90
|
+
if (!stat.isFile() || isAbsolute(rel) || rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || size > 65536 || size > remaining)
|
|
91
|
+
return null;
|
|
92
|
+
remaining -= size;
|
|
93
|
+
value = new TextDecoder("utf-8", { fatal: true }).decode(readFileSync(path));
|
|
94
|
+
}
|
|
95
|
+
catch { /* Unreadable/binary source is not invented as empty evidence. */ }
|
|
96
|
+
cache.set(path, value);
|
|
97
|
+
return value;
|
|
98
|
+
};
|
|
99
|
+
const fixture = (s) => typeof s.workspace === "object" ? s.workspace.fixture : s.fixture;
|
|
100
|
+
return {
|
|
101
|
+
fixtureText: (s, file) => {
|
|
102
|
+
const root = fixture(s);
|
|
103
|
+
if (!root)
|
|
104
|
+
return null;
|
|
105
|
+
// Overlay order is the same as workspace creation: later pending content wins.
|
|
106
|
+
return text(join(root, "_uncommitted", file)) ?? text(join(root, "_staged", file)) ?? text(join(root, file));
|
|
107
|
+
},
|
|
108
|
+
referenceText: text,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=authoring-preflight.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export interface AuthoringAnswers {
|
|
2
|
+
purpose: string;
|
|
3
|
+
outcomes: string;
|
|
4
|
+
boundaries: string;
|
|
5
|
+
tools: string[];
|
|
6
|
+
placement: "same-repo" | "separate-repo";
|
|
7
|
+
delivery: "skill" | "skill-and-agent";
|
|
8
|
+
evalRoot?: string;
|
|
9
|
+
/** Additional operator-agreed context/delegation capabilities for the optional wrapper. */
|
|
10
|
+
agentCapabilities?: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface AuthoringQuestion {
|
|
13
|
+
key: keyof AuthoringAnswers;
|
|
14
|
+
prompt: string;
|
|
15
|
+
options?: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface AuthoringFile {
|
|
18
|
+
area: "skill" | "evals" | "agent";
|
|
19
|
+
path: string;
|
|
20
|
+
content: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AuthoringDraft {
|
|
23
|
+
files: AuthoringFile[];
|
|
24
|
+
requirements: {
|
|
25
|
+
id: string;
|
|
26
|
+
description: string;
|
|
27
|
+
scenarios: string[];
|
|
28
|
+
}[];
|
|
29
|
+
/** An author's assertion about proposed control labels, never calibration or ship approval. */
|
|
30
|
+
labelStatus: "proposed" | "reviewed";
|
|
31
|
+
}
|
|
32
|
+
export interface AuthoringChange {
|
|
33
|
+
area: AuthoringFile["area"];
|
|
34
|
+
path: string;
|
|
35
|
+
destination: string;
|
|
36
|
+
beforeSha256: string | null;
|
|
37
|
+
afterSha256: string;
|
|
38
|
+
kind: "create" | "update" | "unchanged";
|
|
39
|
+
before: string | null;
|
|
40
|
+
after: string;
|
|
41
|
+
}
|
|
42
|
+
export interface AuthoringState {
|
|
43
|
+
schema: 1;
|
|
44
|
+
name: string;
|
|
45
|
+
idea: string;
|
|
46
|
+
root: string;
|
|
47
|
+
skillDir: string;
|
|
48
|
+
skillsRoot: string;
|
|
49
|
+
agentRoot: string;
|
|
50
|
+
answers: Partial<AuthoringAnswers>;
|
|
51
|
+
imported: {
|
|
52
|
+
path: string;
|
|
53
|
+
sha256: string;
|
|
54
|
+
} | null;
|
|
55
|
+
revision: number;
|
|
56
|
+
draft?: {
|
|
57
|
+
data: AuthoringDraft;
|
|
58
|
+
digest: string;
|
|
59
|
+
changes: AuthoringChange[];
|
|
60
|
+
};
|
|
61
|
+
appliedDigest?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface AuthoringPreview {
|
|
64
|
+
state: AuthoringState;
|
|
65
|
+
nextQuestion: AuthoringQuestion | null;
|
|
66
|
+
draftDigest?: string;
|
|
67
|
+
changes: AuthoringChange[];
|
|
68
|
+
issues: string[];
|
|
69
|
+
warnings: string[];
|
|
70
|
+
readyToApply: boolean;
|
|
71
|
+
}
|
|
72
|
+
/** Create a local brief, without running a model, scanning examples or editing a skill. */
|
|
73
|
+
export declare function startAuthoring(options: {
|
|
74
|
+
root: string;
|
|
75
|
+
name: string;
|
|
76
|
+
idea: string;
|
|
77
|
+
answers?: Partial<AuthoringAnswers>;
|
|
78
|
+
skillRoot?: string;
|
|
79
|
+
evalRoot?: string;
|
|
80
|
+
agentRoot?: string;
|
|
81
|
+
skillDir?: string;
|
|
82
|
+
}): AuthoringState;
|
|
83
|
+
export declare function readAuthoring(root: string, name: string): AuthoringState;
|
|
84
|
+
/** Known answers can be supplied together; the UI asks only the next unresolved question. */
|
|
85
|
+
export declare function answerAuthoring(root: string, name: string, answers: Partial<AuthoringAnswers>): AuthoringState;
|
|
86
|
+
/** Stage model-authored content and capture exact existing bytes. This does not apply or certify it. */
|
|
87
|
+
export declare function stageAuthoringDraft(root: string, name: string, draft: AuthoringDraft): AuthoringPreview;
|
|
88
|
+
export declare function inspectAuthoring(root: string, name: string): AuthoringPreview;
|
|
89
|
+
/** Explicit, digest-bound apply. No grants, installation, provider calls, labels or training authorization are inferred. */
|
|
90
|
+
export declare function applyAuthoring(root: string, name: string, options: {
|
|
91
|
+
digest: string;
|
|
92
|
+
}): {
|
|
93
|
+
state: AuthoringState;
|
|
94
|
+
files: string[];
|
|
95
|
+
status: "applied" | "already-applied";
|
|
96
|
+
};
|