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
|
@@ -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
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { HookProtocol } from "../../core/hook-protocol.js";
|
|
2
|
+
import { type SandboxMode, type EgressAttempt } from "./sandbox.js";
|
|
3
|
+
export type { EgressAttempt };
|
|
4
|
+
/** A hook event payload (the JSON Claude Code writes to the hook's stdin). */
|
|
5
|
+
export interface HookInput {
|
|
6
|
+
/** e.g. "PreToolUse", "PostToolUse", "Stop", "SessionStart", "PreCompact". */
|
|
7
|
+
readonly hook_event_name?: string;
|
|
8
|
+
/** PreToolUse/PostToolUse. */
|
|
9
|
+
readonly tool_name?: string;
|
|
10
|
+
readonly tool_input?: unknown;
|
|
11
|
+
readonly tool_response?: unknown;
|
|
12
|
+
/** UserPromptSubmit. */
|
|
13
|
+
readonly prompt?: string;
|
|
14
|
+
/** SessionStart. */
|
|
15
|
+
readonly source?: string;
|
|
16
|
+
/** Stop / SubagentStop. */
|
|
17
|
+
readonly stop_hook_active?: boolean;
|
|
18
|
+
/** Any other event-specific fields. */
|
|
19
|
+
readonly [k: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/** The JSON a hook may print on stdout (all fields optional). */
|
|
22
|
+
export interface HookOutput {
|
|
23
|
+
readonly decision?: "approve" | "block";
|
|
24
|
+
readonly reason?: string;
|
|
25
|
+
readonly continue?: boolean;
|
|
26
|
+
readonly stopReason?: string;
|
|
27
|
+
readonly suppressOutput?: boolean;
|
|
28
|
+
readonly systemMessage?: string;
|
|
29
|
+
readonly hookSpecificOutput?: {
|
|
30
|
+
readonly hookEventName?: string;
|
|
31
|
+
readonly permissionDecision?: "allow" | "deny" | "ask";
|
|
32
|
+
readonly permissionDecisionReason?: string;
|
|
33
|
+
readonly additionalContext?: string;
|
|
34
|
+
};
|
|
35
|
+
readonly [k: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
export interface RunHookOptions {
|
|
38
|
+
/** Working directory for the hook process. Default: a value won't be set. */
|
|
39
|
+
readonly cwd?: string;
|
|
40
|
+
/** Extra env vars (merged over process.env). `{cwd}` in values is left as-is. */
|
|
41
|
+
readonly env?: Record<string, string>;
|
|
42
|
+
/** Per-run timeout ms. Default 10000. */
|
|
43
|
+
readonly timeoutMs?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Provenance of the hook command. `true` (default) means YOU authored it — the
|
|
46
|
+
* usual case at this tier, a command written inline in the test — so it runs
|
|
47
|
+
* directly. `false` marks it foreign (a vendored third-party hook script),
|
|
48
|
+
* which makes confinement the DEFAULT: with no explicit `sandbox`, an untrusted
|
|
49
|
+
* hook behaves as `sandbox: "auto"` — confined under bubblewrap, or refused if
|
|
50
|
+
* none is available — so foreign code is never run unconfined by accident. This
|
|
51
|
+
* mirrors the harness tier, where trust follows `plugin`/`pluginDir`
|
|
52
|
+
* provenance (`specTrusted` in `src/sandbox.ts`); the unit tier takes a raw
|
|
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.
|
|
63
|
+
*/
|
|
64
|
+
readonly trusted?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Confine the hook under bubblewrap (Linux). When unset, the mode follows
|
|
67
|
+
* {@link RunHookOptions.trusted}: a trusted hook runs directly (`false`), an
|
|
68
|
+
* untrusted one is confined-or-refused (`"auto"`). Set it explicitly to
|
|
69
|
+
* override: `"auto"`/`"strict"` force confinement (a no-egress namespace with a
|
|
70
|
+
* cleared environment — your `opts.env` is added back — or a **refusal** if no
|
|
71
|
+
* bwrap is available), and `false` is the opt-out that runs even untrusted code
|
|
72
|
+
* unconfined. macOS/Windows have no bwrap, so `"auto"`/`"strict"` throw there —
|
|
73
|
+
* see `src/sandbox.ts`.
|
|
74
|
+
*/
|
|
75
|
+
readonly sandbox?: SandboxMode;
|
|
76
|
+
/**
|
|
77
|
+
* Record the hook's network egress. Implies confinement (the recorder lives in
|
|
78
|
+
* the sandbox netns, so this forces a sandboxed run and refuses if no sandbox is
|
|
79
|
+
* available). A recording proxy on loopback captures every `host:port` a
|
|
80
|
+
* proxy-honoring tool (npm/pip/curl/fetch) tries to reach — surfaced as
|
|
81
|
+
* {@link HookRunResult.egress} — while the netns still **blocks** it (nothing
|
|
82
|
+
* actually leaves). Use it to test what a hook/skill phones home to, or which
|
|
83
|
+
* registry an install would hit. Raw-socket egress is blocked but not recorded
|
|
84
|
+
* (it never reaches the proxy) — the block is the boundary, the record is
|
|
85
|
+
* best-effort observability over it.
|
|
86
|
+
*/
|
|
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
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export interface HookRunResult {
|
|
106
|
+
readonly exitCode: number;
|
|
107
|
+
readonly stdout: string;
|
|
108
|
+
readonly stderr: string;
|
|
109
|
+
/** Parsed stdout JSON if the hook emitted a JSON decision, else null. */
|
|
110
|
+
readonly json: HookOutput | null;
|
|
111
|
+
/**
|
|
112
|
+
* Normalized decision: a deny/block via exit 2, `decision:"block"`, or
|
|
113
|
+
* `permissionDecision:"deny"` all set `blocked = true`.
|
|
114
|
+
*/
|
|
115
|
+
readonly blocked: boolean;
|
|
116
|
+
/**
|
|
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.
|
|
121
|
+
*/
|
|
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
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Files the hook wrote to its work dir (relative paths), recorded on confined
|
|
134
|
+
* runs — what a hook touched on disk. Empty on a direct (unconfined) run.
|
|
135
|
+
* Assert over it with `assertNoWrite` / `assertWroteOnly`.
|
|
136
|
+
*/
|
|
137
|
+
readonly filesWritten: readonly string[];
|
|
138
|
+
/**
|
|
139
|
+
* The decision the hook expressed, preferring the structured
|
|
140
|
+
* `permissionDecision` ("allow"|"deny"|"ask") then legacy `decision`
|
|
141
|
+
* ("approve"|"block"), else undefined.
|
|
142
|
+
*/
|
|
143
|
+
readonly decision: HookOutput["decision"] | "allow" | "deny" | "ask" | undefined;
|
|
144
|
+
}
|
|
145
|
+
/** Parse stdout as a hook JSON decision (pure, testable without a process). */
|
|
146
|
+
export declare function parseHookOutput(stdout: string): HookOutput | null;
|
|
147
|
+
/**
|
|
148
|
+
* Decide whether a hook result blocked, and the normalized decision. Pure, so
|
|
149
|
+
* the policy is unit-testable independent of spawning anything.
|
|
150
|
+
*/
|
|
151
|
+
export declare function decideHook(exitCode: number, json: HookOutput | null, protocol?: HookProtocol): {
|
|
152
|
+
blocked: boolean;
|
|
153
|
+
decision: HookRunResult["decision"];
|
|
154
|
+
};
|
|
155
|
+
/** The raw fields of a hook spawn that the result parser needs. */
|
|
156
|
+
export interface HookSpawnResult {
|
|
157
|
+
readonly status: number | null;
|
|
158
|
+
readonly signal: string | null;
|
|
159
|
+
readonly stdout: string;
|
|
160
|
+
readonly stderr: string;
|
|
161
|
+
/** Egress attempts captured by the in-sandbox recorder (recordEgress only). */
|
|
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
|
+
};
|
|
168
|
+
/** Files the hook wrote to its work dir (confined runs). */
|
|
169
|
+
readonly filesWritten?: readonly string[];
|
|
170
|
+
}
|
|
171
|
+
/** Spawn a hook (command + piped event) — the injectable seam over the real spawn. */
|
|
172
|
+
export type HookSpawner = (command: string, input: HookInput, opts: RunHookOptions) => HookSpawnResult;
|
|
173
|
+
/** The spawn seams `runHookWith` needs, so its decision logic is testable. */
|
|
174
|
+
export interface RunHookDeps {
|
|
175
|
+
/** Whether bubblewrap confinement is available (Linux + bwrap). */
|
|
176
|
+
readonly available: boolean;
|
|
177
|
+
/** Whether allowlisted egress is available (bwrap + slirp4netns + nft). */
|
|
178
|
+
readonly egressAvailable: boolean;
|
|
179
|
+
/** Run the command directly (unconfined). */
|
|
180
|
+
readonly direct: HookSpawner;
|
|
181
|
+
/** Run the command confined under bubblewrap. */
|
|
182
|
+
readonly sandboxed: HookSpawner;
|
|
183
|
+
/** Run the command confined with an allowlisted-egress netns. */
|
|
184
|
+
readonly egress: HookSpawner;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The hook-run orchestration with injectable spawn seams: pick direct vs.
|
|
188
|
+
* confined via the safe-by-default policy (`decideSandbox`), then parse the exit
|
|
189
|
+
* code + stdout into a normalized decision. Exported so all three branches
|
|
190
|
+
* (direct / sandbox / refuse) are unit-tested with fake spawners — no real
|
|
191
|
+
* bwrap. `runHook` is this with the real seams.
|
|
192
|
+
*/
|
|
193
|
+
export declare function runHookWith(command: string, input: HookInput, opts: RunHookOptions, deps: RunHookDeps): HookRunResult;
|
|
194
|
+
export declare function egressRoutes(): boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Run a hook command, piping `input` as JSON to its stdin, and report the exit
|
|
197
|
+
* code + parsed decision. Synchronous (so it can be used inside an eval's
|
|
198
|
+
* `measure` too). `command` is run through a shell, so the same command string a
|
|
199
|
+
* plugin ships (with args / env refs) works verbatim. Mark a hook you didn't
|
|
200
|
+
* write with `trusted: false` and it is confined by default (or pass `sandbox:
|
|
201
|
+
* "auto"` directly) — see {@link RunHookOptions.trusted}.
|
|
202
|
+
*/
|
|
203
|
+
export declare function runHook(command: string, input: HookInput, opts?: RunHookOptions): HookRunResult;
|
|
204
|
+
//# sourceMappingURL=run-hook.d.ts.map
|