vigiles 11.0.0 → 12.1.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 (49) hide show
  1. package/.claude-plugin/plugin.json +9 -0
  2. package/README.md +10 -5
  3. package/action.yml +13 -2
  4. package/dist/adapter-conformance.js +6 -0
  5. package/dist/adapter-registry.d.ts +20 -0
  6. package/dist/adapter-registry.js +27 -0
  7. package/dist/adapters/claude-code/hook-protocol.js +4 -0
  8. package/dist/adapters/claude-code/runtime.js +12 -0
  9. package/dist/adapters/codex/eval.js +3 -0
  10. package/dist/adapters/codex/hook-protocol.d.ts +9 -1
  11. package/dist/adapters/codex/hook-protocol.js +10 -0
  12. package/dist/adapters/codex/runtime.js +10 -0
  13. package/dist/adapters/opencode/runtime.js +4 -0
  14. package/dist/claude-code.d.ts +2 -0
  15. package/dist/claude-code.js +9 -1
  16. package/dist/cli-commands.d.ts +1 -1
  17. package/dist/cli-commands.js +1 -0
  18. package/dist/cli.js +245 -29
  19. package/dist/core/hook-protocol.d.ts +15 -0
  20. package/dist/core/rule-meta.js +8 -0
  21. package/dist/core/runtime.d.ts +20 -0
  22. package/dist/core/skill-description-budget.d.ts +42 -0
  23. package/dist/core/skill-description-budget.js +47 -0
  24. package/dist/core/types.d.ts +21 -0
  25. package/dist/core/validate.js +3 -0
  26. package/dist/doc-command-coverage.d.ts +20 -0
  27. package/dist/doc-command-coverage.js +60 -0
  28. package/dist/eval-cache.d.ts +6 -0
  29. package/dist/eval-cache.js +2 -0
  30. package/dist/eval-cost.d.ts +75 -0
  31. package/dist/eval-cost.js +134 -0
  32. package/dist/eval-lock.d.ts +192 -0
  33. package/dist/eval-lock.js +286 -0
  34. package/dist/eval.d.ts +37 -20
  35. package/dist/eval.js +227 -56
  36. package/dist/research-index.d.ts +31 -0
  37. package/dist/research-index.js +48 -0
  38. package/dist/scan-behavioral.d.ts +42 -0
  39. package/dist/scan-behavioral.js +67 -0
  40. package/dist/scan.d.ts +3 -23
  41. package/dist/scan.js +18 -69
  42. package/dist/setup-plan.d.ts +38 -1
  43. package/dist/setup-plan.js +67 -4
  44. package/hooks/eval-lock-nudge.sh +21 -0
  45. package/package.json +1 -1
  46. package/skills/adopt-spec/SKILL.md +10 -1
  47. package/skills/edit-spec/SKILL.md +1 -0
  48. package/skills/strengthen/SKILL.md +4 -0
  49. package/skills/test-harness/SKILL.md +44 -0
@@ -39,6 +39,15 @@
39
39
  "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/refs-nudge.sh"
40
40
  }
41
41
  ]
42
+ },
43
+ {
44
+ "matcher": "Edit|Write",
45
+ "hooks": [
46
+ {
47
+ "type": "command",
48
+ "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/eval-lock-nudge.sh"
49
+ }
50
+ ]
42
51
  }
43
52
  ],
