@skill-harness/adapters 0.4.0 → 0.5.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/dist/pi.js +62 -5
- package/package.json +2 -2
package/dist/pi.js
CHANGED
|
@@ -1,17 +1,52 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
4
|
import { exec, onPath, envNum } from "@skill-harness/core";
|
|
5
5
|
const PI_TIMEOUT_MS = envNum("PI_TIMEOUT_MS", 300_000);
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Refuse to hand pi a skill dir it will silently ignore.
|
|
8
|
+
*
|
|
9
|
+
* Measured on pi 0.83.0: `pi --skill /nonexistent/skilldir -p "hi"` answers
|
|
10
|
+
* normally and exits 0. So a wrong path does not fail a run — it turns every
|
|
11
|
+
* scenario into a no-skill baseline that still looks like a result. Two full waves
|
|
12
|
+
* in the reference corpus scored ≈ their naked-model baseline before a
|
|
13
|
+
* contradictory failure mix gave it away.
|
|
14
|
+
*
|
|
15
|
+
* Returns the ABSOLUTE dir: `discover()` builds `join(root, name)`, so
|
|
16
|
+
* `--skills .` yields a relative path, and pi runs in a neutral cwd of the
|
|
17
|
+
* harness's choosing — a relative `--skill` resolved there points at nothing,
|
|
18
|
+
* which is precisely the case pi swallows.
|
|
19
|
+
*/
|
|
20
|
+
function requireSkillDir(skillDir, mode) {
|
|
21
|
+
const abs = resolve(skillDir);
|
|
22
|
+
const md = join(abs, "SKILL.md");
|
|
23
|
+
const isDir = existsSync(abs) && statSync(abs).isDirectory();
|
|
24
|
+
if (!isDir || !existsSync(md)) {
|
|
25
|
+
throw new Error(`mode=${mode} needs a skill directory with a SKILL.md, but ${abs} ${isDir ? "has none" : "is not a directory"}` +
|
|
26
|
+
(abs === skillDir ? "" : ` (given \`${skillDir}\`, resolved against ${process.cwd()})`) +
|
|
27
|
+
` — pi accepts \`--skill <nonexistent>\` silently (exit 0, a normal answer, no skill in context),` +
|
|
28
|
+
` so this run would measure a model with no skill and report it as a result.`);
|
|
29
|
+
}
|
|
30
|
+
return abs;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Skill-activation flags for a given run mode.
|
|
34
|
+
*
|
|
35
|
+
* `green` asks pi to activate the skill and is therefore only as good as pi's
|
|
36
|
+
* delivery: 0.80.x wrapped the prompt with the skill body, 0.83.0 discloses the
|
|
37
|
+
* description and loads the body on demand ("models don't always do this" — pi's
|
|
38
|
+
* own docs). `force` puts SKILL.md in the system prompt, which no pi version has
|
|
39
|
+
* made conditional. Both are checked the same way, because the failure being
|
|
40
|
+
* prevented — a skill dir that isn't there — costs a whole wave either way.
|
|
41
|
+
*/
|
|
7
42
|
function skillFlags(mode, skillDir) {
|
|
8
43
|
switch (mode) {
|
|
9
44
|
case "red":
|
|
10
45
|
return ["--no-skills"];
|
|
11
46
|
case "green":
|
|
12
|
-
return ["--skill", skillDir];
|
|
47
|
+
return ["--skill", requireSkillDir(skillDir, mode)];
|
|
13
48
|
case "force": {
|
|
14
|
-
const body = readFileSync(join(skillDir, "SKILL.md"), "utf8");
|
|
49
|
+
const body = readFileSync(join(requireSkillDir(skillDir, mode), "SKILL.md"), "utf8");
|
|
15
50
|
return ["--no-skills", "--append-system-prompt", body];
|
|
16
51
|
}
|
|
17
52
|
}
|
|
@@ -25,6 +60,28 @@ export const piAdapter = {
|
|
|
25
60
|
available() {
|
|
26
61
|
return Promise.resolve(onPath("pi"));
|
|
27
62
|
},
|
|
63
|
+
/**
|
|
64
|
+
* `pi --version` (it prints a bare version, e.g. `0.83.0`), recorded in
|
|
65
|
+
* results.yaml as `harness_cli_version`.
|
|
66
|
+
*
|
|
67
|
+
* Null on any failure — a non-zero exit, empty output, or pi missing entirely.
|
|
68
|
+
* A run must not abort because provenance was unavailable, and a fabricated
|
|
69
|
+
* version would be worse than an absent one.
|
|
70
|
+
*/
|
|
71
|
+
async version() {
|
|
72
|
+
try {
|
|
73
|
+
const r = await exec("pi", ["--version"], { timeoutMs: 30_000 });
|
|
74
|
+
const line = r.stdout.split("\n")[0]?.trim() ?? "";
|
|
75
|
+
if (r.code !== 0 || line === "")
|
|
76
|
+
return null;
|
|
77
|
+
// Tolerate a future `pi 1.2.3` / `pi version 1.2.3` shape without losing the
|
|
78
|
+
// bare `0.83.0` one: keep the first version-looking token, else the line.
|
|
79
|
+
return /\d+\.\d+\.\d+\S*/.exec(line)?.[0] ?? line;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
28
85
|
/**
|
|
29
86
|
* Run a scenario through pi. Single turn → --no-session -p. Multi turn → a
|
|
30
87
|
* shared --session-dir, -c on every turn after the first. Returns a transcript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-harness/adapters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "skill-harness harness adapters — pi runner + claude-code judge routing (internal API)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,6 +40,6 @@
|
|
|
40
40
|
"prepack": "cp ../../LICENSE ./LICENSE"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@skill-harness/core": "0.
|
|
43
|
+
"@skill-harness/core": "0.5.0"
|
|
44
44
|
}
|
|
45
45
|
}
|