vigiles 2.6.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 (207) hide show
  1. package/.claude-plugin/plugin.json +9 -0
  2. package/README.md +74 -129
  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.d.ts +114 -0
  20. package/dist/adapters/claude-code/egress.js +276 -0
  21. package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
  22. package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
  23. package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
  24. package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
  25. package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
  26. package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
  27. package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
  28. package/dist/adapters/claude-code/hook-protocol.js +10 -0
  29. package/dist/adapters/claude-code/layout.d.ts +8 -0
  30. package/dist/adapters/claude-code/layout.js +18 -0
  31. package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
  32. package/dist/adapters/claude-code/model-mock.d.ts +11 -0
  33. package/dist/adapters/claude-code/model-mock.js +10 -0
  34. package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
  35. package/dist/adapters/claude-code/plugin-loader.js +19 -0
  36. package/dist/{run-hook.d.ts → adapters/claude-code/run-hook.d.ts} +50 -3
  37. package/dist/{run-hook.js → adapters/claude-code/run-hook.js} +178 -18
  38. package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
  39. package/dist/adapters/claude-code/run-scripts.js +150 -0
  40. package/dist/adapters/claude-code/runtime.d.ts +16 -0
  41. package/dist/adapters/claude-code/runtime.js +39 -0
  42. package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +10 -0
  43. package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +5 -3
  44. package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
  45. package/dist/adapters/codex/adapter.d.ts +3 -0
  46. package/dist/adapters/codex/adapter.js +49 -0
  47. package/dist/adapters/codex/dialect.d.ts +10 -0
  48. package/dist/adapters/codex/dialect.js +30 -0
  49. package/dist/adapters/codex/driver.d.ts +20 -0
  50. package/dist/adapters/codex/driver.js +89 -0
  51. package/dist/adapters/codex/hook-protocol.d.ts +10 -0
  52. package/dist/adapters/codex/hook-protocol.js +18 -0
  53. package/dist/adapters/codex/layout.d.ts +16 -0
  54. package/dist/adapters/codex/layout.js +18 -0
  55. package/dist/adapters/codex/mock-model.d.ts +52 -0
  56. package/dist/adapters/codex/mock-model.js +210 -0
  57. package/dist/adapters/codex/model-mock.d.ts +11 -0
  58. package/dist/adapters/codex/model-mock.js +10 -0
  59. package/dist/adapters/codex/runtime.d.ts +38 -0
  60. package/dist/adapters/codex/runtime.js +62 -0
  61. package/dist/adapters/opencode/adapter.d.ts +3 -0
  62. package/dist/adapters/opencode/adapter.js +48 -0
  63. package/dist/adapters/opencode/dialect.d.ts +10 -0
  64. package/dist/adapters/opencode/dialect.js +36 -0
  65. package/dist/adapters/opencode/layout.d.ts +9 -0
  66. package/dist/adapters/opencode/layout.js +25 -0
  67. package/dist/adapters/opencode/model-mock.d.ts +10 -0
  68. package/dist/adapters/opencode/model-mock.js +10 -0
  69. package/dist/adapters/opencode/runtime.d.ts +9 -0
  70. package/dist/adapters/opencode/runtime.js +21 -0
  71. package/dist/claude-code.d.ts +8 -2
  72. package/dist/claude-code.js +8 -2
  73. package/dist/cli-flags.d.ts +22 -0
  74. package/dist/cli-flags.js +38 -0
  75. package/dist/cli.js +406 -206
  76. package/dist/codex.d.ts +20 -0
  77. package/dist/codex.js +36 -0
  78. package/dist/community-skills.d.ts +1 -1
  79. package/dist/community-skills.js +2 -2
  80. package/dist/core/adapter.d.ts +88 -0
  81. package/dist/core/adapter.js +3 -0
  82. package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
  83. package/dist/{compile.js → core/compile.js} +68 -53
  84. package/dist/core/compose.d.ts +79 -0
  85. package/dist/core/compose.js +145 -0
  86. package/dist/core/dialect.d.ts +51 -0
  87. package/dist/core/dialect.js +3 -0
  88. package/dist/core/harness-driver.d.ts +134 -0
  89. package/dist/core/harness-driver.js +3 -0
  90. package/dist/core/hook-protocol.d.ts +28 -0
  91. package/dist/core/hook-protocol.js +3 -0
  92. package/dist/core/layout.d.ts +43 -0
  93. package/dist/core/layout.js +3 -0
  94. package/dist/core/model-mock.d.ts +27 -0
  95. package/dist/core/model-mock.js +3 -0
  96. package/dist/core/refs.d.ts +62 -0
  97. package/dist/{refs.js → core/refs.js} +49 -23
  98. package/dist/core/runtime.d.ts +38 -0
  99. package/dist/core/runtime.js +3 -0
  100. package/dist/{types.d.ts → core/types.d.ts} +29 -0
  101. package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
  102. package/dist/{validate.js → core/validate.js} +41 -4
  103. package/dist/e2e.d.ts +19 -0
  104. package/dist/e2e.js +39 -0
  105. package/dist/harness-assert.d.ts +29 -14
  106. package/dist/harness-assert.js +37 -10
  107. package/dist/integration.d.ts +16 -0
  108. package/dist/integration.js +32 -0
  109. package/dist/leaderboard.d.ts +33 -0
  110. package/dist/leaderboard.js +107 -0
  111. package/dist/linting.d.ts +2 -2
  112. package/dist/linting.js +2 -2
  113. package/dist/plugin-loader.d.ts +6 -3
  114. package/dist/plugin-loader.js +101 -49
  115. package/dist/scan.d.ts +49 -0
  116. package/dist/scan.js +177 -0
  117. package/dist/setup-plan.d.ts +51 -0
  118. package/dist/setup-plan.js +85 -0
  119. package/dist/skill-test.d.ts +1 -1
  120. package/dist/skill-test.js +1 -1
  121. package/dist/test-coverage.d.ts +71 -0
  122. package/dist/test-coverage.js +228 -0
  123. package/dist/testing.d.ts +3 -3
  124. package/dist/testing.js +3 -3
  125. package/dist/unit.d.ts +17 -0
  126. package/dist/unit.js +36 -0
  127. package/hooks/refs-nudge.sh +24 -0
  128. package/package.json +30 -15
  129. package/skills/audit-feedback-loop/SKILL.md +76 -0
  130. package/skills/edit-spec/SKILL.md +131 -0
  131. package/skills/enforce-rules-format/SKILL.md +71 -0
  132. package/skills/generate-logo/SKILL.md +103 -0
  133. package/skills/generate-rule/SKILL.md +64 -0
  134. package/skills/linter-docs/clippy.md +241 -0
  135. package/skills/linter-docs/eslint.md +384 -0
  136. package/skills/linter-docs/pylint.md +288 -0
  137. package/skills/linter-docs/rubocop.md +277 -0
  138. package/skills/linter-docs/ruff.md +187 -0
  139. package/skills/linter-docs/stylelint.md +247 -0
  140. package/skills/migrate-to-spec/SKILL.md +126 -0
  141. package/skills/pr-to-lint-rule/SKILL.md +97 -0
  142. package/skills/strengthen/SKILL.md +168 -0
  143. package/skills/test-harness/SKILL.md +157 -0
  144. package/dist/action.d.ts +0 -7
  145. package/dist/action.js +0 -180
  146. package/dist/refs.d.ts +0 -44
  147. package/dist/run-scripts.d.ts +0 -20
  148. package/dist/run-scripts.js +0 -70
  149. /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
  150. /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
  151. /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
  152. /package/dist/{egress-proxy.d.ts → adapters/claude-code/egress-proxy.d.ts} +0 -0
  153. /package/dist/{egress-proxy.js → adapters/claude-code/egress-proxy.js} +0 -0
  154. /package/dist/{eval-baseline.d.ts → adapters/claude-code/eval-baseline.d.ts} +0 -0
  155. /package/dist/{eval-baseline.js → adapters/claude-code/eval-baseline.js} +0 -0
  156. /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
  157. /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
  158. /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
  159. /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
  160. /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
  161. /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
  162. /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
  163. /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
  164. /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
  165. /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
  166. /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
  167. /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
  168. /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
  169. /package/dist/{coverage.js → core/coverage.js} +0 -0
  170. /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
  171. /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
  172. /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
  173. /package/dist/{evolve.js → core/evolve.js} +0 -0
  174. /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
  175. /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
  176. /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
  177. /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
  178. /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
  179. /package/dist/{generate-types.js → core/generate-types.js} +0 -0
  180. /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
  181. /package/dist/{hash.js → core/hash.js} +0 -0
  182. /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
  183. /package/dist/{inline.js → core/inline.js} +0 -0
  184. /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
  185. /package/dist/{integrity.js → core/integrity.js} +0 -0
  186. /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
  187. /package/dist/{linters.js → core/linters.js} +0 -0
  188. /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
  189. /package/dist/{mcp.js → core/mcp.js} +0 -0
  190. /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
  191. /package/dist/{orphans.js → core/orphans.js} +0 -0
  192. /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
  193. /package/dist/{proofs.js → core/proofs.js} +0 -0
  194. /package/dist/{session.d.ts → core/session.d.ts} +0 -0
  195. /package/dist/{session.js → core/session.js} +0 -0
  196. /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
  197. /package/dist/{sidecar.js → core/sidecar.js} +0 -0
  198. /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
  199. /package/dist/{spec.js → core/spec.js} +0 -0
  200. /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
  201. /package/dist/{symbols.js → core/symbols.js} +0 -0
  202. /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
  203. /package/dist/{test-utils.js → core/test-utils.js} +0 -0
  204. /package/dist/{types.js → core/types.js} +0 -0
  205. /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
  206. /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
  207. /package/{.claude-plugin/hooks → hooks}/session-start.sh +0 -0
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ /**
3
+ * Sync-tool compatibility detector — keep vigiles composable with the rule-sync
4
+ * tools (Ruler, rulesync) instead of fighting them for the same files.
5
+ *
6
+ * Both vigiles and a sync tool want to *write* CLAUDE.md / AGENTS.md. That
7
+ * collision is the whole compatibility problem: vigiles stamps a SHA-256
8
+ * integrity header on line 1 (see `integrity.ts`), but Ruler concatenates its
9
+ * source files into CLAUDE.md (prepending `<!-- Source: … -->`) and rulesync
10
+ * regenerates it — either way the hash silently goes stale. The clean topology
11
+ * is "vigiles upstream": compile into the tool's *source slot* and let the tool
12
+ * distribute (see `research/sync-tool-compatibility.md`).
13
+ *
14
+ * This detector is pure filesystem inspection — the same deterministic-detector
15
+ * shape as `orphans.ts` / `test-coverage.ts`. It reports which tools are present
16
+ * and any target that collides with a file the tool regenerates, so `vigiles
17
+ * audit` can warn before the integrity guarantee is lost.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.detectSyncTools = detectSyncTools;
21
+ exports.detectInstructionMirror = detectInstructionMirror;
22
+ exports.composeCollisions = composeCollisions;
23
+ const node_fs_1 = require("node:fs");
24
+ const node_path_1 = require("node:path");
25
+ // ---------------------------------------------------------------------------
26
+ // Detection
27
+ // ---------------------------------------------------------------------------
28
+ /** True when `rel` under `root` exists and is a directory. */
29
+ function hasDir(root, rel) {
30
+ const p = (0, node_path_1.join)(root, rel);
31
+ return (0, node_fs_1.existsSync)(p) && (0, node_fs_1.statSync)(p).isDirectory();
32
+ }
33
+ /** True when `rel` under `root` exists as a file. */
34
+ function hasFile(root, rel) {
35
+ const p = (0, node_path_1.join)(root, rel);
36
+ return (0, node_fs_1.existsSync)(p) && (0, node_fs_1.statSync)(p).isFile();
37
+ }
38
+ /**
39
+ * Detect the rule-sync tools configured in the repo at `root`. Ruler is keyed
40
+ * on its `.ruler/` source dir or a `ruler.toml`; rulesync on its `.rulesync/`
41
+ * dir. Returns each tool's recommended source slot and the files it owns as
42
+ * distribution output (used to find collisions).
43
+ */
44
+ function detectSyncTools(root) {
45
+ const base = (0, node_path_1.resolve)(root);
46
+ const tools = [];
47
+ if (hasDir(base, ".ruler") || hasFile(base, "ruler.toml")) {
48
+ tools.push({
49
+ name: "ruler",
50
+ sourceSlot: (0, node_path_1.join)(".ruler", "AGENTS.md"),
51
+ // Ruler's default agent outputs that overlap vigiles instruction targets.
52
+ distributes: ["CLAUDE.md", "AGENTS.md"],
53
+ });
54
+ }
55
+ if (hasDir(base, ".rulesync")) {
56
+ tools.push({
57
+ name: "rulesync",
58
+ sourceSlot: (0, node_path_1.join)(".rulesync", "rules", "vigiles.md"),
59
+ distributes: ["CLAUDE.md", "AGENTS.md"],
60
+ });
61
+ }
62
+ return tools;
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // Collision analysis
66
+ // ---------------------------------------------------------------------------
67
+ /** Normalize a target to its bare filename for comparison (handles paths). */
68
+ function targetName(target) {
69
+ const parts = target.split(/[\\/]/);
70
+ return parts[parts.length - 1];
71
+ }
72
+ /**
73
+ * Detect whether `CLAUDE.md` and `AGENTS.md` at `root` are ONE artifact, not two
74
+ * — a symlink in either direction (`ln -s CLAUDE.md AGENTS.md`) or byte-identical
75
+ * content (a sync tool keeping them in lockstep). Claude Code reads CLAUDE.md
76
+ * only ([anthropics/claude-code#34235]); users bridge to the AGENTS.md tools this
77
+ * way (see `research/sync-tool-compatibility.md` requirement 7). When mirrored,
78
+ * vigiles must treat them as the same file — hash + `require-spec` run once on the
79
+ * real one, and the mirror is never flagged as a second, spec-less instruction
80
+ * file. Returns null when one is absent, or both exist but genuinely differ.
81
+ */
82
+ function detectInstructionMirror(root) {
83
+ const claude = (0, node_path_1.join)(root, "CLAUDE.md");
84
+ const agents = (0, node_path_1.join)(root, "AGENTS.md");
85
+ if (!(0, node_fs_1.existsSync)(claude) || !(0, node_fs_1.existsSync)(agents))
86
+ return null;
87
+ // Symlink either direction (or both → a third file) resolves to one realpath.
88
+ try {
89
+ if ((0, node_fs_1.realpathSync)(claude) === (0, node_fs_1.realpathSync)(agents)) {
90
+ const claudeIsLink = (0, node_fs_1.lstatSync)(claude).isSymbolicLink();
91
+ const link = claudeIsLink ? "CLAUDE.md" : "AGENTS.md";
92
+ const realTarget = claudeIsLink ? "AGENTS.md" : "CLAUDE.md";
93
+ return {
94
+ files: ["CLAUDE.md", "AGENTS.md"],
95
+ kind: "symlink",
96
+ link,
97
+ realTarget,
98
+ reason: `${link} is a symlink resolving to the same file as ${realTarget} — one instruction artifact bridged to the AGENTS.md tools; verify and hash the real file once, never flag the mirror as spec-less.`,
99
+ };
100
+ }
101
+ }
102
+ catch {
103
+ /* realpath failed (race / broken link) → fall through to content compare */
104
+ }
105
+ // Byte-identical content: kept in sync by rulesync/Ruler rather than symlinked.
106
+ try {
107
+ if ((0, node_fs_1.readFileSync)(claude).equals((0, node_fs_1.readFileSync)(agents))) {
108
+ return {
109
+ files: ["CLAUDE.md", "AGENTS.md"],
110
+ kind: "identical-content",
111
+ reason: `CLAUDE.md and AGENTS.md are byte-identical — kept in sync (rulesync/Ruler); treat as one artifact and stamp the integrity hash only on the compile source slot.`,
112
+ };
113
+ }
114
+ }
115
+ catch {
116
+ /* unreadable → not a detectable mirror */
117
+ }
118
+ return null;
119
+ }
120
+ /**
121
+ * Given the repo `root` and the spec's compile `targets`, report every case
122
+ * where a target is also a file a detected sync tool regenerates. Each
123
+ * collision carries the source slot to compile into instead — the actionable
124
+ * fix that preserves the integrity hash (the tool distributes from there).
125
+ *
126
+ * No detected tool, or no overlapping target, yields an empty list.
127
+ */
128
+ function composeCollisions(root, targets) {
129
+ const tools = detectSyncTools(root);
130
+ const collisions = [];
131
+ for (const tool of tools) {
132
+ for (const target of targets) {
133
+ if (!tool.distributes.includes(targetName(target)))
134
+ continue;
135
+ collisions.push({
136
+ tool: tool.name,
137
+ target,
138
+ redirectTo: tool.sourceSlot,
139
+ reason: `${tool.name} regenerates ${targetName(target)}, which would overwrite vigiles output and stale its integrity hash — compile into ${tool.sourceSlot} and let ${tool.name} distribute.`,
140
+ });
141
+ }
142
+ }
143
+ return collisions;
144
+ }
145
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * HarnessDialect — the format/dialect PORT (see
3
+ * `research/code-adapter-architecture.md`, the format axis).
4
+ *
5
+ * The compiler needs a handful of harness-specific facts to verify and render an
6
+ * instruction file: the built-in tool catalog a subagent may declare, the tools
7
+ * the platform never exposes, the shape of an MCP tool reference, the hook
8
+ * events the harness fires, the instruction-file targets it reads, and the env
9
+ * token expanded to the plugin root. Those used to be hard-coded literals inside
10
+ * `compile.ts`; here they are a single named value behind an interface.
11
+ *
12
+ * Claude Code is the reference dialect today (`claudeCodeDialect`, defined in its
13
+ * adapter at `src/adapters/claude-code/dialect.ts`). A second harness (Codex
14
+ * likely next) is added by defining a sibling `HarnessDialect` — e.g.
15
+ * `src/adapters/codex/dialect.ts` exporting `codexDialect` — and injecting it
16
+ * (`compileAgent(spec, { dialect })`). The compiler reads these from the injected
17
+ * dialect, so the core never hard-codes — nor even defines — one harness's
18
+ * vocabulary: the concrete dialects live in the adapters, only this interface
19
+ * lives in the core. That is the format axis of the hexagonal boundary.
20
+ */
21
+ /**
22
+ * Which SKILL.md frontmatter keys a harness understands — see
23
+ * `HarnessDialect.skillFrontmatter`.
24
+ */
25
+ export type SkillFrontmatterProfile = "claude-code" | "minimal";
26
+ export interface HarnessDialect {
27
+ /** Stable identifier, e.g. "claude-code". */
28
+ readonly name: string;
29
+ /** Built-in tools a subagent may list in its `tools:` contract. */
30
+ readonly builtinAgentTools: readonly string[];
31
+ /** Tools the platform never exposes to a subagent (a listed one is dead). */
32
+ readonly neverAvailableTools: readonly string[];
33
+ /** Matches an MCP tool reference, e.g. `mcp__server__tool`. */
34
+ readonly mcpToolPattern: RegExp;
35
+ /** Hook event names the harness fires. */
36
+ readonly hookEvents: readonly string[];
37
+ /** Instruction-file targets the harness reads (also the h1 heading). */
38
+ readonly instructionTargets: readonly string[];
39
+ /** The env token expanded to the plugin root in hook commands. */
40
+ readonly pluginRootToken: string;
41
+ /**
42
+ * Which SKILL.md frontmatter keys this harness understands — the profile the
43
+ * compiler renders under:
44
+ * - `"claude-code"` — the full Claude Code set (name, description, plus the
45
+ * CC-only keys: disable-model-invocation, argument-hint, …).
46
+ * - `"minimal"` — name + description ONLY (the cross-tool SKILL.md shape Codex
47
+ * and OpenCode read; CC-only keys are omitted because they'd be inert noise).
48
+ */
49
+ readonly skillFrontmatter: SkillFrontmatterProfile;
50
+ }
51
+ //# sourceMappingURL=dialect.d.ts.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=dialect.js.map
@@ -0,0 +1,134 @@
1
+ /**
2
+ * HarnessTestDriver — the seam that makes the pillar-2 deterministic runner
3
+ * (`runHarnessTest`) adapter-driven instead of hard-wired to Claude Code.
4
+ *
5
+ * Each harness (Claude Code, Codex, …) brings a driver that knows how to: build
6
+ * the non-interactive argv for its binary, start its scripted mock model, and
7
+ * parse its stdout into the unified `Trace` fields. The runner stays
8
+ * harness-agnostic — it spawns `driver.runtime.agentBinary`, points it at the
9
+ * mock via `driver.runtime.wireMock(url)`, and assembles the result through
10
+ * `driver.parseRun`. A `HarnessAdapter` carries its driver as
11
+ * `harnessTestDriver`, so the runner never imports a sibling adapter.
12
+ *
13
+ * The shared trace shapes (`ModelTurn`/`ModelRequest`/`ToolCall`/`HookFire`)
14
+ * live here in core so BOTH the adapters and the runner can reference them
15
+ * without a cross-adapter import. `vigiles/claude-code` re-exports
16
+ * `ModelTurn`/`ModelRequest` for back-compat.
17
+ */
18
+ import type { HarnessRuntime } from "./runtime.js";
19
+ /**
20
+ * One scripted assistant turn: a final text answer, or a tool call. The common
21
+ * shape both harness mocks consume — the Anthropic Messages mock
22
+ * (`src/adapters/claude-code/mock-model.ts`) and the OpenAI Responses mock
23
+ * (`src/adapters/codex/mock-model.ts`, which uses only `text`).
24
+ */
25
+ export interface ModelTurn {
26
+ /** Final text answer (stops the turn). */
27
+ readonly text?: string;
28
+ /** A tool to invoke, e.g. "Bash" | "Write" | "Edit". */
29
+ readonly tool?: string;
30
+ /** The tool input, e.g. `{ file_path, content }` or `{ command }`. */
31
+ readonly input?: Record<string, unknown>;
32
+ }
33
+ /**
34
+ * One model request the scripted mock received, flattened to text for
35
+ * assertions — the seam that lets a harness test prove what reached the model
36
+ * (a SessionStart hook's injected context, a slash command's expansion), not
37
+ * just that a hook fired.
38
+ */
39
+ export interface ModelRequest {
40
+ /** The system prompt, flattened to text (string or text-block array). */
41
+ readonly system: string;
42
+ /** The conversation messages, each flattened to `{ role, text }`. */
43
+ readonly messages: readonly {
44
+ readonly role: string;
45
+ readonly text: string;
46
+ }[];
47
+ }
48
+ /** A tool the agent invoked, paired with its result (transcript mode only). */
49
+ export interface ToolCall {
50
+ readonly name: string;
51
+ readonly input: unknown;
52
+ /** The tool_result text ("" if none / not captured). */
53
+ readonly resultText: string;
54
+ /** Whether the tool_result came back flagged as an error. */
55
+ readonly isError: boolean;
56
+ }
57
+ /**
58
+ * A hook invocation observed during the run, recorded (not inferred) from the
59
+ * harness's stream events — so a test can assert which hook fired and whether
60
+ * it blocked.
61
+ */
62
+ export interface HookFire {
63
+ /** The hook label, e.g. `"PreToolUse:Edit"` (`Event:Matcher`). */
64
+ readonly name: string;
65
+ /** The hook event, e.g. `"PreToolUse"`, `"PostToolUse"`, `"Stop"`. */
66
+ readonly event: string;
67
+ /** The hook process exit code (2 = block), or undefined if not reported. */
68
+ readonly exitCode: number | undefined;
69
+ /** Whether the hook blocked / errored (exit ≠ 0 or outcome "error"). */
70
+ readonly blocked: boolean;
71
+ /** What the hook printed (its block reason / diagnostic), or "". */
72
+ readonly output: string;
73
+ }
74
+ /** A running scripted mock model: the URL to point the binary at + the record. */
75
+ export interface HarnessMockHandle {
76
+ /** Base URL the spawned binary is pointed at. */
77
+ readonly url: string;
78
+ /** Every request the mock received, flattened for assertions, in order. */
79
+ readonly requests: readonly ModelRequest[];
80
+ /** Number of model turns served so far. */
81
+ readonly count: number;
82
+ /** Stop the mock server. */
83
+ close(): void | Promise<void>;
84
+ }
85
+ /** The facts a driver needs to build the non-interactive argv for one run. */
86
+ export interface HarnessDriverContext {
87
+ /** The user prompt. */
88
+ readonly prompt: string;
89
+ /** The temp working dir the binary runs in. */
90
+ readonly cwd: string;
91
+ /** Whether a settings file was written (CC: pass `--settings`). */
92
+ readonly hasSettings: boolean;
93
+ /** Tools the agent may use. */
94
+ readonly tools: readonly string[];
95
+ /** Capture the full event transcript instead of just the final result. */
96
+ readonly transcript: boolean;
97
+ /** Path to a plugin dir to install natively, if any. */
98
+ readonly pluginDir?: string;
99
+ /**
100
+ * The mock-wiring args from `runtime.wireMock(url).args` — the flags that
101
+ * point the binary at the mock model. The DRIVER inserts them at the correct
102
+ * argv position (for Codex they MUST follow `exec`; for env-only harnesses
103
+ * like Claude Code this is empty). Passed in (not prepended by the runner)
104
+ * because only the driver knows its argv structure.
105
+ */
106
+ readonly mockArgs: readonly string[];
107
+ }
108
+ /** A harness's stdout parsed into the unified `Trace` fields. */
109
+ export interface ParsedRun {
110
+ /** Number of model turns, if the stdout reports it (else taken from the mock). */
111
+ readonly turns?: number;
112
+ readonly toolCalls: readonly ToolCall[];
113
+ readonly hooks: readonly HookFire[];
114
+ /** The agent's final answer text, or "". */
115
+ readonly output: string;
116
+ }
117
+ /**
118
+ * What a harness brings so `runHarnessTest` can drive it. Implemented in each
119
+ * adapter (`claudeCodeDriver`, `codexDriver`) and carried on the
120
+ * `HarnessAdapter` as `harnessTestDriver`.
121
+ */
122
+ export interface HarnessTestDriver {
123
+ /** The transport (binary to spawn + how to reach the mock via `wireMock`). */
124
+ readonly runtime: HarnessRuntime;
125
+ /** The non-interactive argv for one run. Pure, so it's unit-tested. */
126
+ buildArgs(ctx: HarnessDriverContext): readonly string[];
127
+ /** Start this harness's scripted mock for the given turn script. */
128
+ startMock(script: readonly ModelTurn[]): Promise<HarnessMockHandle>;
129
+ /** Parse the harness's stdout into the unified trace fields. */
130
+ parseRun(stdout: string): ParsedRun;
131
+ /** Whether the agent binary is available on PATH. */
132
+ available(): boolean;
133
+ }
134
+ //# sourceMappingURL=harness-driver.d.ts.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=harness-driver.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * HookProtocol — the hook-wire PORT (transport axis). How a harness signals that
3
+ * a hook blocked/denied a tool call: the block exit code, the decision values
4
+ * that mean "deny", and the env vars a synthesized event carries. The research
5
+ * (research/harness-landscape.md) found Claude Code and Codex hooks are nearly
6
+ * identical at the wire level (both: JSON on stdin, `permissionDecision: "deny"`
7
+ * / `decision: "block"` / exit 2) — so this descriptor is deliberately thin, and
8
+ * the fact that a second harness needs almost the same values IS the finding.
9
+ * What varies more (config format JSON-vs-TOML, the plugin-root token, the event
10
+ * names) lives in PluginLayout / HarnessDialect, not here.
11
+ *
12
+ * The Claude Code implementation is `claudeCodeHookProtocol` in
13
+ * `src/adapters/claude-code/hook-protocol.ts`.
14
+ */
15
+ export interface HookProtocol {
16
+ /** Stable identifier, e.g. "claude-code". */
17
+ readonly name: string;
18
+ /** Exit code a hook process uses to block/deny a tool call (Claude Code: 2). */
19
+ readonly blockExitCode: number;
20
+ /** decision / permissionDecision values that mean "deny" the tool call. */
21
+ readonly denyDecisionValues: readonly string[];
22
+ /**
23
+ * Env vars a synthesized hook event carries beyond the JSON on stdin (Claude
24
+ * Code passes the event on stdin only; Codex adds session_id/cwd/PLUGIN_ROOT/…).
25
+ */
26
+ readonly eventEnvVars: readonly string[];
27
+ }
28
+ //# sourceMappingURL=hook-protocol.d.ts.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=hook-protocol.js.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * PluginLayout — the plugin/repo LAYOUT port (hexagonal format axis, the
3
+ * filesystem half). A harness's plugin layout is where its instruction file,
4
+ * skills, subagents, commands, hooks and settings live on disk, plus the env
5
+ * token expanded to the plugin root. The loader (`loadPlugin`) reads those from
6
+ * this descriptor instead of hard-coding Claude Code's `.claude-plugin/` /
7
+ * `.claude/` conventions, so a second harness (Codex) supplies its own
8
+ * `PluginLayout` and reuses the same loader.
9
+ *
10
+ * Paths are repo-relative (POSIX-style, `join`-friendly). The Claude Code
11
+ * implementation is `claudeCodeLayout` in `src/adapters/claude-code/layout.ts`.
12
+ */
13
+ export interface PluginLayout {
14
+ /** Stable identifier, e.g. "claude-code". */
15
+ readonly name: string;
16
+ /** Plugin manifest, e.g. `.claude-plugin/plugin.json`. */
17
+ readonly manifestPath: string;
18
+ /** Convention path for a standalone hooks file, e.g. `hooks/hooks.json`. */
19
+ readonly hooksConventionPath: string;
20
+ /** Repo settings carrying hooks, e.g. `.claude/settings.json` or `.codex/config.toml`. */
21
+ readonly settingsPath: string;
22
+ /**
23
+ * How the settings file is encoded — `"json"` (Claude Code's settings.json) or
24
+ * `"toml"` (Codex's `config.toml` `[hooks]`). The loader dispatches a parser on
25
+ * it, so a TOML-configured harness's hooks aren't silently read as zero.
26
+ */
27
+ readonly settingsFormat: "json" | "toml";
28
+ /** Top-level instruction file, e.g. `CLAUDE.md`. */
29
+ readonly instructionFile: string;
30
+ /** Surface dirs materialized into the sandbox, e.g. skills/agents/commands. */
31
+ readonly surfaceDirs: readonly string[];
32
+ /** Dir the surfaces are materialized under, e.g. `.claude`. */
33
+ readonly materializeRoot: string;
34
+ /** Env token expanded to the plugin's absolute root in hook commands. */
35
+ readonly pluginRootToken: string;
36
+ /** Standalone MCP config file, e.g. `.mcp.json`. */
37
+ readonly mcpConfigFile: string;
38
+ /** Manifest key declaring MCP servers, e.g. `mcpServers`. */
39
+ readonly mcpManifestKey: string;
40
+ /** Dirs scanned for dangling intra-plugin file references. */
41
+ readonly intraRefDirs: readonly string[];
42
+ }
43
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * ModelMock — the model-mock PORT (transport axis). A harness test tier runs the
3
+ * real agent against a fake, scripted model server so it needs no API key and is
4
+ * deterministic. This descriptor captures what the mock's WIRE FORMAT is: the API
5
+ * dialect it speaks, the endpoint that consumes a scripted turn, and the optional
6
+ * token-count endpoint a client probes. The research (research/harness-landscape.md)
7
+ * found the two concrete formats differ sharply — Claude Code speaks Anthropic
8
+ * **Messages** SSE at `/v1/messages`; Codex speaks OpenAI **Responses** SSE at
9
+ * `/v1/responses` — which is exactly the second implementation that makes this
10
+ * port designable.
11
+ *
12
+ * The HTTP server + per-event flush mechanics live in the harness's mock module
13
+ * (`startMock` for Claude Code); a second harness implements the same contract
14
+ * with its own renderer. The Claude Code descriptor is `claudeCodeModelMock` in
15
+ * `src/adapters/claude-code/model-mock.ts`.
16
+ */
17
+ export interface ModelMock {
18
+ /** Stable identifier, e.g. "claude-code". */
19
+ readonly name: string;
20
+ /** The model API wire format, e.g. "anthropic-messages" or "openai-responses". */
21
+ readonly wireApi: string;
22
+ /** URL substring identifying a turn-consuming model request (e.g. "/v1/messages"). */
23
+ readonly modelEndpoint: string;
24
+ /** Token-count endpoint substring a client probes (e.g. "count_tokens"); omit if none. */
25
+ readonly countTokensEndpoint?: string;
26
+ }
27
+ //# sourceMappingURL=model-mock.d.ts.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=model-mock.js.map
@@ -0,0 +1,62 @@
1
+ import type { RuleSeverity } from "./types.js";
2
+ /** An inline code span with its 1-based source line. */
3
+ export interface Span {
4
+ readonly text: string;
5
+ readonly line: number;
6
+ }
7
+ /**
8
+ * Extract inline code spans, skipping fenced code blocks (R1). Returns each
9
+ * span's trimmed text and 1-based line.
10
+ */
11
+ export declare function inlineSpans(markdown: string): Span[];
12
+ /** A parsed file-qualified reference. */
13
+ export interface SymbolRef {
14
+ readonly file: string;
15
+ readonly symbol: string;
16
+ readonly line: number;
17
+ }
18
+ /** A reference that failed verification. */
19
+ export interface SymbolRefError extends SymbolRef {
20
+ readonly reason: string;
21
+ }
22
+ /** Extract the `vigiles:symbol` references from a markdown file. */
23
+ export declare function symbolRefs(markdown: string): SymbolRef[];
24
+ /**
25
+ * Verify the file-qualified symbol references in a markdown file: the named
26
+ * file must exist and define the named symbol. `basePath` is the directory the
27
+ * paths resolve against (the instruction file's own directory).
28
+ */
29
+ export declare function verifySymbolRefs(markdown: string, basePath: string): SymbolRefError[];
30
+ /**
31
+ * Whether a span is a **linter-rule reference** that ought to be marked
32
+ * (`enforce()` / inline `<!-- vigiles:enforce -->`) so the audit can verify the
33
+ * rule exists AND is enabled. High-signal only: a slash-scoped name with no file
34
+ * extension. A function-call form `` `foo(args)` `` is reduced to its callee.
35
+ *
36
+ * Deliberately NOT flagged (too noisy / undecidable): bare identifiers
37
+ * (`runHook`, `MAX_RETRIES` — usually API prose, mark opt-in via an explicit
38
+ * `vigiles:symbol`) and file paths (`src/x.ts`, `docs/y.md` — file refs).
39
+ */
40
+ export declare function isCodeShaped(text: string): boolean;
41
+ /**
42
+ * Unmarked linter-rule references — the spans the refs-hook nudges the agent to
43
+ * mark with `enforce()` / `<!-- vigiles:enforce ... -->` (or opt out of with
44
+ * `<!-- vigiles:ignore -->` / `<!-- vigiles:ignore-file -->`).
45
+ */
46
+ export declare function unmarkedCodeRefs(markdown: string): Span[];
47
+ /**
48
+ * The reference issues in an instruction file, as one human-readable line each:
49
+ * a `vigiles:symbol` mark whose symbol is missing, plus every unmarked
50
+ * code-shaped span that ought to be a mark. The shared detector behind both the
51
+ * `vigiles refs` CLI and the PostToolUse refs-hook.
52
+ */
53
+ export declare function collectRefIssues(markdown: string, basePath: string): string[];
54
+ /** What the refs-hook should do given the issue count and configured severity. */
55
+ export type RefsHookAction = "ok" | "nudge" | "block";
56
+ /**
57
+ * Map detected issues + the `unmarked-refs` rule severity to a hook action:
58
+ * no issues or `false` → ok, `"error"` → block (exit 2), anything else
59
+ * (`"warn"`, the default) → a non-blocking nudge.
60
+ */
61
+ export declare function refsHookAction(issueCount: number, severity: RuleSeverity): RefsHookAction;
62
+ //# sourceMappingURL=refs.d.ts.map
@@ -5,6 +5,8 @@ exports.symbolRefs = symbolRefs;
5
5
  exports.verifySymbolRefs = verifySymbolRefs;