44
53
  "SessionStart": [
package/README.md CHANGED
@@ -197,7 +197,8 @@ stray `git push` is caught before it happens. No model, no key, on every commit.
197
197
  _"65% fewer tokens." Says who?_ vigiles[^name] A/Bs the claim on real coding tasks and reports
198
198
  the token bill, whether it hit its target, and whether the code still works. promptfoo
199
199
  and DeepEval bill **per token, every run**; vigiles runs on your own Claude Pro/Max
200
- subscription. **[Measure a skill →](docs/measuring-skills.md)**
200
+ subscription. Evals run locally — a committed lock then lets **CI catch stale results with no
201
+ model call**. **[Measure a skill →](docs/measuring-skills.md)**
201
202
 
202
203
  ## Quick start
203
204
 
@@ -225,13 +226,17 @@ npx vigiles init # adopts your files (non-destructive — eject reverses), add
225
226
 
226
227
  Interactive in a terminal, non-interactive for agents/CI (or `--yes`).
227
228
 
228
- **You don't hand-write any of this your agent does.** `init` installs
229
- model-invocable skills, so a plain-English ask does the work:
229
+ **Adoption is smooth: one command, then your agent does the rest.** `init` installs
230
+ the **skills and hooks**, so a plain-English ask does the work — no specs to
231
+ hand-write, no hooks to wire:
230
232
 
231
- - _"test my skills"_ → scaffolds **and runs** a trigger/behaviour test (`test-harness`)
233
+ - _"test my skills"_ → scaffolds **and runs** a trigger/behaviour test, then commits its result so CI can check it (`test-harness`)
232
234
  - _"harden my rules"_ → upgrades prose guidance into enforced linter rules (`strengthen`)
233
235
  - _"add a rule to my CLAUDE.md"_ → edits the source and recompiles (`edit-spec`)
234
236
 
237
+ The **hooks** keep it honest in-loop — nudging the agent to mark a reference or
238
+ refresh a stale eval — so there are no chores to remember.
239
+
235
240
  <details>
236
241
  <summary>What <code>init</code> sets up</summary>
237
242
 
@@ -249,7 +254,7 @@ JS **or** TS (`*.harness.{mjs,ts}`) — run with `npx vigiles test`.
249
254
  ## FAQ
250
255
 
251
256
  - **Isn't this just a markdown linter?** No — it checks whether your instruction file is _true_ (every path/script/symbol/rule exists and is enabled), then tests and measures your harness. A style linter can't do any of that.
252
- - **Do I have to write TypeScript?** No — your agent writes the spec (`init` adopts your CLAUDE.md into one), or plain markdown lints with zero new files. Compiler-grade guarantees are opt-in, like TS's `strict`.
257
+ - **Do I have to write TypeScript?** No — your agent writes the spec (`init` adopts your CLAUDE.md into one), or plain markdown lints with zero new files. Compiler-grade guarantees are opt-in, like TS's `strict` ([why?](docs/faq.md#why-are-the-strongest-guarantees-opt-in-not-the-default)).
253
258
  - **Non-JS repo?** `npx vigiles lint` verifies your CLAUDE.md with no install (Ruff/Clippy/Pylint/… too).
254
259
 
255
260
  **[Full FAQ →](docs/faq.md)**
package/action.yml CHANGED
@@ -7,7 +7,11 @@ branding:
7
7
 
8
8
  inputs:
9
9
  command:
10
- description: "Which vigiles command to run: 'lint' (verify references + integrity + coverage) or 'compile' (specs → markdown)."
10
+ description: >
11
+ Which vigiles command to run: 'lint' (verify references + integrity +
12
+ coverage), 'compile' (specs → markdown), or 'eval-check' (verify committed
13
+ eval locks against current inputs — the staleness gate; runs NO model, so
14
+ it is the CI-safe half of evals you produce locally with `vigiles eval --update`).
11
15
  required: false
12
16
  default: "lint"
13
17
  paths:
@@ -65,7 +69,14 @@ runs:
65
69
  set -euo pipefail
66
70
 
67
71
  cmd="${VIGILES_COMMAND:-lint}"
68
- args=("$cmd")
72
+ # 'eval-check' is the CI staleness gate — it maps to the real verb
73
+ # `eval --check` (verify committed locks vs current inputs, NO model).
74
+ # Real evals run locally on a subscription (`eval --update`), never in CI.
75
+ if [[ "$cmd" == "eval-check" ]]; then
76
+ args=("eval" "--check")
77
+ else
78
+ args=("$cmd")
79
+ fi
69
80
 
70
81
  # paths: split on commas and whitespace into positional args.
71
82
  paths="${VIGILES_PATHS:-}"
@@ -67,6 +67,12 @@ function checkAdapterConformance(adapter) {
67
67
  need(adapter.hookProtocol !== undefined, "capabilities.shellHooks is true but hookProtocol is missing");
68
68
  if (adapter.hookProtocol) {
69
69
  need(Number.isInteger(adapter.hookProtocol.blockExitCode), "hookProtocol.blockExitCode is not an integer");
70
+ // A shell-hook harness must declare WHICH events can inject developer
71
+ // context (`additionalContext`). Encoding it makes "can this harness
72
+ // deliver an inject hook?" a tested contract — the gap that let Codex's
73
+ // inject support sit unverified in prose. Empty would mean the harness
74
+ // can't inject context from a hook at all; every harness we support can.
75
+ need(adapter.hookProtocol.injectableEvents.length > 0, "hookProtocol.injectableEvents is empty — a shell-hook harness must declare the events that honor additionalContext injection (or it can't deliver an inject/nudge hook)");
70
76
  portNames.push(["hookProtocol", adapter.hookProtocol.name]);
71
77
  }
72
78
  }
@@ -83,4 +83,24 @@ export declare function resolveHarnessSelection(opts: {
83
83
  flag?: string;
84
84
  configHarness?: string | readonly string[];
85
85
  }): HarnessSelection;
86
+ /**
87
+ * The FULL adapter set a compile-time INSTALL should fan out to. Unlike
88
+ * `resolveHarnessSelection` (which picks ONE dialect for a single-output compile,
89
+ * since you emit a markdown file in one harness's format), an install writes the
90
+ * SAME artifact into EVERY enabled harness's native config — so a repo targeting
91
+ * both harnesses gets a compiled hook in `.claude/settings.json` AND
92
+ * `.codex/config.toml`, not just the first. Precedence mirrors the single picker:
93
+ *
94
+ * 1. `--harness=` flag → just that one (an explicit override is singular).
95
+ * 2. config `harness` list → ALL of them (the multi-harness fan-out).
96
+ * 3. no config → auto-detect → the one detected.
97
+ *
98
+ * Returns ≥1 adapter, de-duplicated by name (a config that lists a harness twice,
99
+ * or an alias + its canonical, collapses to one install).
100
+ */
101
+ export declare function resolveHarnessAdapters(opts: {
102
+ root: string;
103
+ flag?: string;
104
+ configHarness?: string | readonly string[];
105
+ }): HarnessAdapter[];
86
106
  //# sourceMappingURL=adapter-registry.d.ts.map
@@ -9,6 +9,7 @@ exports.adapterForInstructionFile = adapterForInstructionFile;
9
9
  exports.resolveAdapter = resolveAdapter;
10
10
  exports.normalizeHarnessList = normalizeHarnessList;
11
11
  exports.resolveHarnessSelection = resolveHarnessSelection;
12
+ exports.resolveHarnessAdapters = resolveHarnessAdapters;
12
13
  const adapter_js_1 = require("./adapters/claude-code/adapter.js");
13
14
  const adapter_js_2 = require("./adapters/codex/adapter.js");
14
15
  /** The default adapter when detection finds no harness markers. */
@@ -127,4 +128,30 @@ function resolveHarnessSelection(opts) {
127
128
  }
128
129
  return { kind: "ok", adapter: det.adapter };
129
130
  }
