vigiles 7.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.
@@ -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
@@ -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: {
@@ -64,13 +64,13 @@ export declare function parseInlineRules(content: string): InlineParseResult;
64
64
  /**
65
65
  * True if the content contains at least one parseable vigiles inline marker —
66
66
  * an `enforce` rule, a `file` reference, or a `cmd` reference (ignoring fenced
67
- * code blocks and malformed markers). Used by `require-spec` validation to
68
- * treat inline mode as spec-equivalent: a file that pins even a single path is
69
- * meaningfully managed.
67
+ * code blocks and malformed markers). A utility for detecting whether a file is
68
+ * inline-managed (Level-0 mode). Deliberately delegates to `parseInlineRules` so
69
+ * a loose prefix regex can't report a malformed marker that produces no real
70
+ * reference.
70
71
  *
71
- * Deliberately delegates to `parseInlineRules` so a loose prefix regex
72
- * can't satisfy require-spec with a malformed marker that produces no
73
- * real reference.
72
+ * NB `require-instructions-spec` is NARROW it is satisfied only by a `.spec.ts`,
73
+ * not by inline markers so this no longer feeds that rule.
74
74
  */
75
75
  export declare function hasInlineRules(content: string): boolean;
76
76
  //# sourceMappingURL=inline.d.ts.map
@@ -174,13 +174,13 @@ function parseInlineRules(content) {
174
174
  /**
175
175
  * True if the content contains at least one parseable vigiles inline marker —
176
176
  * an `enforce` rule, a `file` reference, or a `cmd` reference (ignoring fenced
177
- * code blocks and malformed markers). Used by `require-spec` validation to
178
- * treat inline mode as spec-equivalent: a file that pins even a single path is
179
- * meaningfully managed.
177
+ * code blocks and malformed markers). A utility for detecting whether a file is
178
+ * inline-managed (Level-0 mode). Deliberately delegates to `parseInlineRules` so
179
+ * a loose prefix regex can't report a malformed marker that produces no real
180
+ * reference.
180
181
  *
181
- * Deliberately delegates to `parseInlineRules` so a loose prefix regex
182
- * can't satisfy require-spec with a malformed marker that produces no
183
- * real reference.
182
+ * NB `require-instructions-spec` is NARROW it is satisfied only by a `.spec.ts`,
183
+ * not by inline markers so this no longer feeds that rule.
184
184
  */
185
185
  function hasInlineRules(content) {
186
186
  const r = parseInlineRules(content);
@@ -26,4 +26,35 @@ export interface IntegrityResult {
26
26
  * Files without a hash header are treated as hand-written (intact).
27
27
  */
28
28
  export declare function checkIntegrity(content: string): IntegrityResult;
29
+ /** The marker that tells `require-instructions-spec` a file is intentionally
30
+ * hand-owned (no `.spec.ts` expected). */
31
+ export declare const REQUIRE_INSTRUCTIONS_SPEC_DISABLE = "<!-- vigiles-disable require-instructions-spec -->";
32
+ /**
33
+ * Parse the `vigiles:sha256 … compiled from <spec>` integrity header, if the
34
+ * file carries one. Returns the referenced spec path and the body below the
35
+ * header; `null` when the file is plain markdown (no header).
36
+ */
37
+ export declare function parseIntegrityHeader(content: string): {
38
+ specFile: string;
39
+ body: string;
40
+ } | null;
41
+ /**
42
+ * "Eject" a compiled instruction file to plain, hand-owned markdown: strip the
43
+ * integrity header so the file is no longer spec-managed, and prepend a
44
+ * `require-instructions-spec` disable marker so `vigiles lint` won't ask for a
45
+ * spec back. Pure — the caller writes the file and removes the spec. Returns
46
+ * `null` when there is no header to strip (nothing to eject). Idempotent: a body
47
+ * that already carries the marker is not double-marked.
48
+ *
49
+ * The disable marker is added ONLY for instruction-file bodies. A compiled
50
+ * SKILL.md / subagent body begins with YAML frontmatter (`---`) that MUST stay in
51
+ * first position — prepending an HTML comment there would push the frontmatter
52
+ * out of the lead block and the harness would lose the skill's name/description/
53
+ * tools. The marker is also meaningless for those surfaces (require-instructions-
54
+ * spec doesn't apply to them), so a frontmatter-led body is ejected as-is.
55
+ */
56
+ export declare function ejectMarkdown(content: string): {
57
+ markdown: string;
58
+ specFile: string;
59
+ } | null;
29
60
  //# sourceMappingURL=integrity.d.ts.map
@@ -19,7 +19,10 @@
19
19
  * → CI runs `vigiles compile` then `git diff --exit-code`
20
20
  */
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.REQUIRE_INSTRUCTIONS_SPEC_DISABLE = void 0;
22
23
  exports.checkIntegrity = checkIntegrity;
24
+ exports.parseIntegrityHeader = parseIntegrityHeader;
25
+ exports.ejectMarkdown = ejectMarkdown;
23
26
  const hash_js_1 = require("./hash.js");
24
27
  const HASH_LINE_RE = /^<!-- vigiles:sha256:([a-f0-9]+) compiled from (.+) -->\r?\n\r?\n?/;
25
28
  /**
@@ -41,4 +44,46 @@ function checkIntegrity(content) {
41
44
  }
42
45
  return { intact: true };
43
46
  }
47
+ /** The marker that tells `require-instructions-spec` a file is intentionally
48
+ * hand-owned (no `.spec.ts` expected). */
49
+ exports.REQUIRE_INSTRUCTIONS_SPEC_DISABLE = "<!-- vigiles-disable require-instructions-spec -->";
50
+ /**
51
+ * Parse the `vigiles:sha256 … compiled from <spec>` integrity header, if the
52
+ * file carries one. Returns the referenced spec path and the body below the
53
+ * header; `null` when the file is plain markdown (no header).
54
+ */
55
+ function parseIntegrityHeader(content) {
56
+ const match = content.match(HASH_LINE_RE);
57
+ if (!match)
58
+ return null;
59
+ return { specFile: match[2], body: content.replace(HASH_LINE_RE, "") };
60
+ }
61
+ /**
62
+ * "Eject" a compiled instruction file to plain, hand-owned markdown: strip the
63
+ * integrity header so the file is no longer spec-managed, and prepend a
64
+ * `require-instructions-spec` disable marker so `vigiles lint` won't ask for a
65
+ * spec back. Pure — the caller writes the file and removes the spec. Returns
66
+ * `null` when there is no header to strip (nothing to eject). Idempotent: a body
67
+ * that already carries the marker is not double-marked.
68
+ *
69
+ * The disable marker is added ONLY for instruction-file bodies. A compiled
70
+ * SKILL.md / subagent body begins with YAML frontmatter (`---`) that MUST stay in
71
+ * first position — prepending an HTML comment there would push the frontmatter
72
+ * out of the lead block and the harness would lose the skill's name/description/
73
+ * tools. The marker is also meaningless for those surfaces (require-instructions-
74
+ * spec doesn't apply to them), so a frontmatter-led body is ejected as-is.
75
+ */
76
+ function ejectMarkdown(content) {
77
+ const parsed = parseIntegrityHeader(content);
78
+ if (!parsed)
79
+ return null;
80
+ // A frontmatter-led body is a skill/agent — strip the header, add nothing.
81
+ if (/^---\r?\n/.test(parsed.body)) {
82
+ return { markdown: parsed.body, specFile: parsed.specFile };
83
+ }
84
+ const markdown = parsed.body.startsWith(exports.REQUIRE_INSTRUCTIONS_SPEC_DISABLE)
85
+ ? parsed.body
86
+ : `${exports.REQUIRE_INSTRUCTIONS_SPEC_DISABLE}\n\n${parsed.body}`;
87
+ return { markdown, specFile: parsed.specFile };
88
+ }
44
89
  //# sourceMappingURL=integrity.js.map
@@ -30,7 +30,7 @@ const DEFAULT_IGNORE = [
30
30
  ];
31
31
  /**
32
32
  * A doc carrying this marker opts out of orphan detection — the inline escape
33
- * hatch, mirroring `vigiles-disable require-spec` and `vigiles:ignore-test`.
33
+ * hatch, mirroring `vigiles-disable require-instructions-spec` and `vigiles:ignore-test`.
34
34
  * Use it for an intentionally-unreferenced doc (a changelog, a top-level index)
35
35
  * that nothing else links to but is not rot.
36
36
  */