vigiles 2.5.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/.claude-plugin/plugin.json +9 -0
  2. package/README.md +73 -434
  3. package/action.yml +143 -7
  4. package/dist/action-gate.d.ts +1 -1
  5. package/dist/action-gate.js +1 -1
  6. package/dist/adapter-conformance.d.ts +30 -0
  7. package/dist/adapter-conformance.js +153 -0
  8. package/dist/adapter-registry.d.ts +42 -0
  9. package/dist/adapter-registry.js +55 -0
  10. package/dist/adapter.d.ts +26 -0
  11. package/dist/adapter.js +16 -0
  12. package/dist/adapters/claude-code/adapter.d.ts +3 -0
  13. package/dist/adapters/claude-code/adapter.js +46 -0
  14. package/dist/{agent-result.d.ts → adapters/claude-code/agent-result.d.ts} +1 -1
  15. package/dist/adapters/claude-code/dialect.d.ts +13 -0
  16. package/dist/adapters/claude-code/dialect.js +51 -0
  17. package/dist/adapters/claude-code/egress-entry.d.ts +2 -0
  18. package/dist/adapters/claude-code/egress-entry.js +115 -0
  19. package/dist/adapters/claude-code/egress-proxy.d.ts +2 -0
  20. package/dist/adapters/claude-code/egress-proxy.js +60 -0
  21. package/dist/adapters/claude-code/egress.d.ts +114 -0
  22. package/dist/adapters/claude-code/egress.js +276 -0
  23. package/dist/adapters/claude-code/eval-baseline.d.ts +68 -0
  24. package/dist/adapters/claude-code/eval-baseline.js +173 -0
  25. package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
  26. package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
  27. package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
  28. package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
  29. package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
  30. package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
  31. package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
  32. package/dist/adapters/claude-code/hook-protocol.js +10 -0
  33. package/dist/adapters/claude-code/layout.d.ts +8 -0
  34. package/dist/adapters/claude-code/layout.js +18 -0
  35. package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
  36. package/dist/adapters/claude-code/model-mock.d.ts +11 -0
  37. package/dist/adapters/claude-code/model-mock.js +10 -0
  38. package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
  39. package/dist/adapters/claude-code/plugin-loader.js +19 -0
  40. package/dist/adapters/claude-code/run-hook.d.ts +204 -0
  41. package/dist/adapters/claude-code/run-hook.js +418 -0
  42. package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
  43. package/dist/adapters/claude-code/run-scripts.js +150 -0
  44. package/dist/adapters/claude-code/runtime.d.ts +16 -0
  45. package/dist/adapters/claude-code/runtime.js +39 -0
  46. package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +45 -4
  47. package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +76 -8
  48. package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
  49. package/dist/adapters/codex/adapter.d.ts +3 -0
  50. package/dist/adapters/codex/adapter.js +49 -0
  51. package/dist/adapters/codex/dialect.d.ts +10 -0
  52. package/dist/adapters/codex/dialect.js +30 -0
  53. package/dist/adapters/codex/driver.d.ts +20 -0
  54. package/dist/adapters/codex/driver.js +89 -0
  55. package/dist/adapters/codex/hook-protocol.d.ts +10 -0
  56. package/dist/adapters/codex/hook-protocol.js +18 -0
  57. package/dist/adapters/codex/layout.d.ts +16 -0
  58. package/dist/adapters/codex/layout.js +18 -0
  59. package/dist/adapters/codex/mock-model.d.ts +52 -0
  60. package/dist/adapters/codex/mock-model.js +210 -0
  61. package/dist/adapters/codex/model-mock.d.ts +11 -0
  62. package/dist/adapters/codex/model-mock.js +10 -0
  63. package/dist/adapters/codex/runtime.d.ts +38 -0
  64. package/dist/adapters/codex/runtime.js +62 -0
  65. package/dist/adapters/opencode/adapter.d.ts +3 -0
  66. package/dist/adapters/opencode/adapter.js +48 -0
  67. package/dist/adapters/opencode/dialect.d.ts +10 -0
  68. package/dist/adapters/opencode/dialect.js +36 -0
  69. package/dist/adapters/opencode/layout.d.ts +9 -0
  70. package/dist/adapters/opencode/layout.js +25 -0
  71. package/dist/adapters/opencode/model-mock.d.ts +10 -0
  72. package/dist/adapters/opencode/model-mock.js +10 -0
  73. package/dist/adapters/opencode/runtime.d.ts +9 -0
  74. package/dist/adapters/opencode/runtime.js +21 -0
  75. package/dist/claude-code.d.ts +16 -0
  76. package/dist/claude-code.js +32 -0
  77. package/dist/cli-flags.d.ts +22 -0
  78. package/dist/cli-flags.js +38 -0
  79. package/dist/cli.js +406 -206
  80. package/dist/codex.d.ts +20 -0
  81. package/dist/codex.js +36 -0
  82. package/dist/community-skills.d.ts +1 -1
  83. package/dist/community-skills.js +2 -2
  84. package/dist/core/adapter.d.ts +88 -0
  85. package/dist/core/adapter.js +3 -0
  86. package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
  87. package/dist/{compile.js → core/compile.js} +68 -53
  88. package/dist/core/compose.d.ts +79 -0
  89. package/dist/core/compose.js +145 -0
  90. package/dist/core/dialect.d.ts +51 -0
  91. package/dist/core/dialect.js +3 -0
  92. package/dist/core/harness-driver.d.ts +134 -0
  93. package/dist/core/harness-driver.js +3 -0
  94. package/dist/core/hook-protocol.d.ts +28 -0
  95. package/dist/core/hook-protocol.js +3 -0
  96. package/dist/core/layout.d.ts +43 -0
  97. package/dist/core/layout.js +3 -0
  98. package/dist/core/model-mock.d.ts +27 -0
  99. package/dist/core/model-mock.js +3 -0
  100. package/dist/core/refs.d.ts +62 -0
  101. package/dist/{refs.js → core/refs.js} +49 -23
  102. package/dist/core/runtime.d.ts +38 -0
  103. package/dist/core/runtime.js +3 -0
  104. package/dist/{types.d.ts → core/types.d.ts} +29 -0
  105. package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
  106. package/dist/{validate.js → core/validate.js} +41 -4
  107. package/dist/e2e.d.ts +19 -0
  108. package/dist/e2e.js +39 -0
  109. package/dist/harness-assert.d.ts +60 -11
  110. package/dist/harness-assert.js +111 -9
  111. package/dist/integration.d.ts +16 -0
  112. package/dist/integration.js +32 -0
  113. package/dist/leaderboard.d.ts +33 -0
  114. package/dist/leaderboard.js +107 -0
  115. package/dist/linting.d.ts +9 -0
  116. package/dist/linting.js +25 -0
  117. package/dist/plugin-loader.d.ts +6 -3
  118. package/dist/plugin-loader.js +101 -49
  119. package/dist/scan.d.ts +49 -0
  120. package/dist/scan.js +177 -0
  121. package/dist/setup-plan.d.ts +51 -0
  122. package/dist/setup-plan.js +85 -0
  123. package/dist/skill-test.d.ts +1 -1
  124. package/dist/skill-test.js +1 -1
  125. package/dist/test-coverage.d.ts +71 -0
  126. package/dist/test-coverage.js +228 -0
  127. package/dist/testing.d.ts +12 -0
  128. package/dist/testing.js +28 -0
  129. package/dist/unit.d.ts +17 -0
  130. package/dist/unit.js +36 -0
  131. package/hooks/refs-nudge.sh +24 -0
  132. package/package.json +35 -16
  133. package/skills/audit-feedback-loop/SKILL.md +76 -0
  134. package/skills/edit-spec/SKILL.md +131 -0
  135. package/skills/enforce-rules-format/SKILL.md +71 -0
  136. package/skills/generate-logo/SKILL.md +103 -0
  137. package/skills/generate-rule/SKILL.md +64 -0
  138. package/skills/linter-docs/clippy.md +241 -0
  139. package/skills/linter-docs/eslint.md +384 -0
  140. package/skills/linter-docs/pylint.md +288 -0
  141. package/skills/linter-docs/rubocop.md +277 -0
  142. package/skills/linter-docs/ruff.md +187 -0
  143. package/skills/linter-docs/stylelint.md +247 -0
  144. package/skills/migrate-to-spec/SKILL.md +126 -0
  145. package/skills/pr-to-lint-rule/SKILL.md +97 -0
  146. package/skills/strengthen/SKILL.md +168 -0
  147. package/skills/test-harness/SKILL.md +157 -0
  148. package/dist/action.d.ts +0 -7
  149. package/dist/action.js +0 -180
  150. package/dist/refs.d.ts +0 -44
  151. package/dist/run-hook.d.ts +0 -77
  152. package/dist/run-hook.js +0 -80
  153. package/dist/run-scripts.d.ts +0 -20
  154. package/dist/run-scripts.js +0 -70
  155. /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
  156. /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
  157. /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
  158. /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
  159. /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
  160. /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
  161. /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
  162. /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
  163. /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
  164. /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
  165. /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
  166. /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
  167. /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
  168. /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
  169. /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
  170. /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
  171. /package/dist/{coverage.js → core/coverage.js} +0 -0
  172. /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
  173. /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
  174. /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
  175. /package/dist/{evolve.js → core/evolve.js} +0 -0
  176. /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
  177. /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
  178. /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
  179. /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
  180. /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
  181. /package/dist/{generate-types.js → core/generate-types.js} +0 -0
  182. /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
  183. /package/dist/{hash.js → core/hash.js} +0 -0
  184. /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
  185. /package/dist/{inline.js → core/inline.js} +0 -0
  186. /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
  187. /package/dist/{integrity.js → core/integrity.js} +0 -0
  188. /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
  189. /package/dist/{linters.js → core/linters.js} +0 -0
  190. /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
  191. /package/dist/{mcp.js → core/mcp.js} +0 -0
  192. /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
  193. /package/dist/{orphans.js → core/orphans.js} +0 -0
  194. /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
  195. /package/dist/{proofs.js → core/proofs.js} +0 -0
  196. /package/dist/{session.d.ts → core/session.d.ts} +0 -0
  197. /package/dist/{session.js → core/session.js} +0 -0
  198. /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
  199. /package/dist/{sidecar.js → core/sidecar.js} +0 -0
  200. /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
  201. /package/dist/{spec.js → core/spec.js} +0 -0
  202. /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
  203. /package/dist/{symbols.js → core/symbols.js} +0 -0
  204. /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
  205. /package/dist/{test-utils.js → core/test-utils.js} +0 -0
  206. /package/dist/{types.js → core/types.js} +0 -0
  207. /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
  208. /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
  209. /package/{.claude-plugin/hooks → hooks}/session-start.sh +0 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Pure decision logic for `vigiles init`.