6
6
  exports.isCodeShaped = isCodeShaped;
7
7
  exports.unmarkedCodeRefs = unmarkedCodeRefs;
8
+ exports.collectRefIssues = collectRefIssues;
9
+ exports.refsHookAction = refsHookAction;
8
10
  /**
9
11
  * vigiles — file-qualified symbol reference verification (variant A).
10
12
  *
@@ -97,35 +99,32 @@ function verifySymbolRefs(markdown, basePath) {
97
99
  // ---------------------------------------------------------------------------
98
100
  // Enforcement: force code references to carry the file-qualified mark
99
101
  // ---------------------------------------------------------------------------
100
- const PATH_LIKE = /[/\\]|\.[A-Za-z0-9]+$/; // a path or a bare filename
101
- const PLAIN_ID = /^[A-Za-z_]\w*$/;
102
- const SCOPED = /^[A-Za-z_]\w*(?:#|::)[\w?!]+$/;
102
+ const HAS_EXT = /\.[A-Za-z0-9]+$/; // a file extension → a path/filename, not a rule
103
+ // A linter-rule reference: a slash-scoped `linter/rule` (optionally `@scoped`)
104
+ // with NO file extension — `eslint/no-console`, `@typescript-eslint/no-x`,
105
+ // `boundaries/dependencies`. Deliberately NOT a path (`src/x.ts`) or a bare
106
+ // identifier (`runHook`): those are excluded.
107
+ const RULE_SHAPED = /^@?[a-z][\w-]*(?:\/[\w-]+)+$/;
103
108
  const IGNORE_FILE = /<!--\s*vigiles:ignore-file\s*-->/;
104
109
  const IGNORE_LINE = /<!--\s*vigiles:ignore\s*-->/;
105
110
  /**
106
- * Whether a span looks like a *code reference* that ought to carry a
107
- * file-qualified mark a scoped name, or an identifier that isn't a bare
108
- * lowercase prose word. A function-call form `` `foo(args)` `` is treated as a
109
- * reference to its callee `foo`. Paths/filenames are excluded (they are `file`
110
- * refs).
111
+ * Whether a span is a **linter-rule reference** that ought to be marked
112
+ * (`enforce()` / inline `<!-- vigiles:enforce -->`) so the audit can verify the
113
+ * rule exists AND is enabled. High-signal only: a slash-scoped name with no file
114
+ * extension. A function-call form `` `foo(args)` `` is reduced to its callee.
115
+ *
116
+ * Deliberately NOT flagged (too noisy / undecidable): bare identifiers
117
+ * (`runHook`, `MAX_RETRIES` — usually API prose, mark opt-in via an explicit
118
+ * `vigiles:symbol`) and file paths (`src/x.ts`, `docs/y.md` — file refs).
111
119
  */
