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.
Files changed (205) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +11 -2
  3. package/README.md +76 -129
  4. package/action.yml +144 -8
  5. package/dist/action-gate.d.ts +1 -1
  6. package/dist/action-gate.js +1 -1
  7. package/dist/adapter-conformance.d.ts +30 -0
  8. package/dist/adapter-conformance.js +153 -0
  9. package/dist/adapter-registry.d.ts +42 -0
  10. package/dist/adapter-registry.js +55 -0
  11. package/dist/adapter.d.ts +26 -0
  12. package/dist/adapter.js +16 -0
  13. package/dist/adapters/claude-code/adapter.d.ts +3 -0
  14. package/dist/adapters/claude-code/adapter.js +46 -0
  15. package/dist/{agent-result.d.ts → adapters/claude-code/agent-result.d.ts} +1 -1
  16. package/dist/adapters/claude-code/dialect.d.ts +13 -0
  17. package/dist/adapters/claude-code/dialect.js +51 -0
  18. package/dist/adapters/claude-code/egress-entry.d.ts +2 -0
  19. package/dist/adapters/claude-code/egress-entry.js +115 -0
  20. package/dist/adapters/claude-code/egress.d.ts +114 -0
  21. package/dist/adapters/claude-code/egress.js +276 -0
  22. package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
  23. package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
  24. package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
  25. package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
  26. package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
  27. package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
  28. package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
  29. package/dist/adapters/claude-code/hook-protocol.js +10 -0
  30. package/dist/adapters/claude-code/layout.d.ts +8 -0
  31. package/dist/adapters/claude-code/layout.js +18 -0
  32. package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
  33. package/dist/adapters/claude-code/model-mock.d.ts +11 -0
  34. package/dist/adapters/claude-code/model-mock.js +10 -0
  35. package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
  36. package/dist/adapters/claude-code/plugin-loader.js +19 -0
  37. package/dist/{run-hook.d.ts → adapters/claude-code/run-hook.d.ts} +50 -3
  38. package/dist/{run-hook.js → adapters/claude-code/run-hook.js} +178 -18
  39. package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
  40. package/dist/adapters/claude-code/run-scripts.js +150 -0
  41. package/dist/adapters/claude-code/runtime.d.ts +16 -0
  42. package/dist/adapters/claude-code/runtime.js +39 -0
  43. package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +10 -0
  44. package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +5 -3
  45. package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
  46. package/dist/adapters/codex/adapter.d.ts +3 -0
  47. package/dist/adapters/codex/adapter.js +49 -0
  48. package/dist/adapters/codex/dialect.d.ts +10 -0
  49. package/dist/adapters/codex/dialect.js +30 -0
  50. package/dist/adapters/codex/driver.d.ts +20 -0
  51. package/dist/adapters/codex/driver.js +89 -0
  52. package/dist/adapters/codex/hook-protocol.d.ts +10 -0
  53. package/dist/adapters/codex/hook-protocol.js +18 -0
  54. package/dist/adapters/codex/layout.d.ts +16 -0
  55. package/dist/adapters/codex/layout.js +18 -0
  56. package/dist/adapters/codex/mock-model.d.ts +52 -0
  57. package/dist/adapters/codex/mock-model.js +210 -0
  58. package/dist/adapters/codex/model-mock.d.ts +11 -0
  59. package/dist/adapters/codex/model-mock.js +10 -0
  60. package/dist/adapters/codex/runtime.d.ts +38 -0
  61. package/dist/adapters/codex/runtime.js +62 -0
  62. package/dist/adapters/opencode/adapter.d.ts +3 -0
  63. package/dist/adapters/opencode/adapter.js +48 -0
  64. package/dist/adapters/opencode/dialect.d.ts +10 -0
  65. package/dist/adapters/opencode/dialect.js +36 -0
  66. package/dist/adapters/opencode/layout.d.ts +9 -0
  67. package/dist/adapters/opencode/layout.js +25 -0
  68. package/dist/adapters/opencode/model-mock.d.ts +10 -0
  69. package/dist/adapters/opencode/model-mock.js +10 -0
  70. package/dist/adapters/opencode/runtime.d.ts +9 -0
  71. package/dist/adapters/opencode/runtime.js +21 -0
  72. package/dist/claude-code.d.ts +8 -2
  73. package/dist/claude-code.js +8 -2
  74. package/dist/cli-flags.d.ts +22 -0
  75. package/dist/cli-flags.js +38 -0
  76. package/dist/cli.d.ts +1 -1
  77. package/dist/cli.js +670 -245
  78. package/dist/codex.d.ts +20 -0
  79. package/dist/codex.js +36 -0
  80. package/dist/community-skills.d.ts +1 -1
  81. package/dist/community-skills.js +2 -2
  82. package/dist/core/adapter.d.ts +88 -0
  83. package/dist/core/adapter.js +3 -0
  84. package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
  85. package/dist/{compile.js → core/compile.js} +68 -53
  86. package/dist/core/compose.d.ts +79 -0
  87. package/dist/core/compose.js +145 -0
  88. package/dist/core/dialect.d.ts +51 -0
  89. package/dist/core/dialect.js +3 -0
  90. package/dist/core/harness-driver.d.ts +134 -0
  91. package/dist/core/harness-driver.js +3 -0
  92. package/dist/core/hook-protocol.d.ts +28 -0
  93. package/dist/core/hook-protocol.js +3 -0
  94. package/dist/core/layout.d.ts +43 -0
  95. package/dist/core/layout.js +3 -0
  96. package/dist/core/model-mock.d.ts +27 -0
  97. package/dist/core/model-mock.js +3 -0
  98. package/dist/core/refs.d.ts +62 -0
  99. package/dist/{refs.js → core/refs.js} +49 -23
  100. package/dist/core/runtime.d.ts +38 -0
  101. package/dist/core/runtime.js +3 -0
  102. package/dist/{types.d.ts → core/types.d.ts} +29 -0
  103. package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
  104. package/dist/{validate.js → core/validate.js} +41 -4
  105. package/dist/e2e.d.ts +19 -0
  106. package/dist/e2e.js +39 -0
  107. package/dist/harness-assert.d.ts +29 -14
  108. package/dist/harness-assert.js +37 -10
  109. package/dist/integration.d.ts +16 -0
  110. package/dist/integration.js +32 -0
  111. package/dist/leaderboard.d.ts +33 -0
  112. package/dist/leaderboard.js +107 -0
  113. package/dist/linting.d.ts +2 -2
  114. package/dist/linting.js +2 -2
  115. package/dist/plugin-loader.d.ts +6 -3
  116. package/dist/plugin-loader.js +101 -49
  117. package/dist/scan.d.ts +49 -0
  118. package/dist/scan.js +177 -0
  119. package/dist/setup-plan.d.ts +91 -0
  120. package/dist/setup-plan.js +160 -0
  121. package/dist/skill-test.d.ts +1 -1
  122. package/dist/skill-test.js +1 -1
  123. package/dist/test-coverage.d.ts +71 -0
  124. package/dist/test-coverage.js +228 -0
  125. package/dist/testing.d.ts +3 -3
  126. package/dist/testing.js +3 -3
  127. package/dist/unit.d.ts +17 -0
  128. package/dist/unit.js +36 -0
  129. package/hooks/refs-nudge.sh +24 -0
  130. package/package.json +30 -15
  131. package/skills/edit-spec/SKILL.md +131 -0
  132. package/skills/generate-rule/SKILL.md +64 -0
  133. package/skills/linter-docs/clippy.md +241 -0
  134. package/skills/linter-docs/eslint.md +384 -0
  135. package/skills/linter-docs/pylint.md +288 -0
  136. package/skills/linter-docs/rubocop.md +277 -0
  137. package/skills/linter-docs/ruff.md +187 -0
  138. package/skills/linter-docs/stylelint.md +247 -0
  139. package/skills/migrate-to-spec/SKILL.md +126 -0
  140. package/skills/strengthen/SKILL.md +168 -0
  141. package/skills/test-harness/SKILL.md +157 -0
  142. package/dist/action.d.ts +0 -7
  143. package/dist/action.js +0 -180
  144. package/dist/refs.d.ts +0 -44
  145. package/dist/run-scripts.d.ts +0 -20
  146. package/dist/run-scripts.js +0 -70
  147. /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
  148. /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
  149. /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
  150. /package/dist/{egress-proxy.d.ts → adapters/claude-code/egress-proxy.d.ts} +0 -0
  151. /package/dist/{egress-proxy.js → adapters/claude-code/egress-proxy.js} +0 -0
  152. /package/dist/{eval-baseline.d.ts → adapters/claude-code/eval-baseline.d.ts} +0 -0
  153. /package/dist/{eval-baseline.js → adapters/claude-code/eval-baseline.js} +0 -0
  154. /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
  155. /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
  156. /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
  157. /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
  158. /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
  159. /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
  160. /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
  161. /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
  162. /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
  163. /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
  164. /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
  165. /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
  166. /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
  167. /package/dist/{coverage.js → core/coverage.js} +0 -0
  168. /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
  169. /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
  170. /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
  171. /package/dist/{evolve.js → core/evolve.js} +0 -0
  172. /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
  173. /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
  174. /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
  175. /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
  176. /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
  177. /package/dist/{generate-types.js → core/generate-types.js} +0 -0
  178. /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
  179. /package/dist/{hash.js → core/hash.js} +0 -0
  180. /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
  181. /package/dist/{inline.js → core/inline.js} +0 -0
  182. /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
  183. /package/dist/{integrity.js → core/integrity.js} +0 -0
  184. /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
  185. /package/dist/{linters.js → core/linters.js} +0 -0
  186. /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
  187. /package/dist/{mcp.js → core/mcp.js} +0 -0
  188. /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
  189. /package/dist/{orphans.js → core/orphans.js} +0 -0
  190. /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
  191. /package/dist/{proofs.js → core/proofs.js} +0 -0
  192. /package/dist/{session.d.ts → core/session.d.ts} +0 -0
  193. /package/dist/{session.js → core/session.js} +0 -0
  194. /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
  195. /package/dist/{sidecar.js → core/sidecar.js} +0 -0
  196. /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
  197. /package/dist/{spec.js → core/spec.js} +0 -0
  198. /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
  199. /package/dist/{symbols.js → core/symbols.js} +0 -0
  200. /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
  201. /package/dist/{test-utils.js → core/test-utils.js} +0 -0
  202. /package/dist/{types.js → core/types.js} +0 -0
  203. /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
  204. /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
  205. /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 `claude` CLI is available — harness tests need it. */
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)("claude", ["--version"], { stdio: "ignore" }).status === 0;
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
- function spawnClaude(args, cwd, baseUrl, timeoutMs) {
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)("claude", args, {
255
+ const child = (0, node_child_process_1.spawn)(runtime.agentBinary, [...args], {
226
256
  cwd,
227
- env: {
228
- ...process.env,
229
- ANTHROPIC_BASE_URL: baseUrl,
230
- // Any value works — the mock ignores auth; this avoids needing a real key.
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
- * Run the real `claude` CLI against a scripted mock model, with the given
248
- * fixture and settings (hooks). Deterministic same script, same result.
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 untrusted
251
- * third-party hooks and is confined under bubblewrap (`spec.sandbox`, default
252
- * `"auto"`); if no sandbox is available the run REFUSES rather than executing
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 args = buildClaudeArgs(spec, settings !== undefined);
346
+ const tools = spec.allowedTools ?? ["Read", "Edit", "Write", "Bash"];
271
347
  const timeoutMs = spec.timeoutMs ?? 60000;
272
- const build = (out, turns, modelRequests) => ({
273
- exitCode: out.code,
274
- stdout: out.stdout,
275
- stderr: out.stderr ?? "",
348
+ const buildArgs = (mockArgs) => driver.buildArgs({
349
+ prompt: spec.prompt ?? "go",
276
350
  cwd,
277
- turns,
278
- toolCalls: parseToolCalls(out.stdout),
279
- hooks: parseHooks(out.stdout),
280
- output: parseOutput(out.stdout),
281
- modelRequests,
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 inside the sandbox's netns.
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: args,
363
+ claudeArgs: [...buildArgs([])],
295
364
  script: spec.model,
296
365
  timeoutMs,
297
366
  });
298
- return build(out, out.requests.length, out.requests);
367
+ return makeResult(cwd, out, parseClaudeRun(out.stdout), out.requests.length, out.requests);
299
368
  }
300
- // Direct path: mock runs in this process; claude reaches it over localhost.
301
- const mock = await (0, mock_model_js_1.startMock)(spec.model);
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 out = await spawnClaude(args, cwd, mock.url, timeoutMs);
304
- return build(out, mock.count, [...mock.requests]);
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
- /** One scripted assistant turn: a final text answer, or a tool call. */
2
- export interface ModelTurn {
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, recorded then blocked. Empty unless
92
- * {@link RunHookOptions.recordEgress} was set (and the run was confined).
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