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
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkAdapterConformance = checkAdapterConformance;
4
+ exports.assertAdapterConformance = assertAdapterConformance;
5
+ exports.assertHarnessTestable = assertHarnessTestable;
6
+ exports.assertAdapterLoadsHooks = assertAdapterLoadsHooks;
7
+ /**
8
+ * Adapter conformance kit — the reusable check every `HarnessAdapter` runs, so
9
+ * authoring one is guided and safe rather than "hope it's wired right". It
10
+ * verifies each port is populated AND a behavioural invariant: the adapter's
11
+ * dialect actually drives tool-contract verification (its own built-in tool is
12
+ * accepted). A third-party adapter author runs `assertAdapterConformance(myAdapter)`
13
+ * in their test suite. See `docs/authoring-an-adapter.md`.
14
+ */
15
+ const node_fs_1 = require("node:fs");
16
+ const node_os_1 = require("node:os");
17
+ const node_path_1 = require("node:path");
18
+ const compile_js_1 = require("./core/compile.js");
19
+ const spec_js_1 = require("./core/spec.js");
20
+ const plugin_loader_js_1 = require("./plugin-loader.js");
21
+ /** Check an adapter against the port contracts; returns the (possibly empty) failure list. */
22
+ function checkAdapterConformance(adapter) {
23
+ const failures = [];
24
+ const need = (cond, msg) => {
25
+ if (!cond)
26
+ failures.push(msg);
27
+ };
28
+ const caps = adapter.capabilities;
29
+ // Widen to boolean so a malformed (non-TS) adapter that set this false is still
30
+ // caught at runtime — the literal `true` type would make a direct check redundant.
31
+ const refVerification = caps.referenceVerification;
32
+ need(adapter.name.length > 0, "name is empty");
33
+ need(refVerification, "capabilities.referenceVerification must be true (every adapter does pillar 1)");
34
+ // --- Pillar 1 (always required): dialect + layout ---
35
+ need(adapter.dialect.builtinAgentTools.length > 0, "dialect has no builtinAgentTools");
36
+ need(adapter.dialect.instructionTargets.length > 0, "dialect has no instructionTargets");
37
+ need(adapter.layout.instructionFile.length > 0, "layout.instructionFile is empty");
38
+ need(adapter.layout.manifestPath.length > 0, "layout.manifestPath is empty");
39
+ need(adapter.layout.surfaceDirs.length > 0, "layout has no surfaceDirs");
40
+ need(typeof adapter.detect === "function", "detect is not a function");
41
+ // --- Pillar 2 transport ports: required ONLY for the capabilities the
42
+ // adapter declares. A pillar-1-only adapter (harnessTesting:false) may omit
43
+ // runtime/modelMock; a code-module-hook adapter (shellHooks:false) may omit
44
+ // hookProtocol — and conformance must NOT demand a fake one. The flip side:
45
+ // if it CLAIMS the capability, the port must be there and populated.
46
+ const portNames = [
47
+ ["dialect", adapter.dialect.name],
48
+ ["layout", adapter.layout.name],
49
+ ];
50
+ if (caps.harnessTesting) {
51
+ need(adapter.runtime !== undefined, "capabilities.harnessTesting is true but runtime is missing");
52
+ need(adapter.modelMock !== undefined, "capabilities.harnessTesting is true but modelMock is missing");
53
+ if (adapter.runtime) {
54
+ need(adapter.runtime.agentBinary.length > 0, "runtime.agentBinary is empty");
55
+ need(adapter.runtime.modelBaseUrlEnv.length > 0, "runtime.modelBaseUrlEnv is empty");
56
+ portNames.push(["runtime", adapter.runtime.name]);
57
+ }
58
+ if (adapter.modelMock) {
59
+ need(adapter.modelMock.modelEndpoint.length > 0, "modelMock.modelEndpoint is empty");
60
+ portNames.push(["modelMock", adapter.modelMock.name]);
61
+ }
62
+ }
63
+ else {
64
+ need(adapter.runtime === undefined && adapter.modelMock === undefined, "capabilities.harnessTesting is false — omit runtime/modelMock (a pillar-1-only adapter must not ship a half-wired transport)");
65
+ }
66
+ if (caps.shellHooks) {
67
+ need(adapter.hookProtocol !== undefined, "capabilities.shellHooks is true but hookProtocol is missing");
68
+ if (adapter.hookProtocol) {
69
+ need(Number.isInteger(adapter.hookProtocol.blockExitCode), "hookProtocol.blockExitCode is not an integer");
70
+ portNames.push(["hookProtocol", adapter.hookProtocol.name]);
71
+ }
72
+ }
73
+ else {
74
+ need(adapter.hookProtocol === undefined, "capabilities.shellHooks is false — omit hookProtocol (hooks are code modules, not shell processes)");
75
+ }
76
+ // Cross-port invariants — the kind of mismatch a copy-paste authoring slip
77
+ // produces, that no single-port check would catch. Only the present ports.
78
+ for (const [port, name] of portNames) {
79
+ need(name === adapter.name, `${port}.name "${name}" != adapter.name "${adapter.name}"`);
80
+ }
81
+ need(adapter.layout.pluginRootToken === adapter.dialect.pluginRootToken, "layout.pluginRootToken and dialect.pluginRootToken disagree");
82
+ need(adapter.dialect.instructionTargets.includes(adapter.layout.instructionFile), `layout.instructionFile "${adapter.layout.instructionFile}" is not one of dialect.instructionTargets`);
83
+ need(adapter.layout.settingsFormat === "json" ||
84
+ adapter.layout.settingsFormat === "toml", `layout.settingsFormat "${adapter.layout.settingsFormat}" is not "json" | "toml"`);
85
+ // Behavioural: the dialect drives the compiler — its own built-in tool must
86
+ // pass the subagent tool-contract check under this dialect.
87
+ const tool = adapter.dialect.builtinAgentTools[0];
88
+ if (tool) {
89
+ const spec = (0, spec_js_1.agent)({
90
+ name: "conformance",
91
+ description: "conformance probe",
92
+ tools: [tool],
93
+ body: "probe",
94
+ });
95
+ const r = (0, compile_js_1.compileAgent)(spec, {
96
+ specFile: "conformance.md.spec.ts",
97
+ dialect: adapter.dialect,
98
+ });
99
+ need(!r.errors.some((e) => e.type === "unknown-tool"), `dialect rejects its own built-in tool "${tool}"`);
100
+ }
101
+ return { ok: failures.length === 0, failures };
102
+ }
103
+ /** Throw if the adapter fails conformance — drop this in an adapter's test suite. */
104
+ function assertAdapterConformance(adapter) {
105
+ const r = checkAdapterConformance(adapter);
106
+ if (!r.ok) {
107
+ throw new Error(`Adapter "${adapter.name}" failed conformance:\n - ${r.failures.join("\n - ")}`);
108
+ }
109
+ }
110
+ /**
111
+ * Guard for the pillar-2 entry points (runHarnessTest/runEval): a pillar-1-only
112
+ * adapter (Cursor, Devin, Amp, Amazon Q) has no mockable transport, so driving
113
+ * the deterministic/eval tiers against it would hang or spawn nothing. Calling
114
+ * this up front turns that into a clear, immediate error. Returns the narrowed
115
+ * runtime+modelMock so the caller can use them without re-checking for undefined.
116
+ */
117
+ function assertHarnessTestable(adapter) {
118
+ if (!adapter.capabilities.harnessTesting ||
119
+ !adapter.runtime ||
120
+ !adapter.modelMock) {
121
+ throw new Error(`Adapter "${adapter.name}" does not support harness testing (pillar 2): it is reference-verification-only (no mockable runtime). Use it for compile/scan/audit, not runHarnessTest/runEval.`);
122
+ }
123
+ return { runtime: adapter.runtime, modelMock: adapter.modelMock };
124
+ }
125
+ /**
126
+ * Behavioural conformance the pure checks can't reach: write a minimal settings
127
+ * file in the adapter's declared `settingsFormat` (with a hook), load it through
128
+ * the adapter's `layout`, and assert the hooks actually came back. This is what
129
+ * catches a layout that points at the right file but in the wrong format (the
130
+ * JSON-vs-TOML trap) — the pure checker would pass it, the agent would silently
131
+ * run with zero hooks. Does filesystem IO, so it's a separate opt-in assert.
132
+ */
133
+ function assertAdapterLoadsHooks(adapter) {
134
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-conformance-"));
135
+ try {
136
+ const settingsAbs = (0, node_path_1.join)(dir, adapter.layout.settingsPath);
137
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(settingsAbs), { recursive: true });
138
+ const content = adapter.layout.settingsFormat === "toml"
139
+ ? '[[hooks.PreToolUse]]\ncommand = "echo conformance"\n'
140
+ : JSON.stringify({
141
+ hooks: { PreToolUse: [{ command: "echo conformance" }] },
142
+ });
143
+ (0, node_fs_1.writeFileSync)(settingsAbs, content);
144
+ const loaded = (0, plugin_loader_js_1.loadPlugin)(dir, adapter.layout);
145
+ if (!loaded.settings.hooks) {
146
+ throw new Error(`Adapter "${adapter.name}": loadPlugin read no hooks from a ${adapter.layout.settingsFormat} settings file at ${adapter.layout.settingsPath} — the settings-format wiring is broken.`);
147
+ }
148
+ }
149
+ finally {
150
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
151
+ }
152
+ }
153
+ //# sourceMappingURL=adapter-conformance.js.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Adapter registry — the composition-root list the CLI uses to auto-detect which
3
+ * harness a repo targets. The library API selects an adapter by import
4
+ * (`vigiles/claude-code`); the CLI can't, so it walks this registry. Claude Code
5
+ * is the default, so detection is backwards-compatible: an undetected repo (or a
6
+ * repo with no adapter markers) resolves to Claude Code exactly as before.
7
+ *
8
+ * Adding a harness = add its `HarnessAdapter` to `ADAPTERS` (and a
9
+ * `vigiles/<harness>` export). Order matters only if two adapters could both
10
+ * match a repo; `detect()` returns a specificity score so the strongest signal
11
+ * wins regardless of order.
12
+ */
13
+ import type { HarnessAdapter } from "./core/adapter.js";
14
+ /** The default adapter when detection finds no harness markers. */
15
+ export declare const defaultAdapter: HarnessAdapter;
16
+ /** All registered adapters. detect() specificity (not order) breaks ties. */
17
+ export declare const ADAPTERS: readonly HarnessAdapter[];
18
+ /** The result of auto-detecting a harness from a repo's layout. */
19
+ export interface DetectResult {
20
+ readonly adapter: HarnessAdapter;
21
+ /** True when detection found no harness markers and fell back to the default. */
22
+ readonly fallback: boolean;
23
+ /**
24
+ * Other adapters that matched at the same top specificity — a non-empty list
25
+ * means the repo looks like more than one harness (e.g. a CLAUDE.md + an
26
+ * AGENTS.md), so the pick is ambiguous; resolve with `--harness`.
27
+ */
28
+ readonly ambiguousWith: readonly string[];
29
+ }
30
+ /** Auto-detect the harness for a repo at `root` by highest detect() specificity. */
31
+ export declare function detectAdapterResult(root: string): DetectResult;
32
+ /** The detected adapter (highest specificity), else the default (Claude Code). */
33
+ export declare function detectAdapter(root: string): HarnessAdapter;
34
+ /** Look up a registered adapter by `name` (e.g. for a `--harness` override). */
35
+ export declare function getAdapter(name: string): HarnessAdapter | undefined;
36
+ /**
37
+ * Resolve the adapter for a command: an explicit `--harness <name>` wins (throws
38
+ * if unknown); otherwise auto-detect from `root`. The single entry point the CLI
39
+ * uses so detection + override live in one place.
40
+ */
41
+ export declare function resolveAdapter(root: string, harness?: string): HarnessAdapter;
42
+ //# sourceMappingURL=adapter-registry.d.ts.map
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ADAPTERS = exports.defaultAdapter = void 0;
4
+ exports.detectAdapterResult = detectAdapterResult;
5
+ exports.detectAdapter = detectAdapter;
6
+ exports.getAdapter = getAdapter;
7
+ exports.resolveAdapter = resolveAdapter;
8
+ const adapter_js_1 = require("./adapters/claude-code/adapter.js");
9
+ const adapter_js_2 = require("./adapters/codex/adapter.js");
10
+ /** The default adapter when detection finds no harness markers. */
11
+ exports.defaultAdapter = adapter_js_1.claudeCodeAdapter;
12
+ /** All registered adapters. detect() specificity (not order) breaks ties. */
13
+ exports.ADAPTERS = [
14
+ adapter_js_1.claudeCodeAdapter,
15
+ adapter_js_2.codexAdapter,
16
+ ];
17
+ /** Auto-detect the harness for a repo at `root` by highest detect() specificity. */
18
+ function detectAdapterResult(root) {
19
+ const scored = exports.ADAPTERS.map((a) => ({ a, score: a.detect(root) })).filter((s) => s.score > 0);
20
+ if (scored.length === 0) {
21
+ return { adapter: exports.defaultAdapter, fallback: true, ambiguousWith: [] };
22
+ }
23
+ const top = Math.max(...scored.map((s) => s.score));
24
+ const winners = scored.filter((s) => s.score === top).map((s) => s.a);
25
+ return {
26
+ adapter: winners[0],
27
+ fallback: false,
28
+ ambiguousWith: winners.slice(1).map((a) => a.name),
29
+ };
30
+ }
31
+ /** The detected adapter (highest specificity), else the default (Claude Code). */
32
+ function detectAdapter(root) {
33
+ return detectAdapterResult(root).adapter;
34
+ }
35
+ /** Look up a registered adapter by `name` (e.g. for a `--harness` override). */
36
+ function getAdapter(name) {
37
+ return exports.ADAPTERS.find((a) => a.name === name);
38
+ }
39
+ /**
40
+ * Resolve the adapter for a command: an explicit `--harness <name>` wins (throws
41
+ * if unknown); otherwise auto-detect from `root`. The single entry point the CLI
42
+ * uses so detection + override live in one place.
43
+ */
44
+ function resolveAdapter(root, harness) {
45
+ if (harness !== undefined) {
46
+ const a = getAdapter(harness);
47
+ if (!a) {
48
+ const known = exports.ADAPTERS.map((x) => x.name).join(", ");
49
+ throw new Error(`Unknown harness "${harness}". Known: ${known}.`);
50
+ }
51
+ return a;
52
+ }
53
+ return detectAdapter(root);
54
+ }
55
+ //# sourceMappingURL=adapter-registry.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `vigiles/adapter` — the harness-adapter authoring kit.
3
+ *
4
+ * Everything you need to teach vigiles a new harness, in one import: the five
5
+ * port interfaces to implement, the `HarnessAdapter` bundle that groups them,
6
+ * the conformance kit to validate yours, and the registry the CLI detects
7
+ * through. vigiles ships a Claude Code adapter; building your own is welcome and
8
+ * supported — see `docs/authoring-an-adapter.md`.
9
+ *
10
+ * import {
11
+ * type HarnessAdapter, type HarnessDialect, type PluginLayout,
12
+ * type HarnessRuntime, type HookProtocol, type ModelMock,
13
+ * assertAdapterConformance,
14
+ * } from "vigiles/adapter";
15
+ *
16
+ * export const myHarnessAdapter: HarnessAdapter = { name: "my-harness", … };
17
+ */
18
+ export type { HarnessAdapter, AdapterCapabilities } from "./core/adapter.js";
19
+ export type { HarnessDialect } from "./core/dialect.js";
20
+ export type { PluginLayout } from "./core/layout.js";
21
+ export type { HarnessRuntime } from "./core/runtime.js";
22
+ export type { HookProtocol } from "./core/hook-protocol.js";
23
+ export type { ModelMock } from "./core/model-mock.js";
24
+ export { checkAdapterConformance, assertAdapterConformance, assertAdapterLoadsHooks, assertHarnessTestable, type ConformanceResult, } from "./adapter-conformance.js";
25
+ export { ADAPTERS, defaultAdapter, detectAdapter, detectAdapterResult, resolveAdapter, getAdapter, type DetectResult, } from "./adapter-registry.js";
26
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAdapter = exports.resolveAdapter = exports.detectAdapterResult = exports.detectAdapter = exports.defaultAdapter = exports.ADAPTERS = exports.assertHarnessTestable = exports.assertAdapterLoadsHooks = exports.assertAdapterConformance = exports.checkAdapterConformance = void 0;
4
+ var adapter_conformance_js_1 = require("./adapter-conformance.js");
5
+ Object.defineProperty(exports, "checkAdapterConformance", { enumerable: true, get: function () { return adapter_conformance_js_1.checkAdapterConformance; } });
6
+ Object.defineProperty(exports, "assertAdapterConformance", { enumerable: true, get: function () { return adapter_conformance_js_1.assertAdapterConformance; } });
7
+ Object.defineProperty(exports, "assertAdapterLoadsHooks", { enumerable: true, get: function () { return adapter_conformance_js_1.assertAdapterLoadsHooks; } });
8
+ Object.defineProperty(exports, "assertHarnessTestable", { enumerable: true, get: function () { return adapter_conformance_js_1.assertHarnessTestable; } });
9
+ var adapter_registry_js_1 = require("./adapter-registry.js");
10
+ Object.defineProperty(exports, "ADAPTERS", { enumerable: true, get: function () { return adapter_registry_js_1.ADAPTERS; } });
11
+ Object.defineProperty(exports, "defaultAdapter", { enumerable: true, get: function () { return adapter_registry_js_1.defaultAdapter; } });
12
+ Object.defineProperty(exports, "detectAdapter", { enumerable: true, get: function () { return adapter_registry_js_1.detectAdapter; } });
13
+ Object.defineProperty(exports, "detectAdapterResult", { enumerable: true, get: function () { return adapter_registry_js_1.detectAdapterResult; } });
14
+ Object.defineProperty(exports, "resolveAdapter", { enumerable: true, get: function () { return adapter_registry_js_1.resolveAdapter; } });
15
+ Object.defineProperty(exports, "getAdapter", { enumerable: true, get: function () { return adapter_registry_js_1.getAdapter; } });
16
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1,3 @@
1
+ import type { HarnessAdapter } from "../../core/adapter.js";
2
+ export declare const claudeCodeAdapter: HarnessAdapter;
3
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.claudeCodeAdapter = void 0;
4
+ /**
5
+ * claudeCodeAdapter — the Claude Code `HarnessAdapter`: the five port
6
+ * implementations bundled, plus a `detect` that recognizes a Claude Code repo
7
+ * (a `.claude-plugin/` manifest, a `.claude/settings.json`, or a `CLAUDE.md`).
8
+ * This is the reference adapter a second harness (Codex, Gemini, …) mirrors.
9
+ */
10
+ const node_fs_1 = require("node:fs");
11
+ const node_path_1 = require("node:path");
12
+ const dialect_js_1 = require("./dialect.js");
13
+ const layout_js_1 = require("./layout.js");
14
+ const runtime_js_1 = require("./runtime.js");
15
+ const hook_protocol_js_1 = require("./hook-protocol.js");
16
+ const model_mock_js_1 = require("./model-mock.js");
17
+ const harness_test_js_1 = require("./harness-test.js");
18
+ exports.claudeCodeAdapter = {
19
+ name: "claude-code",
20
+ // The reference harness: every tier. Mockable transport (Anthropic SSE) and
21
+ // shell hooks (exit 2 / decision JSON) — both pillars, all tiers.
22
+ capabilities: {
23
+ referenceVerification: true,
24
+ harnessTesting: true,
25
+ shellHooks: true,
26
+ },
27
+ dialect: dialect_js_1.claudeCodeDialect,
28
+ layout: layout_js_1.claudeCodeLayout,
29
+ runtime: runtime_js_1.claudeCodeRuntime,
30
+ hookProtocol: hook_protocol_js_1.claudeCodeHookProtocol,
31
+ modelMock: model_mock_js_1.claudeCodeModelMock,
32
+ harnessTestDriver: harness_test_js_1.claudeCodeDriver,
33
+ detect(root) {
34
+ // Most specific signal wins: a plugin manifest (3) > repo settings (2) >
35
+ // a bare CLAUDE.md (1, weak — many tools also read it / AGENTS.md).
36
+ const has = (rel) => (0, node_fs_1.existsSync)((0, node_path_1.join)(root, rel));
37
+ if (has(layout_js_1.claudeCodeLayout.manifestPath))
38
+ return 3;
39
+ if (has(layout_js_1.claudeCodeLayout.settingsPath))
40
+ return 2;
41
+ if (has(layout_js_1.claudeCodeLayout.instructionFile))
42
+ return 1;
43
+ return 0;
44
+ },
45
+ };
46
+ //# sourceMappingURL=adapter.js.map
@@ -17,7 +17,7 @@
17
17
  * third `malformed` track for a worker that didn't honor its contract (no block,