131
+ /**
132
+ * The FULL adapter set a compile-time INSTALL should fan out to. Unlike
133
+ * `resolveHarnessSelection` (which picks ONE dialect for a single-output compile,
134
+ * since you emit a markdown file in one harness's format), an install writes the
135
+ * SAME artifact into EVERY enabled harness's native config — so a repo targeting
136
+ * both harnesses gets a compiled hook in `.claude/settings.json` AND
137
+ * `.codex/config.toml`, not just the first. Precedence mirrors the single picker:
138
+ *
139
+ * 1. `--harness=` flag → just that one (an explicit override is singular).
140
+ * 2. config `harness` list → ALL of them (the multi-harness fan-out).
141
+ * 3. no config → auto-detect → the one detected.
142
+ *
143
+ * Returns ≥1 adapter, de-duplicated by name (a config that lists a harness twice,
144
+ * or an alias + its canonical, collapses to one install).
145
+ */
146
+ function resolveHarnessAdapters(opts) {
147
+ const { root, flag, configHarness } = opts;
148
+ if (flag !== undefined && flag !== "")
149
+ return [resolveAdapter(root, flag)];
150
+ const list = normalizeHarnessList(configHarness);
151
+ const adapters = list.length > 0
152
+ ? list.map((h) => resolveAdapter(root, h))
153
+ : [detectAdapterResult(root).adapter];
154
+ const seen = new Set();
155
+ return adapters.filter((a) => !seen.has(a.name) && seen.add(a.name));
156
+ }
130
157
  //# sourceMappingURL=adapter-registry.js.map
