vigiles 2.5.0 → 3.0.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/.claude-plugin/plugin.json +9 -0
- package/README.md +73 -434
- package/action.yml +143 -7
- package/dist/action-gate.d.ts +1 -1
- package/dist/action-gate.js +1 -1
- package/dist/adapter-conformance.d.ts +30 -0
- package/dist/adapter-conformance.js +153 -0
- package/dist/adapter-registry.d.ts +42 -0
- package/dist/adapter-registry.js +55 -0
- package/dist/adapter.d.ts +26 -0
- package/dist/adapter.js +16 -0
- package/dist/adapters/claude-code/adapter.d.ts +3 -0
- package/dist/adapters/claude-code/adapter.js +46 -0
- package/dist/{agent-result.d.ts → adapters/claude-code/agent-result.d.ts} +1 -1
- package/dist/adapters/claude-code/dialect.d.ts +13 -0
- package/dist/adapters/claude-code/dialect.js +51 -0
- package/dist/adapters/claude-code/egress-entry.d.ts +2 -0
- package/dist/adapters/claude-code/egress-entry.js +115 -0
- package/dist/adapters/claude-code/egress-proxy.d.ts +2 -0
- package/dist/adapters/claude-code/egress-proxy.js +60 -0
- package/dist/adapters/claude-code/egress.d.ts +114 -0
- package/dist/adapters/claude-code/egress.js +276 -0
- package/dist/adapters/claude-code/eval-baseline.d.ts +68 -0
- package/dist/adapters/claude-code/eval-baseline.js +173 -0
- package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
- package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
- package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
- package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
- package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
- package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
- package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
- package/dist/adapters/claude-code/hook-protocol.js +10 -0
- package/dist/adapters/claude-code/layout.d.ts +8 -0
- package/dist/adapters/claude-code/layout.js +18 -0
- package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
- package/dist/adapters/claude-code/model-mock.d.ts +11 -0
- package/dist/adapters/claude-code/model-mock.js +10 -0
- package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
- package/dist/adapters/claude-code/plugin-loader.js +19 -0
- package/dist/adapters/claude-code/run-hook.d.ts +204 -0
- package/dist/adapters/claude-code/run-hook.js +418 -0
- package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
- package/dist/adapters/claude-code/run-scripts.js +150 -0
- package/dist/adapters/claude-code/runtime.d.ts +16 -0
- package/dist/adapters/claude-code/runtime.js +39 -0
- package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +45 -4
- package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +76 -8
- package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
- package/dist/adapters/codex/adapter.d.ts +3 -0
- package/dist/adapters/codex/adapter.js +49 -0
- package/dist/adapters/codex/dialect.d.ts +10 -0
- package/dist/adapters/codex/dialect.js +30 -0
- package/dist/adapters/codex/driver.d.ts +20 -0
- package/dist/adapters/codex/driver.js +89 -0
- package/dist/adapters/codex/hook-protocol.d.ts +10 -0
- package/dist/adapters/codex/hook-protocol.js +18 -0
- package/dist/adapters/codex/layout.d.ts +16 -0
- package/dist/adapters/codex/layout.js +18 -0
- package/dist/adapters/codex/mock-model.d.ts +52 -0
- package/dist/adapters/codex/mock-model.js +210 -0
- package/dist/adapters/codex/model-mock.d.ts +11 -0
- package/dist/adapters/codex/model-mock.js +10 -0
- package/dist/adapters/codex/runtime.d.ts +38 -0
- package/dist/adapters/codex/runtime.js +62 -0
- package/dist/adapters/opencode/adapter.d.ts +3 -0
- package/dist/adapters/opencode/adapter.js +48 -0
- package/dist/adapters/opencode/dialect.d.ts +10 -0
- package/dist/adapters/opencode/dialect.js +36 -0
- package/dist/adapters/opencode/layout.d.ts +9 -0
- package/dist/adapters/opencode/layout.js +25 -0
- package/dist/adapters/opencode/model-mock.d.ts +10 -0
- package/dist/adapters/opencode/model-mock.js +10 -0
- package/dist/adapters/opencode/runtime.d.ts +9 -0
- package/dist/adapters/opencode/runtime.js +21 -0
- package/dist/claude-code.d.ts +16 -0
- package/dist/claude-code.js +32 -0
- package/dist/cli-flags.d.ts +22 -0
- package/dist/cli-flags.js +38 -0
- package/dist/cli.js +406 -206
- package/dist/codex.d.ts +20 -0
- package/dist/codex.js +36 -0
- package/dist/community-skills.d.ts +1 -1
- package/dist/community-skills.js +2 -2
- package/dist/core/adapter.d.ts +88 -0
- package/dist/core/adapter.js +3 -0
- package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
- package/dist/{compile.js → core/compile.js} +68 -53
- package/dist/core/compose.d.ts +79 -0
- package/dist/core/compose.js +145 -0
- package/dist/core/dialect.d.ts +51 -0
- package/dist/core/dialect.js +3 -0
- package/dist/core/harness-driver.d.ts +134 -0
- package/dist/core/harness-driver.js +3 -0
- package/dist/core/hook-protocol.d.ts +28 -0
- package/dist/core/hook-protocol.js +3 -0
- package/dist/core/layout.d.ts +43 -0
- package/dist/core/layout.js +3 -0
- package/dist/core/model-mock.d.ts +27 -0
- package/dist/core/model-mock.js +3 -0
- package/dist/core/refs.d.ts +62 -0
- package/dist/{refs.js → core/refs.js} +49 -23
- package/dist/core/runtime.d.ts +38 -0
- package/dist/core/runtime.js +3 -0
- package/dist/{types.d.ts → core/types.d.ts} +29 -0
- package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
- package/dist/{validate.js → core/validate.js} +41 -4
- package/dist/e2e.d.ts +19 -0
- package/dist/e2e.js +39 -0
- package/dist/harness-assert.d.ts +60 -11
- package/dist/harness-assert.js +111 -9
- package/dist/integration.d.ts +16 -0
- package/dist/integration.js +32 -0
- package/dist/leaderboard.d.ts +33 -0
- package/dist/leaderboard.js +107 -0
- package/dist/linting.d.ts +9 -0
- package/dist/linting.js +25 -0
- package/dist/plugin-loader.d.ts +6 -3
- package/dist/plugin-loader.js +101 -49
- package/dist/scan.d.ts +49 -0
- package/dist/scan.js +177 -0
- package/dist/setup-plan.d.ts +51 -0
- package/dist/setup-plan.js +85 -0
- package/dist/skill-test.d.ts +1 -1
- package/dist/skill-test.js +1 -1
- package/dist/test-coverage.d.ts +71 -0
- package/dist/test-coverage.js +228 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.js +28 -0
- package/dist/unit.d.ts +17 -0
- package/dist/unit.js +36 -0
- package/hooks/refs-nudge.sh +24 -0
- package/package.json +35 -16
- package/skills/audit-feedback-loop/SKILL.md +76 -0
- package/skills/edit-spec/SKILL.md +131 -0
- package/skills/enforce-rules-format/SKILL.md +71 -0
- package/skills/generate-logo/SKILL.md +103 -0
- package/skills/generate-rule/SKILL.md +64 -0
- package/skills/linter-docs/clippy.md +241 -0
- package/skills/linter-docs/eslint.md +384 -0
- package/skills/linter-docs/pylint.md +288 -0
- package/skills/linter-docs/rubocop.md +277 -0
- package/skills/linter-docs/ruff.md +187 -0
- package/skills/linter-docs/stylelint.md +247 -0
- package/skills/migrate-to-spec/SKILL.md +126 -0
- package/skills/pr-to-lint-rule/SKILL.md +97 -0
- package/skills/strengthen/SKILL.md +168 -0
- package/skills/test-harness/SKILL.md +157 -0
- package/dist/action.d.ts +0 -7
- package/dist/action.js +0 -180
- package/dist/refs.d.ts +0 -44
- package/dist/run-hook.d.ts +0 -77
- package/dist/run-hook.js +0 -80
- package/dist/run-scripts.d.ts +0 -20
- package/dist/run-scripts.js +0 -70
- /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
- /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
- /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
- /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
- /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
- /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
- /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
- /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
- /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
- /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
- /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
- /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
- /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
- /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
- /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
- /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
- /package/dist/{coverage.js → core/coverage.js} +0 -0
- /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
- /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
- /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
- /package/dist/{evolve.js → core/evolve.js} +0 -0
- /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
- /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
- /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
- /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
- /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
- /package/dist/{generate-types.js → core/generate-types.js} +0 -0
- /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
- /package/dist/{hash.js → core/hash.js} +0 -0
- /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
- /package/dist/{inline.js → core/inline.js} +0 -0
- /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
- /package/dist/{integrity.js → core/integrity.js} +0 -0
- /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
- /package/dist/{linters.js → core/linters.js} +0 -0
- /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
- /package/dist/{mcp.js → core/mcp.js} +0 -0
- /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
- /package/dist/{orphans.js → core/orphans.js} +0 -0
- /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
- /package/dist/{proofs.js → core/proofs.js} +0 -0
- /package/dist/{session.d.ts → core/session.d.ts} +0 -0
- /package/dist/{session.js → core/session.js} +0 -0
- /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
- /package/dist/{sidecar.js → core/sidecar.js} +0 -0
- /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
- /package/dist/{spec.js → core/spec.js} +0 -0
- /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
- /package/dist/{symbols.js → core/symbols.js} +0 -0
- /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
- /package/dist/{test-utils.js → core/test-utils.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/session-start.sh +0 -0
|
@@ -8,10 +8,16 @@ import { type ModelTurn, type ModelRequest } from "./mock-model.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export type SandboxMode = "auto" | "strict" | false;
|
|
10
10
|
/**
|
|
11
|
-
* Whether
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Whether this environment can ACTUALLY confine untrusted code under bubblewrap.
|
|
12
|
+
* **Linux only.** Critically, `bwrap --version` succeeding is NOT enough: many CI
|
|
13
|
+
* runners and hardened hosts ship bubblewrap but disable the **unprivileged user
|
|
14
|
+
* namespaces** it depends on, so a real confined exec fails even though the binary
|
|
15
|
+
* is present. We probe that real capability — a throwaway `bwrap --unshare-all …
|
|
16
|
+
* true` — and cache it, so we never *claim* confinement we can't deliver.
|
|
17
|
+
* `decideSandbox` then correctly refuses untrusted code in such an environment
|
|
18
|
+
* (rather than running it in a "sandbox" that doesn't actually sandbox), and the
|
|
19
|
+
* sandbox-gated tests skip instead of failing. The result is cached because the
|
|
20
|
+
* probe spawns a process and the answer can't change within a run.
|
|
15
21
|
*/
|
|
16
22
|
export declare function sandboxAvailable(): boolean;
|
|
17
23
|
/**
|
|
@@ -59,8 +65,43 @@ export declare function bwrapArgs(opts: {
|
|
|
59
65
|
home: string;
|
|
60
66
|
path: string;
|
|
61
67
|
}): string[];
|
|
68
|
+
/**
|
|
69
|
+
* `--setenv K V` pairs to add back specific variables after `--clearenv` — e.g.
|
|
70
|
+
* a hook's configured env (the `GUARD=path` a plugin's command relies on), which
|
|
71
|
+
* `bwrapArgs`' `--clearenv` would otherwise drop. Pure, so it's unit-tested.
|
|
72
|
+
*/
|
|
73
|
+
export declare function setenvArgs(env: Record<string, string>): string[];
|
|
62
74
|
/** Parse the in-sandbox mock's ndjson request log into {@link ModelRequest}s. */
|
|
63
75
|
export declare function parseRequestLog(ndjson: string): ModelRequest[];
|
|
76
|
+
/** A network egress attempt a confined hook made — recorded, then blocked. */
|
|
77
|
+
export interface EgressAttempt {
|
|
78
|
+
readonly host: string;
|
|
79
|
+
readonly port: number;
|
|
80
|
+
/** ms epoch when the attempt was recorded. */
|
|
81
|
+
readonly ts: number;
|
|
82
|
+
/**
|
|
83
|
+
* Allowlist mode (`egress: { allow }`) only: whether this host was on the
|
|
84
|
+
* allowlist (and so reachable). Undefined for the `recordEgress` proxy, where
|
|
85
|
+
* every attempt is blocked.
|
|
86
|
+
*/
|
|
87
|
+
readonly allowed?: boolean;
|
|
88
|
+
/** Allowlist mode: packets the nftables counter recorded for this host. */
|
|
89
|
+
readonly packets?: number;
|
|
90
|
+
/** Allowlist mode: bytes the nftables counter recorded for this host. */
|
|
91
|
+
readonly bytes?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse the egress recorder's ndjson log into {@link EgressAttempt}s. Pure, so
|
|
95
|
+
* the record-shape and the malformed-line tolerance are unit-tested without a
|
|
96
|
+
* sandbox. A line missing host/port is skipped (a partially-flushed final line).
|
|
97
|
+
*/
|
|
98
|
+
export declare function parseEgressLog(ndjson: string): EgressAttempt[];
|
|
99
|
+
/**
|
|
100
|
+
* The files in `after` that are new or changed vs `before` — i.e. what a confined
|
|
101
|
+
* run wrote to its work dir. Each tree maps a relative path to a content
|
|
102
|
+
* signature (size + mtime). Pure, so the diff is unit-tested without a sandbox.
|
|
103
|
+
*/
|
|
104
|
+
export declare function diffTrees(before: Readonly<Record<string, string>>, after: Readonly<Record<string, string>>): string[];
|
|
64
105
|
/** The raw output of a sandboxed run: exit code, captured stdout, and requests. */
|
|
65
106
|
export interface SandboxRunOut {
|
|
66
107
|
readonly code: number;
|
|
@@ -4,7 +4,10 @@ exports.sandboxAvailable = sandboxAvailable;
|
|
|
4
4
|
exports.specTrusted = specTrusted;
|
|
5
5
|
exports.decideSandbox = decideSandbox;
|
|
6
6
|
exports.bwrapArgs = bwrapArgs;
|
|
7
|
+
exports.setenvArgs = setenvArgs;
|
|
7
8
|
exports.parseRequestLog = parseRequestLog;
|
|
9
|
+
exports.parseEgressLog = parseEgressLog;
|
|
10
|
+
exports.diffTrees = diffTrees;
|
|
8
11
|
exports.runSandboxed = runSandboxed;
|
|
9
12
|
/**
|
|
10
13
|
* vigiles — safe-by-default confinement for executing untrusted harness code.
|
|
@@ -32,18 +35,36 @@ const node_child_process_1 = require("node:child_process");
|
|
|
32
35
|
const node_fs_1 = require("node:fs");
|
|
33
36
|
const node_os_1 = require("node:os");
|
|
34
37
|
const node_path_1 = require("node:path");
|
|
38
|
+
const runtime_js_1 = require("./runtime.js");
|
|
39
|
+
let cachedAvailable;
|
|
35
40
|
/**
|
|
36
|
-
* Whether
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
41
|
+
* Whether this environment can ACTUALLY confine untrusted code under bubblewrap.
|
|
42
|
+
* **Linux only.** Critically, `bwrap --version` succeeding is NOT enough: many CI
|
|
43
|
+
* runners and hardened hosts ship bubblewrap but disable the **unprivileged user
|
|
44
|
+
* namespaces** it depends on, so a real confined exec fails even though the binary
|
|
45
|
+
* is present. We probe that real capability — a throwaway `bwrap --unshare-all …
|
|
46
|
+
* true` — and cache it, so we never *claim* confinement we can't deliver.
|
|
47
|
+
* `decideSandbox` then correctly refuses untrusted code in such an environment
|
|
48
|
+
* (rather than running it in a "sandbox" that doesn't actually sandbox), and the
|
|
49
|
+
* sandbox-gated tests skip instead of failing. The result is cached because the
|
|
50
|
+
* probe spawns a process and the answer can't change within a run.
|
|
40
51
|
*/
|
|
41
52
|
function sandboxAvailable() {
|
|
53
|
+
if (cachedAvailable === undefined)
|
|
54
|
+
cachedAvailable = probeSandbox();
|
|
55
|
+
return cachedAvailable;
|
|
56
|
+
}
|
|
57
|
+
function probeSandbox() {
|
|
42
58
|
/* v8 ignore next -- non-Linux has no bwrap; CI/coverage runs on Linux */
|
|
43
59
|
if (process.platform !== "linux")
|
|
44
60
|
return false;
|
|
45
61
|
try {
|
|
46
|
-
|
|
62
|
+
// The capability that fails when user namespaces are disabled is the
|
|
63
|
+
// namespace creation itself (`--unshare-all`), so probe exactly that.
|
|
64
|
+
return ((0, node_child_process_1.spawnSync)("bwrap", ["--unshare-all", "--ro-bind", "/", "/", "true"], {
|
|
65
|
+
stdio: "ignore",
|
|
66
|
+
timeout: 10_000,
|
|
67
|
+
}).status === 0);
|
|
47
68
|
}
|
|
48
69
|
catch {
|
|
49
70
|
/* v8 ignore next -- defensive: spawnSync only throws on a fork failure */
|
|
@@ -138,6 +159,17 @@ function bwrapArgs(opts) {
|
|
|
138
159
|
"--new-session",
|
|
139
160
|
];
|
|
140
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* `--setenv K V` pairs to add back specific variables after `--clearenv` — e.g.
|
|
164
|
+
* a hook's configured env (the `GUARD=path` a plugin's command relies on), which
|
|
165
|
+
* `bwrapArgs`' `--clearenv` would otherwise drop. Pure, so it's unit-tested.
|
|
166
|
+
*/
|
|
167
|
+
function setenvArgs(env) {
|
|
168
|
+
const out = [];
|
|
169
|
+
for (const [k, v] of Object.entries(env))
|
|
170
|
+
out.push("--setenv", k, v);
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
141
173
|
/** Parse the in-sandbox mock's ndjson request log into {@link ModelRequest}s. */
|
|
142
174
|
function parseRequestLog(ndjson) {
|
|
143
175
|
const out = [];
|
|
@@ -153,6 +185,41 @@ function parseRequestLog(ndjson) {
|
|
|
153
185
|
}
|
|
154
186
|
return out;
|
|
155
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Parse the egress recorder's ndjson log into {@link EgressAttempt}s. Pure, so
|
|
190
|
+
* the record-shape and the malformed-line tolerance are unit-tested without a
|
|
191
|
+
* sandbox. A line missing host/port is skipped (a partially-flushed final line).
|
|
192
|
+
*/
|
|
193
|
+
function parseEgressLog(ndjson) {
|
|
194
|
+
const out = [];
|
|
195
|
+
for (const line of ndjson.split("\n")) {
|
|
196
|
+
if (!line.trim())
|
|
197
|
+
continue;
|
|
198
|
+
try {
|
|
199
|
+
const o = JSON.parse(line);
|
|
200
|
+
if (typeof o.host === "string" && typeof o.port === "number") {
|
|
201
|
+
out.push({ host: o.host, port: o.port, ts: Number(o.ts) || 0 });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
/* a partially-written final line — skip */
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The files in `after` that are new or changed vs `before` — i.e. what a confined
|
|
212
|
+
* run wrote to its work dir. Each tree maps a relative path to a content
|
|
213
|
+
* signature (size + mtime). Pure, so the diff is unit-tested without a sandbox.
|
|
214
|
+
*/
|
|
215
|
+
function diffTrees(before, after) {
|
|
216
|
+
const out = [];
|
|
217
|
+
for (const [path, sig] of Object.entries(after)) {
|
|
218
|
+
if (before[path] !== sig)
|
|
219
|
+
out.push(path);
|
|
220
|
+
}
|
|
221
|
+
return out.sort();
|
|
222
|
+
}
|
|
156
223
|
/**
|
|
157
224
|
* Co-launch the scripted mock and `claude` inside ONE bubblewrap network
|
|
158
225
|
* namespace: the mock serves on the sandbox's loopback (reachable), egress is
|
|
@@ -166,9 +233,10 @@ const WRAPPER = [
|
|
|
166
233
|
"MOCKPID=$!",
|
|
167
234
|
"i=0",
|
|
168
235
|
'while [ ! -s "$VIG_PORT" ] && [ "$i" -lt 200 ]; do sleep 0.05; i=$((i+1)); done',
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
236
|
+
// base-URL / API-key vars + the agent binary come from the runtime port.
|
|
237
|
+
`export ${runtime_js_1.claudeCodeRuntime.modelBaseUrlEnv}="http://127.0.0.1:$(cat "$VIG_PORT")"`,
|
|
238
|
+
`export ${runtime_js_1.claudeCodeRuntime.modelApiKeyEnv}=${runtime_js_1.claudeCodeRuntime.mockApiKey}`,
|
|
239
|
+
`${runtime_js_1.claudeCodeRuntime.agentBinary} "$@"`,
|
|
172
240
|
"code=$?",
|
|
173
241
|
'kill "$MOCKPID" 2>/dev/null',
|
|
174
242
|
'exit "$code"',
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* is fed back into the generator). Gates are deterministic and reuse the gate
|
|
14
14
|
* runtime. This module is the mechanics — fully testable without a live model.
|
|
15
15
|
*/
|
|
16
|
-
import type { Gate } from "
|
|
16
|
+
import type { Gate } from "../../core/spec.js";
|
|
17
17
|
import { type GateOutcome } from "./skill-runtime.js";
|
|
18
18
|
export type SkillEffect = {
|
|
19
19
|
readonly kind: "act";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* codexAdapter — the OpenAI Codex `HarnessAdapter`. Bundles the five Codex ports
|
|
6
|
+
* + a `detect`. SHIPPED: registered in `src/adapter-registry.ts` (the CLI
|
|
7
|
+
* auto-detects a `.codex/config.toml` or `AGENTS.md` repo) and exported as
|
|
8
|
+
* `vigiles/codex`. It passes `assertAdapterConformance`/`assertAdapterLoadsHooks`,
|
|
9
|
+
* drives the compiler + loader against real Codex fixtures (codex.test.ts), and
|
|
10
|
+
* its transport (`mock-model.ts`) is proven against the real `codex` binary.
|
|
11
|
+
*
|
|
12
|
+
* Caveat — pillar 1 (compile) is partial: the instruction/skill *renderers* still
|
|
13
|
+
* emit the Claude-Code shape until the format-axis renderers land
|
|
14
|
+
* (`research/code-adapter-architecture.md`). Pillar 2 (harness testing) is full.
|
|
15
|
+
*/
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
18
|
+
const dialect_js_1 = require("./dialect.js");
|
|
19
|
+
const layout_js_1 = require("./layout.js");
|
|
20
|
+
const runtime_js_1 = require("./runtime.js");
|
|
21
|
+
const hook_protocol_js_1 = require("./hook-protocol.js");
|
|
22
|
+
const model_mock_js_1 = require("./model-mock.js");
|
|
23
|
+
const driver_js_1 = require("./driver.js");
|
|
24
|
+
exports.codexAdapter = {
|
|
25
|
+
name: "codex",
|
|
26
|
+
// Full convergence with Claude Code: mockable (Responses SSE) + shell hooks
|
|
27
|
+
// with veto (permissionDecision/exit 2). Both pillars, all tiers.
|
|
28
|
+
capabilities: {
|
|
29
|
+
referenceVerification: true,
|
|
30
|
+
harnessTesting: true,
|
|
31
|
+
shellHooks: true,
|
|
32
|
+
},
|
|
33
|
+
dialect: dialect_js_1.codexDialect,
|
|
34
|
+
layout: layout_js_1.codexLayout,
|
|
35
|
+
runtime: runtime_js_1.codexRuntime,
|
|
36
|
+
hookProtocol: hook_protocol_js_1.codexHookProtocol,
|
|
37
|
+
modelMock: model_mock_js_1.codexModelMock,
|
|
38
|
+
harnessTestDriver: driver_js_1.codexDriver,
|
|
39
|
+
detect(root) {
|
|
40
|
+
// A `.codex/config.toml` is a strong signal; a bare AGENTS.md is weak (many
|
|
41
|
+
// harnesses read it). (Unused while unregistered — kept for symmetry.)
|
|
42
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(root, ".codex", "config.toml")))
|
|
43
|
+
return 3;
|
|
44
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(root, layout_js_1.codexLayout.instructionFile)))
|
|
45
|
+
return 1;
|
|
46
|
+
return 0;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codexDialect — EXPERIMENTAL, internal-only. A prototype `HarnessDialect` for
|
|
3
|
+
* OpenAI Codex, built from the mid-2026 research (research/harness-landscape.md)
|
|
4
|
+
* to validate that the format axis actually generalizes beyond Claude Code. NOT
|
|
5
|
+
* exported from `vigiles/*` and NOT registered in the adapter registry — it
|
|
6
|
+
* exists to be run through the conformance kit + real Codex-shaped fixtures.
|
|
7
|
+
*/
|
|
8
|
+
import type { HarnessDialect } from "../../core/dialect.js";
|
|
9
|
+
export declare const codexDialect: HarnessDialect;
|
|
10
|
+
//# sourceMappingURL=dialect.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexDialect = void 0;
|
|
4
|
+
exports.codexDialect = {
|
|
5
|
+
name: "codex",
|
|
6
|
+
// Codex model-facing tools (shell/exec, file edits, plan, web), + MCP tools.
|
|
7
|
+
builtinAgentTools: ["shell", "apply_patch", "update_plan", "web_search"],
|
|
8
|
+
// No documented "never available to a subagent" set yet — left empty.
|
|
9
|
+
neverAvailableTools: [],
|
|
10
|
+
mcpToolPattern: /^mcp__[a-z0-9_-]+__[a-z0-9_-]+$/i,
|
|
11
|
+
// Codex hooks (near-1:1 with Claude Code) + its extras.
|
|
12
|
+
hookEvents: [
|
|
13
|
+
"SessionStart",
|
|
14
|
+
"SubagentStart",
|
|
15
|
+
"SubagentStop",
|
|
16
|
+
"PreToolUse",
|
|
17
|
+
"PostToolUse",
|
|
18
|
+
"PermissionRequest",
|
|
19
|
+
"PreCompact",
|
|
20
|
+
"PostCompact",
|
|
21
|
+
"UserPromptSubmit",
|
|
22
|
+
"Stop",
|
|
23
|
+
],
|
|
24
|
+
instructionTargets: ["AGENTS.md"],
|
|
25
|
+
pluginRootToken: "${PLUGIN_ROOT}",
|
|
26
|
+
// Codex SKILL.md frontmatter is name + description ONLY — the CC-only keys
|
|
27
|
+
// (disable-model-invocation, argument-hint, …) are not part of its format.
|
|
28
|
+
skillFrontmatter: "minimal",
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=dialect.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HarnessTestDriver, HarnessDriverContext, ParsedRun } from "../../core/harness-driver.js";
|
|
2
|
+
/**
|
|
3
|
+
* The non-interactive `codex exec` argv. The mock-wiring `-c` flags
|
|
4
|
+
* (`ctx.mockArgs`, from `runtime.wireMock`) MUST follow `exec` — placed before
|
|
5
|
+
* `exec` they're parsed as global flags and ignored (codex falls back to the
|
|
6
|
+
* real OpenAI provider). `--ephemeral` +
|
|
7
|
+
* `--dangerously-bypass-approvals-and-sandbox` make it run unattended;
|
|
8
|
+
* `--skip-git-repo-check` lets it run in a bare temp dir; `--ignore-user-config`
|
|
9
|
+
* keeps the host's `~/.codex` out. The prompt is the trailing positional.
|
|
10
|
+
* Pure, so the arg shape is unit-tested.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildCodexArgs(ctx: HarnessDriverContext): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Extract the final assistant text from codex stdout. Codex `exec` prints the
|
|
15
|
+
* assistant message as plain text (no `--json` here); we return the trimmed
|
|
16
|
+
* stdout as the output. Pure, so it's unit-tested without spawning codex.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseCodexRun(stdout: string): ParsedRun;
|
|
19
|
+
export declare const codexDriver: HarnessTestDriver;
|
|
20
|
+
//# sourceMappingURL=driver.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexDriver = void 0;
|
|
4
|
+
exports.buildCodexArgs = buildCodexArgs;
|
|
5
|
+
exports.parseCodexRun = parseCodexRun;
|
|
6
|
+
/**
|
|
7
|
+
* codexDriver — the Codex `HarnessTestDriver`. Lets the adapter-driven
|
|
8
|
+
* `runHarnessTest` drive real `codex exec` against the proven OpenAI-Responses
|
|
9
|
+
* mock (`startCodexMock`), keylessly, via the `wireMock` flag recipe in
|
|
10
|
+
* `runtime.ts`. The Codex-side analogue of `claudeCodeDriver`.
|
|
11
|
+
*
|
|
12
|
+
* Scope: pillar-2 deterministic harness testing of the *output* turn — codex's
|
|
13
|
+
* non-interactive `exec` runs the scripted mock to completion and the final
|
|
14
|
+
* assistant text lands in `parseRun().output`. Tool-call / hook stream parsing
|
|
15
|
+
* is left empty (the JSONL schema isn't worth fully parsing for the output
|
|
16
|
+
* check); a test that needs those uses Claude Code today.
|
|
17
|
+
*/
|
|
18
|
+
const node_child_process_1 = require("node:child_process");
|
|
19
|
+
const runtime_js_1 = require("./runtime.js");
|
|
20
|
+
const mock_model_js_1 = require("./mock-model.js");
|
|
21
|
+
/**
|
|
22
|
+
* The non-interactive `codex exec` argv. The mock-wiring `-c` flags
|
|
23
|
+
* (`ctx.mockArgs`, from `runtime.wireMock`) MUST follow `exec` — placed before
|
|
24
|
+
* `exec` they're parsed as global flags and ignored (codex falls back to the
|
|
25
|
+
* real OpenAI provider). `--ephemeral` +
|
|
26
|
+
* `--dangerously-bypass-approvals-and-sandbox` make it run unattended;
|
|
27
|
+
* `--skip-git-repo-check` lets it run in a bare temp dir; `--ignore-user-config`
|
|
28
|
+
* keeps the host's `~/.codex` out. The prompt is the trailing positional.
|
|
29
|
+
* Pure, so the arg shape is unit-tested.
|
|
30
|
+
*/
|
|
31
|
+
function buildCodexArgs(ctx) {
|
|
32
|
+
return [
|
|
33
|
+
"exec",
|
|
34
|
+
"--ignore-user-config",
|
|
35
|
+
"--skip-git-repo-check",
|
|
36
|
+
"--ephemeral",
|
|
37
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
38
|
+
...ctx.mockArgs,
|
|
39
|
+
"-c",
|
|
40
|
+
'model="gpt-5-codex"',
|
|
41
|
+
ctx.prompt,
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Extract the final assistant text from codex stdout. Codex `exec` prints the
|
|
46
|
+
* assistant message as plain text (no `--json` here); we return the trimmed
|
|
47
|
+
* stdout as the output. Pure, so it's unit-tested without spawning codex.
|
|
48
|
+
*/
|
|
49
|
+
function parseCodexRun(stdout) {
|
|
50
|
+
return { toolCalls: [], hooks: [], output: stdout.trim() };
|
|
51
|
+
}
|
|
52
|
+
/* v8 ignore start -- spawns the real codex CLI; exercised by the gated
|
|
53
|
+
codex-backed suite, the pure helpers above carry the testable logic. */
|
|
54
|
+
function codexAvailable() {
|
|
55
|
+
try {
|
|
56
|
+
return ((0, node_child_process_1.spawnSync)(runtime_js_1.codexRuntime.agentBinary, ["--version"], {
|
|
57
|
+
stdio: "ignore",
|
|
58
|
+
}).status === 0);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function startCodexMockHandle(script) {
|
|
65
|
+
const mock = await (0, mock_model_js_1.startCodexMock)(script.map((t) => ({ text: t.text ?? "" })));
|
|
66
|
+
return {
|
|
67
|
+
url: mock.url,
|
|
68
|
+
get requests() {
|
|
69
|
+
// Codex requests carry only the flattened prompt; adapt to ModelRequest.
|
|
70
|
+
return mock.requests.map((r) => ({
|
|
71
|
+
system: "",
|
|
72
|
+
messages: [{ role: "user", text: r.prompt }],
|
|
73
|
+
}));
|
|
74
|
+
},
|
|
75
|
+
get count() {
|
|
76
|
+
return mock.requests.length;
|
|
77
|
+
},
|
|
78
|
+
close: () => mock.close(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
exports.codexDriver = {
|
|
82
|
+
runtime: runtime_js_1.codexRuntime,
|
|
83
|
+
buildArgs: buildCodexArgs,
|
|
84
|
+
startMock: startCodexMockHandle,
|
|
85
|
+
parseRun: parseCodexRun,
|
|
86
|
+
available: codexAvailable,
|
|
87
|
+
};
|
|
88
|
+
/* v8 ignore stop */
|
|
89
|
+
//# sourceMappingURL=driver.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codexHookProtocol — EXPERIMENTAL, internal-only. Codex's hook wire protocol.
|
|
3
|
+
* Finding: it is essentially IDENTICAL to Claude Code's (exit 2 / `decision:block`
|
|
4
|
+
* / `permissionDecision:deny`) — the thin `HookProtocol` port was the right call.
|
|
5
|
+
* The genuine deltas are the env vars a hook receives + the TOML config format
|
|
6
|
+
* (the latter lives in PluginLayout.settingsFormat, not here).
|
|
7
|
+
*/
|
|
8
|
+
import type { HookProtocol } from "../../core/hook-protocol.js";
|
|
9
|
+
export declare const codexHookProtocol: HookProtocol;
|
|
10
|
+
//# sourceMappingURL=hook-protocol.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexHookProtocol = void 0;
|
|
4
|
+
exports.codexHookProtocol = {
|
|
5
|
+
name: "codex",
|
|
6
|
+
blockExitCode: 2,
|
|
7
|
+
denyDecisionValues: ["block", "deny"],
|
|
8
|
+
eventEnvVars: [
|
|
9
|
+
"session_id",
|
|
10
|
+
"cwd",
|
|
11
|
+
"hook_event_name",
|
|
12
|
+
"model",
|
|
13
|
+
"turn_id",
|
|
14
|
+
"permission_mode",
|
|
15
|
+
"PLUGIN_ROOT",
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=hook-protocol.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codexLayout — EXPERIMENTAL, internal-only. A prototype `PluginLayout` for
|
|
3
|
+
* Codex: `.codex/` conventions, `config.toml` (TOML) hooks, `AGENTS.md`,
|
|
4
|
+
* `${PLUGIN_ROOT}`. Validates the layout port + the settings-format axis against
|
|
5
|
+
* real Codex shapes. NOT exported / NOT registered.
|
|
6
|
+
*
|
|
7
|
+
* Findings surfaced by writing it (see research/codex-prototype-findings.md):
|
|
8
|
+
* - Codex has no separate JSON *manifest* — `config.toml` carries everything; we
|
|
9
|
+
* point `manifestPath` at it (its JSON parse simply fails → the loader falls
|
|
10
|
+
* through to the TOML `settingsPath`). The manifest field is CC-JSON-shaped.
|
|
11
|
+
* - MCP detection (`mcpConfigFile`/`mcpManifestKey`) is JSON-shaped, so it won't
|
|
12
|
+
* see Codex's `[mcp_servers]` TOML table — a known layout-port gap.
|
|
13
|
+
*/
|
|
14
|
+
import type { PluginLayout } from "../../core/layout.js";
|
|
15
|
+
export declare const codexLayout: PluginLayout;
|
|
16
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexLayout = void 0;
|
|
4
|
+
exports.codexLayout = {
|
|
5
|
+
name: "codex",
|
|
6
|
+
manifestPath: ".codex/config.toml",
|
|
7
|
+
hooksConventionPath: ".codex/hooks.json",
|
|
8
|
+
settingsPath: ".codex/config.toml",
|
|
9
|
+
settingsFormat: "toml",
|
|
10
|
+
instructionFile: "AGENTS.md",
|
|
11
|
+
surfaceDirs: ["skills", "prompts"],
|
|
12
|
+
materializeRoot: ".codex",
|
|
13
|
+
pluginRootToken: "${PLUGIN_ROOT}",
|
|
14
|
+
mcpConfigFile: ".mcp.json",
|
|
15
|
+
mcpManifestKey: "mcp_servers",
|
|
16
|
+
intraRefDirs: ["skills", "prompts", "hooks"],
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** One scripted assistant turn: the final text answer codex should emit. */
|
|
2
|
+
export interface CodexTurn {
|
|
3
|
+
/** Final text answer for this turn. */
|
|
4
|
+
readonly text: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* One `/v1/responses` request the mock received, parsed for assertions: the
|
|
8
|
+
* user's prompt (last `input` item with `role:"user"`), the requested model,
|
|
9
|
+
* and the declared function-tool names.
|
|
10
|
+
*/
|
|
11
|
+
export interface CodexRequest {
|
|
12
|
+
readonly prompt: string;
|
|
13
|
+
readonly model: string;
|
|
14
|
+
readonly toolNames: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export interface CodexMockHandle {
|
|
17
|
+
readonly url: string;
|
|
18
|
+
readonly port: number;
|
|
19
|
+
/** Every `/v1/responses` request the mock received, in order. */
|
|
20
|
+
readonly requests: readonly CodexRequest[];
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Render the PROVEN OpenAI-Responses SSE event sequence for a single assistant
|
|
25
|
+
* text message — `response.created` through `response.completed`. This exact
|
|
26
|
+
* sequence is confirmed to make real `codex` complete a turn. Pure and exported
|
|
27
|
+
* so the renderer is testable without the HTTP server.
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderResponsesSSE(text: string, opts?: {
|
|
30
|
+
model?: string;
|
|
31
|
+
}): string;
|
|
32
|
+
/**
|
|
33
|
+
* Parse a Responses request body — the user's prompt (the LAST `input` item
|
|
34
|
+
* with `role:"user"`, joining its `content[].text`), the requested `model`, and
|
|
35
|
+
* the declared function-tool names. Tolerates malformed JSON (returns an empty
|
|
36
|
+
* prompt). Pure and exported so request capture is testable without the server.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseResponsesRequest(body: string): {
|
|
39
|
+
prompt: string;
|
|
40
|
+
model: string;
|
|
41
|
+
toolNames: string[];
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Start the scripted Codex Responses mock on a free port. Each
|
|
45
|
+
* `POST /v1/responses` consumes the next scripted turn (the last turn repeats if
|
|
46
|
+
* codex asks for more); HEAD and any other path get `{}`. Resolves to a handle
|
|
47
|
+
* with the base `url`, the `port`, the recorded `requests`, and `close()`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function startCodexMock(script: readonly CodexTurn[], opts?: {
|
|
50
|
+
onRequest?: (req: CodexRequest) => void;
|
|
51
|
+
}): Promise<CodexMockHandle>;
|
|
52
|
+
//# sourceMappingURL=mock-model.d.ts.map
|