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.
Files changed (209) hide show
  1. package/.claude-plugin/plugin.json +9 -0
  2. package/README.md +73 -434
  3. package/action.yml +143 -7
  4. package/dist/action-gate.d.ts +1 -1
  5. package/dist/action-gate.js +1 -1
  6. package/dist/adapter-conformance.d.ts +30 -0
  7. package/dist/adapter-conformance.js +153 -0
  8. package/dist/adapter-registry.d.ts +42 -0
  9. package/dist/adapter-registry.js +55 -0
  10. package/dist/adapter.d.ts +26 -0
  11. package/dist/adapter.js +16 -0
  12. package/dist/adapters/claude-code/adapter.d.ts +3 -0
  13. package/dist/adapters/claude-code/adapter.js +46 -0
  14. package/dist/{agent-result.d.ts → adapters/claude-code/agent-result.d.ts} +1 -1
  15. package/dist/adapters/claude-code/dialect.d.ts +13 -0
  16. package/dist/adapters/claude-code/dialect.js +51 -0
  17. package/dist/adapters/claude-code/egress-entry.d.ts +2 -0
  18. package/dist/adapters/claude-code/egress-entry.js +115 -0
  19. package/dist/adapters/claude-code/egress-proxy.d.ts +2 -0
  20. package/dist/adapters/claude-code/egress-proxy.js +60 -0
  21. package/dist/adapters/claude-code/egress.d.ts +114 -0
  22. package/dist/adapters/claude-code/egress.js +276 -0
  23. package/dist/adapters/claude-code/eval-baseline.d.ts +68 -0
  24. package/dist/adapters/claude-code/eval-baseline.js +173 -0
  25. package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
  26. package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
  27. package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
  28. package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
  29. package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
  30. package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
  31. package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
  32. package/dist/adapters/claude-code/hook-protocol.js +10 -0
  33. package/dist/adapters/claude-code/layout.d.ts +8 -0
  34. package/dist/adapters/claude-code/layout.js +18 -0
  35. package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
  36. package/dist/adapters/claude-code/model-mock.d.ts +11 -0
  37. package/dist/adapters/claude-code/model-mock.js +10 -0
  38. package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
  39. package/dist/adapters/claude-code/plugin-loader.js +19 -0
  40. package/dist/adapters/claude-code/run-hook.d.ts +204 -0
  41. package/dist/adapters/claude-code/run-hook.js +418 -0
  42. package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
  43. package/dist/adapters/claude-code/run-scripts.js +150 -0
  44. package/dist/adapters/claude-code/runtime.d.ts +16 -0
  45. package/dist/adapters/claude-code/runtime.js +39 -0
  46. package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +45 -4
  47. package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +76 -8
  48. package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
  49. package/dist/adapters/codex/adapter.d.ts +3 -0
  50. package/dist/adapters/codex/adapter.js +49 -0
  51. package/dist/adapters/codex/dialect.d.ts +10 -0
  52. package/dist/adapters/codex/dialect.js +30 -0
  53. package/dist/adapters/codex/driver.d.ts +20 -0
  54. package/dist/adapters/codex/driver.js +89 -0
  55. package/dist/adapters/codex/hook-protocol.d.ts +10 -0
  56. package/dist/adapters/codex/hook-protocol.js +18 -0
  57. package/dist/adapters/codex/layout.d.ts +16 -0
  58. package/dist/adapters/codex/layout.js +18 -0
  59. package/dist/adapters/codex/mock-model.d.ts +52 -0
  60. package/dist/adapters/codex/mock-model.js +210 -0
  61. package/dist/adapters/codex/model-mock.d.ts +11 -0
  62. package/dist/adapters/codex/model-mock.js +10 -0
  63. package/dist/adapters/codex/runtime.d.ts +38 -0
  64. package/dist/adapters/codex/runtime.js +62 -0
  65. package/dist/adapters/opencode/adapter.d.ts +3 -0
  66. package/dist/adapters/opencode/adapter.js +48 -0
  67. package/dist/adapters/opencode/dialect.d.ts +10 -0
  68. package/dist/adapters/opencode/dialect.js +36 -0
  69. package/dist/adapters/opencode/layout.d.ts +9 -0
  70. package/dist/adapters/opencode/layout.js +25 -0
  71. package/dist/adapters/opencode/model-mock.d.ts +10 -0
  72. package/dist/adapters/opencode/model-mock.js +10 -0
  73. package/dist/adapters/opencode/runtime.d.ts +9 -0
  74. package/dist/adapters/opencode/runtime.js +21 -0
  75. package/dist/claude-code.d.ts +16 -0
  76. package/dist/claude-code.js +32 -0
  77. package/dist/cli-flags.d.ts +22 -0
  78. package/dist/cli-flags.js +38 -0
  79. package/dist/cli.js +406 -206
  80. package/dist/codex.d.ts +20 -0
  81. package/dist/codex.js +36 -0
  82. package/dist/community-skills.d.ts +1 -1
  83. package/dist/community-skills.js +2 -2
  84. package/dist/core/adapter.d.ts +88 -0
  85. package/dist/core/adapter.js +3 -0
  86. package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
  87. package/dist/{compile.js → core/compile.js} +68 -53
  88. package/dist/core/compose.d.ts +79 -0
  89. package/dist/core/compose.js +145 -0
  90. package/dist/core/dialect.d.ts +51 -0
  91. package/dist/core/dialect.js +3 -0
  92. package/dist/core/harness-driver.d.ts +134 -0
  93. package/dist/core/harness-driver.js +3 -0
  94. package/dist/core/hook-protocol.d.ts +28 -0
  95. package/dist/core/hook-protocol.js +3 -0
  96. package/dist/core/layout.d.ts +43 -0
  97. package/dist/core/layout.js +3 -0
  98. package/dist/core/model-mock.d.ts +27 -0
  99. package/dist/core/model-mock.js +3 -0
  100. package/dist/core/refs.d.ts +62 -0
  101. package/dist/{refs.js → core/refs.js} +49 -23
  102. package/dist/core/runtime.d.ts +38 -0
  103. package/dist/core/runtime.js +3 -0
  104. package/dist/{types.d.ts → core/types.d.ts} +29 -0
  105. package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
  106. package/dist/{validate.js → core/validate.js} +41 -4
  107. package/dist/e2e.d.ts +19 -0
  108. package/dist/e2e.js +39 -0
  109. package/dist/harness-assert.d.ts +60 -11
  110. package/dist/harness-assert.js +111 -9
  111. package/dist/integration.d.ts +16 -0
  112. package/dist/integration.js +32 -0
  113. package/dist/leaderboard.d.ts +33 -0
  114. package/dist/leaderboard.js +107 -0
  115. package/dist/linting.d.ts +9 -0
  116. package/dist/linting.js +25 -0
  117. package/dist/plugin-loader.d.ts +6 -3
  118. package/dist/plugin-loader.js +101 -49
  119. package/dist/scan.d.ts +49 -0
  120. package/dist/scan.js +177 -0
  121. package/dist/setup-plan.d.ts +51 -0
  122. package/dist/setup-plan.js +85 -0
  123. package/dist/skill-test.d.ts +1 -1
  124. package/dist/skill-test.js +1 -1
  125. package/dist/test-coverage.d.ts +71 -0
  126. package/dist/test-coverage.js +228 -0
  127. package/dist/testing.d.ts +12 -0
  128. package/dist/testing.js +28 -0
  129. package/dist/unit.d.ts +17 -0
  130. package/dist/unit.js +36 -0
  131. package/hooks/refs-nudge.sh +24 -0
  132. package/package.json +35 -16
  133. package/skills/audit-feedback-loop/SKILL.md +76 -0
  134. package/skills/edit-spec/SKILL.md +131 -0
  135. package/skills/enforce-rules-format/SKILL.md +71 -0
  136. package/skills/generate-logo/SKILL.md +103 -0
  137. package/skills/generate-rule/SKILL.md +64 -0
  138. package/skills/linter-docs/clippy.md +241 -0
  139. package/skills/linter-docs/eslint.md +384 -0
  140. package/skills/linter-docs/pylint.md +288 -0
  141. package/skills/linter-docs/rubocop.md +277 -0
  142. package/skills/linter-docs/ruff.md +187 -0
  143. package/skills/linter-docs/stylelint.md +247 -0
  144. package/skills/migrate-to-spec/SKILL.md +126 -0
  145. package/skills/pr-to-lint-rule/SKILL.md +97 -0
  146. package/skills/strengthen/SKILL.md +168 -0
  147. package/skills/test-harness/SKILL.md +157 -0
  148. package/dist/action.d.ts +0 -7
  149. package/dist/action.js +0 -180
  150. package/dist/refs.d.ts +0 -44
  151. package/dist/run-hook.d.ts +0 -77
  152. package/dist/run-hook.js +0 -80
  153. package/dist/run-scripts.d.ts +0 -20
  154. package/dist/run-scripts.js +0 -70
  155. /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
  156. /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
  157. /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
  158. /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
  159. /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
  160. /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
  161. /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
  162. /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
  163. /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
  164. /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
  165. /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
  166. /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
  167. /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
  168. /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
  169. /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
  170. /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
  171. /package/dist/{coverage.js → core/coverage.js} +0 -0
  172. /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
  173. /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
  174. /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
  175. /package/dist/{evolve.js → core/evolve.js} +0 -0
  176. /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
  177. /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
  178. /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
  179. /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
  180. /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
  181. /package/dist/{generate-types.js → core/generate-types.js} +0 -0
  182. /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
  183. /package/dist/{hash.js → core/hash.js} +0 -0
  184. /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
  185. /package/dist/{inline.js → core/inline.js} +0 -0
  186. /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
  187. /package/dist/{integrity.js → core/integrity.js} +0 -0
  188. /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
  189. /package/dist/{linters.js → core/linters.js} +0 -0
  190. /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
  191. /package/dist/{mcp.js → core/mcp.js} +0 -0
  192. /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
  193. /package/dist/{orphans.js → core/orphans.js} +0 -0
  194. /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
  195. /package/dist/{proofs.js → core/proofs.js} +0 -0
  196. /package/dist/{session.d.ts → core/session.d.ts} +0 -0
  197. /package/dist/{session.js → core/session.js} +0 -0
  198. /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
  199. /package/dist/{sidecar.js → core/sidecar.js} +0 -0
  200. /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
  201. /package/dist/{spec.js → core/spec.js} +0 -0
  202. /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
  203. /package/dist/{symbols.js → core/symbols.js} +0 -0
  204. /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
  205. /package/dist/{test-utils.js → core/test-utils.js} +0 -0
  206. /package/dist/{types.js → core/types.js} +0 -0
  207. /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
  208. /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
  209. /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
@@ -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