@@ -6,5 +6,9 @@ exports.claudeCodeHookProtocol = {
6
6
  blockExitCode: 2,
7
7
  denyDecisionValues: ["block", "deny"],
8
8
  eventEnvVars: [],
9
+ // Events that honor `hookSpecificOutput.additionalContext` (developer-context
10
+ // injection). Covers vigiles's shipped inject hooks: the SessionStart lint
11
+ // summary and the PostToolUse refs / eval-lock nudges.
12
+ injectableEvents: ["SessionStart", "UserPromptSubmit", "PostToolUse"],
9
13
  };
10
14
  //# sourceMappingURL=hook-protocol.js.map
@@ -22,6 +22,18 @@ exports.claudeCodeRuntime = {
22
22
  },
23
23
  };
24
24
  },
25
+ /**
26
+ * Claude Code keys on **major.minor**: a minor/major bump is where the system
27
+ * prompt + tool defs actually move (0.2 → 1.0 → 2.0 → 2.1, ~quarterly), while
28
+ * the daily patch stream rarely changes behavior — so keying patches would
29
+ * churn the cache for no signal. Falls back to the trimmed raw string when no
30
+ * semver is found. (If a specific patch is known to matter, clear the cache or
31
+ * bump `CACHE_FORMAT_VERSION`.)
32
+ */
33
+ versionKey(raw) {
34
+ const m = /(\d+)\.(\d+)\.\d+/.exec(raw);
35
+ return m ? `${m[1]}.${m[2]}` : raw.trim();
36
+ },
25
37
  };
26
38
  /**
27
39
  * Build the spawn env that points the agent CLI at the mock model: the caller's
@@ -195,6 +195,9 @@ exports.codexEvalDriver = {
195
195
  runner: codexEvalAgentRunner,
196
196
  parse: parseCodexEvalRun,
197
197
  runError: codexRunError,
198
+ // The harness identity → folded into the trigger-rate lock hash, so a report
199
+ // recorded on Claude Code is STALE if the eval is switched to Codex (and v.v.).
200
+ harness: "codex",
198
201
  };
199
202
  /**
200
203
  * Spawn real `codex exec --json` for the eval tier (real model, the user's codex
@@ -1,9 +1,17 @@
1
1
  /**
2
- * codexHookProtocol — EXPERIMENTAL, internal-only. Codex's hook wire protocol.
2
+ * codexHookProtocol — Codex's hook wire protocol.
3
3
  * Finding: it is essentially IDENTICAL to Claude Code's (exit 2 / `decision:block`
4
4
  * / `permissionDecision:deny`) — the thin `HookProtocol` port was the right call.
5
5
  * The genuine deltas are the env vars a hook receives + the TOML config format
6
6
  * (the latter lives in PluginLayout.settingsFormat, not here).
7
+ *
8
+ * Context injection (`hookSpecificOutput.additionalContext`) is ALSO shared — same
9
+ * shape, confirmed against the official Codex hooks docs
10
+ * (developers.openai.com/codex/hooks): supported on SessionStart, UserPromptSubmit,
11
+ * PreToolUse, PostToolUse, SubagentStart. (Earlier docs called this "deferred" —
12
+ * it is not.) So vigiles's PostToolUse nudges + SessionStart summary deliver on
13
+ * Codex unchanged. Caveats: Stop/SubagentStop/PreCompact carry no context, and
14
+ * Codex marks a hook run failed if it emits an unsupported field for the event.
7
15
  */