3
+ *
4
+ * Turns CLI args + whether a human's at a TTY into a concrete setup PLAN, so the
5
+ * IO-heavy `setup()` in cli.ts stays a thin shell and every choice is unit-tested.
6
+ * Best-practice onboarding: **interactive** when a human runs it in a terminal,
7
+ * **non-interactive** (sensible defaults) for agents / CI / piped input — so
8
+ * "set up vigiles" from a Claude Code or Codex prompt Just Works without hanging
9
+ * on a prompt. See docs/agent-setup.md.
10
+ */
11
+ /** What `vigiles init` will set up. */
12
+ export interface SetupPlan {
13
+ /** Pillar 1 — verify instruction files (specs, types, compile, audit, hooks). */
14
+ verify: boolean;
15
+ /** Pillar 2 — test the harness (scaffold a starter harness test + CI job). */
16
+ test: boolean;
17
+ /** Wire CI (the `zernie/vigiles@v1` Action; creates a workflow if none). */
18
+ gha: boolean;
19
+ /** Install the Claude Code plugin (hooks + skills). */
20
+ plugin: boolean;
21
+ /** Strict rule severities in `.vigilesrc.json`. */
22
+ strict: boolean;
23
+ }
24
+ /** The explicit choices a user pinned via flags (undefined = "not specified"). */
25
+ export interface ParsedSetupArgs {
26
+ target?: string;
27
+ strict: boolean;
28
+ yes: boolean;
29
+ pillars?: "verify" | "test" | "both";
30
+ gha?: boolean;
31
+ plugin?: boolean;
32
+ }
33
+ /** Parse `init` args into the choices the user pinned. */
34
+ export declare function parseSetupArgs(args: readonly string[]): ParsedSetupArgs;
35
+ /** The non-interactive defaults: both pillars, CI, and the plugin. */
36
+ export declare function defaultPlan(strict?: boolean): SetupPlan;
37
+ /**
38
+ * Whether to drop into interactive prompts: a human at a TTY who passed neither
39
+ * `--yes` nor an explicit `--target`, and who hasn't already pinned every choice
40
+ * via flags. Agents / CI / piped input (no TTY) never prompt.
41
+ */
42
+ export declare function shouldPrompt(parsed: ParsedSetupArgs, isTTY: boolean): boolean;
43
+ /** Interactive answers (only the fields the prompts cover). */
44
+ export type SetupAnswers = Partial<Pick<SetupPlan, "verify" | "test" | "gha" | "plugin">>;
45
+ /**
46
+ * Resolve the final plan: defaults, then flags, then interactive answers (each
47
+ * layer overrides the previous only where it has an opinion). `--target` pins a
48
+ * bare Pillar-1 spec (no harness scaffold).
49
+ */
50
+ export declare function resolvePlan(parsed: ParsedSetupArgs, answers?: SetupAnswers): SetupPlan;
51
+ //# sourceMappingURL=setup-plan.d.ts.map
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ /**
3
+ * Pure decision logic for `vigiles init`.
4
+ *
5
+ * Turns CLI args + whether a human's at a TTY into a concrete setup PLAN, so the
6
+ * IO-heavy `setup()` in cli.ts stays a thin shell and every choice is unit-tested.
7
+ * Best-practice onboarding: **interactive** when a human runs it in a terminal,
8
+ * **non-interactive** (sensible defaults) for agents / CI / piped input — so
9
+ * "set up vigiles" from a Claude Code or Codex prompt Just Works without hanging
10
+ * on a prompt. See docs/agent-setup.md.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.parseSetupArgs = parseSetupArgs;
14
+ exports.defaultPlan = defaultPlan;
15
+ exports.shouldPrompt = shouldPrompt;
16
+ exports.resolvePlan = resolvePlan;
17
+ function flagValue(args, prefix) {
18
+ return args.find((a) => a.startsWith(prefix))?.slice(prefix.length);
19
+ }
20
+ /** Parse `init` args into the choices the user pinned. */
21
+ function parseSetupArgs(args) {
22
+ const pillarsRaw = flagValue(args, "--pillars=");
23
+ const pillars = pillarsRaw === "verify" || pillarsRaw === "test" || pillarsRaw === "both"
24
+ ? pillarsRaw
25
+ : undefined;
26
+ return {
27
+ target: flagValue(args, "--target="),
28
+ strict: args.includes("--strict"),
29
+ yes: args.includes("--yes") || args.includes("-y"),
30
+ pillars,
31
+ gha: args.includes("--no-gha") ? false : undefined,
32
+ plugin: args.includes("--no-plugin") ? false : undefined,
33
+ };
34
+ }
35
+ /** The non-interactive defaults: both pillars, CI, and the plugin. */
36
+ function defaultPlan(strict = false) {
37
+ return { verify: true, test: true, gha: true, plugin: true, strict };
38
+ }
39
+ /**
40
+ * Whether to drop into interactive prompts: a human at a TTY who passed neither
41
+ * `--yes` nor an explicit `--target`, and who hasn't already pinned every choice
42
+ * via flags. Agents / CI / piped input (no TTY) never prompt.
43
+ */
44
+ function shouldPrompt(parsed, isTTY) {
45
+ if (!isTTY || parsed.yes || parsed.target)
46
+ return false;
47
+ const allPinned = parsed.pillars !== undefined &&
48
+ parsed.gha !== undefined &&
49
+ parsed.plugin !== undefined;
50
+ return !allPinned;
51
+ }
52
+ /**
53
+ * Resolve the final plan: defaults, then flags, then interactive answers (each
54
+ * layer overrides the previous only where it has an opinion). `--target` pins a
55
+ * bare Pillar-1 spec (no harness scaffold).
56
+ */
57
+ function resolvePlan(parsed, answers) {
58
+ const plan = defaultPlan(parsed.strict);
59
+ if (parsed.pillars === "verify") {
60
+ plan.verify = true;
61
+ plan.test = false;
62
+ }
63
+ else if (parsed.pillars === "test") {
64
+ plan.verify = false;
65
+ plan.test = true;
66
+ }
67
+ if (parsed.gha === false)
68
+ plan.gha = false;
69
+ if (parsed.plugin === false)
70
+ plan.plugin = false;
71
+ if (parsed.target)
72
+ plan.test = false;
73
+ if (answers) {
74
+ if (answers.verify !== undefined)
75
+ plan.verify = answers.verify;
76
+ if (answers.test !== undefined)
77
+ plan.test = answers.test;
78
+ if (answers.gha !== undefined)
79
+ plan.gha = answers.gha;
80
+ if (answers.plugin !== undefined)
81
+ plan.plugin = answers.plugin;
82
+ }
83
+ return plan;
84
+ }
85
+ //# sourceMappingURL=setup-plan.js.map
@@ -8,7 +8,7 @@
8
8
  * gate blocked. The model's prose quality is never asserted — that's the