18
18
  * bad JSON, or a shape that doesn't match the declared schema).
19
19
  */
20
- import type { OutputContract } from "./spec.js";
20
+ import type { OutputContract } from "../../core/spec.js";
21
21
  /** The outcome of parsing a worker's result block. */
22
22
  export type ParsedAgentResult<S = Record<string, unknown>, E = Record<string, unknown>> = {
23
23
  readonly kind: "ok";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * claudeCodeDialect — the Claude Code adapter's `HarnessDialect` (format axis).
3
+ * The concrete dialect is DEFINED here, in the adapter, symmetric with the other
4
+ * four ports (`claudeCodeLayout`/`Runtime`/`HookProtocol`/`ModelMock`): the core
5
+ * holds only the `HarnessDialect` interface and never a harness's vocabulary. The
6
+ * compiler/validator receive this by injection (`compileAgent(spec, { dialect })`,
7
+ * the CLI/composition root supplies it). A second harness defines its own dialect
8
+ * in its adapter (e.g. `src/adapters/codex/dialect.ts` exporting `codexDialect`).
9
+ */
10
+ import type { HarnessDialect } from "../../core/dialect.js";
11
+ export declare const claudeCodeDialect: HarnessDialect;
12
+ export type { HarnessDialect } from "../../core/dialect.js";
13
+ //# sourceMappingURL=dialect.d.ts.map
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.claudeCodeDialect = void 0;
4
+ exports.claudeCodeDialect = {
5
+ name: "claude-code",
6
+ // The tool contract a subagent may declare — the rails it runs on. Anything
7
+ // else must be an MCP tool, else it's a typo / nonexistent tool.
8
+ builtinAgentTools: [
9
+ "Read",
10
+ "Write",
11
+ "Edit",
12
+ "Bash",
13
+ "Grep",
14
+ "Glob",
15
+ "WebSearch",
16
+ "WebFetch",
17
+ "NotebookEdit",
18
+ "TodoWrite",
19
+ "Task",
20
+ "Skill",
21
+ ],
22
+ // Tools the platform never exposes to a subagent, whatever the list says — so
23
+ // a subagent listing one is a guaranteed-dead reference only a compiler catches.
24
+ neverAvailableTools: [
25
+ "Agent",
26
+ "AskUserQuestion",
27
+ "EnterPlanMode",
28
+ "ExitPlanMode",
29
+ "ScheduleWakeup",
30
+ "WaitForMcpServers",
31
+ ],
32
+ mcpToolPattern: /^mcp__[a-z0-9_-]+__[a-z0-9_-]+$/i,
33
+ hookEvents: [
34
+ "PreToolUse",
35
+ "PostToolUse",
36
+ "PreSession",
37
+ "PostSession",
38
+ "Notification",
39
+ ],
40
+ // Claude Code natively reads CLAUDE.md only — it does NOT auto-load AGENTS.md
41
+ // (anthropics/claude-code#34235 is open; AGENTS.md works solely via an
42
+ // `@AGENTS.md` import inside CLAUDE.md or a symlink). AGENTS.md is the
43
+ // cross-tool standard (Codex's native target), not a CC dialect fact; vigiles's
44
+ // tool-agnostic recognition of it lives in validate.ts's INSTRUCTION_FILES.
45
+ instructionTargets: ["CLAUDE.md"],
46
+ pluginRootToken: "${CLAUDE_PLUGIN_ROOT}",
47
+ // Claude Code reads the full SKILL.md frontmatter set (description,
48
+ // disable-model-invocation, argument-hint, …).
49
+ skillFrontmatter: "claude-code",
50
+ };
51
+ //# sourceMappingURL=dialect.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=egress-entry.d.ts.map
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * vigiles — the allowlisted-egress orchestrator (in-process subprocess entry).
5
+ *
6
+ * `runHook` is synchronous (`spawnSync`), but allowlisted egress needs TWO
7
+ * processes alive at once: `bwrap` (which creates the netns and runs the hook)
8
+ * and `slirp4netns` (which, from the PARENT netns, attaches a tap to bwrap's netns
9
+ * to give it controlled egress). So the parent `spawnSync`s THIS entry, which runs
10
+ * the concurrent dance and writes a result file the parent reads back — keeping
11
+ * `runHook` synchronous while the handoff happens here.
12
+ *
13
+ * The dance (proven in `research/spikes/sandbox-network-allowlist.sh`):
14
+ * 1. spawn bwrap with `--info-fd` → learn the sandboxed child's PID;
15
+ * 2. `slirp4netns --configure --ready-fd N <pid> tap0` → tap up inside the netns;
16
+ * 3. touch the netready file → the in-sandbox wrapper (blocked on it) proceeds:
17
+ * it loads the nft allowlist, runs the hook, then dumps the nft counters to a
18
+ * bound file BEFORE exiting (the netns dies with the child, so the read-back
19
+ * must happen inside);
20
+ * 4. capture bwrap's stdout/stderr/exit + the counters → write the result file.
21
+ *
22
+ * Run as: `node dist/egress-entry.js <config.json>`. v8-ignored: it spawns real
23
+ * bwrap + slirp4netns and is exercised only by the bwrap/slirp-gated integration
24
+ * test; the testable logic (ruleset, counter parse) lives in `src/egress.ts`.
25
+ */
26
+ /* v8 ignore start */
27
+ const node_child_process_1 = require("node:child_process");
28
+ const node_fs_1 = require("node:fs");
29
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
30
+ /** Is a binary on PATH? (probe via `--version`; ENOENT sets `.error`.) */
31
+ function hasBinary(name) {
32
+ return !(0, node_child_process_1.spawnSync)(name, ["--version"], { stdio: "ignore" }).error;
33
+ }
34
+ /** Poll a file until it has content (the info / ready fd targets), or time out. */
35
+ async function waitForFile(path, timeoutMs) {
36
+ const deadline = Date.now() + timeoutMs;
37
+ while (Date.now() < deadline) {
38
+ try {
39
+ if ((0, node_fs_1.readFileSync)(path, "utf-8").trim().length > 0)
40
+ return true;
41
+ }
42
+ catch {
43
+ /* not written yet */
44
+ }
45
+ await sleep(50);
46
+ }
47
+ return false;
48
+ }
49
+ async function main() {
50
+ const cfg = JSON.parse((0, node_fs_1.readFileSync)(process.argv[2], "utf-8"));
51
+ // bwrap writes the sandboxed child's PID (init-namespace view) to fd 3.
52
+ const infoFd = (0, node_fs_1.openSync)(cfg.infoFile, "w");
53
+ const bwrap = (0, node_child_process_1.spawn)("bwrap", cfg.bwrapArgv, {
54
+ stdio: ["ignore", "pipe", "pipe", infoFd],
55
+ });
56
+ (0, node_fs_1.closeSync)(infoFd);
57
+ let stdout = "";
58
+ let stderr = "";
59
+ bwrap.stdout?.on("data", (d) => (stdout += d.toString()));
60
+ bwrap.stderr?.on("data", (d) => (stderr += d.toString()));
61
+ let connector;
62
+ const timer = setTimeout(() => bwrap.kill("SIGKILL"), cfg.timeoutMs);
63
+ const exit = new Promise((resolve) => {
64
+ bwrap.on("close", (status, signal) => {
65
+ resolve({ status, signal });
66
+ });
67
+ });
68
+ // Learn the child PID, attach a rootless egress connector to its netns, release
69
+ // the wrapper. Prefer pasta (passt): it routes on hosted runners where
70
+ // slirp4netns's tap-attach silently fails (the netns ends up with only `lo`).
71
+ // Fall back to slirp4netns where pasta isn't installed. nft (the allowlist wall)
72
+ // matches on destination, not interface name, so it's connector-agnostic. See
73
+ // research/egress-sandbox-tooling.md.
74
+ if (await waitForFile(cfg.infoFile, 5_000)) {
75
+ const info = (0, node_fs_1.readFileSync)(cfg.infoFile, "utf-8");
76
+ const m = /"child-pid":\s*(\d+)/.exec(info);
77
+ if (m) {
78
+ if (cfg.connector === "pasta" && hasBinary("pasta")) {
79
+ // pasta configures the netns then forks to background; no ready-fd, so
80
+ // give it a moment to bring the interface + routes up. (Experimental:
81
+ // pasta's attach-to-existing-netns path is fragile in nested-namespace
82
+ // sandboxes — see research/egress-sandbox-tooling.md. The connector
83
+ // defaults to slirp4netns; both need /dev/net/tun + privilege, which is
84
+ // why the e2e CI job runs privileged.)
85
+ connector = (0, node_child_process_1.spawn)("pasta", [...cfg.pastaArgs, m[1]], {
86
+ stdio: "ignore",
87
+ });
88
+ await sleep(800);
89
+ }
90
+ else {
91
+ const readyFd = (0, node_fs_1.openSync)(cfg.readyFile, "w");
92
+ connector = (0, node_child_process_1.spawn)("slirp4netns", [...cfg.slirpArgs, "--ready-fd", "4", m[1], "tap0"], { stdio: ["ignore", "ignore", "ignore", "ignore", readyFd] });
93
+ (0, node_fs_1.closeSync)(readyFd);
94
+ // slirp writes "1" to the ready fd once tap0 is configured; fall back to a
95
+ // short sleep if it never signals (older builds).
96
+ if (!(await waitForFile(cfg.readyFile, 4_000)))
97
+ await sleep(800);
98
+ }
99
+ }
100
+ }
101
+ (0, node_fs_1.writeFileSync)(cfg.netreadyFile, "1");
102
+ const { status, signal } = await exit;
103
+ clearTimeout(timer);
104
+ connector?.kill("SIGKILL");
105
+ const counters = (0, node_fs_1.existsSync)(cfg.countersFile)
106
+ ? (0, node_fs_1.readFileSync)(cfg.countersFile, "utf-8")
107
+ : "";
108
+ (0, node_fs_1.writeFileSync)(cfg.resultFile, JSON.stringify({ status, signal, stdout, stderr, counters }));
109
+ }
110
+ main().catch((e) => {
111
+ (0, node_fs_1.writeFileSync)(process.argv[2] + ".error", e instanceof Error ? (e.stack ?? e.message) : String(e));
112
+ process.exit(1);
113
+ });
114
+ /* v8 ignore stop */
115
+ //# sourceMappingURL=egress-entry.js.map
@@ -0,0 +1,114 @@
1
+ import { type EgressAttempt } from "./sandbox.js";
2
+ /** A host plus the IPs it resolved to, split by family (nft needs them apart). */
3
+ export interface ResolvedHost {
4
+ readonly host: string;
5
+ readonly v4: readonly string[];
6
+ readonly v6: readonly string[];
7
+ }
8
+ /** Per-host and aggregate nftables counters read back after a confined run. */
9
+ export interface EgressCounters {
10
+ /** Allowlisted hosts that saw traffic, with the nft counter for each. */
11
+ readonly allowed: readonly {
12
+ readonly host: string;
13
+ readonly packets: number;
14
+ readonly bytes: number;
15
+ }[];
16
+ /** The catch-all drop rule: traffic to anything OFF the allowlist. */
17
+ readonly dropped: {
18
+ readonly packets: number;
19
+ readonly bytes: number;
20
+ };
21
+ }
22
+ /**
23
+ * Whether this host can run the allowlisted-egress sandbox: it needs the same
24
+ * bubblewrap confinement the other tiers use PLUS `slirp4netns` (the rootless
25
+ * gateway) and `nft` (the packet-layer allowlist). Cached — the answer can't
26
+ * change within a run. `available` is injected so the bwrap probe isn't repeated.
27
+ */
28
+ export declare function probeEgressAvailable(available: boolean): boolean;
29
+ /** Cached {@link probeEgressAvailable}. `available` = the bwrap-sandbox probe. */
30
+ export declare function egressAvailable(available: boolean): boolean;
31
+ /**
32
+ * Parse `getent ahosts <host>` output into the unique IPs, split by family. The
33
+ * first whitespace token of each line is an address; `:` marks IPv6. Pure, so the
34
+ * resolution-parsing is unit-tested without touching DNS.
35
+ */
36
+ export declare function parseGetent(stdout: string): {
37
+ v4: string[];
38
+ v6: string[];
39
+ };
40
+ /**
41
+ * Resolve every allowlisted host to IPs. The lookup is injectable so the
42
+ * empty-result / family-split behaviour is unit-tested with a fake resolver.
43
+ */
44
+ export declare function resolveAllow(hosts: readonly string[], lookup?: (host: string) => {
45
+ v4: string[];
46
+ v6: string[];
47
+ }): ResolvedHost[];
48
+ /**
49
+ * Nameservers usable from INSIDE the egress netns. Parses a resolv.conf body but
50
+ * drops loopback stubs (see {@link isLoopbackResolver}) — they can't be reached
51
+ * across the namespace — and falls back to a public resolver (8.8.8.8, which
52
+ * slirp4netns NATs out) when nothing routable remains.
53
+ */
54
+ export declare function parseResolvers(resolvConf: string): string[];
55
+ /**
56
+ * Build the nftables ruleset that enforces the allowlist INSIDE the netns: a
57
+ * `policy drop` output chain that accepts loopback, established replies, DNS to
58
+ * the resolvers, and each allowlisted host's resolved IPs (a per-host `counter` +
59
+ * `comment "allow:<host>"` so the read-back maps traffic to a name), then a
60
+ * catch-all `log`+`counter`+`drop`. v4 and v6 are separate rules (an `inet` set
61
+ * can't mix families) sharing the host's comment. Pure → the ruleset is asserted
62
+ * in a unit test.
63
+ */
64
+ export declare function buildEgressNft(opts: {
65
+ allow: readonly ResolvedHost[];
66
+ resolvers: readonly string[];
67
+ }): string;
68
+ /** The bound paths the in-netns wrapper and the orchestrator hand back through. */
69
+ export interface EgressFiles {
70
+ readonly ioDir: string;
71
+ readonly netready: string;
72
+ readonly nft: string;
73
+ readonly event: string;
74
+ readonly counters: string;
75
+ }
76
+ /**
77
+ * Assemble the full bwrap argv for an allowlisted-egress run: the shared
78
+ * confinement args, the hook's added-back env, `CAP_NET_ADMIN` (so the in-netns
79
+ * wrapper can load nft), `--info-fd 3` (so the orchestrator learns the child PID
80
+ * to hand slirp4netns), the `VIG_*` paths the wrapper reads, and the trailing
81
+ * `sh -c <wrapper>`. Pure (the bwrap/setenv args are computed by the caller), so
82
+ * the assembled shape — caps, info-fd, the VIG_* env, the wrapper payload — is
83
+ * asserted in a unit test.
84
+ */
85
+ export declare function buildEgressBwrapArgv(opts: {
86
+ base: readonly string[];
87
+ setenv: readonly string[];
88
+ files: EgressFiles;
89
+ command: string;
90
+ wrapper: string;
91
+ /** Host path to the generated resolv.conf bound over /etc/resolv.conf in-netns,
92
+ * so the hook resolves via the routable resolvers, not the host's loopback stub. */
93
+ resolvConf: string;
94
+ }): string[];
95
+ /**
96
+ * Parse `nft list chain inet vig output` back into per-host allowed counters and
97
+ * the aggregate drop counter. Sums multiple rules that share a host comment (the
98
+ * v4 + v6 split). Pure, so the read-back is unit-tested without a sandbox.
99
+ */
100
+ export declare function parseNftCounters(nftText: string): EgressCounters;
101
+ /**
102
+ * Map parsed counters to the run result: one {@link EgressAttempt} per
103
+ * allowlisted host that saw traffic (`allowed: true`, with its packet/byte
104
+ * counts), plus the aggregate dropped counter. `ts` is stamped once per run (the
105
+ * counters are end-of-run totals, not per-connection events). Pure.
106
+ */
107
+ export declare function countersToResult(counters: EgressCounters, now: number): {
108
+ egress: EgressAttempt[];
109
+ egressDropped: {
110
+ packets: number;
111
+ bytes: number;
112
+ };
113
+ };
114
+ //# sourceMappingURL=egress.d.ts.map