skill-harness 0.13.0 → 0.14.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 +4 -1
- package/assets/report.grade.js +177 -0
- package/assets/report.template.html +470 -0
- package/dist/index.js +17311 -0
- package/dist/prompt-capture-extension.js +118 -0
- package/package.json +10 -2
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// packages/adapters/src/prompt-capture-extension.ts
|
|
2
|
+
import { appendFileSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
// packages/adapters/src/prompt-provenance.ts
|
|
5
|
+
import { createHash as createHash2, createHmac, timingSafeEqual } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
// packages/core/src/prompt-normalization.ts
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
var PROMPT_NORMALIZATION_RULE = "cwd-line-v1";
|
|
10
|
+
var PROMPT_NORMALIZATION_PATTERN = "^(Current working directory:)[^\\r\\n]*(\\r?)$";
|
|
11
|
+
var PROMPT_NORMALIZATION_FLAGS = "gm";
|
|
12
|
+
var PROMPT_NORMALIZATION_REPLACEMENT = "$1<normalized>$2";
|
|
13
|
+
var PROMPT_NORMALIZATION_SOURCE_DIGEST = createHash("sha256").update(JSON.stringify([
|
|
14
|
+
"prompt-normalization-registry",
|
|
15
|
+
PROMPT_NORMALIZATION_RULE,
|
|
16
|
+
PROMPT_NORMALIZATION_PATTERN,
|
|
17
|
+
PROMPT_NORMALIZATION_FLAGS,
|
|
18
|
+
PROMPT_NORMALIZATION_REPLACEMENT
|
|
19
|
+
])).digest("hex");
|
|
20
|
+
|
|
21
|
+
// packages/adapters/src/prompt-provenance.ts
|
|
22
|
+
function sha(bytes) {
|
|
23
|
+
return createHash2("sha256").update(bytes, "utf8").digest("hex");
|
|
24
|
+
}
|
|
25
|
+
function normalizePromptPayload(value, rule) {
|
|
26
|
+
if (rule !== PROMPT_NORMALIZATION_RULE) throw new Error(`unknown prompt normalization rule ${String(rule)}`);
|
|
27
|
+
if (typeof value === "string") return value.replace(new RegExp(PROMPT_NORMALIZATION_PATTERN, PROMPT_NORMALIZATION_FLAGS), PROMPT_NORMALIZATION_REPLACEMENT);
|
|
28
|
+
if (Array.isArray(value)) return value.map((entry) => normalizePromptPayload(entry, rule));
|
|
29
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, normalizePromptPayload(entry, rule)]));
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function countInStrings(value, needle) {
|
|
33
|
+
if (typeof value === "string") {
|
|
34
|
+
if (!needle) return 0;
|
|
35
|
+
let count = 0, at = 0;
|
|
36
|
+
while ((at = value.indexOf(needle, at)) !== -1) {
|
|
37
|
+
count++;
|
|
38
|
+
at += needle.length;
|
|
39
|
+
}
|
|
40
|
+
return count;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) return value.reduce((sum, entry) => sum + countInStrings(entry, needle), 0);
|
|
43
|
+
if (value && typeof value === "object") {
|
|
44
|
+
const record = value;
|
|
45
|
+
if (record.role === "user") {
|
|
46
|
+
const content = Array.isArray(record.content) ? record.content : Array.isArray(record.parts) ? record.parts : [];
|
|
47
|
+
return content.filter((block) => block && typeof block === "object" && (["tool_result", "tool_response", "function_response"].includes(String(block.type ?? "")) || "functionResponse" in block || "function_response" in block)).reduce((sum, block) => sum + countInStrings(block, needle), 0);
|
|
48
|
+
}
|
|
49
|
+
return Object.values(record).reduce((sum, entry) => sum + countInStrings(entry, needle), 0);
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
var PROMPT_FIELDS = /* @__PURE__ */ new Set(["instructions", "input", "system", "messages", "prompt", "contents"]);
|
|
54
|
+
function promptProjection(payload) {
|
|
55
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
56
|
+
const record = payload;
|
|
57
|
+
const projected = Object.fromEntries(Object.entries(record).filter(([key]) => PROMPT_FIELDS.has(key)));
|
|
58
|
+
const config = record.config && typeof record.config === "object" ? record.config : null;
|
|
59
|
+
if (config?.systemInstruction !== void 0) projected.systemInstruction = config.systemInstruction;
|
|
60
|
+
return Object.keys(projected).length ? projected : null;
|
|
61
|
+
}
|
|
62
|
+
function observationMac(observation, authenticationKey) {
|
|
63
|
+
return createHmac("sha256", authenticationKey).update(JSON.stringify(observation)).digest("hex");
|
|
64
|
+
}
|
|
65
|
+
function authenticatePromptObservation(observation, authenticationKey) {
|
|
66
|
+
return { observation, mac: observationMac(observation, authenticationKey) };
|
|
67
|
+
}
|
|
68
|
+
function authenticatePromptSummary(count, authenticationKey) {
|
|
69
|
+
const summary = { count };
|
|
70
|
+
return { summary, mac: createHmac("sha256", authenticationKey).update(JSON.stringify(summary)).digest("hex") };
|
|
71
|
+
}
|
|
72
|
+
function statusFor(occurrences, mechanism, observable) {
|
|
73
|
+
if (!observable) return "ERROR";
|
|
74
|
+
return occurrences === (mechanism === "none" ? 0 : 1) ? "PASS" : "NOT-MEASURED";
|
|
75
|
+
}
|
|
76
|
+
function observeProviderPayload(payload, contract, mechanism, requestIndex) {
|
|
77
|
+
const projection = promptProjection(payload);
|
|
78
|
+
const raw = JSON.stringify(projection ?? {});
|
|
79
|
+
const normalized = JSON.stringify(normalizePromptPayload(projection ?? {}, PROMPT_NORMALIZATION_RULE));
|
|
80
|
+
const occurrences = countInStrings(projection ?? {}, contract);
|
|
81
|
+
const observable = projection !== null;
|
|
82
|
+
return {
|
|
83
|
+
capture_version: "prompt-provenance-v1",
|
|
84
|
+
request_index: requestIndex,
|
|
85
|
+
raw_sha256: sha(raw),
|
|
86
|
+
normalized_sha256: sha(normalized),
|
|
87
|
+
normalization_rule: PROMPT_NORMALIZATION_RULE,
|
|
88
|
+
bytes: Buffer.byteLength(raw),
|
|
89
|
+
contract_sha256: sha(contract),
|
|
90
|
+
contract_bytes: Buffer.byteLength(contract),
|
|
91
|
+
contract_occurrences: occurrences,
|
|
92
|
+
mechanism,
|
|
93
|
+
status: statusFor(occurrences, mechanism, observable),
|
|
94
|
+
...observable ? {} : { error: "provider payload has no supported model-visible prompt field" }
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// packages/adapters/src/prompt-capture-extension.ts
|
|
99
|
+
function promptCapture(pi) {
|
|
100
|
+
const target = process.env.SKILL_HARNESS_PROMPT_CAPTURE_FILE;
|
|
101
|
+
const contractPath = process.env.SKILL_HARNESS_PROMPT_CONTRACT_FILE;
|
|
102
|
+
if (!target || !contractPath) throw new Error("prompt capture paths are required");
|
|
103
|
+
const contract = JSON.parse(readFileSync(contractPath, "utf8"));
|
|
104
|
+
delete process.env.SKILL_HARNESS_PROMPT_CAPTURE_FILE;
|
|
105
|
+
delete process.env.SKILL_HARNESS_PROMPT_CONTRACT_FILE;
|
|
106
|
+
rmSync(contractPath, { force: true });
|
|
107
|
+
let requestIndex = 0;
|
|
108
|
+
pi.on("before_provider_request", (event) => {
|
|
109
|
+
const observation = observeProviderPayload(event.payload, contract.text, contract.mechanism, requestIndex++);
|
|
110
|
+
appendFileSync(target, JSON.stringify(authenticatePromptObservation(observation, contract.authentication_key)) + "\n", { encoding: "utf8", mode: 384 });
|
|
111
|
+
});
|
|
112
|
+
pi.on("session_shutdown", () => {
|
|
113
|
+
appendFileSync(target, JSON.stringify(authenticatePromptSummary(requestIndex, contract.authentication_key)) + "\n", { encoding: "utf8", mode: 384 });
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
promptCapture as default
|
|
118
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skill-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Test/optimize loop for agent skills — run spec'd scenarios on pi, LLM-judge, score, review, re-run",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -17,13 +17,21 @@
|
|
|
17
17
|
"bin": {
|
|
18
18
|
"skill-harness": "bin.js"
|
|
19
19
|
},
|
|
20
|
+
"pi": {
|
|
21
|
+
"extensions": ["./dist/index.js"]
|
|
22
|
+
},
|
|
20
23
|
"files": [
|
|
21
24
|
"bin.js",
|
|
25
|
+
"dist/index.js",
|
|
26
|
+
"dist/prompt-capture-extension.js",
|
|
27
|
+
"assets/report.template.html",
|
|
28
|
+
"assets/report.grade.js",
|
|
22
29
|
"LICENSE",
|
|
23
30
|
"README.md"
|
|
24
31
|
],
|
|
25
32
|
"dependencies": {
|
|
26
|
-
"@skill-harness/cli": "0.
|
|
33
|
+
"@skill-harness/cli": "0.14.0",
|
|
34
|
+
"typebox": "^1.1.38"
|
|
27
35
|
},
|
|
28
36
|
"repository": {
|
|
29
37
|
"type": "git",
|