vigiles 19.0.0 → 20.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.
package/README.md CHANGED
@@ -260,6 +260,12 @@ Targets Claude Code and Codex out of the box, or [your own harness](docs/authori
260
260
  - **Reference** — [CLI](docs/cli.md) · [rules matrix](docs/verifying-instruction-files.md#the-validation-rules--the-full-matrix) · [testing API](docs/testing-api.md) · [full API](https://zernie.github.io/vigiles/api/)
261
261
  - **Explanation** — [what it catches](docs/what-vigiles-catches.md) · [how it compares](docs/comparison.md) · [FAQ](docs/faq.md)
262
262
 
263
+ > **A name starting with `experimental_` is not covered by semver.** It may change
264
+ > shape or disappear in a patch release. Everything so named has a stable
265
+ > alternative, given in that feature's own docs page — and the prefix is the only
266
+ > signal you need to look for, since it is on every call site rather than on an
267
+ > import line you scrolled past. See [Stability](STABILITY.md).
268
+
263
269
  **Project** — [Stability](STABILITY.md) · [Related tools](docs/comparison.md#what-vigiles-composes-with) · ships as an [Agent Plugins](https://agent-plugins.org) 1.0.0 plugin ([how to do the same](docs/for-plugin-authors.md#6-ship-it-in-the-portable-agent-plugins-format))
264
270
 
265
271
  <!-- The "companion to [Feedback Loop Is All You Need](https://zernie.com/blog/feedback-loop-is-all-you-need)"
@@ -109,7 +109,7 @@ function checkAdapterConformance(adapter) {
109
109
  // pass the subagent tool-contract check under this dialect.
110
110
  const tool = adapter.dialect.builtinAgentTools[0];
111
111
  if (tool) {
112
- const spec = (0, spec_js_1.agent)({
112
+ const spec = (0, spec_js_1.experimental_agent)({
113
113
  name: "conformance",
114
114
  description: "conformance probe",
115
115
  tools: [tool],
@@ -2,23 +2,23 @@
2
2
  * Typed Claude Code authoring surface — the compile-time half of the purity
3
3
  * contract, bound to the Claude Code tool vocabulary.
4
4
  *
5
- * The core `agent()` / `skill()` builders (`vigiles/spec`) are generic over a
5
+ * The core `experimental_agent()` / `skill()` builders (`vigiles/spec`) are generic over a
6
6
  * tool `ToolVocabulary` that DEFAULTS to fully-open (`string` at every purity
7
7
  * level), so they accept any tools — backwards-compatible, harness-agnostic.
8
8
  * This module re-binds them to the CONCRETE Claude Code vocabulary derived from
9
9
  * `claudeCodeDialect`, so authoring a spec with an invalid `purity`×`tools`
10
10
  * combination is a `tsc` error at EDIT TIME, before any vigiles command runs:
11
11
  *
12
- * import { agent } from "vigiles/claude-code";
12
+ * import { experimental_agent } from "vigiles/claude-code";
13
13
  *
14
- * agent({ purity: "pure", tools: ["Read", "Bash"] });
14
+ * experimental_agent({ purity: "pure", tools: ["Read", "Bash"] });
15
15
  * // ^^^^^^ tsc error — Bash side-effecting
16
16
  *
17
- * agent({ purity: "bounded", tools: ["Read", "Bash", "Write"] }); // OK
18
- * agent({ purity: "bounded", tools: ["mcp__x__y"] });
17
+ * experimental_agent({ purity: "bounded", tools: ["Read", "Bash", "Write"] }); // OK
18
+ * experimental_agent({ purity: "bounded", tools: ["mcp__x__y"] });
19
19
  * // ^^^^^^^^^^^ tsc error — MCP not decidable
20
20
  *
21
- * agent({ tools: ["anything", "mcp__x__y"] }); // no purity → open, OK
21
+ * experimental_agent({ tools: ["anything", "mcp__x__y"] }); // no purity → open, OK
22
22
  *
23
23
  * This is a STRICT ADDITION to the runtime/compile checks: `purityViolations`
24
24
  * (`vigiles compile`) and `decidePurityGate` (the PreToolUse gate) are unchanged
@@ -44,11 +44,11 @@ export interface ClaudeCodeToolVocabulary extends ToolVocabulary {
44
44
  }
45
45
  /**
46
46
  * Define a Claude Code subagent with the purity floor enforced AT COMPILE TIME
47
- * against the Claude Code tool catalog. Identical to the core `agent()` at
47
+ * against the Claude Code tool catalog. Identical to the core `experimental_agent()` at
48
48
  * runtime (it IS the core builder); the only difference is the typed `tools`
49
49
  * constraint. `P` is inferred from the literal `purity` field.
50
50
  */
51
- export declare function agent<const P extends AuthoredPurity | undefined = undefined>(spec: AgentSpecInput<P, ClaudeCodeToolVocabulary>): AgentSpec;
51
+ export declare function experimental_agent<const P extends AuthoredPurity | undefined = undefined>(spec: AgentSpecInput<P, ClaudeCodeToolVocabulary>): AgentSpec;
52
52
  /**
53
53
  * Define a Claude Code skill with the purity floor enforced AT COMPILE TIME
54
54
  * against the Claude Code tool catalog. Identical to the core
@@ -1,28 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.experimental_skill = void 0;
4
- exports.agent = agent;
4
+ exports.experimental_agent = experimental_agent;
5
5
  /**
6
6
  * Typed Claude Code authoring surface — the compile-time half of the purity
7
7
  * contract, bound to the Claude Code tool vocabulary.
8
8
  *
9
- * The core `agent()` / `skill()` builders (`vigiles/spec`) are generic over a
9
+ * The core `experimental_agent()` / `skill()` builders (`vigiles/spec`) are generic over a
10
10
  * tool `ToolVocabulary` that DEFAULTS to fully-open (`string` at every purity
11
11
  * level), so they accept any tools — backwards-compatible, harness-agnostic.
12
12
  * This module re-binds them to the CONCRETE Claude Code vocabulary derived from
13
13
  * `claudeCodeDialect`, so authoring a spec with an invalid `purity`×`tools`
14
14
  * combination is a `tsc` error at EDIT TIME, before any vigiles command runs:
15
15
  *
16
- * import { agent } from "vigiles/claude-code";
16
+ * import { experimental_agent } from "vigiles/claude-code";
17
17
  *
18
- * agent({ purity: "pure", tools: ["Read", "Bash"] });
18
+ * experimental_agent({ purity: "pure", tools: ["Read", "Bash"] });
19
19
  * // ^^^^^^ tsc error — Bash side-effecting
20
20
  *
21
- * agent({ purity: "bounded", tools: ["Read", "Bash", "Write"] }); // OK
22
- * agent({ purity: "bounded", tools: ["mcp__x__y"] });
21
+ * experimental_agent({ purity: "bounded", tools: ["Read", "Bash", "Write"] }); // OK
22
+ * experimental_agent({ purity: "bounded", tools: ["mcp__x__y"] });
23
23
  * // ^^^^^^^^^^^ tsc error — MCP not decidable
24
24
  *
25
- * agent({ tools: ["anything", "mcp__x__y"] }); // no purity → open, OK
25
+ * experimental_agent({ tools: ["anything", "mcp__x__y"] }); // no purity → open, OK
26
26
  *
27
27
  * This is a STRICT ADDITION to the runtime/compile checks: `purityViolations`
28
28
  * (`vigiles compile`) and `decidePurityGate` (the PreToolUse gate) are unchanged
@@ -37,12 +37,12 @@ exports.agent = agent;
37
37
  const spec_js_1 = require("../../core/spec.js");
38
38
  /**
39
39
  * Define a Claude Code subagent with the purity floor enforced AT COMPILE TIME
40
- * against the Claude Code tool catalog. Identical to the core `agent()` at
40
+ * against the Claude Code tool catalog. Identical to the core `experimental_agent()` at
41
41
  * runtime (it IS the core builder); the only difference is the typed `tools`
42
42
  * constraint. `P` is inferred from the literal `purity` field.
43
43
  */
44
- function agent(spec) {
45
- return (0, spec_js_1.agent)(spec);
44
+ function experimental_agent(spec) {
45
+ return (0, spec_js_1.experimental_agent)(spec);
46
46
  }
47
47
  /**
48
48
  * Define a Claude Code skill with the purity floor enforced AT COMPILE TIME
@@ -9,7 +9,13 @@ export * from "./adapters/claude-code/plugin-loader.js";
9
9
  export * from "./mock-model.js";
10
10
  export { claudeCodeDriver, buildClaudeArgs, parseClaudeRun, claudeAvailable, } from "./harness-test.js";
11
11
  export * from "./adapters/claude-code/dialect.js";
12
- export { agent, experimental_skill, type ClaudeCodeToolVocabulary, } from "./adapters/claude-code/typed-spec.js";
12
+ export { experimental_agent, experimental_skill, type ClaudeCodeToolVocabulary, } from "./adapters/claude-code/typed-spec.js";
13
+ export {
14
+ /**
15
+ * @deprecated Renamed to `experimental_agent` — the shape is not settled.
16
+ * Removed one major AFTER the one that introduces it.
17
+ */
18
+ experimental_agent as agent, } from "./adapters/claude-code/typed-spec.js";
13
19
  export { measureSelectionMatrix, assertNoCollision, formatSelectionReport, } from "./scan-behavioral.js";
14
20
  export type { SelectionReport, SkillSelectionStat, SelectionOptions, SelectionMatrixOptions, } from "./scan-behavioral.js";
15
21
  export * from "./adapters/claude-code/layout.js";
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.formatSelectionReport = exports.assertNoCollision = exports.measureSelectionMatrix = exports.experimental_skill = exports.agent = exports.claudeAvailable = exports.parseClaudeRun = exports.buildClaudeArgs = exports.claudeCodeDriver = void 0;
17
+ exports.formatSelectionReport = exports.assertNoCollision = exports.measureSelectionMatrix = exports.agent = exports.experimental_skill = exports.experimental_agent = exports.claudeAvailable = exports.parseClaudeRun = exports.buildClaudeArgs = exports.claudeCodeDriver = void 0;
18
18
  /**
19
19
  * `vigiles/claude-code` — the Claude Code-specific harness pieces a *different*
20
20
  * harness would swap out: the plugin/repo loader (reads real Claude Code plugin
@@ -35,10 +35,10 @@ Object.defineProperty(exports, "buildClaudeArgs", { enumerable: true, get: funct
35
35
  Object.defineProperty(exports, "parseClaudeRun", { enumerable: true, get: function () { return harness_test_js_1.parseClaudeRun; } });
36
36
  Object.defineProperty(exports, "claudeAvailable", { enumerable: true, get: function () { return harness_test_js_1.claudeAvailable; } });
37
37
  __exportStar(require("./adapters/claude-code/dialect.js"), exports);
38
- // The typed Claude Code authoring surface: `agent` / `experimental_skill` with
38
+ // The typed Claude Code authoring surface: `experimental_agent` / `experimental_skill` with
39
39
  // the `purity` floor enforced AT COMPILE TIME against the CC tool catalog (a
40
40
  // `tsc` error for e.g. `purity: "pure"` + `"Bash"`). A strict addition to the
41
- // runtime/compile purity checks; the bare core `agent()`/`experimental_skill()`
41
+ // runtime/compile purity checks; the bare core `experimental_agent()`/`experimental_skill()`
42
42
  // (`vigiles/spec`) stay open.
43
43
  //
44
44
  // NOTE the collision this rename also resolves: `vigiles/testing` exports a
@@ -46,8 +46,19 @@ __exportStar(require("./adapters/claude-code/dialect.js"), exports);
46
46
  // this skill fire?", taking an id string. One word, two concepts, told apart
47
47
  // only by which door you imported from. Only the authoring builder is prefixed.
48
48
  var typed_spec_js_1 = require("./adapters/claude-code/typed-spec.js");
49
- Object.defineProperty(exports, "agent", { enumerable: true, get: function () { return typed_spec_js_1.agent; } });
49
+ Object.defineProperty(exports, "experimental_agent", { enumerable: true, get: function () { return typed_spec_js_1.experimental_agent; } });
50
50
  Object.defineProperty(exports, "experimental_skill", { enumerable: true, get: function () { return typed_spec_js_1.experimental_skill; } });
51
+ // ─── ОКНО АЛИАСА (один мажор) ──────────────────────────────────────────────────
52
+ // Та же политика, что в `core/spec.ts`: старое имя живёт ровно один мажор. Эта
53
+ // дверь обязана иметь окно ОТДЕЛЬНО — потребитель, импортировавший `agent` из
54
+ // `vigiles/claude-code`, никогда не видел `vigiles/spec`, и окно на той стороне
55
+ // его не спасает.
56
+ var typed_spec_js_2 = require("./adapters/claude-code/typed-spec.js");
57
+ /**
58
+ * @deprecated Renamed to `experimental_agent` — the shape is not settled.
59
+ * Removed one major AFTER the one that introduces it.
60
+ */
61
+ Object.defineProperty(exports, "agent", { enumerable: true, get: function () { return typed_spec_js_2.experimental_agent; } });
51
62
  // Selection-collision — a Claude-Code-ONLY behavioral measurement (Codex has no
52
63
  // skill-selection event to read), so it lives on this surface, not the agnostic
53
64
  // `vigiles`. `measureSelectionMatrix` builds the N×N "which skill fired?"
package/dist/cli.js CHANGED
@@ -1431,7 +1431,7 @@ function targetHasHash(absPath) {
1431
1431
  /** Classify an adoption target by its path: a `SKILL.md` is a skill, a file under
1432
1432
  * an `agents/` dir is a subagent, everything else is an instruction file. Used to
1433
1433
  * pick the right adopt function so `init --target=skills/x/SKILL.md` (the
1434
- * per-surface path the audit report points at) makes a `skill()`/`agent()` spec. */
1434
+ * per-surface path the audit report points at) makes a `skill()`/`experimental_agent()` spec. */
1435
1435
  function surfaceKind(target) {
1436
1436
  if (/^SKILL\.md$/i.test((0, node_path_1.basename)(target)))
1437
1437
  return "skill";
@@ -1835,11 +1835,11 @@ function scaffoldSpec(args) {
1835
1835
  // the moment this is committed, because the enforce()/guidance() examples
1836
1836
  // below are commented out. The commented import shows what to add when you
1837
1837
  // write a real rule.
1838
- const template = `import { claude } from "vigiles/spec";
1838
+ const template = `import { instructionFile } from "vigiles/spec";
1839
1839
  // When you add rules below, import the builders you use, e.g.:
1840
- // import { claude, enforce, guidance } from "vigiles/spec";
1840
+ // import { instructionFile, enforce, guidance } from "vigiles/spec";
1841
1841
 
1842
- export default claude({${targetLine}
1842
+ export default instructionFile({${targetLine}
1843
1843
  sections: {
1844
1844
  // Prose sections become ## headings in the compiled output.
1845
1845
  // Do not add # or ## headers inside sections.
@@ -11,7 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
11
11
  * compiled build artifact (`vigiles compile`).
12
12
  */
13
13
  const spec_js_1 = require("./spec.js");
14
- exports.default = (0, spec_js_1.claude)({
14
+ exports.default = (0, spec_js_1.instructionFile)({
15
15
  sections: {
16
16
  scope: `Working in \`src/core/\`? This is the harness-AGNOSTIC domain (spec, compile, linters, the lint/audit detectors). The root \`CLAUDE.md\` holds the full positioning + rule set — read it first. Two invariants live closest to this code: the core must not import an adapter (\`core ⊄ adapter\`, eslint-enforced) and must not hard-code a Claude Code literal (read it from the injected layout/dialect). This file adds the rule for ADDING or CHANGING a detector.`,
17
17
  },
@@ -2,7 +2,7 @@
2
2
  * Faithful markdown → typed-spec adoption — the deterministic half of `init`
3
3
  * auto-adopt (research/install-enforcement-dx.md).
4
4
  *
5
- * Turns an existing instruction file (CLAUDE.md / AGENTS.md) into a `claude()`
5
+ * Turns an existing instruction file (CLAUDE.md / AGENTS.md) into a `instructionFile()`
6
6
  * spec source that compiles back to ~the same file, so adopting a rich,
7
7
  * hand-tuned instruction file is SAFE: every heading becomes a prose section
8
8
  * (verbatim), no rule is invented, nothing is dropped. The contract to the user
@@ -35,7 +35,7 @@ export interface AdoptResult {
35
35
  sectionCount: number;
36
36
  }
37
37
  /**
38
- * The intermediate adoption result: the `claude()` spec FIELDS (before
38
+ * The intermediate adoption result: the `instructionFile()` spec FIELDS (before
39
39
  * rendering to source). Exposed so the renderer and the round-trip tests share
40
40
  * one parse — the test can feed `sections` straight into `compileClaude` and
41
41
  * assert the file is reproduced, without evaluating generated TS source.
@@ -49,7 +49,7 @@ export interface AdoptedSpec {
49
49
  tier: AdoptTier;
50
50
  }
51
51
  /**
52
- * Parse an instruction file's markdown into the faithful `claude()` spec FIELDS.
52
+ * Parse an instruction file's markdown into the faithful `instructionFile()` spec FIELDS.
53
53
  * The shared core of {@link adoptMarkdown} and the round-trip tests.
54
54
  *
55
55
  * @param markdown the file's current content (an existing integrity header, if
@@ -59,7 +59,7 @@ export interface AdoptedSpec {
59
59
  */
60
60
  export declare function adoptToSpec(markdown: string, target: string): AdoptedSpec;
61
61
  /**
62
- * Convert an instruction file's markdown into a faithful `claude()` spec source
62
+ * Convert an instruction file's markdown into a faithful `instructionFile()` spec source
63
63
  * (the deliverable `init` writes).
64
64
  */
65
65
  export declare function adoptMarkdown(markdown: string, target: string): AdoptResult;
@@ -3,7 +3,7 @@
3
3
  * Faithful markdown → typed-spec adoption — the deterministic half of `init`
4
4
  * auto-adopt (research/install-enforcement-dx.md).
5
5
  *
6
- * Turns an existing instruction file (CLAUDE.md / AGENTS.md) into a `claude()`
6
+ * Turns an existing instruction file (CLAUDE.md / AGENTS.md) into a `instructionFile()`
7
7
  * spec source that compiles back to ~the same file, so adopting a rich,
8
8
  * hand-tuned instruction file is SAFE: every heading becomes a prose section
9
9
  * (verbatim), no rule is invented, nothing is dropped. The contract to the user
@@ -123,15 +123,15 @@ function renderSpecSource(spec) {
123
123
  return `// Adopted from ${spec.target} by \`vigiles init\` — faithful by default.
124
124
  // Each heading became a prose section; no rules were inferred. Run the
125
125
  // \`/strengthen\` skill to upgrade prose to verified enforce()/guard() rules.
126
- import { claude } from "vigiles/spec";
126
+ import { instructionFile } from "vigiles/spec";
127
127
 
128
- export default claude({${targetLine}${maxLine}${sectionsBlock}
128
+ export default instructionFile({${targetLine}${maxLine}${sectionsBlock}
129
129
  rules: {},
130
130
  });
131
131
  `;
132
132
  }
133
133
  /**
134
- * Parse an instruction file's markdown into the faithful `claude()` spec FIELDS.
134
+ * Parse an instruction file's markdown into the faithful `instructionFile()` spec FIELDS.
135
135
  * The shared core of {@link adoptMarkdown} and the round-trip tests.
136
136
  *
137
137
  * @param markdown the file's current content (an existing integrity header, if
@@ -189,7 +189,7 @@ function adoptToSpec(markdown, target) {
189
189
  };
190
190
  }
191
191
  /**
192
- * Convert an instruction file's markdown into a faithful `claude()` spec source
192
+ * Convert an instruction file's markdown into a faithful `instructionFile()` spec source
193
193
  * (the deliverable `init` writes).
194
194
  */
195
195
  function adoptMarkdown(markdown, target) {
@@ -348,7 +348,7 @@ function adoptSkill(markdown, dirName) {
348
348
  return { source, kind: "skill", spec, unmappedKeys };
349
349
  }
350
350
  /**
351
- * Adopt an existing subagent (`agents/<name>.md`) into an `agent()` spec. Unlike
351
+ * Adopt an existing subagent (`agents/<name>.md`) into an `experimental_agent()` spec. Unlike
352
352
  * a skill, an agent's `sections` reject `##` headers, so the body is split: the
353
353
  * lead preamble becomes `body` and each `##`/`#` heading becomes a named section
354
354
  * (reusing the instruction-file splitter). The tool contract is carried as-is —
@@ -378,7 +378,7 @@ function splitAgentBody(body) {
378
378
  }
379
379
  return { lead, sectionEntries };
380
380
  }
381
- /** Render the `agent({…})` source lines from the extracted fields. */
381
+ /** Render the `experimental_agent({…})` source lines from the extracted fields. */
382
382
  function buildAgentLines(f) {
383
383
  const lines = [
384
384
  ` name: ${JSON.stringify(f.name)},`,
@@ -419,7 +419,7 @@ function adoptAgent(markdown, fileBase) {
419
419
  const sections = {};
420
420
  for (const { key, content } of f.sectionEntries)
421
421
  sections[key] = content;
422
- const spec = (0, spec_js_1.agent)({
422
+ const spec = (0, spec_js_1.experimental_agent)({
423
423
  name: f.name,
424
424
  description: f.description,
425
425
  ...(f.model ? { model: f.model } : {}),
@@ -433,8 +433,8 @@ function adoptAgent(markdown, fileBase) {
433
433
  });
434
434
  const source = SURFACE_HEADER(`${fileBase}.md`) +
435
435
  unmappedNote("agent", unmappedKeys) +
436
- `import { agent } from "vigiles/spec";\n\n` +
437
- `export default agent({\n${buildAgentLines(f).join("\n")}\n});\n`;
436
+ `import { experimental_agent } from "vigiles/spec";\n\n` +
437
+ `export default experimental_agent({\n${buildAgentLines(f).join("\n")}\n});\n`;
438
438
  return { source, kind: "agent", spec, unmappedKeys };
439
439
  }
440
440
  //# sourceMappingURL=adopt.js.map
@@ -34,6 +34,7 @@ const hash_js_1 = require("./hash.js");
34
34
  const integrity_js_1 = require("./integrity.js");
35
35
  const markdown_js_1 = require("./markdown.js");
36
36
  const symbols_js_1 = require("./symbols.js");
37
+ const skill_normalize_js_1 = require("./skill-normalize.js");
37
38
  const linters_js_1 = require("./linters.js");
38
39
  const tool_contract_js_1 = require("./tool-contract.js");
39
40
  const effects_js_1 = require("./effects.js");
@@ -301,7 +302,7 @@ function compileRule(id, rule) {
301
302
  // chars; TS #52243 unresolved), so a helper's content is guarded at COMPILE time
302
303
  // instead — the ESLint-max-len / Prettier-printWidth precedent. Deliberately
303
304
  // generous (don't-cry-wolf): real prose sections are short, so this only trips on
304
- // an egregious dump (a whole essay pasted into one section / instructions``).
305
+ // an egregious dump (a whole essay pasted into one section / prose``).
305
306
  // Override per spec with `maxSectionLines`; `maxTokens` is the global backstop.
306
307
  const DEFAULT_MAX_SECTION_LINES = 200;
307
308
  function validateSectionContent(name, text, maxSectionLines) {
@@ -619,8 +620,8 @@ function collectSkillRefs(spec) {
619
620
  if (s.gate && s.gate._ref !== "role")
620
621
  refs.push(s.gate);
621
622
  }
622
- if (spec.result && spec.result._ref !== "role")
623
- refs.push(spec.result);
623
+ if (spec.postcondition && spec.postcondition._ref !== "role")
624
+ refs.push(spec.postcondition);
624
625
  return refs;
625
626
  }
626
627
  /**
@@ -725,8 +726,8 @@ function renderSkillSections(spec) {
725
726
  if (spec.steps && spec.steps.length > 0) {
726
727
  sections.push(renderSteps(spec.steps));
727
728
  }
728
- if (spec.result)
729
- sections.push(renderResult(spec.result));
729
+ if (spec.postcondition)
730
+ sections.push(renderResult(spec.postcondition));
730
731
  // A forked skill (context: fork) runs as a subagent, so it may carry the SAME
731
732
  // typed Result outcome — reuse the subagent renderer (one-renderer-no-drift).
732
733
  if (spec.output)
@@ -774,6 +775,14 @@ function checkInlineCode(markdown, max) {
774
775
  * Compile a SkillSpec into SKILL.md markdown with YAML frontmatter.
775
776
  */
776
777
  function compileSkill(spec, options = {}) {
778
+ // 🔴 NORMALISE FIRST, before anything reads the spec. `compileSkill` accepts a
779
+ // `SkillSpec` STRUCTURALLY, so a caller can hand us `{ _specType: "skill", …,
780
+ // result: cmd(…) }` without ever touching `experimental_skill()`. Both readers
781
+ // below (`collectSkillRefs`, `renderSkillSections`) look only at
782
+ // `postcondition`, so without this line such a spec loses its `## Result`
783
+ // section AND its reference verification, silently. Doing it here rather than
784
+ // in the readers means a reader added later cannot reintroduce the gap.
785
+ spec = (0, skill_normalize_js_1.foldLegacyPostcondition)(spec);
777
786
  const basePath = options.basePath ?? process.cwd();
778
787
  const specFile = options.specFile ?? "SKILL.md.spec.ts";
779
788
  const profile = options.dialect?.skillFrontmatter ?? "claude-code";
@@ -859,13 +868,13 @@ function compileSkill(spec, options = {}) {
859
868
  // harness's format-axis vocabulary — it lives in the HarnessDialect port
860
869
  // (src/core/dialect.ts), injected here, never hard-coded for one harness.
861
870
  //
862
- // SCOPE: compileAgent renders vigiles's agent() — a VERIFIED TOOL CONTRACT — to
871
+ // SCOPE: compileAgent renders vigiles's experimental_agent() — a VERIFIED TOOL CONTRACT — to
863
872
  // a Claude-Code-shaped subagent markdown file. Compiling that to Codex is a
864
873
  // deliberate NON-GOAL, not a missing renderer: a Codex "subagent" is an
865
874
  // [agents.<name>] TOML concurrency table (max_threads / max_depth), which is a
866
875
  // runtime-orchestration knob, NOT a tool contract. The two models don't map, so
867
876
  // vigiles does not emit a TOML [agents] block. The Codex dialect still verifies
868
- // an agent()'s tool contract (its built-in catalog) — only the OUTPUT renderer
877
+ // an experimental_agent()'s tool contract (its built-in catalog) — only the OUTPUT renderer
869
878
  // is CC-only here. See research/codex-prototype-findings.md (gaps).
870
879
  /** Verify a subagent's allowed-tools contract — the rails are real tools. The
871
880
  * detection lives in the shared `verifyToolContract` detector (one-detector-no-
@@ -263,10 +263,13 @@ export declare const tool: (name: string) => {
263
263
  tool: string;
264
264
  };
265
265
  /**
266
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
267
- * Imported as `experimental_defineHook`; alias it at the import site.
266
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
267
+ * Imported and CALLED as `experimental_defineHook` do not alias the prefix away at
268
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
269
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
270
+ * lines down sees `defineHook` without it and cannot tell it is provisional.
268
271
  */
269
- export declare function defineHook<const N extends readonly NeedSpec[] = readonly []>(p: HookProgram<N>): HookProgram<N>;
272
+ export declare function experimental_defineHook<const N extends readonly NeedSpec[] = readonly []>(p: HookProgram<N>): HookProgram<N>;
270
273
  /**
271
274
  * Build the typed event from a raw PreToolUse event, then decide.
272
275
  *
@@ -511,10 +514,13 @@ export declare const tools: (...names: string[]) => {
511
514
  tools: string[];
512
515
  };
513
516
  /**
514
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
515
- * Imported as `experimental_defineFileGate`; alias it at the import site.
517
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
518
+ * Imported and CALLED as `experimental_defineFileGate` do not alias the prefix away at
519
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
520
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
521
+ * lines down sees `defineFileGate` without it and cannot tell it is provisional.
516
522
  */
517
- export declare function defineFileGate<const N extends readonly NeedSpec[] = readonly []>(p: Omit<FileGateHook<N>, "role">): FileGateHook<N>;
523
+ export declare function experimental_defineFileGate<const N extends readonly NeedSpec[] = readonly []>(p: Omit<FileGateHook<N>, "role">): FileGateHook<N>;
518
524
  /**
519
525
  * Run a file-tool gate against a raw PreToolUse event (reads `file_path`).
520
526
  *
@@ -551,10 +557,13 @@ export interface PromptGateHook<N extends readonly NeedSpec[] = readonly Provide
551
557
  readonly decide: (e: PromptEvent<N>) => Decision;
552
558
  }
553
559
  /**
554
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
555
- * Imported as `experimental_definePromptGate`; alias it at the import site.
560
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
561
+ * Imported and CALLED as `experimental_definePromptGate` do not alias the prefix away at
562
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
563
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
564
+ * lines down sees `definePromptGate` without it and cannot tell it is provisional.
556
565
  */
557
- export declare function definePromptGate<const N extends readonly NeedSpec[] = readonly []>(p: Omit<PromptGateHook<N>, "role">): PromptGateHook<N>;
566
+ export declare function experimental_definePromptGate<const N extends readonly NeedSpec[] = readonly []>(p: Omit<PromptGateHook<N>, "role">): PromptGateHook<N>;
558
567
  /** Run a prompt gate against a raw UserPromptSubmit event (reads `prompt`). */
559
568
  export declare function decidePromptGate<N extends readonly NeedSpec[]>(hook: PromptGateHook<N>, raw: {
560
569
  prompt?: unknown;
@@ -586,10 +595,13 @@ export interface StopGateHook<N extends readonly NeedSpec[] = readonly ProviderN
586
595
  readonly decide: (e: StopEvent<N>) => Decision;
587
596
  }
588
597
  /**
589
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
590
- * Imported as `experimental_defineStopGate`; alias it at the import site.
598
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
599
+ * Imported and CALLED as `experimental_defineStopGate` do not alias the prefix away at
600
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
601
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
602
+ * lines down sees `defineStopGate` without it and cannot tell it is provisional.
591
603
  */
592
- export declare function defineStopGate<const N extends readonly NeedSpec[] = readonly []>(p: Omit<StopGateHook<N>, "role">): StopGateHook<N>;
604
+ export declare function experimental_defineStopGate<const N extends readonly NeedSpec[] = readonly []>(p: Omit<StopGateHook<N>, "role">): StopGateHook<N>;
593
605
  /** Run a Stop gate against a raw Stop/SubagentStop event (reads `stop_hook_active`). */
594
606
  export declare function decideStopGate<N extends readonly NeedSpec[]>(hook: StopGateHook<N>, raw: {
595
607
  stop_hook_active?: unknown;
@@ -635,10 +647,13 @@ export interface InjectHook<N extends readonly NeedSpec[] = readonly ProviderNam
635
647
  readonly produce: (e: SessionEvent<N>) => Injection;
636
648
  }
637
649
  /**
638
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
639
- * Imported as `experimental_defineInject`; alias it at the import site.
650
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
651
+ * Imported and CALLED as `experimental_defineInject` do not alias the prefix away at
652
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
653
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
654
+ * lines down sees `defineInject` without it and cannot tell it is provisional.
640
655
  */
641
- export declare function defineInject<const N extends readonly NeedSpec[] = readonly []>(p: Omit<InjectHook<N>, "role">): InjectHook<N>;
656
+ export declare function experimental_defineInject<const N extends readonly NeedSpec[] = readonly []>(p: Omit<InjectHook<N>, "role">): InjectHook<N>;
642
657
  /**
643
658
  * Run an inject hook → the CC JSON the author never hand-writes. The compiler
644
659
  * targets `additionalContext` (the RIGHT field for this event), so the
@@ -740,10 +755,13 @@ export interface ReactHook<N extends readonly NeedSpec[] = readonly ProviderName
740
755
  readonly react: (e: ReactEvent<N>) => Reaction;
741
756
  }
742
757
  /**
743
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
744
- * Imported as `experimental_defineReact`; alias it at the import site.
758
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
759
+ * Imported and CALLED as `experimental_defineReact` do not alias the prefix away at
760
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
761
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
762
+ * lines down sees `defineReact` without it and cannot tell it is provisional.
745
763
  */
746
- export declare function defineReact<const N extends readonly NeedSpec[] = readonly []>(p: Omit<ReactHook<N>, "role">): ReactHook<N>;
764
+ export declare function experimental_defineReact<const N extends readonly NeedSpec[] = readonly []>(p: Omit<ReactHook<N>, "role">): ReactHook<N>;
747
765
  /**
748
766
  * Run a react hook against a raw PostToolUse event → the (classified) Reaction.
749
767
  *
@@ -6,7 +6,7 @@ exports.invalidToolPatterns = invalidToolPatterns;
6
6
  exports.gateAction = gateAction;
7
7
  exports.trimTrailingSeparators = trimTrailingSeparators;
8
8
  exports.commandView = commandView;
9
- exports.defineHook = defineHook;
9
+ exports.experimental_defineHook = experimental_defineHook;
10
10
  exports.decideProgram = decideProgram;
11
11
  exports.decisionExitCode = decisionExitCode;
12
12
  exports.checkHookImports = checkHookImports;
@@ -20,17 +20,17 @@ exports.verifyHookStamp = verifyHookStamp;
20
20
  exports.pathView = pathView;
21
21
  exports.projectRootOf = projectRootOf;
22
22
  exports.undecidablePathWarning = undecidablePathWarning;
23
- exports.defineFileGate = defineFileGate;
23
+ exports.experimental_defineFileGate = experimental_defineFileGate;
24
24
  exports.decideFileGate = decideFileGate;
25
- exports.definePromptGate = definePromptGate;
25
+ exports.experimental_definePromptGate = experimental_definePromptGate;
26
26
  exports.decidePromptGate = decidePromptGate;
27
- exports.defineStopGate = defineStopGate;
27
+ exports.experimental_defineStopGate = experimental_defineStopGate;
28
28
  exports.decideStopGate = decideStopGate;
29
- exports.defineInject = defineInject;
29
+ exports.experimental_defineInject = experimental_defineInject;
30
30
  exports.runInject = runInject;
31
31
  exports.injectionOf = injectionOf;
32
32
  exports.responseView = responseView;
33
- exports.defineReact = defineReact;
33
+ exports.experimental_defineReact = experimental_defineReact;
34
34
  exports.runReact = runReact;
35
35
  exports.outcomeWrites = outcomeWrites;
36
36
  exports.rememberHookSource = rememberHookSource;
@@ -536,10 +536,13 @@ function commandView(raw, root) {
536
536
  const tool = (name) => ({ tool: name });
537
537
  exports.tool = tool;
538
538
  /**
539
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
540
- * Imported as `experimental_defineHook`; alias it at the import site.
539
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
540
+ * Imported and CALLED as `experimental_defineHook` do not alias the prefix away at
541
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
542
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
543
+ * lines down sees `defineHook` without it and cannot tell it is provisional.
541
544
  */
542
- function defineHook(p) {
545
+ function experimental_defineHook(p) {
543
546
  return p;
544
547
  }
545
548
  // ---------------------------------------------------------------------------
@@ -918,10 +921,13 @@ const tools = (...names) => ({
918
921
  });
919
922
  exports.tools = tools;
920
923
  /**
921
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
922
- * Imported as `experimental_defineFileGate`; alias it at the import site.
924
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
925
+ * Imported and CALLED as `experimental_defineFileGate` do not alias the prefix away at
926
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
927
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
928
+ * lines down sees `defineFileGate` without it and cannot tell it is provisional.
923
929
  */
924
- function defineFileGate(p) {
930
+ function experimental_defineFileGate(p) {
925
931
  return { role: "gate", ...p };
926
932
  }
927
933
  /**
@@ -949,10 +955,13 @@ function decideFileGate(hook, raw, ctx = {}, root = typeof raw.cwd === "string"
949
955
  });
950
956
  }
951
957
  /**
952
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
953
- * Imported as `experimental_definePromptGate`; alias it at the import site.
958
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
959
+ * Imported and CALLED as `experimental_definePromptGate` do not alias the prefix away at
960
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
961
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
962
+ * lines down sees `definePromptGate` without it and cannot tell it is provisional.
954
963
  */
955
- function definePromptGate(p) {
964
+ function experimental_definePromptGate(p) {
956
965
  return { role: "prompt-gate", ...p };
957
966
  }
958
967
  /** Run a prompt gate against a raw UserPromptSubmit event (reads `prompt`). */
@@ -965,10 +974,13 @@ function decidePromptGate(hook, raw, ctx = {}) {
965
974
  });
966
975
  }
967
976
  /**
968
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
969
- * Imported as `experimental_defineStopGate`; alias it at the import site.
977
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
978
+ * Imported and CALLED as `experimental_defineStopGate` do not alias the prefix away at
979
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
980
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
981
+ * lines down sees `defineStopGate` without it and cannot tell it is provisional.
970
982
  */
971
- function defineStopGate(p) {
983
+ function experimental_defineStopGate(p) {
972
984
  return { role: "stop-gate", ...p };
973
985
  }
974
986
  /** Run a Stop gate against a raw Stop/SubagentStop event (reads `stop_hook_active`). */
@@ -994,10 +1006,13 @@ const inject = (context, ...records) => ({
994
1006
  });
995
1007
  exports.inject = inject;
996
1008
  /**
997
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
998
- * Imported as `experimental_defineInject`; alias it at the import site.
1009
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
1010
+ * Imported and CALLED as `experimental_defineInject` do not alias the prefix away at
1011
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
1012
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
1013
+ * lines down sees `defineInject` without it and cannot tell it is provisional.
999
1014
  */
1000
- function defineInject(p) {
1015
+ function experimental_defineInject(p) {
1001
1016
  return { role: "inject", ...p };
1002
1017
  }
1003
1018
  /**
@@ -1076,10 +1091,13 @@ const nothing = (...records) => ({
1076
1091
  });
1077
1092
  exports.nothing = nothing;
1078
1093
  /**
1079
- * @experimental Compiled hooks are provisional — see docs/experimental.md.
1080
- * Imported as `experimental_defineReact`; alias it at the import site.
1094
+ * @experimental Compiled hooks are provisional — see docs/compiled-hooks.md#status--pending.
1095
+ * Imported and CALLED as `experimental_defineReact` do not alias the prefix away at
1096
+ * the import. Measured 2026-08-21: with the alias in place the marker survived
1097
+ * at 0 of 5 call sites in the only user-facing example, because a reader 200
1098
+ * lines down sees `defineReact` without it and cannot tell it is provisional.
1081
1099
  */
1082
- function defineReact(p) {
1100
+ function experimental_defineReact(p) {
1083
1101
  return { role: "react", ...p };
1084
1102
  }
1085
1103
  /**