112
120
  function isCodeShaped(text) {
113
121
  const callee = text.replace(/\s*\([^)]*\)\s*$/, ""); // `foo(args)` → `foo`
114
- if (SCOPED.test(callee))
115
- return true;
116
- if (!PLAIN_ID.test(callee))
117
- return false;
118
- const hasUnderscore = callee.includes("_");
119
- const hasCamel = /[a-z][A-Z]/.test(callee);
120
- const isPascal = /^[A-Z][a-z]/.test(callee);
121
- const isScreaming = /^[A-Z][A-Z0-9_]+$/.test(callee);
122
- return hasUnderscore || hasCamel || isPascal || isScreaming;
122
+ return RULE_SHAPED.test(callee) && !HAS_EXT.test(callee);
123
123
  }
124
124
  /**
125
- * Code-shaped inline references that are NOT yet marked the spans the
126
- * enforcement hook makes the agent mark as `` `vigiles:symbol path.ext#symbol` ``
127
- * or opt out of with `<!-- vigiles:ignore -->` (or `<!-- vigiles:ignore-file -->`
128
- * for the whole file).
125
+ * Unmarked linter-rule references the spans the refs-hook nudges the agent to
126
+ * mark with `enforce()` / `<!-- vigiles:enforce ... -->` (or opt out of with
127
+ * `<!-- vigiles:ignore -->` / `<!-- vigiles:ignore-file -->`).
129
128
  */
