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 +6 -0
- package/dist/adapter-conformance.js +1 -1
- package/dist/adapters/claude-code/typed-spec.d.ts +8 -8
- package/dist/adapters/claude-code/typed-spec.js +10 -10
- package/dist/claude-code.d.ts +7 -1
- package/dist/claude-code.js +15 -4
- package/dist/cli.js +4 -4
- package/dist/core/CLAUDE.md.spec.js +1 -1
- package/dist/core/adopt.d.ts +4 -4
- package/dist/core/adopt.js +10 -10
- package/dist/core/compile.js +16 -7
- package/dist/core/hook-program.d.ts +36 -18
- package/dist/core/hook-program.js +42 -24
- package/dist/core/rule-meta.js +2 -2
- package/dist/core/skill-normalize.d.ts +50 -0
- package/dist/core/skill-normalize.js +60 -0
- package/dist/core/spec.d.ts +63 -17
- package/dist/core/spec.js +70 -16
- package/dist/eval-surface.d.ts +8 -3
- package/dist/eval-surface.js +8 -3
- package/dist/harness-assert.js +1 -1
- package/dist/hook-install.js +35 -3
- package/dist/hook.d.ts +6 -6
- package/dist/hook.js +24 -13
- package/dist/linting.d.ts +33 -4
- package/dist/linting.js +62 -21
- package/dist/load-hook.js +1 -1
- package/dist/scaffold-test.js +1 -1
- package/dist/services-docker.js +2 -2
- package/dist/services.d.ts +2 -2
- package/dist/services.js +2 -2
- package/dist/test.d.ts +3 -0
- package/dist/test.js +42 -2
- package/package.json +2 -3
- package/dist/experimental.d.ts +0 -34
- package/dist/experimental.js +0 -44
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.
|
|
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 `
|
|
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 {
|
|
12
|
+
* import { experimental_agent } from "vigiles/claude-code";
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* experimental_agent({ purity: "pure", tools: ["Read", "Bash"] });
|
|
15
15
|
* // ^^^^^^ tsc error — Bash side-effecting
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
-
*
|
|
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 `
|
|
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
|
|
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.
|
|
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 `
|
|
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 {
|
|
16
|
+
* import { experimental_agent } from "vigiles/claude-code";
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* experimental_agent({ purity: "pure", tools: ["Read", "Bash"] });
|
|
19
19
|
* // ^^^^^^ tsc error — Bash side-effecting
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
-
*
|
|
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 `
|
|
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
|
|
45
|
-
return (0, spec_js_1.
|
|
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
|
package/dist/claude-code.d.ts
CHANGED
|
@@ -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 {
|
|
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";
|
package/dist/claude-code.js
CHANGED
|
@@ -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.
|
|
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: `
|
|
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 `
|
|
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, "
|
|
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()`/`
|
|
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 {
|
|
1838
|
+
const template = `import { instructionFile } from "vigiles/spec";
|
|
1839
1839
|
// When you add rules below, import the builders you use, e.g.:
|
|
1840
|
-
// import {
|
|
1840
|
+
// import { instructionFile, enforce, guidance } from "vigiles/spec";
|
|
1841
1841
|
|
|
1842
|
-
export default
|
|
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.
|
|
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
|
},
|
package/dist/core/adopt.d.ts
CHANGED
|
@@ -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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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;
|
package/dist/core/adopt.js
CHANGED
|
@@ -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 `
|
|
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 {
|
|
126
|
+
import { instructionFile } from "vigiles/spec";
|
|
127
127
|
|
|
128
|
-
export default
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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.
|
|
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 {
|
|
437
|
-
`export default
|
|
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
|
package/dist/core/compile.js
CHANGED
|
@@ -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 /
|
|
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.
|
|
623
|
-
refs.push(spec.
|
|
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.
|
|
729
|
-
sections.push(renderResult(spec.
|
|
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
|
|
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
|
|
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/
|
|
267
|
-
* Imported as `experimental_defineHook
|
|
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
|
|
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/
|
|
515
|
-
* Imported as `experimental_defineFileGate
|
|
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
|
|
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/
|
|
555
|
-
* Imported as `experimental_definePromptGate
|
|
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
|
|
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/
|
|
590
|
-
* Imported as `experimental_defineStopGate
|
|
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
|
|
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/
|
|
639
|
-
* Imported as `experimental_defineInject
|
|
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
|
|
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/
|
|
744
|
-
* Imported as `experimental_defineReact
|
|
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
|
|
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.
|
|
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.
|
|
23
|
+
exports.experimental_defineFileGate = experimental_defineFileGate;
|
|
24
24
|
exports.decideFileGate = decideFileGate;
|
|
25
|
-
exports.
|
|
25
|
+
exports.experimental_definePromptGate = experimental_definePromptGate;
|
|
26
26
|
exports.decidePromptGate = decidePromptGate;
|
|
27
|
-
exports.
|
|
27
|
+
exports.experimental_defineStopGate = experimental_defineStopGate;
|
|
28
28
|
exports.decideStopGate = decideStopGate;
|
|
29
|
-
exports.
|
|
29
|
+
exports.experimental_defineInject = experimental_defineInject;
|
|
30
30
|
exports.runInject = runInject;
|
|
31
31
|
exports.injectionOf = injectionOf;
|
|
32
32
|
exports.responseView = responseView;
|
|
33
|
-
exports.
|
|
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/
|
|
540
|
-
* Imported as `experimental_defineHook
|
|
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
|
|
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/
|
|
922
|
-
* Imported as `experimental_defineFileGate
|
|
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
|
|
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/
|
|
953
|
-
* Imported as `experimental_definePromptGate
|
|
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
|
|
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/
|
|
969
|
-
* Imported as `experimental_defineStopGate
|
|
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
|
|
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/
|
|
998
|
-
* Imported as `experimental_defineInject
|
|
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
|
|
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/
|
|
1080
|
-
* Imported as `experimental_defineReact
|
|
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
|
|
1100
|
+
function experimental_defineReact(p) {
|
|
1083
1101
|
return { role: "react", ...p };
|
|
1084
1102
|
}
|
|
1085
1103
|
/**
|