9
9
  * probabilistic boundary; everything else is deterministic and checkable.
10
10
  */
11
- import { type SkillProgram, type GeneratorSkill } from "./skill-driver.js";
11
+ import { type SkillProgram, type GeneratorSkill } from "./adapters/claude-code/skill-driver.js";
12
12
  export type ModelFn = (prose: string) => string;
13
13
  /**
14
14
  * Script the mocked model. Pass:
@@ -12,7 +12,7 @@
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.scriptModel = scriptModel;
14
14
  exports.runSkill = runSkill;
15
- const skill_driver_js_1 = require("./skill-driver.js");
15
+ const skill_driver_js_1 = require("./adapters/claude-code/skill-driver.js");
16
16
  function gateLabel(g) {
17
17
  if (g._ref === "cmd")
18
18
  return g.command;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Untested-surface detection: the third gap detector, alongside orphan-docs.
3
+ *
4
+ * Stale-ref detection catches specs pointing at files that vanished.
5
+ * Orphan-docs catches docs nothing references. This catches harness *surfaces*
6
+ * — skills, subagents, and hooks — that ship without a test or eval. A surface
7
+ * with no test is a probabilistic-compliance gap hiding in the deterministic
8
+ * layer: nothing measures whether it still does what it claims.
9
+ *
10
+ * Two detectors decide "tested", OR'd, so a test placed ANYWHERE counts:
11
+ * 1. colocation — a `*.{harness,eval}.mjs` next to the surface (the zero-config
12
+ * convention the warning suggests): `skills/foo/*.eval.mjs`,
13
+ * `agents/bar.harness.mjs`, `hooks/pre-edit.harness.mjs`.
14
+ * 2. content-reference — any discovered test (incl. `*.test.ts`) that names the
15
+ * surface by PATH (`skills/foo`, `hooks/pre-edit.sh`) or NAMESPACE
16
+ * (`vigiles:foo`). Not bare-name — too fuzzy.
17
+ *
18
+ * Warning-by-default (a nudge, not a gate). User-invoked skills
19
+ * (`disable-model-invocation: true`) can't auto-trigger, so they're exempt by
20
+ * default; flip `includeUserInvokedSkills` to demand an outcome test for them.
21
+ * Per-surface opt-out: a `vigiles:ignore-test` marker in the surface file.
22
+ */
23
+ export type SurfaceKind = "skill" | "agent" | "hook";
24
+ export interface Surface {
25
+ readonly kind: SurfaceKind;
26
+ /** Repo-relative path to the surface file (SKILL.md / agent .md / hook script). */
27
+ readonly path: string;
28
+ /** Stable name: skill dir, agent basename, or hook script basename. */
29
+ readonly name: string;
30
+ /** Substrings a test may reference to "cover" this surface (path / namespace). */
31
+ readonly tokens: readonly string[];
32
+ /** True for skills with `disable-model-invocation: true`. */
33
+ readonly userInvoked: boolean;
34
+ }
35
+ export interface UntestedReport {
36
+ /** Total surfaces considered (after exemptions). */
37
+ readonly total: number;
38
+ readonly covered: readonly Surface[];
39
+ readonly untested: readonly Surface[];
40
+ /** Surfaces skipped (user-invoked skills, `vigiles:ignore-test`). */
41
+ readonly exempt: number;
42
+ }
43
+ export interface TestCoverageOptions {
44
+ /** Repository root. Defaults to `process.cwd()`. */
45
+ readonly basePath?: string;
46
+ /** Scan skills under `skills/` and `.claude/skills/`. Default true. */
47
+ readonly skills?: boolean;
48
+ /** Scan subagents under `agents/` and `.claude/agents/`. Default true. */
49
+ readonly agents?: boolean;
50
+ /** Scan hook scripts referenced from plugin.json / settings.json. Default true. */
51
+ readonly hooks?: boolean;
52
+ /** Require a test for user-invoked (disable-model-invocation) skills. Default false. */
53
+ readonly includeUserInvokedSkills?: boolean;
54
+ /** Globs of test files that count as coverage. */
55
+ readonly testGlobs?: readonly string[];
56
+ /** Extra ignore globs (added to node_modules/dist/.git/.vigiles). */
57
+ readonly exclude?: readonly string[];
58
+ }
59
+ /**
60
+ * Find harness surfaces (skills / agents / hooks) that no test or eval covers.
61
+ * A surface is covered by a colocated `*.{harness,eval}.mjs` OR any discovered
62
+ * test that references its path/namespace. User-invoked skills are exempt unless
63
+ * `includeUserInvokedSkills` is set; any surface with a `vigiles:ignore-test`
64
+ * marker is exempt.
65
+ */
66
+ export declare function findUntestedSurfaces(options?: TestCoverageOptions): UntestedReport;
67
+ /** Suggested colocated test path for an untested surface (shown in the warning). */
68
+ export declare function suggestedTestPath(surface: Surface): string;
69
+ /** Format an untested-surface report as human-readable text. */
70
+ export declare function formatUntestedReport(report: UntestedReport): string;
71
+ //# sourceMappingURL=test-coverage.d.ts.map
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ /**
3
+ * Untested-surface detection: the third gap detector, alongside orphan-docs.
4
+ *
5
+ * Stale-ref detection catches specs pointing at files that vanished.
6
+ * Orphan-docs catches docs nothing references. This catches harness *surfaces*
7
+ * — skills, subagents, and hooks — that ship without a test or eval. A surface
8
+ * with no test is a probabilistic-compliance gap hiding in the deterministic
9
+ * layer: nothing measures whether it still does what it claims.
10
+ *
11
+ * Two detectors decide "tested", OR'd, so a test placed ANYWHERE counts:
12
+ * 1. colocation — a `*.{harness,eval}.mjs` next to the surface (the zero-config
13
+ * convention the warning suggests): `skills/foo/*.eval.mjs`,
14
+ * `agents/bar.harness.mjs`, `hooks/pre-edit.harness.mjs`.
15
+ * 2. content-reference — any discovered test (incl. `*.test.ts`) that names the
16
+ * surface by PATH (`skills/foo`, `hooks/pre-edit.sh`) or NAMESPACE
17
+ * (`vigiles:foo`). Not bare-name — too fuzzy.
18
+ *
19
+ * Warning-by-default (a nudge, not a gate). User-invoked skills
20
+ * (`disable-model-invocation: true`) can't auto-trigger, so they're exempt by
21
+ * default; flip `includeUserInvokedSkills` to demand an outcome test for them.
22
+ * Per-surface opt-out: a `vigiles:ignore-test` marker in the surface file.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.findUntestedSurfaces = findUntestedSurfaces;
26
+ exports.suggestedTestPath = suggestedTestPath;
27
+ exports.formatUntestedReport = formatUntestedReport;
28
+ const node_fs_1 = require("node:fs");
29
+ const node_path_1 = require("node:path");
30
+ const glob_1 = require("glob");
31
+ // ---------------------------------------------------------------------------
32
+ // Internals
33
+ // ---------------------------------------------------------------------------
34
+ const DEFAULT_TEST_GLOBS = [
35
+ "**/*.harness.mjs",
36
+ "**/*.eval.mjs",
37
+ "**/*.test.ts",
38
+ "**/*.test.mts",
39
+ "**/*.test.cts",
40
+ "**/*.test.js",
41
+ "**/*.test.mjs",
42
+ "**/*.test.cjs",
43
+ ];
44
+ const DEFAULT_IGNORE = [
45
+ "node_modules/**",
46
+ "dist/**",
47
+ ".vigiles/**",
48
+ ".git/**",
49
+ ];
50
+ const IGNORE_MARKER = "vigiles:ignore-test";
51
+ const SCRIPT_RE = /[\w./${}@-]+\.(?:sh|mjs|cjs|js|ts|py|rb)/g;
52
+ function read(path) {
53
+ try {
54
+ return (0, node_fs_1.readFileSync)(path, "utf-8");
55
+ }
56
+ catch {
57
+ return "";
58
+ }
59
+ }
60
+ /** A SKILL.md is user-invoked when its frontmatter sets disable-model-invocation. */
61
+ function isUserInvoked(content) {
62
+ return /^\s*disable-model-invocation:\s*true\s*$/m.test(content);
63
+ }
64
+ function discoverSkills(basePath, ignore) {
65
+ const out = [];
66
+ const found = (0, glob_1.globSync)(["skills/*/SKILL.md", ".claude/skills/*/SKILL.md"], {
67
+ cwd: basePath,
68
+ ignore,
69
+ });
70
+ for (const path of found.sort()) {
71
+ const name = (0, node_path_1.basename)((0, node_path_1.dirname)(path));
72
+ const content = read((0, node_path_1.join)(basePath, path));
73
+ if (content.includes(IGNORE_MARKER))
74
+ continue;
75
+ out.push({
76
+ kind: "skill",
77
+ path,
78
+ name,
79
+ tokens: [`skills/${name}`, `:${name}`],
80
+ userInvoked: isUserInvoked(content),
81
+ });
82
+ }
83
+ return out;
84
+ }
85
+ function discoverAgents(basePath, ignore) {
86
+ const out = [];
87
+ const found = (0, glob_1.globSync)(["agents/*.md", ".claude/agents/*.md"], {
88
+ cwd: basePath,
89
+ ignore,
90
+ });
91
+ for (const path of found.sort()) {
92
+ if (path.endsWith(".spec.ts"))
93
+ continue;
94
+ const content = read((0, node_path_1.join)(basePath, path));
95
+ if (content.includes(IGNORE_MARKER))
96
+ continue;
97
+ const name = (0, node_path_1.basename)(path, ".md");
98
+ const dir = (0, node_path_1.dirname)(path);
99
+ out.push({
100
+ kind: "agent",
101
+ path,
102
+ name,
103
+ tokens: [`${dir}/${name}`],
104
+ userInvoked: false,
105
+ });
106
+ }
107
+ return out;
108
+ }
109
+ /** Hook-script paths referenced from a manifest's `hooks` block (file hooks only). */
110
+ function hookScripts(basePath, manifest) {
111
+ if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(basePath, manifest)))
112
+ return [];
113
+ let hooks;
114
+ try {
115
+ hooks = JSON.parse(read((0, node_path_1.join)(basePath, manifest)))
116
+ .hooks;
117
+ }
118
+ catch {
119
+ return [];
120
+ }
121
+ if (hooks === undefined)
122
+ return [];
123
+ const text = JSON.stringify(hooks);
124
+ const scripts = new Set();
125
+ for (const m of text.matchAll(SCRIPT_RE)) {
126
+ const rel = m[0]
127
+ .replace("${CLAUDE_PLUGIN_ROOT}/", "")
128
+ .replace(/^\$\{CLAUDE_PLUGIN_ROOT\}/, "")
129
+ .replace(/^\.\//, "");
130
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(basePath, rel)))
131
+ scripts.add(rel);
132
+ }
133
+ return [...scripts];
134
+ }
135
+ function discoverHooks(basePath) {
136
+ const scripts = new Set();
137
+ for (const m of [
138
+ ".claude-plugin/plugin.json",
139
+ ".claude/settings.json",
140
+ ".claude/settings.local.json",
141
+ ]) {
142
+ for (const s of hookScripts(basePath, m))
143
+ scripts.add(s);
144
+ }
145
+ return [...scripts].sort().map((path) => ({
146
+ kind: "hook",
147
+ path,
148
+ name: (0, node_path_1.basename)(path).replace(/\.[^.]+$/, ""),
149
+ tokens: [path],
150
+ userInvoked: false,
151
+ }));
152
+ }
153
+ function discoverTests(basePath, globs, ignore) {
154
+ const found = (0, glob_1.globSync)([...globs], { cwd: basePath, ignore });
155
+ return found.map((path) => ({ path, content: read((0, node_path_1.join)(basePath, path)) }));
156
+ }
157
+ /** Colocated: a test inside a skill dir, or a name-prefixed sibling of an agent/hook. */
158
+ function isColocated(surface, testPath) {
159
+ if (surface.kind === "skill") {
160
+ return testPath.startsWith(`${(0, node_path_1.dirname)(surface.path)}/`);
161
+ }
162
+ return ((0, node_path_1.dirname)(testPath) === (0, node_path_1.dirname)(surface.path) &&
163
+ (0, node_path_1.basename)(testPath).startsWith(`${surface.name}.`));
164
+ }
165
+ function isCovered(surface, tests) {
166
+ for (const t of tests) {
167
+ if (t.path === surface.path)
168
+ continue;
169
+ if (isColocated(surface, t.path))
170
+ return true;
171
+ if (surface.tokens.some((tok) => t.content.includes(tok)))
172
+ return true;
173
+ }
174
+ return false;
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Public API
178
+ // ---------------------------------------------------------------------------
179
+ /**
180
+ * Find harness surfaces (skills / agents / hooks) that no test or eval covers.
181
+ * A surface is covered by a colocated `*.{harness,eval}.mjs` OR any discovered
182
+ * test that references its path/namespace. User-invoked skills are exempt unless
183
+ * `includeUserInvokedSkills` is set; any surface with a `vigiles:ignore-test`
184
+ * marker is exempt.
185
+ */
186
+ function findUntestedSurfaces(options = {}) {
187
+ const basePath = options.basePath ?? process.cwd();
188
+ const ignore = [...DEFAULT_IGNORE, ...(options.exclude ?? [])];
189
+ const globs = options.testGlobs ?? DEFAULT_TEST_GLOBS;
190
+ const surfaces = [];
191
+ if (options.skills !== false)
192
+ surfaces.push(...discoverSkills(basePath, ignore));
193
+ if (options.agents !== false)
194
+ surfaces.push(...discoverAgents(basePath, ignore));
195
+ if (options.hooks !== false)
196
+ surfaces.push(...discoverHooks(basePath));
197
+ const considered = surfaces.filter((s) => !s.userInvoked || s.kind !== "skill" || options.includeUserInvokedSkills);
198
+ const exempt = surfaces.length - considered.length;
199
+ const tests = discoverTests(basePath, globs, ignore);
200
+ const covered = [];
201
+ const untested = [];
202
+ for (const s of considered) {
203
+ (isCovered(s, tests) ? covered : untested).push(s);
204
+ }
205
+ return { total: considered.length, covered, untested, exempt };
206
+ }
207
+ /** Suggested colocated test path for an untested surface (shown in the warning). */
208
+ function suggestedTestPath(surface) {
209
+ if (surface.kind === "skill") {
210
+ return `${(0, node_path_1.dirname)(surface.path)}/${surface.name}.eval.mjs`;
211
+ }
212
+ return `${(0, node_path_1.dirname)(surface.path)}/${surface.name}.harness.mjs`;
213
+ }
214
+ /** Format an untested-surface report as human-readable text. */
215
+ function formatUntestedReport(report) {
216
+ if (report.untested.length === 0) {
217
+ const tail = report.exempt > 0 ? ` (${String(report.exempt)} exempt)` : "";
218
+ return `✓ all ${String(report.total)} surface(s) have a test or eval${tail}`;
219
+ }
220
+ const lines = [
221
+ `⚠ ${String(report.untested.length)} surface(s) with no test or eval:`,
222
+ ];
223
+ for (const s of report.untested) {
224
+ lines.push(` ${s.kind} ${s.path} — add e.g. ${suggestedTestPath(s)}`);
225
+ }
226
+ return lines.join("\n");
227
+ }
228
+ //# sourceMappingURL=test-coverage.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `vigiles/testing` — Pillar 2 entry point: the **harness-testing** API. Re-exports
3
+ * the three tiers — `runHook` (unit), `runHarnessTest` (deterministic), `runEval`
4
+ * (eval) — plus the runner-agnostic predicates/assertions. Kept deliberately
5
+ * separate from `vigiles/claude-code` so this surface can stay harness-agnostic as
6
+ * more harnesses are added. Granular paths (`vigiles/run-hook`, etc.) still work.
7
+ */
8
+ export * from "./adapters/claude-code/run-hook.js";
9
+ export * from "./adapters/claude-code/harness-test.js";
10
+ export * from "./adapters/claude-code/eval.js";
11
+ export * from "./harness-assert.js";
12
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * `vigiles/testing` — Pillar 2 entry point: the **harness-testing** API. Re-exports
19
+ * the three tiers — `runHook` (unit), `runHarnessTest` (deterministic), `runEval`
20
+ * (eval) — plus the runner-agnostic predicates/assertions. Kept deliberately
21
+ * separate from `vigiles/claude-code` so this surface can stay harness-agnostic as
22
+ * more harnesses are added. Granular paths (`vigiles/run-hook`, etc.) still work.
23
+ */
24
+ __exportStar(require("./adapters/claude-code/run-hook.js"), exports);
25
+ __exportStar(require("./adapters/claude-code/harness-test.js"), exports);
26
+ __exportStar(require("./adapters/claude-code/eval.js"), exports);
27
+ __exportStar(require("./harness-assert.js"), exports);
28
+ //# sourceMappingURL=testing.js.map
package/dist/unit.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `vigiles/unit` — the **no-capability** harness-testing surface.
3
+ *
4
+ * Importing from here is a contract: this test needs **no `claude`, no model, no
5
+ * bubblewrap, no network** — only a process. It is the cheap base of the pyramid:
6
+ * `runHook` (pipe a synthesized event to a hook and read the block/allow
7
+ * decision), the bare `Trace` predicates, the `assert*` helpers, and the pure
8
+ * parsers. A `*.test.ts` should import **only** from here (enforced by lint).
9
+ *
10
+ * Higher tiers re-export this one — dependencies point downward only
11
+ * (e2e → integration → unit), never up. See `vigiles/integration` and
12
+ * `vigiles/e2e`.
13
+ */
14
+ export * from "./harness-assert.js";
15
+ export { runHook, parseHookOutput, decideHook, } from "./adapters/claude-code/run-hook.js";
16
+ export type { HookInput, HookOutput, HookRunResult, RunHookOptions, } from "./adapters/claude-code/run-hook.js";
17
+ //# sourceMappingURL=unit.d.ts.map
package/dist/unit.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.decideHook = exports.parseHookOutput = exports.runHook = void 0;
18
+ /**
19
+ * `vigiles/unit` — the **no-capability** harness-testing surface.
20
+ *
21
+ * Importing from here is a contract: this test needs **no `claude`, no model, no
22
+ * bubblewrap, no network** — only a process. It is the cheap base of the pyramid:
23
+ * `runHook` (pipe a synthesized event to a hook and read the block/allow
24
+ * decision), the bare `Trace` predicates, the `assert*` helpers, and the pure
25
+ * parsers. A `*.test.ts` should import **only** from here (enforced by lint).
26
+ *
27
+ * Higher tiers re-export this one — dependencies point downward only
28
+ * (e2e → integration → unit), never up. See `vigiles/integration` and
29
+ * `vigiles/e2e`.
30
+ */
31
+ __exportStar(require("./harness-assert.js"), exports);
32
+ var run_hook_js_1 = require("./adapters/claude-code/run-hook.js");
33
+ Object.defineProperty(exports, "runHook", { enumerable: true, get: function () { return run_hook_js_1.runHook; } });
34
+ Object.defineProperty(exports, "parseHookOutput", { enumerable: true, get: function () { return run_hook_js_1.parseHookOutput; } });
35
+ Object.defineProperty(exports, "decideHook", { enumerable: true, get: function () { return run_hook_js_1.decideHook; } });
36
+ //# sourceMappingURL=unit.js.map
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ # PostToolUse hook — nudge the agent to express references in instruction files
3
+ # (CLAUDE.md / AGENTS.md / SKILL.md) as vigiles marks, so `vigiles audit` can
4
+ # actually verify them. Non-blocking by default; set the `unmarked-refs` rule to
5
+ # "error" in .vigilesrc.json to turn the nudge into a hard block, or to false to
6
+ # disable it. Runs as its OWN PostToolUse entry so its stdout stays clean JSON.
7
+
8
+ set -uo pipefail
9
+
10
+ INPUT=$(cat)
11
+
12
+ # No npx / not a Node project → nothing to do, never disrupt the edit.
13
+ command -v npx >/dev/null 2>&1 || exit 0
14
+ [ -f package.json ] || exit 0
15
+
16
+ printf '%s' "$INPUT" | npx vigiles refs-hook
17
+ status=${PIPESTATUS[1]}
18
+
19
+ # Propagate ONLY an explicit block (exit 2). Swallow tool/setup errors so a
20
+ # missing dep or transient failure never blocks the agent's edit.
21
+ if [ "$status" -eq 2 ]; then
22
+ exit 2
23
+ fi
24
+ exit 0