vigiles 6.0.0 → 8.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 (92) hide show
  1. package/README.md +189 -88
  2. package/dist/action-gate.js +1 -1
  3. package/dist/adapters/claude-code/agent-runtime.d.ts +46 -11
  4. package/dist/adapters/claude-code/agent-runtime.js +95 -24
  5. package/dist/adapters/claude-code/effect-region.js +1 -1
  6. package/dist/adapters/claude-code/skill-runtime.d.ts +1 -1
  7. package/dist/adapters/claude-code/skill-runtime.js +1 -1
  8. package/dist/adapters/codex/hook-protocol.js +3 -0
  9. package/dist/adapters/codex/mock-model.js +1 -1
  10. package/dist/cli-commands.d.ts +19 -0
  11. package/dist/cli-commands.js +47 -0
  12. package/dist/cli.d.ts +1 -1
  13. package/dist/cli.js +1054 -201
  14. package/dist/core/adopt.d.ts +65 -0
  15. package/dist/core/adopt.js +199 -0
  16. package/dist/core/bash-effects.d.ts +12 -0
  17. package/dist/core/bash-effects.js +31 -0
  18. package/dist/core/capability-diff.d.ts +46 -0
  19. package/dist/core/capability-diff.js +97 -0
  20. package/dist/core/compose.d.ts +1 -1
  21. package/dist/core/compose.js +1 -1
  22. package/dist/core/evolve.d.ts +4 -0
  23. package/dist/core/evolve.js +4 -0
  24. package/dist/core/frontmatter.d.ts +8 -7
  25. package/dist/core/frontmatter.js +8 -7
  26. package/dist/core/generate-harness.d.ts +1 -1
  27. package/dist/core/generate-harness.js +3 -3
  28. package/dist/core/generate-schema.js +1 -1
  29. package/dist/core/guards.d.ts +126 -0
  30. package/dist/core/guards.js +309 -0
  31. package/dist/core/harness-driver.d.ts +1 -1
  32. package/dist/core/hook-program.d.ts +459 -0
  33. package/dist/core/hook-program.js +468 -0
  34. package/dist/core/hook-protocol.d.ts +7 -0
  35. package/dist/core/hook-providers.d.ts +138 -0
  36. package/dist/core/hook-providers.js +155 -0
  37. package/dist/core/hook-spec.d.ts +74 -0
  38. package/dist/core/hook-spec.js +130 -0
  39. package/dist/core/inline.d.ts +6 -6
  40. package/dist/core/inline.js +7 -7
  41. package/dist/core/integrity.d.ts +31 -0
  42. package/dist/core/integrity.js +45 -0
  43. package/dist/core/mcp-tool.d.ts +12 -0
  44. package/dist/core/mcp-tool.js +20 -0
  45. package/dist/core/mcp.d.ts +13 -0
  46. package/dist/core/mcp.js +67 -0
  47. package/dist/core/orphans.js +1 -1
  48. package/dist/core/spec.d.ts +40 -2
  49. package/dist/core/spec.js +16 -1
  50. package/dist/core/types.d.ts +37 -5
  51. package/dist/core/validate.js +26 -26
  52. package/dist/dialect-drift.d.ts +65 -0
  53. package/dist/dialect-drift.js +216 -0
  54. package/dist/eval.d.ts +40 -5
  55. package/dist/eval.js +59 -5
  56. package/dist/guardrail-check.d.ts +85 -0
  57. package/dist/guardrail-check.js +152 -0
  58. package/dist/harness-assert.d.ts +10 -0
  59. package/dist/harness-assert.js +30 -0
  60. package/dist/hook-install.d.ts +43 -0
  61. package/dist/hook-install.js +91 -0
  62. package/dist/hook.d.ts +52 -0
  63. package/dist/hook.js +98 -0
  64. package/dist/leaderboard.d.ts +6 -0
  65. package/dist/leaderboard.js +43 -1
  66. package/dist/linting.d.ts +9 -5
  67. package/dist/linting.js +17 -5
  68. package/dist/optimize.js +1 -1
  69. package/dist/scaffold-test.js +21 -7
  70. package/dist/scan-behavioral.d.ts +60 -0
  71. package/dist/scan-behavioral.js +239 -1
  72. package/dist/scan-trigger-suggest.d.ts +54 -0
  73. package/dist/scan-trigger-suggest.js +70 -0
  74. package/dist/scan.d.ts +31 -1
  75. package/dist/scan.js +65 -3
  76. package/dist/score-explainer.js +1 -1
  77. package/dist/self-command-refs.d.ts +21 -0
  78. package/dist/self-command-refs.js +125 -0
  79. package/dist/setup-plan.d.ts +59 -1
  80. package/dist/setup-plan.js +103 -5
  81. package/dist/testing.d.ts +5 -3
  82. package/dist/testing.js +37 -23
  83. package/dist/tool-intercept.d.ts +4 -4
  84. package/dist/tool-intercept.js +5 -5
  85. package/dist/unit.d.ts +2 -0
  86. package/dist/unit.js +8 -1
  87. package/hooks/post-edit.sh +1 -1
  88. package/hooks/refs-nudge.sh +1 -1
  89. package/package.json +5 -3
  90. package/skills/adopt-spec/SKILL.md +7 -7
  91. package/skills/linter-docs/eslint.md +1 -1
  92. package/skills/strengthen/SKILL.md +1 -1
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Faithful markdown → typed-spec adoption — the deterministic half of `init`
3
+ * auto-adopt (research/install-enforcement-dx.md).
4
+ *
5
+ * Turns an existing instruction file (CLAUDE.md / AGENTS.md) into a `claude()`
6
+ * spec source that compiles back to ~the same file, so adopting a rich,
7
+ * hand-tuned instruction file is SAFE: every heading becomes a prose section
8
+ * (verbatim), no rule is invented, nothing is dropped. The contract to the user
9
+ * is "review the diff" — for a well-headed file that diff is small (whitespace +
10
+ * the canonical `# <target>` h1). The agentic path (the `adopt-spec` skill)
11
+ * handles irregular prose better; this is the zero-model floor.
12
+ *
13
+ * WHY IT ALWAYS COMPILES: the compiler only rejects `#`/`##` headers INSIDE a
14
+ * section body (sections render as `##`), so we split the file on every `#`/`##`
15
+ * heading — each becomes its own section, and `###`+ subheadings ride along
16
+ * inside the body untouched. Reserved lowercase keys
17
+ * (`commands`/`keyFiles`/`rules`) are never produced (`safeKey`). `guidance()`
18
+ * vs `enforce()` is deliberately NOT guessed here — that cross-referencing is
19
+ * `strengthen`'s separate, later job; adoption is lossless transcription.
20
+ */
21
+ export type AdoptTier = "structured" | "raw";
22
+ export interface AdoptResult {
23
+ /** Generated `.spec.ts` source (compiles back to ~the original file). */
24
+ source: string;
25
+ /**
26
+ * `structured` = a clean `##`-headed file mapped 1:1 to sections (the diff is
27
+ * just the canonical h1 + whitespace). `raw` = a heading-less or
28
+ * intro-bearing file we wrapped under a synthesized `Overview` section —
29
+ * content is preserved verbatim, but the diff adds a heading, so "review the
30
+ * diff" matters more.
31
+ */
32
+ tier: AdoptTier;
33
+ /** Number of named sections produced (excluding the auto-rendered h1). */
34
+ sectionCount: number;
35
+ }
36
+ /**
37
+ * The intermediate adoption result: the `claude()` spec FIELDS (before
38
+ * rendering to source). Exposed so the renderer and the round-trip tests share
39
+ * one parse — the test can feed `sections` straight into `compileClaude` and
40
+ * assert the file is reproduced, without evaluating generated TS source.
41
+ */
42
+ export interface AdoptedSpec {
43
+ target: string;
44
+ /** Heading → verbatim section body, in document order. */
45
+ sections: Record<string, string>;
46
+ /** Set only when a faithful section exceeds the compiler's 200-line guard. */
47
+ maxSectionLines?: number;
48
+ tier: AdoptTier;
49
+ }
50
+ /**
51
+ * Parse an instruction file's markdown into the faithful `claude()` spec FIELDS.
52
+ * The shared core of {@link adoptMarkdown} and the round-trip tests.
53
+ *
54
+ * @param markdown the file's current content (an existing integrity header, if
55
+ * any, is stripped — we adopt the body)
56
+ * @param target the bare target filename (`"CLAUDE.md"` / `"AGENTS.md"`),
57
+ * which the compiler renders as the h1
58
+ */
59
+ export declare function adoptToSpec(markdown: string, target: string): AdoptedSpec;
60
+ /**
61
+ * Convert an instruction file's markdown into a faithful `claude()` spec source
62
+ * (the deliverable `init` writes).
63
+ */
64
+ export declare function adoptMarkdown(markdown: string, target: string): AdoptResult;
65
+ //# sourceMappingURL=adopt.d.ts.map
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ /**
3
+ * Faithful markdown → typed-spec adoption — the deterministic half of `init`
4
+ * auto-adopt (research/install-enforcement-dx.md).
5
+ *
6
+ * Turns an existing instruction file (CLAUDE.md / AGENTS.md) into a `claude()`
7
+ * spec source that compiles back to ~the same file, so adopting a rich,
8
+ * hand-tuned instruction file is SAFE: every heading becomes a prose section
9
+ * (verbatim), no rule is invented, nothing is dropped. The contract to the user
10
+ * is "review the diff" — for a well-headed file that diff is small (whitespace +
11
+ * the canonical `# <target>` h1). The agentic path (the `adopt-spec` skill)
12
+ * handles irregular prose better; this is the zero-model floor.
13
+ *
14
+ * WHY IT ALWAYS COMPILES: the compiler only rejects `#`/`##` headers INSIDE a
15
+ * section body (sections render as `##`), so we split the file on every `#`/`##`
16
+ * heading — each becomes its own section, and `###`+ subheadings ride along
17
+ * inside the body untouched. Reserved lowercase keys
18
+ * (`commands`/`keyFiles`/`rules`) are never produced (`safeKey`). `guidance()`
19
+ * vs `enforce()` is deliberately NOT guessed here — that cross-referencing is
20
+ * `strengthen`'s separate, later job; adoption is lossless transcription.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.adoptToSpec = adoptToSpec;
24
+ exports.adoptMarkdown = adoptMarkdown;
25
+ const integrity_js_1 = require("./integrity.js");
26
+ // A top-level heading is `#` or `##` (the levels the compiler reserves for
27
+ // document/section structure). `###`+ stay inside a section body.
28
+ const HEADING_RE = /^ {0,3}(#{1,2})\s+(.*)$/;
29
+ const FENCE_RE = /^ {0,3}(?:`{3,}|~{3,})/;
30
+ // Mirrors compile.ts RESERVED_SECTION_KEYS — keys that clash with the structured
31
+ // `commands`/`keyFiles`/`rules` fields and would be a compile error as a section.
32
+ const RESERVED_SECTION_KEYS = new Set([
33
+ "commands",
34
+ "keyFiles",
35
+ "key-files",
36
+ "key_files",
37
+ "rules",
38
+ ]);
39
+ /**
40
+ * Split a markdown body into a leading preamble block plus one block per
41
+ * top-level (`#`/`##`) heading. Fence-aware, so a `## ` inside a fenced code
42
+ * block is not a split point (matching the compiler's own section validator).
43
+ */
44
+ function splitIntoBlocks(body) {
45
+ const blocks = [];
46
+ let current = { heading: null, level: null, lines: [] };
47
+ let inFence = false;
48
+ for (const line of body.split("\n")) {
49
+ if (FENCE_RE.test(line)) {
50
+ inFence = !inFence;
51
+ current.lines.push(line);
52
+ continue;
53
+ }
54
+ const m = inFence ? null : line.match(HEADING_RE);
55
+ if (m) {
56
+ blocks.push(current);
57
+ current = {
58
+ heading: m[2].trim(),
59
+ level: m[1].length,
60
+ lines: [],
61
+ };
62
+ }
63
+ else {
64
+ current.lines.push(line);
65
+ }
66
+ }
67
+ blocks.push(current);
68
+ return blocks;
69
+ }
70
+ /**
71
+ * Avoid a reserved lowercase section key (`commands`/`rules`/…) by capitalizing
72
+ * the first letter — which is exactly the heading the compiler renders anyway,
73
+ * so there's no visible diff.
74
+ */
75
+ function safeKey(heading) {
76
+ return RESERVED_SECTION_KEYS.has(heading)
77
+ ? heading.charAt(0).toUpperCase() + heading.slice(1)
78
+ : heading;
79
+ }
80
+ /**
81
+ * Allocate a unique section key, disambiguating a duplicate with ` (2)`, ` (3)`,
82
+ * … and recording it in `used`. Shared by the real-heading loop and the
83
+ * synthesized `Overview`, so no two sections collide on one object key (which
84
+ * would silently drop the earlier one's content).
85
+ */
86
+ function allocKey(base, used) {
87
+ let key = base;
88
+ for (let n = 2; used.has(key); n++)
89
+ key = `${base} (${n})`;
90
+ used.add(key);
91
+ return key;
92
+ }
93
+ /**
94
+ * Emit a readable multi-line TS template literal for arbitrary section content,
95
+ * escaping the three sequences that would break it: backslash, backtick, and the
96
+ * `${` interpolation opener. TS un-escapes them back to the original string, so
97
+ * the round-trip is exact.
98
+ */
99
+ function tsTemplate(s) {
100
+ const esc = s
101
+ .replace(/\\/g, "\\\\")
102
+ .replace(/`/g, "\\`")
103
+ .replace(/\$\{/g, "\\${");
104
+ return "`" + esc + "`";
105
+ }
106
+ function renderSpecSource(spec) {
107
+ const targetLine = spec.target !== "CLAUDE.md"
108
+ ? `\n target: ${JSON.stringify(spec.target)},`
109
+ : "";
110
+ const maxLine = spec.maxSectionLines !== undefined
111
+ ? `\n maxSectionLines: ${String(spec.maxSectionLines)},`
112
+ : "";
113
+ const entries = Object.entries(spec.sections)
114
+ .map(([key, content]) => ` ${JSON.stringify(key)}: ${tsTemplate(content)},`)
115
+ .join("\n");
116
+ const sectionsBlock = entries
117
+ ? `\n sections: {\n${entries}\n },`
118
+ : `\n sections: {},`;
119
+ return `// Adopted from ${spec.target} by \`vigiles init\` — faithful by default.
120
+ // Each heading became a prose section; no rules were inferred. Run the
121
+ // \`/strengthen\` skill to upgrade prose to verified enforce()/guard() rules.
122
+ import { claude } from "vigiles/spec";
123
+
124
+ export default claude({${targetLine}${maxLine}${sectionsBlock}
125
+ rules: {},
126
+ });
127
+ `;
128
+ }
129
+ /**
130
+ * Parse an instruction file's markdown into the faithful `claude()` spec FIELDS.
131
+ * The shared core of {@link adoptMarkdown} and the round-trip tests.
132
+ *
133
+ * @param markdown the file's current content (an existing integrity header, if
134
+ * any, is stripped — we adopt the body)
135
+ * @param target the bare target filename (`"CLAUDE.md"` / `"AGENTS.md"`),
136
+ * which the compiler renders as the h1
137
+ */
138
+ function adoptToSpec(markdown, target) {
139
+ const header = (0, integrity_js_1.parseIntegrityHeader)(markdown);
140
+ const body = header ? header.body : markdown;
141
+ const blocks = splitIntoBlocks(body);
142
+ const overviewLines = [];
143
+ const ordered = [];
144
+ const usedKeys = new Set();
145
+ let titleConsumed = false;
146
+ let synthesizedHeading = false;
147
+ for (const block of blocks) {
148
+ if (block.level === null) {
149
+ // Preamble before any heading — has no structural home, so it goes to a
150
+ // synthesized Overview section.
151
+ overviewLines.push(...block.lines);
152
+ continue;
153
+ }
154
+ if (!titleConsumed && block.level === 1) {
155
+ // The document title — the compiler re-renders `# <target>` from the
156
+ // filename, so drop the heading line; its body (intro prose under the h1)
157
+ // also has no slot, so it joins Overview.
158
+ titleConsumed = true;
159
+ overviewLines.push(...block.lines);
160
+ continue;
161
+ }
162
+ const key = allocKey(safeKey(block.heading ?? ""), usedKeys);
163
+ ordered.push({ key, content: block.lines.join("\n").trim() });
164
+ }
165
+ const overview = overviewLines.join("\n").trim();
166
+ if (overview) {
167
+ synthesizedHeading = true;
168
+ // Allocate the synthesized key with the SAME dedup as real headings, so a
169
+ // file that already has a literal `## Overview` doesn't collide and silently
170
+ // drop the intro when the sections object is built (it becomes "Overview (2)").
171
+ ordered.unshift({ key: allocKey("Overview", usedKeys), content: overview });
172
+ }
173
+ const sections = {};
174
+ for (const { key, content } of ordered)
175
+ sections[key] = content;
176
+ // A faithful section can legitimately be long; lift the 200-line guard above
177
+ // the longest one so adoption never trips it (only when actually needed, so a
178
+ // normal spec stays free of the override).
179
+ const longest = ordered.reduce((n, s) => Math.max(n, s.content.split("\n").length), 0);
180
+ return {
181
+ target,
182
+ sections,
183
+ maxSectionLines: longest > 190 ? longest + 50 : undefined,
184
+ tier: synthesizedHeading || ordered.length === 0 ? "raw" : "structured",
185
+ };
186
+ }
187
+ /**
188
+ * Convert an instruction file's markdown into a faithful `claude()` spec source
189
+ * (the deliverable `init` writes).
190
+ */
191
+ function adoptMarkdown(markdown, target) {
192
+ const spec = adoptToSpec(markdown, target);
193
+ return {
194
+ source: renderSpecSource(spec),
195
+ tier: spec.tier,
196
+ sectionCount: Object.keys(spec.sections).length,
197
+ };
198
+ }
199
+ //# sourceMappingURL=adopt.js.map
@@ -38,4 +38,16 @@ export declare function classifyBashCommand(command: string): BashEffect;
38
38
  * caller uses to decide "this Bash is provably an observation."
39
39
  */
40
40
  export declare function isReadOnlyBash(command: string): boolean;
41
+ /**
42
+ * Extract the static argv of every simple command (CallExpr) in `command`, each
43
+ * as an array of literal words (dynamic / quoted-interpolated words are dropped).
44
+ * AST-backed, so a leaf nested in a pipeline, `&&`/`;`/`|`, a subshell, or a
45
+ * compound command is still found — the structural query a robust matcher needs
46
+ * (a regex over the raw string misses `cd x && git push`). Parse failure → [].
47
+ *
48
+ * This is the matching primitive a typed hook's `command.runs("git push")` is
49
+ * built on: it sees the real `git push` leaf however it's wrapped, which the
50
+ * native `Bash(git:*)` glob (issue #30519) and a hand-written `grep` both miss.
51
+ */
52
+ export declare function leafCommands(command: string): string[][];
41
53
  //# sourceMappingURL=bash-effects.d.ts.map
@@ -26,6 +26,7 @@
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
27
  exports.classifyBashCommand = classifyBashCommand;
28
28
  exports.isReadOnlyBash = isReadOnlyBash;
29
+ exports.leafCommands = leafCommands;
29
30
  // mvdan-sh is a CJS package (GopherJS build) with no bundled TypeScript types.
30
31
  // The project compiles to CommonJS (Node16, no "type":"module"), so plain
31
32
  // require() works and is the idiomatic pattern here (see linters.ts).
@@ -402,4 +403,34 @@ function classifyBashCommand(command) {
402
403
  function isReadOnlyBash(command) {
403
404
  return classifyBashCommand(command) === "read-only";
404
405
  }
406
+ /**
407
+ * Extract the static argv of every simple command (CallExpr) in `command`, each
408
+ * as an array of literal words (dynamic / quoted-interpolated words are dropped).
409
+ * AST-backed, so a leaf nested in a pipeline, `&&`/`;`/`|`, a subshell, or a
410
+ * compound command is still found — the structural query a robust matcher needs
411
+ * (a regex over the raw string misses `cd x && git push`). Parse failure → [].
412
+ *
413
+ * This is the matching primitive a typed hook's `command.runs("git push")` is
414
+ * built on: it sees the real `git push` leaf however it's wrapped, which the
415
+ * native `Bash(git:*)` glob (issue #30519) and a hand-written `grep` both miss.
416
+ */
417
+ function leafCommands(command) {
418
+ let file;
419
+ try {
420
+ file = sh.syntax.NewParser().Parse(command, "cmd.sh");
421
+ }
422
+ catch {
423
+ return [];
424
+ }
425
+ const out = [];
426
+ sh.syntax.Walk(file, (node) => {
427
+ if (sh.syntax.NodeType(node) === "CallExpr" && node.Args) {
428
+ const argv = node.Args.map((w) => getLiteral(w)).filter((s) => s !== null);
429
+ if (argv.length > 0)
430
+ out.push(argv);
431
+ }
432
+ return true;
433
+ });
434
+ return out;
435
+ }
405
436
  //# sourceMappingURL=bash-effects.js.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Capability-diff — "did this change widen the agent's blast radius?" (moat #2).
3
+ *
4
+ * The whole-harness capability lattice ({@link HarnessCapabilities}, computed by
5
+ * `computeHarnessCapabilities`) is the set of effects an agent/harness can reach:
6
+ * read-only tools, side-effecting tools, unknown/MCP tools, and the loosest purity.
7
+ * Diffing two lattices (a PR's base vs head) yields a deterministic, model-free
8
+ * verdict: a change WIDENED the blast radius iff it adds a side-effecting or
9
+ * unknown/MCP tool, or loosens the purity floor. New read-only tools and removals
10
+ * are reported but are NOT a widening.
11
+ *
12
+ * Honesty / don't-cry-wolf: a widening is INFORMATIONAL by default (a PR comment),
13
+ * not an automatic failure — widening the surface is often intended. The CLI gates
14
+ * a non-zero exit behind an explicit `--fail-on-widen`. Pure + harness-agnostic
15
+ * (operates on the lattice, no dialect needed). See research/typed-spec-moat.md (#2).
16
+ */
17
+ import type { HarnessCapabilities } from "./generate-harness.js";
18
+ import type { PurityLevel } from "./effects.js";
19
+ /** A purity move between two lattices (omitted when unchanged). */
20
+ export interface PurityChange {
21
+ readonly from: PurityLevel;
22
+ readonly to: PurityLevel;
23
+ /** `widened` = loosened (pure→bounded→unrestricted); `narrowed` = tightened. */
24
+ readonly direction: "widened" | "narrowed";
25
+ }
26
+ export interface CapabilityDiff {
27
+ /** Side-effecting tools reachable AFTER but not before — the core blast-radius growth. */
28
+ readonly addedSideEffecting: readonly string[];
29
+ /** Unknown-effect (MCP / unrecognized) tools newly reachable — also widened reach. */
30
+ readonly addedUnknown: readonly string[];
31
+ /** Read-only tools newly reachable — benign (reported, NOT a widening). */
32
+ readonly addedReadOnly: readonly string[];
33
+ /** Tools reachable before but not after — a NARROWING (good; informational). */
34
+ readonly removed: readonly string[];
35
+ /** The purity move, or null when unchanged. */
36
+ readonly purity: PurityChange | null;
37
+ /** The verdict: did the blast radius GROW (new side-effecting/unknown, or purity loosened)? */
38
+ readonly widened: boolean;
39
+ }
40
+ /** Diff two capability lattices → what changed + the widened verdict. Pure. */
41
+ export declare function diffCapabilities(before: HarnessCapabilities, after: HarnessCapabilities): CapabilityDiff;
42
+ /** True when the diff carries no change at all (the common, quiet case). */
43
+ export declare function isNoOpDiff(d: CapabilityDiff): boolean;
44
+ /** Render a capability-diff as a PR-comment-style report (Markdown-friendly). */
45
+ export declare function formatCapabilityDiff(d: CapabilityDiff): string;
46
+ //# sourceMappingURL=capability-diff.d.ts.map
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Capability-diff — "did this change widen the agent's blast radius?" (moat #2).
4
+ *
5
+ * The whole-harness capability lattice ({@link HarnessCapabilities}, computed by
6
+ * `computeHarnessCapabilities`) is the set of effects an agent/harness can reach:
7
+ * read-only tools, side-effecting tools, unknown/MCP tools, and the loosest purity.
8
+ * Diffing two lattices (a PR's base vs head) yields a deterministic, model-free
9
+ * verdict: a change WIDENED the blast radius iff it adds a side-effecting or
10
+ * unknown/MCP tool, or loosens the purity floor. New read-only tools and removals
11
+ * are reported but are NOT a widening.
12
+ *
13
+ * Honesty / don't-cry-wolf: a widening is INFORMATIONAL by default (a PR comment),
14
+ * not an automatic failure — widening the surface is often intended. The CLI gates
15
+ * a non-zero exit behind an explicit `--fail-on-widen`. Pure + harness-agnostic
16
+ * (operates on the lattice, no dialect needed). See research/typed-spec-moat.md (#2).
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.diffCapabilities = diffCapabilities;
20
+ exports.isNoOpDiff = isNoOpDiff;
21
+ exports.formatCapabilityDiff = formatCapabilityDiff;
22
+ const PURITY_RANK = {
23
+ pure: 0,
24
+ bounded: 1,
25
+ unrestricted: 2,
26
+ };
27
+ const addedIn = (before, after) => after.filter((x) => !before.includes(x));
28
+ /** Diff two capability lattices → what changed + the widened verdict. Pure. */
29
+ function diffCapabilities(before, after) {
30
+ const addedSideEffecting = addedIn(before.sideEffecting, after.sideEffecting);
31
+ const addedUnknown = addedIn(before.unknown, after.unknown);
32
+ const addedReadOnly = addedIn(before.readOnly, after.readOnly);
33
+ // A tool is "removed" if it was reachable in ANY bucket before and in NONE after.
34
+ const afterAll = new Set([
35
+ ...after.readOnly,
36
+ ...after.sideEffecting,
37
+ ...after.unknown,
38
+ ]);
39
+ const removed = [
40
+ ...before.readOnly,
41
+ ...before.sideEffecting,
42
+ ...before.unknown,
43
+ ].filter((x) => !afterAll.has(x));
44
+ const fromRank = PURITY_RANK[before.purity];
45
+ const toRank = PURITY_RANK[after.purity];
46
+ const purity = fromRank === toRank
47
+ ? null
48
+ : {
49
+ from: before.purity,
50
+ to: after.purity,
51
+ direction: toRank > fromRank ? "widened" : "narrowed",
52
+ };
53
+ const widened = addedSideEffecting.length > 0 ||
54
+ addedUnknown.length > 0 ||
55
+ purity?.direction === "widened";
56
+ return {
57
+ addedSideEffecting,
58
+ addedUnknown,
59
+ addedReadOnly,
60
+ removed,
61
+ purity,
62
+ widened,
63
+ };
64
+ }
65
+ /** True when the diff carries no change at all (the common, quiet case). */
66
+ function isNoOpDiff(d) {
67
+ return (d.addedSideEffecting.length === 0 &&
68
+ d.addedUnknown.length === 0 &&
69
+ d.addedReadOnly.length === 0 &&
70
+ d.removed.length === 0 &&
71
+ d.purity === null);
72
+ }
73
+ /** Render a capability-diff as a PR-comment-style report (Markdown-friendly). */
74
+ function formatCapabilityDiff(d) {
75
+ if (isNoOpDiff(d)) {
76
+ return "Capability surface unchanged — no blast-radius change.";
77
+ }
78
+ const lines = [
79
+ d.widened
80
+ ? "⚠️ Capability surface **WIDENED** — this change grows the agent's blast radius:"
81
+ : "Capability surface changed (no widening — narrowing / read-only only):",
82
+ ];
83
+ if (d.addedSideEffecting.length > 0)
84
+ lines.push(` + side-effecting: ${d.addedSideEffecting.join(", ")}`);
85
+ if (d.addedUnknown.length > 0)
86
+ lines.push(` + unknown/MCP: ${d.addedUnknown.join(", ")}`);
87
+ if (d.purity?.direction === "widened")
88
+ lines.push(` + purity loosened: ${d.purity.from} → ${d.purity.to}`);
89
+ if (d.addedReadOnly.length > 0)
90
+ lines.push(` · read-only added (benign): ${d.addedReadOnly.join(", ")}`);
91
+ if (d.removed.length > 0)
92
+ lines.push(` − narrowed (removed): ${d.removed.join(", ")}`);
93
+ if (d.purity?.direction === "narrowed")
94
+ lines.push(` − purity tightened: ${d.purity.from} → ${d.purity.to}`);
95
+ return lines.join("\n");
96
+ }
97
+ //# sourceMappingURL=capability-diff.js.map
@@ -62,7 +62,7 @@ export declare function detectSyncTools(root: string): DetectedSyncTool[];
62
62
  * content (a sync tool keeping them in lockstep). Claude Code reads CLAUDE.md
63
63
  * only ([anthropics/claude-code#34235]); users bridge to the AGENTS.md tools this
64
64
  * way (see `research/sync-tool-compatibility.md` requirement 7). When mirrored,
65
- * vigiles must treat them as the same file — hash + `require-spec` run once on the
65
+ * vigiles must treat them as the same file — hash + `require-instructions-spec` run once on the
66
66
  * real one, and the mirror is never flagged as a second, spec-less instruction
67
67
  * file. Returns null when one is absent, or both exist but genuinely differ.
68
68
  */
@@ -75,7 +75,7 @@ function targetName(target) {
75
75
  * content (a sync tool keeping them in lockstep). Claude Code reads CLAUDE.md
76
76
  * only ([anthropics/claude-code#34235]); users bridge to the AGENTS.md tools this
77
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
78
+ * vigiles must treat them as the same file — hash + `require-instructions-spec` run once on the
79
79
  * real one, and the mirror is never flagged as a second, spec-less instruction
80
80
  * file. Returns null when one is absent, or both exist but genuinely differ.
81
81
  */
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * vigiles — Evolution engine for self-evolving specifications.
3
3
  *
4
+ * @internal Research-stage / experimental — NOT exported from any public entry
5
+ * point and NOT part of the frozen public surface (pre-1.0). Kept for the
6
+ * self-evolving-specs line; `proofs.ts` (used by ncd / covering-array) stays.
7
+ *
4
8
  * AI agents propose mutations. The engine applies them, runs the proof suite,
5
9
  * and only accepts mutations that pass all proofs AND improve fitness.
6
10
  *
@@ -2,6 +2,10 @@
2
2
  /**
3
3
  * vigiles — Evolution engine for self-evolving specifications.
4
4
  *
5
+ * @internal Research-stage / experimental — NOT exported from any public entry
6
+ * point and NOT part of the frozen public surface (pre-1.0). Kept for the
7
+ * self-evolving-specs line; `proofs.ts` (used by ncd / covering-array) stays.
8
+ *
5
9
  * AI agents propose mutations. The engine applies them, runs the proof suite,
6
10
  * and only accepts mutations that pass all proofs AND improve fitness.
7
11
  *
@@ -1,11 +1,11 @@
1
1
  /**
2
- * vigiles — YAML frontmatter rule mode (Level 1 adoption).
2
+ * vigiles — YAML frontmatter rule mode.
3
3
  *
4
4
  * Parses a `vigiles.enforce` block out of a markdown file's YAML
5
5
  * frontmatter, so a project can declare enforce rules in structured YAML
6
- * instead of `<!-- vigiles:enforce ... -->` HTML comments (Level 0) or a
7
- * typed `.spec.ts` (Level 2). Every frontmatter rule goes through the same
8
- * `checkLinterRule` verification as inline and spec rules.
6
+ * instead of `<!-- vigiles:enforce ... -->` inline HTML comments or a typed
7
+ * `.spec.ts`. Every frontmatter rule goes through the same `checkLinterRule`
8
+ * verification as inline and spec rules.
9
9
  *
10
10
  * Shape (verbose — chosen so a JSON Schema can give `rule` an enum that
11
11
  * YAML LSP autocompletes and squiggles on typo):
@@ -67,9 +67,10 @@ export interface FrontmatterParseResult {
67
67
  export declare function parseFrontmatterRules(content: string): FrontmatterParseResult;
68
68
  /**
69
69
  * True if the content has at least one parseable `vigiles` reference in its
70
- * frontmatter — an `enforce` rule, a `files` entry, or a `commands` entry.
71
- * Used by `require-spec` validation to treat frontmatter mode as
72
- * spec-equivalent, mirroring `hasInlineRules`.
70
+ * frontmatter — an `enforce` rule, a `files` entry, or a `commands` entry. A
71
+ * utility for detecting frontmatter (Level-1) mode, mirroring `hasInlineRules`.
72
+ * (Note `require-instructions-spec` is narrow — only a `.spec.ts` satisfies it —
73
+ * so this no longer feeds that rule.)
73
74
  */
74
75
  export declare function hasFrontmatterRules(content: string): boolean;
75
76
  //# sourceMappingURL=frontmatter.d.ts.map
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  /**
3
- * vigiles — YAML frontmatter rule mode (Level 1 adoption).
3
+ * vigiles — YAML frontmatter rule mode.
4
4
  *
5
5
  * Parses a `vigiles.enforce` block out of a markdown file's YAML
6
6
  * frontmatter, so a project can declare enforce rules in structured YAML
7
- * instead of `<!-- vigiles:enforce ... -->` HTML comments (Level 0) or a
8
- * typed `.spec.ts` (Level 2). Every frontmatter rule goes through the same
9
- * `checkLinterRule` verification as inline and spec rules.
7
+ * instead of `<!-- vigiles:enforce ... -->` inline HTML comments or a typed
8
+ * `.spec.ts`. Every frontmatter rule goes through the same `checkLinterRule`
9
+ * verification as inline and spec rules.
10
10
  *
11
11
  * Shape (verbose — chosen so a JSON Schema can give `rule` an enum that
12
12
  * YAML LSP autocompletes and squiggles on typo):
@@ -252,9 +252,10 @@ function parseFrontmatterRules(content) {
252
252
  }
253
253
  /**
254
254
  * True if the content has at least one parseable `vigiles` reference in its
255
- * frontmatter — an `enforce` rule, a `files` entry, or a `commands` entry.
256
- * Used by `require-spec` validation to treat frontmatter mode as
257
- * spec-equivalent, mirroring `hasInlineRules`.
255
+ * frontmatter — an `enforce` rule, a `files` entry, or a `commands` entry. A
256
+ * utility for detecting frontmatter (Level-1) mode, mirroring `hasInlineRules`.
257
+ * (Note `require-instructions-spec` is narrow — only a `.spec.ts` satisfies it —
258
+ * so this no longer feeds that rule.)
258
259
  */
259
260
  function hasFrontmatterRules(content) {
260
261
  const r = parseFrontmatterRules(content);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * vigiles generate-harness — emit ONE typed registry over the whole harness.
2
+ * vigiles generate harness — emit ONE typed registry over the whole harness.
3
3
  *
4
4
  * The third generated artifact beside `generate-types` (`.d.ts`) and
5
5
  * `generate-schema` (JSON Schema): a `harness.gen.ts` that imports every
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * vigiles generate-harness — emit ONE typed registry over the whole harness.
3
+ * vigiles generate harness — emit ONE typed registry over the whole harness.
4
4
  *
5
5
  * The third generated artifact beside `generate-types` (`.d.ts`) and
6
6
  * `generate-schema` (JSON Schema): a `harness.gen.ts` that imports every
@@ -178,10 +178,10 @@ function generateHarness(model, options) {
178
178
  import: relImport(outDir, a.file),
179
179
  }));
180
180
  const L = [];
181
- L.push("// AUTO-GENERATED by `vigiles generate-harness` — DO NOT EDIT.");
181
+ L.push("// AUTO-GENERATED by `vigiles generate harness` — DO NOT EDIT.");
182
182
  L.push("// One typed registry over every *.spec.ts in the harness, so a single");
183
183
  L.push("// `tsc --noEmit` cross-checks the WHOLE harness as one program.");
184
- L.push("// Regenerate with `vigiles generate-harness` (wired to a spec guard).");
184
+ L.push("// Regenerate with `vigiles generate harness` (wired to a spec guard).");
185
185
  L.push("");
186
186
  const handoffs = model.handoffs ?? [];
187
187
  const specTypeImports = handoffs.length > 0 ? "KnownAgentName, Handoff, OkOf" : "KnownAgentName";
@@ -75,7 +75,7 @@ function generateSchema(options = {}) {
75
75
  $schema: "http://json-schema.org/draft-07/schema#",
76
76
  $id: "https://vigiles.dev/frontmatter.schema.json",
77
77
  title: "vigiles frontmatter",
78
- description: "vigiles enforce rules declared in markdown YAML frontmatter (Level 1).",
78
+ description: "vigiles enforce rules declared in markdown YAML frontmatter.",
79
79
  type: "object",
80
80
  properties: {
81
81
  vigiles: {