8
16
  import type { HookProtocol } from "../../core/hook-protocol.js";
9
17
  export declare const codexHookProtocol: HookProtocol;
@@ -8,6 +8,16 @@ exports.codexHookProtocol = {
8
8
  // Codex matchers are anchored regexes (`matcher = "^Bash$"`), unlike Claude
9
9
  // Code's exact tool name / `A|B` alternation.
10
10
  matcherStyle: "regex",
11
+ // Events that honor `hookSpecificOutput.additionalContext` on Codex, per the
12
+ // official hooks docs. Includes the events vigiles's shipped hooks use
13
+ // (PostToolUse, SessionStart), so those nudges reach the Codex agent too.
14
+ injectableEvents: [
15
+ "SessionStart",
16
+ "UserPromptSubmit",
17
+ "PreToolUse",
18
+ "PostToolUse",
19
+ "SubagentStart",
20
+ ],
11
21
  eventEnvVars: [
12
22
  "session_id",
13
23
  "cwd",
@@ -22,6 +22,16 @@ exports.codexRuntime = {
22
22
  wireMock(baseUrl) {
23
23
  return { args: codexMockArgs(baseUrl), env: codexMockEnv() };
24
24
  },
25
+ /**
26
+ * Codex opts OUT of version partitioning (`""`). It is perpetual `0.x` where
27
+ * the *minor* is the patch cadence (~2 bumps/week, 134 minors in 14 months), so
28
+ * keying `major.minor` like Claude Code would churn the cache/lock weekly. With
29
+ * no stable behavior boundary in the version string, Codex relies on the dated
30
+ * model id + `evalApiVersion` for staleness instead. See research/cache-invalidation.md.
31
+ */
32
+ versionKey(_raw) {
33
+ return "";
34
+ },
25
35
  };
26
36
  /**
27
37
  * The PROVEN `-c` flag array that points `codex exec` at a mock served at
@@ -17,5 +17,9 @@ exports.opencodeRuntime = {
17
17
  },
18
18
  };
19
19
  },
20
+ /** Prototype: opts out of version partitioning (like Codex). */
21
+ versionKey(_raw) {
22
+ return "";
23
+ },
20
24
  };
21
25
  //# sourceMappingURL=runtime.js.map
@@ -10,6 +10,8 @@ export * from "./mock-model.js";
10
10
  export { claudeCodeDriver, buildClaudeArgs, parseClaudeRun, claudeAvailable, } from "./harness-test.js";
11
11
  export * from "./adapters/claude-code/dialect.js";
12
12
  export { agent, skill, type ClaudeCodeToolVocabulary, } from "./adapters/claude-code/typed-spec.js";
13
+ export { measureSelectionMatrix, assertNoCollision, formatSelectionReport, } from "./scan-behavioral.js";
14
+ export type { SelectionReport, SkillSelectionStat, SelectionOptions, SelectionMatrixOptions, } from "./scan-behavioral.js";
13
15
  export * from "./adapters/claude-code/layout.js";
14
16
  export * from "./adapters/claude-code/runtime.js";
15
17
  export * from "./adapters/claude-code/hook-protocol.js";
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.skill = exports.agent = exports.claudeAvailable = exports.parseClaudeRun = exports.buildClaudeArgs = exports.claudeCodeDriver = void 0;
17
+ exports.formatSelectionReport = exports.assertNoCollision = exports.measureSelectionMatrix = exports.skill = exports.agent = exports.claudeAvailable = exports.parseClaudeRun = exports.buildClaudeArgs = exports.claudeCodeDriver = void 0;
18
18
  /**
19
19
  * `vigiles/claude-code` — the Claude Code-specific harness pieces a *different*
20
20
  * harness would swap out: the plugin/repo loader (reads real Claude Code plugin
@@ -42,6 +42,14 @@ __exportStar(require("./adapters/claude-code/dialect.js"), exports);
42
42
  var typed_spec_js_1 = require("./adapters/claude-code/typed-spec.js");
43
43
  Object.defineProperty(exports, "agent", { enumerable: true, get: function () { return typed_spec_js_1.agent; } });
44
44
  Object.defineProperty(exports, "skill", { enumerable: true, get: function () { return typed_spec_js_1.skill; } });
45
+ // Selection-collision — a Claude-Code-ONLY behavioral measurement (Codex has no
46
+ // skill-selection event to read), so it lives on this surface, not the agnostic
47
+ // `vigiles/testing`. `measureSelectionMatrix` builds the N×N "which skill fired?"
48
+ // matrix (diagonal = recall, off-diagonal = collision); `assertNoCollision` gates it.
49
+ var scan_behavioral_js_1 = require("./scan-behavioral.js");
50
+ Object.defineProperty(exports, "measureSelectionMatrix", { enumerable: true, get: function () { return scan_behavioral_js_1.measureSelectionMatrix; } });
51
+ Object.defineProperty(exports, "assertNoCollision", { enumerable: true, get: function () { return scan_behavioral_js_1.assertNoCollision; } });
52
+ Object.defineProperty(exports, "formatSelectionReport", { enumerable: true, get: function () { return scan_behavioral_js_1.formatSelectionReport; } });
45
53
  __exportStar(require("./adapters/claude-code/layout.js"), exports);
46
54
  __exportStar(require("./adapters/claude-code/runtime.js"), exports);
47
55
  __exportStar(require("./adapters/claude-code/hook-protocol.js"), exports);
@@ -13,7 +13,7 @@
13
13
  /** Human-facing verbs (printed in help; typed by a human/agent/CI). */
14
14
  export declare const VERBS: readonly ["init", "compile", "eject", "lint", "test", "eval", "audit", "scaffold-test", "generate", "hook-runtime"];
15
15
  /** Runtime entrypoint kinds under `vigiles hook-runtime <kind>` (emitted, not typed). */
16
- export declare const HOOK_RUNTIME_KINDS: readonly ["run-program", "agent", "agent-start", "agent-done", "skill", "skill-tool", "skill-start", "skill-done", "run-skill", "intercept-tool", "guard", "action", "refs", "effect-enter", "effect-exit"];
16
+ export declare const HOOK_RUNTIME_KINDS: readonly ["run-program", "agent", "agent-start", "agent-done", "skill", "skill-tool", "skill-start", "skill-done", "run-skill", "intercept-tool", "guard", "action", "refs", "eval-lock-nudge", "effect-enter", "effect-exit"];
17
17
  export type Verb = (typeof VERBS)[number];
18
18
  export type HookRuntimeKind = (typeof HOOK_RUNTIME_KINDS)[number];
19
19
  //# sourceMappingURL=cli-commands.d.ts.map
@@ -41,6 +41,7 @@ exports.HOOK_RUNTIME_KINDS = [
41
41
  "guard",
42
42
  "action",
43
43
  "refs",
44
+ "eval-lock-nudge",
44
45
  "effect-enter",
45
46
  "effect-exit",
46
47
  ];