vigiles 2.6.0 → 4.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/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +11 -2
- package/README.md +76 -129
- package/action.yml +144 -8
- 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.d.ts +114 -0
- package/dist/adapters/claude-code/egress.js +276 -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/{run-hook.d.ts → adapters/claude-code/run-hook.d.ts} +50 -3
- package/dist/{run-hook.js → adapters/claude-code/run-hook.js} +178 -18
- 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} +10 -0
- package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +5 -3
- 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 +8 -2
- package/dist/claude-code.js +8 -2
- package/dist/cli-flags.d.ts +22 -0
- package/dist/cli-flags.js +38 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +670 -245
- 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 +29 -14
- package/dist/harness-assert.js +37 -10
- 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 +2 -2
- package/dist/linting.js +2 -2
- 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 +91 -0
- package/dist/setup-plan.js +160 -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 +3 -3
- package/dist/testing.js +3 -3
- package/dist/unit.d.ts +17 -0
- package/dist/unit.js +36 -0
- package/hooks/refs-nudge.sh +24 -0
- package/package.json +30 -15
- package/skills/edit-spec/SKILL.md +131 -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/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-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/{egress-proxy.d.ts → adapters/claude-code/egress-proxy.d.ts} +0 -0
- /package/dist/{egress-proxy.js → adapters/claude-code/egress-proxy.js} +0 -0
- /package/dist/{eval-baseline.d.ts → adapters/claude-code/eval-baseline.d.ts} +0 -0
- /package/dist/{eval-baseline.js → adapters/claude-code/eval-baseline.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
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sandboxAvailable = exports.specTrusted = exports.decideSandbox = exports.resolveHarness = exports.loadPlugin = exports.scriptModel = void 0;
|
|
3
|
+
exports.claudeCodeDriver = exports.sandboxAvailable = exports.specTrusted = exports.decideSandbox = exports.resolveHarness = exports.loadPlugin = exports.scriptModel = void 0;
|
|
4
4
|
exports.parseToolCalls = parseToolCalls;
|
|
5
5
|
exports.parseResultEvent = parseResultEvent;
|
|
6
6
|
exports.parseOutput = parseOutput;
|
|
7
7
|
exports.parseHooks = parseHooks;
|
|
8
8
|
exports.buildClaudeArgs = buildClaudeArgs;
|
|
9
|
+
exports.parseClaudeRun = parseClaudeRun;
|
|
9
10
|
exports.claudeAvailable = claudeAvailable;
|
|
10
11
|
exports.runHarnessTest = runHarnessTest;
|
|
11
12
|
/**
|
|
@@ -42,6 +43,8 @@ const node_child_process_1 = require("node:child_process");
|
|
|
42
43
|
const node_fs_1 = require("node:fs");
|
|
43
44
|
const node_os_1 = require("node:os");
|
|
44
45
|
const node_path_1 = require("node:path");
|
|
46
|
+
const adapter_conformance_js_1 = require("../../adapter-conformance.js");
|
|
47
|
+
const runtime_js_1 = require("./runtime.js");
|
|
45
48
|
const mock_model_js_1 = require("./mock-model.js");
|
|
46
49
|
const plugin_loader_js_1 = require("./plugin-loader.js");
|
|
47
50
|
const sandbox_js_1 = require("./sandbox.js");
|
|
@@ -194,13 +197,33 @@ function buildClaudeArgs(spec, hasSettings) {
|
|
|
194
197
|
...tools,
|
|
195
198
|
];
|
|
196
199
|
}
|
|
200
|
+
/** Build the `claude` argv from the driver context — wraps `buildClaudeArgs`. */
|
|
201
|
+
function buildClaudeArgsFromCtx(ctx) {
|
|
202
|
+
return buildClaudeArgs({
|
|
203
|
+
model: [],
|
|
204
|
+
prompt: ctx.prompt,
|
|
205
|
+
transcript: ctx.transcript,
|
|
206
|
+
pluginDir: ctx.pluginDir,
|
|
207
|
+
allowedTools: ctx.tools,
|
|
208
|
+
}, ctx.hasSettings);
|
|
209
|
+
}
|
|
210
|
+
/** Parse the `claude` stdout/stream into the unified trace fields. */
|
|
211
|
+
function parseClaudeRun(stdout) {
|
|
212
|
+
return {
|
|
213
|
+
toolCalls: parseToolCalls(stdout),
|
|
214
|
+
hooks: parseHooks(stdout),
|
|
215
|
+
output: parseOutput(stdout),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
197
218
|
/* v8 ignore start -- spawns the real claude CLI + filesystem; exercised by the
|
|
198
219
|
claude-backed suite, excluded from the deterministic coverage gate (the parse
|
|
199
220
|
helpers above carry the testable logic). */
|
|
200
|
-
/** Whether the
|
|
221
|
+
/** Whether the agent CLI is available — harness tests need it. */
|
|
201
222
|
function claudeAvailable() {
|
|
202
223
|
try {
|
|
203
|
-
return (0, node_child_process_1.spawnSync)(
|
|
224
|
+
return ((0, node_child_process_1.spawnSync)(runtime_js_1.claudeCodeRuntime.agentBinary, ["--version"], {
|
|
225
|
+
stdio: "ignore",
|
|
226
|
+
}).status === 0);
|
|
204
227
|
}
|
|
205
228
|
catch {
|
|
206
229
|
return false;
|
|
@@ -220,16 +243,21 @@ function writeFixture(cwd, files, settings) {
|
|
|
220
243
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(cwd, "settings.json"), json);
|
|
221
244
|
}
|
|
222
245
|
}
|
|
223
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Spawn the agent binary against the mock. The mock-wiring *args* are already in
|
|
248
|
+
* `args` (the driver placed `wireMock(url).args` at the correct argv position
|
|
249
|
+
* via `ctx.mockArgs`); here we only layer `wireMock(url).env` over the caller's
|
|
250
|
+
* env. Driver-agnostic at the transport seam.
|
|
251
|
+
*/
|
|
252
|
+
function spawnAgent(runtime, args, cwd, baseUrl, timeoutMs) {
|
|
253
|
+
const wired = runtime.wireMock(baseUrl);
|
|
224
254
|
return new Promise((resolvePromise) => {
|
|
225
|
-
const child = (0, node_child_process_1.spawn)(
|
|
255
|
+
const child = (0, node_child_process_1.spawn)(runtime.agentBinary, [...args], {
|
|
226
256
|
cwd,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
ANTHROPIC_API_KEY: "sk-vigiles-mock",
|
|
232
|
-
},
|
|
257
|
+
// Any key works — the mock ignores auth. wireMock supplies the overlay
|
|
258
|
+
// env (base-URL var + dummy key for CC; the dummy key for Codex); layer it
|
|
259
|
+
// over the caller's env.
|
|
260
|
+
env: { ...process.env, ...wired.env },
|
|
233
261
|
stdio: ["ignore", "pipe", "pipe"],
|
|
234
262
|
});
|
|
235
263
|
let stdout = "";
|
|
@@ -244,15 +272,60 @@ function spawnClaude(args, cwd, baseUrl, timeoutMs) {
|
|
|
244
272
|
});
|
|
245
273
|
}
|
|
246
274
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
275
|
+
* The Claude Code `HarnessTestDriver`: the existing argv/mock/parse seams bundled
|
|
276
|
+
* behind the port the adapter-driven runner dispatches through. Behaviourally
|
|
277
|
+
* identical to the previous hard-wired path.
|
|
278
|
+
*/
|
|
279
|
+
exports.claudeCodeDriver = {
|
|
280
|
+
runtime: runtime_js_1.claudeCodeRuntime,
|
|
281
|
+
buildArgs: buildClaudeArgsFromCtx,
|
|
282
|
+
startMock: (script) => (0, mock_model_js_1.startMock)(script),
|
|
283
|
+
parseRun: parseClaudeRun,
|
|
284
|
+
available: claudeAvailable,
|
|
285
|
+
};
|
|
286
|
+
/* v8 ignore start -- spawns the real agent CLI + filesystem; exercised by the
|
|
287
|
+
claude-backed + gated codex suites, excluded from the deterministic coverage
|
|
288
|
+
gate (the parse helpers above carry the testable logic). */
|
|
289
|
+
function makeResult(cwd, out, parsed, turns, modelRequests) {
|
|
290
|
+
return {
|
|
291
|
+
exitCode: out.code,
|
|
292
|
+
stdout: out.stdout,
|
|
293
|
+
stderr: out.stderr ?? "",
|
|
294
|
+
cwd,
|
|
295
|
+
turns,
|
|
296
|
+
toolCalls: parsed.toolCalls,
|
|
297
|
+
hooks: parsed.hooks,
|
|
298
|
+
output: parsed.output,
|
|
299
|
+
modelRequests,
|
|
300
|
+
file: (p) => {
|
|
301
|
+
const f = (0, node_path_1.resolve)(cwd, p);
|
|
302
|
+
return (0, node_fs_1.existsSync)(f) ? (0, node_fs_1.readFileSync)(f, "utf-8") : null;
|
|
303
|
+
},
|
|
304
|
+
cleanup: () => {
|
|
305
|
+
(0, node_fs_1.rmSync)(cwd, { recursive: true, force: true });
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Run the real agent CLI against a scripted mock model, with the given fixture
|
|
311
|
+
* and settings (hooks). Deterministic — same script, same result. Adapter-driven
|
|
312
|
+
* (`opts.adapter`, default Claude Code): the Claude Code path is unchanged
|
|
313
|
+
* (incl. the safe-by-default sandbox); pass `codexAdapter` to drive real codex.
|
|
249
314
|
*
|
|
250
|
-
* Safe by default: an external `plugin` / `pluginDir` brings in
|
|
251
|
-
* third-party hooks and is confined under bubblewrap (`spec.sandbox`,
|
|
252
|
-
* `"auto"`); if no sandbox is available the run REFUSES rather than
|
|
253
|
-
* unconfined. See `src/sandbox.ts`.
|
|
315
|
+
* Safe by default (Claude Code): an external `plugin` / `pluginDir` brings in
|
|
316
|
+
* untrusted third-party hooks and is confined under bubblewrap (`spec.sandbox`,
|
|
317
|
+
* default `"auto"`); if no sandbox is available the run REFUSES rather than
|
|
318
|
+
* executing unconfined. See `src/sandbox.ts`. The sandbox path is Claude Code
|
|
319
|
+
* only — requesting confinement for another harness throws.
|
|
254
320
|
*/
|
|
255
|
-
async function runHarnessTest(spec) {
|
|
321
|
+
async function runHarnessTest(spec, opts = {}) {
|
|
322
|
+
const adapter = opts.adapter;
|
|
323
|
+
// Default (no adapter): the unchanged Claude Code driver — keeps the
|
|
324
|
+
// sandbox/confined path and behaviour byte-for-byte identical.
|
|
325
|
+
const driver = adapter
|
|
326
|
+
? requireDriver(adapter)
|
|
327
|
+
: exports.claudeCodeDriver;
|
|
328
|
+
const isClaudeCode = driver.runtime.name === runtime_js_1.claudeCodeRuntime.name;
|
|
256
329
|
const decision = (0, sandbox_js_1.decideSandbox)({
|
|
257
330
|
trusted: (0, sandbox_js_1.specTrusted)(spec),
|
|
258
331
|
mode: spec.sandbox ?? "auto",
|
|
@@ -260,6 +333,9 @@ async function runHarnessTest(spec) {
|
|
|
260
333
|
});
|
|
261
334
|
if (decision.action === "throw")
|
|
262
335
|
throw new Error(decision.reason);
|
|
336
|
+
if (decision.action === "sandbox" && !isClaudeCode) {
|
|
337
|
+
throw new Error(`sandbox not supported for ${driver.runtime.name}: confined execution is Claude Code only. Pass sandbox: false to run ${driver.runtime.name} unconfined (you audited the code, or trust the outer container).`);
|
|
338
|
+
}
|
|
263
339
|
const cwd = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-harness-"));
|
|
264
340
|
const { files, settings } = (0, plugin_loader_js_1.resolveHarness)({
|
|
265
341
|
plugin: spec.plugin,
|
|
@@ -267,45 +343,50 @@ async function runHarnessTest(spec) {
|
|
|
267
343
|
files: spec.files,
|
|
268
344
|
});
|
|
269
345
|
writeFixture(cwd, files, settings);
|
|
270
|
-
const
|
|
346
|
+
const tools = spec.allowedTools ?? ["Read", "Edit", "Write", "Bash"];
|
|
271
347
|
const timeoutMs = spec.timeoutMs ?? 60000;
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
stdout: out.stdout,
|
|
275
|
-
stderr: out.stderr ?? "",
|
|
348
|
+
const buildArgs = (mockArgs) => driver.buildArgs({
|
|
349
|
+
prompt: spec.prompt ?? "go",
|
|
276
350
|
cwd,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
file: (p) => {
|
|
283
|
-
const f = (0, node_path_1.resolve)(cwd, p);
|
|
284
|
-
return (0, node_fs_1.existsSync)(f) ? (0, node_fs_1.readFileSync)(f, "utf-8") : null;
|
|
285
|
-
},
|
|
286
|
-
cleanup: () => {
|
|
287
|
-
(0, node_fs_1.rmSync)(cwd, { recursive: true, force: true });
|
|
288
|
-
},
|
|
351
|
+
hasSettings: settings !== undefined,
|
|
352
|
+
tools,
|
|
353
|
+
transcript: spec.transcript ?? false,
|
|
354
|
+
pluginDir: spec.pluginDir,
|
|
355
|
+
mockArgs,
|
|
289
356
|
});
|
|
290
|
-
// Confined path: the mock is co-launched
|
|
357
|
+
// Confined path (Claude Code only): the mock is co-launched in the netns, so
|
|
358
|
+
// the agent reaches it over the loopback URL the sandbox sets — Claude Code is
|
|
359
|
+
// env-only (empty mockArgs), so the argv needs no mock-wiring flags.
|
|
291
360
|
if (decision.action === "sandbox") {
|
|
292
361
|
const out = await (0, sandbox_js_1.runSandboxed)({
|
|
293
362
|
cwd,
|
|
294
|
-
claudeArgs:
|
|
363
|
+
claudeArgs: [...buildArgs([])],
|
|
295
364
|
script: spec.model,
|
|
296
365
|
timeoutMs,
|
|
297
366
|
});
|
|
298
|
-
return
|
|
367
|
+
return makeResult(cwd, out, parseClaudeRun(out.stdout), out.requests.length, out.requests);
|
|
299
368
|
}
|
|
300
|
-
// Direct path: mock runs in this process;
|
|
301
|
-
|
|
369
|
+
// Direct path: mock runs in this process; the agent reaches it over localhost.
|
|
370
|
+
// Start the mock first so its URL feeds the driver's mock-wiring args.
|
|
371
|
+
const mock = await driver.startMock(spec.model);
|
|
302
372
|
try {
|
|
303
|
-
const
|
|
304
|
-
|
|
373
|
+
const args = buildArgs(driver.runtime.wireMock(mock.url).args);
|
|
374
|
+
const out = await spawnAgent(driver.runtime, args, cwd, mock.url, timeoutMs);
|
|
375
|
+
return makeResult(cwd, out, driver.parseRun(out.stdout), mock.count, [
|
|
376
|
+
...mock.requests,
|
|
377
|
+
]);
|
|
305
378
|
}
|
|
306
379
|
finally {
|
|
307
|
-
mock.close();
|
|
380
|
+
await mock.close();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/** Pull the pillar-2 driver off an adapter, asserting it supports testing. */
|
|
384
|
+
function requireDriver(adapter) {
|
|
385
|
+
(0, adapter_conformance_js_1.assertHarnessTestable)(adapter);
|
|
386
|
+
if (!adapter.harnessTestDriver) {
|
|
387
|
+
throw new Error(`Adapter "${adapter.name}" declares harnessTesting but carries no harnessTestDriver — it cannot drive runHarnessTest.`);
|
|
308
388
|
}
|
|
389
|
+
return adapter.harnessTestDriver;
|
|
309
390
|
}
|
|
310
391
|
/* v8 ignore stop */
|
|
311
392
|
//# sourceMappingURL=harness-test.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claudeCodeHookProtocol — the Claude Code `HookProtocol`: a hook blocks via exit
|
|
3
|
+
* code 2 or a `permissionDecision: "deny"` / `decision: "block"` JSON decision;
|
|
4
|
+
* the event arrives on stdin. `decideHook` reads the block code + deny values
|
|
5
|
+
* from here. A Codex adapter's `codexHookProtocol` is nearly identical (the same
|
|
6
|
+
* decision model), adding its own `eventEnvVars` (session_id/PLUGIN_ROOT/…).
|
|
7
|
+
*/
|
|
8
|
+
import type { HookProtocol } from "../../core/hook-protocol.js";
|
|
9
|
+
export declare const claudeCodeHookProtocol: HookProtocol;
|
|
10
|
+
//# sourceMappingURL=hook-protocol.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.claudeCodeHookProtocol = void 0;
|
|
4
|
+
exports.claudeCodeHookProtocol = {
|
|
5
|
+
name: "claude-code",
|
|
6
|
+
blockExitCode: 2,
|
|
7
|
+
denyDecisionValues: ["block", "deny"],
|
|
8
|
+
eventEnvVars: [],
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=hook-protocol.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claudeCodeLayout — the Claude Code plugin/repo layout (the `PluginLayout`
|
|
3
|
+
* port's reference implementation). `loadPlugin` defaults to it; a Codex adapter
|
|
4
|
+
* defines a sibling `codexLayout` and passes it to the same loader.
|
|
5
|
+
*/
|
|
6
|
+
import type { PluginLayout } from "../../core/layout.js";
|
|
7
|
+
export declare const claudeCodeLayout: PluginLayout;
|
|
8
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.claudeCodeLayout = void 0;
|
|
4
|
+
exports.claudeCodeLayout = {
|
|
5
|
+
name: "claude-code",
|
|
6
|
+
manifestPath: ".claude-plugin/plugin.json",
|
|
7
|
+
hooksConventionPath: "hooks/hooks.json",
|
|
8
|
+
settingsPath: ".claude/settings.json",
|
|
9
|
+
settingsFormat: "json",
|
|
10
|
+
instructionFile: "CLAUDE.md",
|
|
11
|
+
surfaceDirs: ["skills", "agents", "commands"],
|
|
12
|
+
materializeRoot: ".claude",
|
|
13
|
+
pluginRootToken: "${CLAUDE_PLUGIN_ROOT}",
|
|
14
|
+
mcpConfigFile: ".mcp.json",
|
|
15
|
+
mcpManifestKey: "mcpServers",
|
|
16
|
+
intraRefDirs: ["hooks", "skills", "agents", "commands"],
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
/** Final text answer (stops the turn). */
|
|
4
|
-
readonly text?: string;
|
|
5
|
-
/** A tool to invoke, e.g. "Bash" | "Write" | "Edit". */
|
|
6
|
-
readonly tool?: string;
|
|
7
|
-
/** The tool input, e.g. `{ file_path, content }` or `{ command }`. */
|
|
8
|
-
readonly input?: Record<string, unknown>;
|
|
9
|
-
}
|
|
1
|
+
import type { ModelTurn, ModelRequest } from "../../core/harness-driver.js";
|
|
2
|
+
export type { ModelTurn, ModelRequest } from "../../core/harness-driver.js";
|
|
10
3
|
/** Build a scripted model from an ordered list of turns. */
|
|
11
4
|
export declare function scriptModel(turns: readonly ModelTurn[]): ModelTurn[];
|
|
12
5
|
export interface TurnInfo {
|
|
@@ -14,21 +7,6 @@ export interface TurnInfo {
|
|
|
14
7
|
readonly stream: boolean;
|
|
15
8
|
readonly hasToolResult: boolean;
|
|
16
9
|
}
|
|
17
|
-
/**
|
|
18
|
-
* One `/v1/messages` request the mock received, flattened to text for
|
|
19
|
-
* assertions. This is the seam that lets a harness test prove what reached the
|
|
20
|
-
* model — a SessionStart hook's injected `additionalContext`, or a slash
|
|
21
|
-
* command's expansion — not just that a hook fired.
|
|
22
|
-
*/
|
|
23
|
-
export interface ModelRequest {
|
|
24
|
-
/** The system prompt, flattened to text (string or text-block array). */
|
|
25
|
-
readonly system: string;
|
|
26
|
-
/** The conversation messages, each flattened to `{ role, text }`. */
|
|
27
|
-
readonly messages: readonly {
|
|
28
|
-
readonly role: string;
|
|
29
|
-
readonly text: string;
|
|
30
|
-
}[];
|
|
31
|
-
}
|
|
32
10
|
export interface MockHandle {
|
|
33
11
|
readonly url: string;
|
|
34
12
|
close(): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claudeCodeModelMock — the Claude Code `ModelMock`: the mock speaks Anthropic
|
|
3
|
+
* **Messages** SSE; a turn-consuming request hits `/v1/messages`, and the client
|
|
4
|
+
* probes `/v1/messages/count_tokens`. `startMock` reads these endpoints from
|
|
5
|
+
* here. A Codex adapter's `codexModelMock` would set `wireApi:
|
|
6
|
+
* "openai-responses"` and `modelEndpoint: "/v1/responses"` with no count-tokens
|
|
7
|
+
* endpoint, and ship its own Responses-SSE renderer.
|
|
8
|
+
*/
|
|
9
|
+
import type { ModelMock } from "../../core/model-mock.js";
|
|
10
|
+
export declare const claudeCodeModelMock: ModelMock;
|
|
11
|
+
//# sourceMappingURL=model-mock.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.claudeCodeModelMock = void 0;
|
|
4
|
+
exports.claudeCodeModelMock = {
|
|
5
|
+
name: "claude-code",
|
|
6
|
+
wireApi: "anthropic-messages",
|
|
7
|
+
modelEndpoint: "/v1/messages",
|
|
8
|
+
countTokensEndpoint: "count_tokens",
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=model-mock.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vigiles — the Claude Code wrapper over the harness-agnostic plugin loader.
|
|
3
|
+
*
|
|
4
|
+
* The generic loader lives at the composition root (`src/plugin-loader.ts`) and
|
|
5
|
+
* takes a `PluginLayout` by injection, so no adapter imports a sibling adapter.
|
|
6
|
+
* This thin wrapper supplies the Claude Code layout as the default, preserving
|
|
7
|
+
* the `loadPlugin(dir)` / `resolveHarness(opts)` ergonomics and the public
|
|
8
|
+
* `vigiles/claude-code` + `vigiles/plugin-loader` exports unchanged.
|
|
9
|
+
*/
|
|
10
|
+
import type { PluginLayout } from "../../core/layout.js";
|
|
11
|
+
import { loadPlugin as loadPluginWith, resolveHarness as resolveHarnessWith } from "../../plugin-loader.js";
|
|
12
|
+
export type { LoadedPlugin } from "../../plugin-loader.js";
|
|
13
|
+
/** Load the real Claude Code harness at `pluginPath` (defaults to `claudeCodeLayout`). */
|
|
14
|
+
export declare function loadPlugin(pluginPath: string, layout?: PluginLayout): ReturnType<typeof loadPluginWith>;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the effective harness for a test/eval (arm) under the Claude Code
|
|
17
|
+
* layout by default. Delegates to the generic `resolveHarness` at the
|
|
18
|
+
* composition root.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveHarness(opts: {
|
|
21
|
+
plugin?: string;
|
|
22
|
+
settings?: unknown;
|
|
23
|
+
files?: Record<string, string>;
|
|
24
|
+
}, layout?: PluginLayout): ReturnType<typeof resolveHarnessWith>;
|
|
25
|
+
//# sourceMappingURL=plugin-loader.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadPlugin = loadPlugin;
|
|
4
|
+
exports.resolveHarness = resolveHarness;
|
|
5
|
+
const plugin_loader_js_1 = require("../../plugin-loader.js");
|
|
6
|
+
const layout_js_1 = require("./layout.js");
|
|
7
|
+
/** Load the real Claude Code harness at `pluginPath` (defaults to `claudeCodeLayout`). */
|
|
8
|
+
function loadPlugin(pluginPath, layout = layout_js_1.claudeCodeLayout) {
|
|
9
|
+
return (0, plugin_loader_js_1.loadPlugin)(pluginPath, layout);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the effective harness for a test/eval (arm) under the Claude Code
|
|
13
|
+
* layout by default. Delegates to the generic `resolveHarness` at the
|
|
14
|
+
* composition root.
|
|
15
|
+
*/
|
|
16
|
+
function resolveHarness(opts, layout = layout_js_1.claudeCodeLayout) {
|
|
17
|
+
return (0, plugin_loader_js_1.resolveHarness)(opts, layout);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=plugin-loader.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HookProtocol } from "../../core/hook-protocol.js";
|
|
1
2
|
import { type SandboxMode, type EgressAttempt } from "./sandbox.js";
|
|
2
3
|
export type { EgressAttempt };
|
|
3
4
|
/** A hook event payload (the JSON Claude Code writes to the hook's stdin). */
|
|
@@ -50,6 +51,15 @@ export interface RunHookOptions {
|
|
|
50
51
|
* mirrors the harness tier, where trust follows `plugin`/`pluginDir`
|
|
51
52
|
* provenance (`specTrusted` in `src/sandbox.ts`); the unit tier takes a raw
|
|
52
53
|
* command string with no provenance signal, so you declare it here.
|
|
54
|
+
*
|
|
55
|
+
* Why this is opt-in (untrusted) and not always-on: the sandbox isn't always
|
|
56
|
+
* available (Linux + working userns only — forcing it would *refuse* a hook you
|
|
57
|
+
* wrote on macOS/hardened CI), it's deliberately hostile (no egress,
|
|
58
|
+
* `--clearenv`, empty HOME, read-only fs — a false failure for trusted code that
|
|
59
|
+
* needs the network or an env var), trust follows provenance (you already
|
|
60
|
+
* vouched for inline code), and direct exec is ms vs. the confined path's
|
|
61
|
+
* setup+spawn. See `docs/sandboxing.md` ("Why confinement is opt-in"). Use
|
|
62
|
+
* `sandbox: "strict"` to force confinement even on trusted code.
|
|
53
63
|
*/
|
|
54
64
|
readonly trusted?: boolean;
|
|
55
65
|
/**
|
|
@@ -75,6 +85,22 @@ export interface RunHookOptions {
|
|
|
75
85
|
* best-effort observability over it.
|
|
76
86
|
*/
|
|
77
87
|
readonly recordEgress?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Allowlisted egress: let the hook actually reach the network, but ONLY the
|
|
90
|
+
* listed hosts, with the boundary at the **packet layer** (an `nft` allowlist
|
|
91
|
+
* inside the sandbox netns, fed by `slirp4netns`) — so a raw socket to an
|
|
92
|
+
* off-list host is dropped too, which a `recordEgress`/`HTTP_PROXY` allowlist
|
|
93
|
+
* cannot guarantee. Use it to test a hook/skill whose setup needs a real
|
|
94
|
+
* `npm install` from a registry you expect, and nothing else. Implies
|
|
95
|
+
* confinement (it needs the netns), and **refuses** if bubblewrap + slirp4netns
|
|
96
|
+
* + nft aren't available. The hosts are resolved to IPs at launch; results land
|
|
97
|
+
* in {@link HookRunResult.egress} (the allowlisted hosts that were reached, with
|
|
98
|
+
* `allowed: true`) and {@link HookRunResult.egressDropped} (how much off-list
|
|
99
|
+
* traffic was blocked). See `docs/sandboxing.md`.
|
|
100
|
+
*/
|
|
101
|
+
readonly egress?: {
|
|
102
|
+
readonly allow: readonly string[];
|
|
103
|
+
};
|
|
78
104
|
}
|
|
79
105
|
export interface HookRunResult {
|
|
80
106
|
readonly exitCode: number;
|
|
@@ -88,10 +114,21 @@ export interface HookRunResult {
|
|
|
88
114
|
*/
|
|
89
115
|
readonly blocked: boolean;
|
|
90
116
|
/**
|
|
91
|
-
* Network egress the hook attempted
|
|
92
|
-
* {@link RunHookOptions.
|
|
117
|
+
* Network egress the hook attempted. With {@link RunHookOptions.recordEgress}:
|
|
118
|
+
* every (blocked) attempt the proxy saw. With {@link RunHookOptions.egress}: the
|
|
119
|
+
* allowlisted hosts that were actually reached (`allowed: true`, with packet
|
|
120
|
+
* counts). Empty otherwise.
|
|
93
121
|
*/
|
|
94
122
|
readonly egress: readonly EgressAttempt[];
|
|
123
|
+
/**
|
|
124
|
+
* Allowlisted-egress mode only: the aggregate off-allowlist traffic the
|
|
125
|
+
* packet-layer wall dropped. `packets === 0` means the hook stayed entirely
|
|
126
|
+
* within the allowlist. Undefined when {@link RunHookOptions.egress} was unset.
|
|
127
|
+
*/
|
|
128
|
+
readonly egressDropped?: {
|
|
129
|
+
readonly packets: number;
|
|
130
|
+
readonly bytes: number;
|
|
131
|
+
};
|
|
95
132
|
/**
|
|
96
133
|
* Files the hook wrote to its work dir (relative paths), recorded on confined
|
|
97
134
|
* runs — what a hook touched on disk. Empty on a direct (unconfined) run.
|
|
@@ -111,7 +148,7 @@ export declare function parseHookOutput(stdout: string): HookOutput | null;
|
|
|
111
148
|
* Decide whether a hook result blocked, and the normalized decision. Pure, so
|
|
112
149
|
* the policy is unit-testable independent of spawning anything.
|
|
113
150
|
*/
|
|
114
|
-
export declare function decideHook(exitCode: number, json: HookOutput | null): {
|
|
151
|
+
export declare function decideHook(exitCode: number, json: HookOutput | null, protocol?: HookProtocol): {
|
|
115
152
|
blocked: boolean;
|
|
116
153
|
decision: HookRunResult["decision"];
|
|
117
154
|
};
|
|
@@ -123,6 +160,11 @@ export interface HookSpawnResult {
|
|
|
123
160
|
readonly stderr: string;
|
|
124
161
|
/** Egress attempts captured by the in-sandbox recorder (recordEgress only). */
|
|
125
162
|
readonly egress?: readonly EgressAttempt[];
|
|
163
|
+
/** Off-allowlist traffic the packet-layer wall dropped (egress mode only). */
|
|
164
|
+
readonly egressDropped?: {
|
|
165
|
+
readonly packets: number;
|
|
166
|
+
readonly bytes: number;
|
|
167
|
+
};
|
|
126
168
|
/** Files the hook wrote to its work dir (confined runs). */
|
|
127
169
|
readonly filesWritten?: readonly string[];
|
|
128
170
|
}
|
|
@@ -132,10 +174,14 @@ export type HookSpawner = (command: string, input: HookInput, opts: RunHookOptio
|
|
|
132
174
|
export interface RunHookDeps {
|
|
133
175
|
/** Whether bubblewrap confinement is available (Linux + bwrap). */
|
|
134
176
|
readonly available: boolean;
|
|
177
|
+
/** Whether allowlisted egress is available (bwrap + slirp4netns + nft). */
|
|
178
|
+
readonly egressAvailable: boolean;
|
|
135
179
|
/** Run the command directly (unconfined). */
|
|
136
180
|
readonly direct: HookSpawner;
|
|
137
181
|
/** Run the command confined under bubblewrap. */
|
|
138
182
|
readonly sandboxed: HookSpawner;
|
|
183
|
+
/** Run the command confined with an allowlisted-egress netns. */
|
|
184
|
+
readonly egress: HookSpawner;
|
|
139
185
|
}
|
|
140
186
|
/**
|
|
141
187
|
* The hook-run orchestration with injectable spawn seams: pick direct vs.
|
|
@@ -145,6 +191,7 @@ export interface RunHookDeps {
|
|
|
145
191
|
* bwrap. `runHook` is this with the real seams.
|
|
146
192
|
*/
|
|
147
193
|
export declare function runHookWith(command: string, input: HookInput, opts: RunHookOptions, deps: RunHookDeps): HookRunResult;
|
|
194
|
+
export declare function egressRoutes(): boolean;
|
|
148
195
|
/**
|
|
149
196
|
* Run a hook command, piping `input` as JSON to its stdin, and report the exit
|
|
150
197
|
* code + parsed decision. Synchronous (so it can be used inside an eval's
|