130
129
  function unmarkedCodeRefs(markdown) {
131
130
  if (IGNORE_FILE.test(markdown))
@@ -136,9 +135,36 @@ function unmarkedCodeRefs(markdown) {
136
135
  return false;
137
136
  if (SYMBOL_MARK.test(span.text))
138
137
  return false; // already a vigiles:symbol mark
139
- if (PATH_LIKE.test(span.text))
140
- return false; // a path/filename → file ref
141
138
  return isCodeShaped(span.text);
142
139
  });
143
140
  }
141
+ /**
142
+ * The reference issues in an instruction file, as one human-readable line each:
143
+ * a `vigiles:symbol` mark whose symbol is missing, plus every unmarked
144
+ * code-shaped span that ought to be a mark. The shared detector behind both the
145
+ * `vigiles refs` CLI and the PostToolUse refs-hook.
146
+ */
147
+ function collectRefIssues(markdown, basePath) {
148
+ const out = [];
149
+ for (const b of verifySymbolRefs(markdown, basePath)) {
150
+ out.push(`line ${String(b.line)}: ${b.reason}`);
151
+ }
152
+ for (const u of unmarkedCodeRefs(markdown)) {
153
+ out.push(`line ${String(u.line)}: \`${u.text}\` is an unmarked linter-rule ` +
154
+ `reference — mark it as \`enforce("${u.text}")\` (typed spec) or ` +
155
+ `\`<!-- vigiles:enforce ${u.text} -->\` (markdown) so audit can verify ` +
156
+ `it exists and is enabled, or add <!-- vigiles:ignore --> if it is prose`);
157
+ }
158
+ return out;
159
+ }
160
+ /**
161
+ * Map detected issues + the `unmarked-refs` rule severity to a hook action:
162
+ * no issues or `false` → ok, `"error"` → block (exit 2), anything else
163
+ * (`"warn"`, the default) → a non-blocking nudge.
164
+ */
165
+ function refsHookAction(issueCount, severity) {
166
+ if (issueCount === 0 || severity === false)
167
+ return "ok";
168
+ return severity === "error" ? "block" : "nudge";
169
+ }
144
170
  //# sourceMappingURL=refs.js.map