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/dist/core/rule-meta.js
CHANGED
|
@@ -86,7 +86,7 @@ exports.RULE_META = {
|
|
|
86
86
|
defaultSeverity: "warn",
|
|
87
87
|
summary: "A subagent's tools: are all real (no never-available / typo).",
|
|
88
88
|
detector: "verifyToolContract / scoredIssues",
|
|
89
|
-
upstreamPrevention: "typed
|
|
89
|
+
upstreamPrevention: "typed experimental_agent() vocabulary + compileAgent — an unknown tool is a tsc/compile error",
|
|
90
90
|
},
|
|
91
91
|
"disallowed-tools-contract": {
|
|
92
92
|
id: "disallowed-tools-contract",
|
|
@@ -95,7 +95,7 @@ exports.RULE_META = {
|
|
|
95
95
|
defaultSeverity: "warn",
|
|
96
96
|
summary: "A disallowedTools: entry isn't a typo that blocks nothing.",
|
|
97
97
|
detector: "disallowedToolIssues",
|
|
98
|
-
upstreamPrevention: "typed
|
|
98
|
+
upstreamPrevention: "typed experimental_agent() vocabulary (a typo is a tsc error)",
|
|
99
99
|
},
|
|
100
100
|
"subagent-frontmatter": {
|
|
101
101
|
id: "subagent-frontmatter",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one implementation of "fold the deprecated `result:` into `postcondition:`".
|
|
3
|
+
*
|
|
4
|
+
* 🔴 WHY THIS IS ITS OWN MODULE, and the mistake that produced it. The fold
|
|
5
|
+
* originally lived inside `experimental_skill()` alone, with a comment arguing
|
|
6
|
+
* that normalising "at the door" beats normalising at each reader — because a
|
|
7
|
+
* reader added later would silently read only the new field and drop every spec
|
|
8
|
+
* still on the old one. The argument was right. The identification of the door
|
|
9
|
+
* was wrong: `experimental_skill()` is not the only entrance.
|
|
10
|
+
*
|
|
11
|
+
* `compileSkill()` is public, and it accepts a `SkillSpec` STRUCTURALLY. That
|
|
12
|
+
* interface still advertises `result?: Gate`, so this compiles and is legal:
|
|
13
|
+
*
|
|
14
|
+
* compileSkill({ _specType: "skill", name, description, body, result: cmd("npm test") })
|
|
15
|
+
*
|
|
16
|
+
* Such a caller never touches the builder, so before this module the `## Result`
|
|
17
|
+
* section and its reference verification were both silently dropped — the exact
|
|
18
|
+
* defect the original comment predicted, arriving through the entrance it did not
|
|
19
|
+
* count. Found by a reviewer, not by me.
|
|
20
|
+
*
|
|
21
|
+
* So: one function, called at BOTH doors. That is not two sources of truth — it
|
|
22
|
+
* is one, used twice. Putting it in spec.ts would have meant either exporting it
|
|
23
|
+
* from `vigiles/spec` (public surface for an internal concern) or duplicating it
|
|
24
|
+
* in compile.ts (the thing being avoided). This module is imported by both and
|
|
25
|
+
* re-exported by neither, so it stays off every api report.
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ IT IMPORTS NOTHING, including from spec.ts, and that is deliberate.
|
|
28
|
+
* `core/spec.ts` is the dependency ROOT of this package — it has zero imports of
|
|
29
|
+
* its own — and spec.ts has to call this. Naming `Gate` here would put a back
|
|
30
|
+
* edge into the root; a type-only import erases at runtime, but the graph would
|
|
31
|
+
* still read as a cycle to anyone (or any lint rule) looking at it. The fold does
|
|
32
|
+
* not care what a gate IS, only which of two properties holds one, so the shape
|
|
33
|
+
* is described structurally and the dependency stays one-directional.
|
|
34
|
+
*/
|
|
35
|
+
/** Anything carrying the two spellings of a skill's terminal gate. */
|
|
36
|
+
interface HasPostcondition<G> {
|
|
37
|
+
readonly postcondition?: G;
|
|
38
|
+
/** @deprecated the old spelling; folded away by {@link foldLegacyPostcondition}. */
|
|
39
|
+
readonly result?: G;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Return `spec` with `result:` folded into `postcondition:` and `result` removed.
|
|
43
|
+
*
|
|
44
|
+
* Throws when both are set: they are the same field under two names, so which
|
|
45
|
+
* gate runs would otherwise be decided by which branch of the fold ran last —
|
|
46
|
+
* a coin flip in a place where the answer is a gate.
|
|
47
|
+
*/
|
|
48
|
+
export declare function foldLegacyPostcondition<G, T extends HasPostcondition<G>>(spec: T): T;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=skill-normalize.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The one implementation of "fold the deprecated `result:` into `postcondition:`".
|
|
4
|
+
*
|
|
5
|
+
* 🔴 WHY THIS IS ITS OWN MODULE, and the mistake that produced it. The fold
|
|
6
|
+
* originally lived inside `experimental_skill()` alone, with a comment arguing
|
|
7
|
+
* that normalising "at the door" beats normalising at each reader — because a
|
|
8
|
+
* reader added later would silently read only the new field and drop every spec
|
|
9
|
+
* still on the old one. The argument was right. The identification of the door
|
|
10
|
+
* was wrong: `experimental_skill()` is not the only entrance.
|
|
11
|
+
*
|
|
12
|
+
* `compileSkill()` is public, and it accepts a `SkillSpec` STRUCTURALLY. That
|
|
13
|
+
* interface still advertises `result?: Gate`, so this compiles and is legal:
|
|
14
|
+
*
|
|
15
|
+
* compileSkill({ _specType: "skill", name, description, body, result: cmd("npm test") })
|
|
16
|
+
*
|
|
17
|
+
* Such a caller never touches the builder, so before this module the `## Result`
|
|
18
|
+
* section and its reference verification were both silently dropped — the exact
|
|
19
|
+
* defect the original comment predicted, arriving through the entrance it did not
|
|
20
|
+
* count. Found by a reviewer, not by me.
|
|
21
|
+
*
|
|
22
|
+
* So: one function, called at BOTH doors. That is not two sources of truth — it
|
|
23
|
+
* is one, used twice. Putting it in spec.ts would have meant either exporting it
|
|
24
|
+
* from `vigiles/spec` (public surface for an internal concern) or duplicating it
|
|
25
|
+
* in compile.ts (the thing being avoided). This module is imported by both and
|
|
26
|
+
* re-exported by neither, so it stays off every api report.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ IT IMPORTS NOTHING, including from spec.ts, and that is deliberate.
|
|
29
|
+
* `core/spec.ts` is the dependency ROOT of this package — it has zero imports of
|
|
30
|
+
* its own — and spec.ts has to call this. Naming `Gate` here would put a back
|
|
31
|
+
* edge into the root; a type-only import erases at runtime, but the graph would
|
|
32
|
+
* still read as a cycle to anyone (or any lint rule) looking at it. The fold does
|
|
33
|
+
* not care what a gate IS, only which of two properties holds one, so the shape
|
|
34
|
+
* is described structurally and the dependency stays one-directional.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.foldLegacyPostcondition = foldLegacyPostcondition;
|
|
38
|
+
/**
|
|
39
|
+
* Return `spec` with `result:` folded into `postcondition:` and `result` removed.
|
|
40
|
+
*
|
|
41
|
+
* Throws when both are set: they are the same field under two names, so which
|
|
42
|
+
* gate runs would otherwise be decided by which branch of the fold ran last —
|
|
43
|
+
* a coin flip in a place where the answer is a gate.
|
|
44
|
+
*/
|
|
45
|
+
function foldLegacyPostcondition(spec) {
|
|
46
|
+
// This IS the one place the deprecated field may be read — the fold is what
|
|
47
|
+
// makes the old spelling work at all, so a lint that forbade it everywhere
|
|
48
|
+
// would forbid the alias window itself.
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- see above
|
|
50
|
+
const { result: legacy, ...rest } = spec;
|
|
51
|
+
if (!legacy)
|
|
52
|
+
return spec;
|
|
53
|
+
if (rest.postcondition) {
|
|
54
|
+
throw new Error("skill spec sets BOTH `postcondition:` and the deprecated `result:` — " +
|
|
55
|
+
"they are the same field under two names, so which gate runs is a " +
|
|
56
|
+
"coin flip. Keep `postcondition:` and delete `result:`.");
|
|
57
|
+
}
|
|
58
|
+
return { ...rest, postcondition: legacy };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=skill-normalize.js.map
|
package/dist/core/spec.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ export type StrictCmd = [keyof KnownNpmScripts] extends [never] ? string : `npm
|
|
|
71
71
|
* MCP / unknown / wildcard.
|
|
72
72
|
*
|
|
73
73
|
* The default (`string` at both) imposes no constraint — any tool is accepted at
|
|
74
|
-
* every level, the historical behaviour of an untyped `
|
|
74
|
+
* every level, the historical behaviour of an untyped `experimental_agent()`/`skill()`.
|
|
75
75
|
*/
|
|
76
76
|
export interface ToolVocabulary {
|
|
77
77
|
/** Union of tool names allowed under `purity: "pure"`. */
|
|
@@ -94,8 +94,6 @@ export interface OpenToolVocabulary extends ToolVocabulary {
|
|
|
94
94
|
* untyped surface accepts any tools.
|
|
95
95
|
*/
|
|
96
96
|
export type AllowedAt<P extends AuthoredPurity | undefined, V extends ToolVocabulary> = P extends "pure" ? V["readOnly"] : P extends "bounded" ? V["bounded"] : string;
|
|
97
|
-
export type ClaudeTool = "Read" | "Write" | "Edit" | "Bash" | "Grep" | "Glob" | "Agent" | "TodoWrite" | "WebSearch" | "WebFetch" | "NotebookEdit";
|
|
98
|
-
export type HookEvent = "PreToolUse" | "PostToolUse" | "PreSession" | "PostSession" | "Notification";
|
|
99
97
|
/** A rule delegated to an external tool (linter, ast-grep, dependency-cruiser, etc.) or to a vigiles-internal check. */
|
|
100
98
|
export interface EnforceRule {
|
|
101
99
|
readonly _kind: "enforce";
|
|
@@ -257,18 +255,18 @@ export type InstructionFragment = string | Ref | EffectRegion;
|
|
|
257
255
|
/**
|
|
258
256
|
* Tagged template literal for skill instructions with typed references.
|
|
259
257
|
*
|
|
260
|
-
*
|
|
258
|
+
* prose`
|
|
261
259
|
* Check ${file("eslint.config.ts")} for rules.
|
|
262
260
|
* Run ${cmd("npm test")} to verify.
|
|
263
261
|
* See ${ref("skills/other/SKILL.md")} for format.
|
|
264
262
|
* `
|
|
265
263
|
*/
|
|
266
|
-
export declare function
|
|
264
|
+
export declare function prose(strings: TemplateStringsArray, ...values: InstructionFragment[]): InstructionFragment[];
|
|
267
265
|
/**
|
|
268
266
|
* Tagged template literal marking a side-effect boundary — usable as an
|
|
269
267
|
* interpolated fragment inside a body / `instructions\`\``:
|
|
270
268
|
*
|
|
271
|
-
*
|
|
269
|
+
* prose`
|
|
272
270
|
* ## Apply
|
|
273
271
|
* ${effect`
|
|
274
272
|
* Side effects are allowed ONLY here:
|
|
@@ -332,7 +330,7 @@ export interface ClaudeSpec {
|
|
|
332
330
|
readonly rules: Record<string, Rule>;
|
|
333
331
|
}
|
|
334
332
|
/**
|
|
335
|
-
* Input type for
|
|
333
|
+
* Input type for instructionFile() — maxSectionLines is only valid when sections are provided.
|
|
336
334
|
* TypeScript errors if you set maxSectionLines without defining sections.
|
|
337
335
|
*/
|
|
338
336
|
type ClaudeSpecBase = {
|
|
@@ -354,9 +352,9 @@ type ClaudeSpecInput = ClaudeSpecBase & ClaudeSpecSections;
|
|
|
354
352
|
* Define a CLAUDE.md specification.
|
|
355
353
|
*
|
|
356
354
|
* // CLAUDE.md.spec.ts
|
|
357
|
-
* export default
|
|
355
|
+
* export default instructionFile({ commands: {...}, rules: {...} });
|
|
358
356
|
*/
|
|
359
|
-
export declare function
|
|
357
|
+
export declare function instructionFile(spec: ClaudeSpecInput): ClaudeSpec;
|
|
360
358
|
/**
|
|
361
359
|
* A deterministic gate on a skill step or its final result. A gate is one of:
|
|
362
360
|
* a command (exit 0), a file (must exist), or a *project role* that resolves to
|
|
@@ -484,6 +482,25 @@ export interface SkillSpec {
|
|
|
484
482
|
/**
|
|
485
483
|
* Terminal postcondition — the skill is "done" only when this gate passes.
|
|
486
484
|
* Compiles to a `## Result` section + a `vigiles:result` marker.
|
|
485
|
+
*
|
|
486
|
+
* 🔴 NAMED `postcondition`, NOT `result`, because `result` already means
|
|
487
|
+
* something else one screen down: {@link result} builds a subagent's typed
|
|
488
|
+
* ok/err CONTRACT (and reaches a skill through `output:`). Two concepts under
|
|
489
|
+
* one word, told apart only by whether you wrote `result:` or `output:
|
|
490
|
+
* result(...)` — the doc comment on {@link result} had to spend a line saying
|
|
491
|
+
* "distinct from a skill's `result:` postcondition gate", which is the tell.
|
|
492
|
+
* A name that needs a disambiguating sentence is the wrong name.
|
|
493
|
+
*
|
|
494
|
+
* The compiled MARKER stays `vigiles:result` deliberately: it is the wire
|
|
495
|
+
* format between the compiler and {@link parseSkillGates}, and every already
|
|
496
|
+
* compiled SKILL.md on disk carries it. Renaming the authoring field is a
|
|
497
|
+
* source-level change; renaming the marker would invalidate stamps.
|
|
498
|
+
*/
|
|
499
|
+
readonly postcondition?: Gate;
|
|
500
|
+
/**
|
|
501
|
+
* @deprecated Renamed to {@link SkillSpec.postcondition}. Removed next
|
|
502
|
+
* major. Measured 2026-08-21: 0 of 46 specs in the consuming knowledge base
|
|
503
|
+
* set this field, so the window costs nothing and closes the collision above.
|
|
487
504
|
*/
|
|
488
505
|
readonly result?: Gate;
|
|
489
506
|
/**
|
|
@@ -524,7 +541,7 @@ export type SkillSpecInput<P extends AuthoredPurity | undefined, V extends ToolV
|
|
|
524
541
|
* export default experimental_skill({ name: "my-skill", description: "…" });
|
|
525
542
|
*
|
|
526
543
|
* Generic over a tool `Vocabulary` (default `OpenToolVocabulary` — no
|
|
527
|
-
* constraint), exactly like `
|
|
544
|
+
* constraint), exactly like `experimental_agent()`: a vocabulary-bound `experimental_skill`
|
|
528
545
|
* (e.g. `vigiles/claude-code`) makes `purity: "pure"` + a side-effecting tool a
|
|
529
546
|
* `tsc` error; the bare core one accepts any tools, as before.
|
|
530
547
|
*
|
|
@@ -629,7 +646,7 @@ export interface AgentSpec {
|
|
|
629
646
|
readonly purity?: AuthoredPurity;
|
|
630
647
|
}
|
|
631
648
|
/**
|
|
632
|
-
* The input to `
|
|
649
|
+
* The input to `experimental_agent()` — `AgentSpec` minus the internal `_specType`, with the
|
|
633
650
|
* `tools` list constrained by the declared `purity` and the tool vocabulary `V`.
|
|
634
651
|
* `P` is inferred from the literal `purity` field (`const` inference), and
|
|
635
652
|
* `tools` is then typed `AllowedAt<P, V>[]`:
|
|
@@ -637,7 +654,7 @@ export interface AgentSpec {
|
|
|
637
654
|
* - `purity: "bounded"`→ `tools` may list `V["bounded"]` tools (admits `Bash`).
|
|
638
655
|
* - no `purity` / `"dangerously-unrestricted"` → `tools` is `string[]` (open).
|
|
639
656
|
*
|
|
640
|
-
* With the open default vocabulary (core `
|
|
657
|
+
* With the open default vocabulary (core `experimental_agent()`) every level widens to
|
|
641
658
|
* `string`, so any tools compile — backwards-compatible.
|
|
642
659
|
*/
|
|
643
660
|
export type AgentSpecInput<P extends AuthoredPurity | undefined, V extends ToolVocabulary, Ok extends Shape = Shape, Err extends Shape = Shape> = Omit<AgentSpec, "_specType" | "tools" | "purity" | "output"> & {
|
|
@@ -672,12 +689,12 @@ export type OkOf<T> = T extends TypedOutcome<infer Ok, Shape> ? Ok : Shape;
|
|
|
672
689
|
* Define a subagent specification (compiles to `agents/<name>.md`).
|
|
673
690
|
*
|
|
674
691
|
* // agents/reviewer.md.spec.ts
|
|
675
|
-
* export default
|
|
692
|
+
* export default experimental_agent({
|
|
676
693
|
* name: "reviewer",
|
|
677
694
|
* description: "Review a diff for correctness. Dispatch PROACTIVELY after edits.",
|
|
678
695
|
* model: "sonnet",
|
|
679
696
|
* tools: ["Read", "Grep", "Bash"],
|
|
680
|
-
* body:
|
|
697
|
+
* body: prose`Review the diff. Run ${cmd("npm test")} first.`,
|
|
681
698
|
* rules: {
|
|
682
699
|
* "no-floating": enforce("@typescript-eslint/no-floating-promises", "Await promises."),
|
|
683
700
|
* },
|
|
@@ -686,15 +703,21 @@ export type OkOf<T> = T extends TypedOutcome<infer Ok, Shape> ? Ok : Shape;
|
|
|
686
703
|
* Generic over a tool `Vocabulary` (default `OpenToolVocabulary` — no
|
|
687
704
|
* constraint). A harness adapter re-exports a vocabulary-bound `agent` (e.g.
|
|
688
705
|
* `vigiles/claude-code`) so `purity: "pure"` + a side-effecting tool is a `tsc`
|
|
689
|
-
* error at edit time; the bare core `
|
|
706
|
+
* error at edit time; the bare core `experimental_agent()` accepts any tools, as before.
|
|
690
707
|
*
|
|
691
708
|
* Also generic over the result's `Ok`/`Err` shapes, inferred from `output:
|
|
692
709
|
* result(...)`. The returned value is a `TypedAgentSpec<Ok, Err>` — an
|
|
693
710
|
* `AgentSpec` that carries those shapes at the type level, so a typed `pipe`
|
|
694
711
|
* can cross-reference the handoff. With no `output` the shapes default to the
|
|
695
712
|
* erased `Shape`, and the value is still a plain `AgentSpec` — backwards-compatible.
|
|
713
|
+
*
|
|
714
|
+
* @experimental The SHAPE is not settled — the author is unsure of the design,
|
|
715
|
+
* which is exactly what this marker promises: the form may change. It is NOT a
|
|
716
|
+
* claim that the surface is unproven. Measured 2026-06-20: real Claude Code
|
|
717
|
+
* loaded a compiled `agents/code-reviewer.md`, dispatched to it and read it,
|
|
718
|
+
* 100% of trials — stronger end-to-end evidence than `experimental_skill` has.
|
|
696
719
|
*/
|
|
697
|
-
export declare function
|
|
720
|
+
export declare function experimental_agent<const P extends AuthoredPurity | undefined = undefined, V extends ToolVocabulary = OpenToolVocabulary, Ok extends Shape = Shape, Err extends Shape = Shape>(spec: AgentSpecInput<P, V, Ok, Err>): TypedAgentSpec<Ok, Err>;
|
|
698
721
|
/**
|
|
699
722
|
* The field types a result contract can declare (kept tiny + dependency-free).
|
|
700
723
|
*
|
|
@@ -848,7 +871,7 @@ export interface PipeStep<Needs extends Shape, Ok extends Shape, Err extends Sha
|
|
|
848
871
|
}
|
|
849
872
|
/**
|
|
850
873
|
* Pair a typed agent with the input it `needs` from the previous step. The first
|
|
851
|
-
* argument is an `
|
|
874
|
+
* argument is an `experimental_agent()` VALUE (which carries its `result()` shape); the
|
|
852
875
|
* second is the `needs(...)` input contract.
|
|
853
876
|
*
|
|
854
877
|
* pipeStep(implementer, needs({ plan: "string", files: "string[]" }))
|
|
@@ -1028,5 +1051,28 @@ export interface VigilesV2Config {
|
|
|
1028
1051
|
readonly linters?: Record<string, LinterMode>;
|
|
1029
1052
|
}
|
|
1030
1053
|
export declare function defineConfig(config: VigilesV2Config): VigilesV2Config;
|
|
1054
|
+
/**
|
|
1055
|
+
* @deprecated Renamed to {@link instructionFile}. The builder compiles to
|
|
1056
|
+
* `CLAUDE.md` **and** `AGENTS.md` (see `InstructionTarget`), so a name taken from
|
|
1057
|
+
* one of the two harnesses was never right. Removed one major AFTER the one that introduces it.
|
|
1058
|
+
*/
|
|
1059
|
+
export declare const claude: typeof instructionFile;
|
|
1060
|
+
/**
|
|
1061
|
+
* @deprecated Renamed to {@link prose}. It builds a prose FRAGMENT with typed
|
|
1062
|
+
* refs; the plural read as "the instruction file", which is what
|
|
1063
|
+
* {@link instructionFile} builds. Removed one major AFTER the one that introduces it.
|
|
1064
|
+
*/
|
|
1065
|
+
export declare const instructions: typeof prose;
|
|
1066
|
+
/**
|
|
1067
|
+
* @deprecated Renamed to {@link experimental_agent} — the shape is not settled.
|
|
1068
|
+
* Removed one major AFTER the one that introduces it.
|
|
1069
|
+
*
|
|
1070
|
+
* @experimental
|
|
1071
|
+
* vigiles:experimental-name-ok this IS the old spelling — prefixing a deprecated
|
|
1072
|
+
* alias would defeat the alias, which exists precisely so code written against
|
|
1073
|
+
* the unprefixed name keeps compiling for one major. It carries the tag because
|
|
1074
|
+
* it is the same function, and the tag is what the deprecation notice points at.
|
|
1075
|
+
*/
|
|
1076
|
+
export declare const agent: typeof experimental_agent;
|
|
1031
1077
|
export {};
|
|
1032
1078
|
//# sourceMappingURL=spec.d.ts.map
|
package/dist/core/spec.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* guidance() — prose only, no mechanical enforcement
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.experimental_skill = exports.BUILTIN_LINTERS = void 0;
|
|
13
|
+
exports.agent = exports.instructions = exports.claude = exports.experimental_skill = exports.BUILTIN_LINTERS = void 0;
|
|
14
14
|
exports.enforce = enforce;
|
|
15
15
|
exports.guidance = guidance;
|
|
16
16
|
exports.guard = guard;
|
|
@@ -20,11 +20,11 @@ exports.symbol = symbol;
|
|
|
20
20
|
exports.ref = ref;
|
|
21
21
|
exports.dir = dir;
|
|
22
22
|
exports.glob = glob;
|
|
23
|
-
exports.
|
|
23
|
+
exports.prose = prose;
|
|
24
24
|
exports.experimental_effect = experimental_effect;
|
|
25
|
-
exports.
|
|
25
|
+
exports.instructionFile = instructionFile;
|
|
26
26
|
exports.project = project;
|
|
27
|
-
exports.
|
|
27
|
+
exports.experimental_agent = experimental_agent;
|
|
28
28
|
exports.result = result;
|
|
29
29
|
exports.delegate = delegate;
|
|
30
30
|
exports.railway = railway;
|
|
@@ -37,6 +37,7 @@ exports.defineConfig = defineConfig;
|
|
|
37
37
|
// ---------------------------------------------------------------------------
|
|
38
38
|
// Template literal types for type-safe linter references
|
|
39
39
|
// ---------------------------------------------------------------------------
|
|
40
|
+
const skill_normalize_js_1 = require("./skill-normalize.js");
|
|
40
41
|
/** Linters and policy catalogs vigiles can cross-reference. */
|
|
41
42
|
/**
|
|
42
43
|
* The built-in linter / policy catalogs vigiles cross-references — the SINGLE
|
|
@@ -156,13 +157,13 @@ function glob(pattern) {
|
|
|
156
157
|
/**
|
|
157
158
|
* Tagged template literal for skill instructions with typed references.
|
|
158
159
|
*
|
|
159
|
-
*
|
|
160
|
+
* prose`
|
|
160
161
|
* Check ${file("eslint.config.ts")} for rules.
|
|
161
162
|
* Run ${cmd("npm test")} to verify.
|
|
162
163
|
* See ${ref("skills/other/SKILL.md")} for format.
|
|
163
164
|
* `
|
|
164
165
|
*/
|
|
165
|
-
function
|
|
166
|
+
function prose(strings, ...values) {
|
|
166
167
|
const result = [];
|
|
167
168
|
for (let i = 0; i < strings.length; i++) {
|
|
168
169
|
if (strings[i])
|
|
@@ -176,7 +177,7 @@ function instructions(strings, ...values) {
|
|
|
176
177
|
* Tagged template literal marking a side-effect boundary — usable as an
|
|
177
178
|
* interpolated fragment inside a body / `instructions\`\``:
|
|
178
179
|
*
|
|
179
|
-
*
|
|
180
|
+
* prose`
|
|
180
181
|
* ## Apply
|
|
181
182
|
* ${effect`
|
|
182
183
|
* Side effects are allowed ONLY here:
|
|
@@ -206,9 +207,9 @@ function experimental_effect(strings, ...values) {
|
|
|
206
207
|
* Define a CLAUDE.md specification.
|
|
207
208
|
*
|
|
208
209
|
* // CLAUDE.md.spec.ts
|
|
209
|
-
* export default
|
|
210
|
+
* export default instructionFile({ commands: {...}, rules: {...} });
|
|
210
211
|
*/
|
|
211
|
-
function
|
|
212
|
+
function instructionFile(spec) {
|
|
212
213
|
return { _specType: "claude", ...spec };
|
|
213
214
|
}
|
|
214
215
|
/**
|
|
@@ -262,7 +263,7 @@ function step(instr, opts = {}) {
|
|
|
262
263
|
* export default experimental_skill({ name: "my-skill", description: "…" });
|
|
263
264
|
*
|
|
264
265
|
* Generic over a tool `Vocabulary` (default `OpenToolVocabulary` — no
|
|
265
|
-
* constraint), exactly like `
|
|
266
|
+
* constraint), exactly like `experimental_agent()`: a vocabulary-bound `experimental_skill`
|
|
266
267
|
* (e.g. `vigiles/claude-code`) makes `purity: "pure"` + a side-effecting tool a
|
|
267
268
|
* `tsc` error; the bare core one accepts any tools, as before.
|
|
268
269
|
*
|
|
@@ -289,7 +290,11 @@ function step(instr, opts = {}) {
|
|
|
289
290
|
* @experimental
|
|
290
291
|
*/
|
|
291
292
|
function skillSpec(spec) {
|
|
292
|
-
|
|
293
|
+
// The deprecated `result:` is folded into `postcondition:` by the shared
|
|
294
|
+
// helper — see `skill-normalize.ts` for why it is shared rather than inlined
|
|
295
|
+
// here (short version: this builder is not the only public entrance, and the
|
|
296
|
+
// other one silently dropped the gate until a reviewer noticed).
|
|
297
|
+
return (0, skill_normalize_js_1.foldLegacyPostcondition)({ _specType: "skill", ...spec });
|
|
293
298
|
}
|
|
294
299
|
/**
|
|
295
300
|
* @experimental
|
|
@@ -299,12 +304,12 @@ exports.experimental_skill = Object.assign(skillSpec, { input, step });
|
|
|
299
304
|
* Define a subagent specification (compiles to `agents/<name>.md`).
|
|
300
305
|
*
|
|
301
306
|
* // agents/reviewer.md.spec.ts
|
|
302
|
-
* export default
|
|
307
|
+
* export default experimental_agent({
|
|
303
308
|
* name: "reviewer",
|
|
304
309
|
* description: "Review a diff for correctness. Dispatch PROACTIVELY after edits.",
|
|
305
310
|
* model: "sonnet",
|
|
306
311
|
* tools: ["Read", "Grep", "Bash"],
|
|
307
|
-
* body:
|
|
312
|
+
* body: prose`Review the diff. Run ${cmd("npm test")} first.`,
|
|
308
313
|
* rules: {
|
|
309
314
|
* "no-floating": enforce("@typescript-eslint/no-floating-promises", "Await promises."),
|
|
310
315
|
* },
|
|
@@ -313,15 +318,21 @@ exports.experimental_skill = Object.assign(skillSpec, { input, step });
|
|
|
313
318
|
* Generic over a tool `Vocabulary` (default `OpenToolVocabulary` — no
|
|
314
319
|
* constraint). A harness adapter re-exports a vocabulary-bound `agent` (e.g.
|
|
315
320
|
* `vigiles/claude-code`) so `purity: "pure"` + a side-effecting tool is a `tsc`
|
|
316
|
-
* error at edit time; the bare core `
|
|
321
|
+
* error at edit time; the bare core `experimental_agent()` accepts any tools, as before.
|
|
317
322
|
*
|
|
318
323
|
* Also generic over the result's `Ok`/`Err` shapes, inferred from `output:
|
|
319
324
|
* result(...)`. The returned value is a `TypedAgentSpec<Ok, Err>` — an
|
|
320
325
|
* `AgentSpec` that carries those shapes at the type level, so a typed `pipe`
|
|
321
326
|
* can cross-reference the handoff. With no `output` the shapes default to the
|
|
322
327
|
* erased `Shape`, and the value is still a plain `AgentSpec` — backwards-compatible.
|
|
328
|
+
*
|
|
329
|
+
* @experimental The SHAPE is not settled — the author is unsure of the design,
|
|
330
|
+
* which is exactly what this marker promises: the form may change. It is NOT a
|
|
331
|
+
* claim that the surface is unproven. Measured 2026-06-20: real Claude Code
|
|
332
|
+
* loaded a compiled `agents/code-reviewer.md`, dispatched to it and read it,
|
|
333
|
+
* 100% of trials — stronger end-to-end evidence than `experimental_skill` has.
|
|
323
334
|
*/
|
|
324
|
-
function
|
|
335
|
+
function experimental_agent(spec) {
|
|
325
336
|
return { _specType: "agent", ...spec };
|
|
326
337
|
}
|
|
327
338
|
/**
|
|
@@ -392,7 +403,7 @@ function experimental_needs(shape) {
|
|
|
392
403
|
}
|
|
393
404
|
/**
|
|
394
405
|
* Pair a typed agent with the input it `needs` from the previous step. The first
|
|
395
|
-
* argument is an `
|
|
406
|
+
* argument is an `experimental_agent()` VALUE (which carries its `result()` shape); the
|
|
396
407
|
* second is the `needs(...)` input contract.
|
|
397
408
|
*
|
|
398
409
|
* pipeStep(implementer, needs({ plan: "string", files: "string[]" }))
|
|
@@ -468,4 +479,47 @@ function experimental_pipe(first, ...rest) {
|
|
|
468
479
|
function defineConfig(config) {
|
|
469
480
|
return config;
|
|
470
481
|
}
|
|
482
|
+
// ─── ОКНО АЛИАСА (один мажор) ──────────────────────────────────────────────────
|
|
483
|
+
// Старые имена остаются рабочими ровно один мажорный релиз, помеченные
|
|
484
|
+
// `@deprecated`, и убираются в следующем.
|
|
485
|
+
//
|
|
486
|
+
// 🔴 ПОЧЕМУ ЭТО НЕ ВЕЖЛИВОСТЬ, А НЕОБХОДИМОСТЬ, замерено 2026-08-21: предыдущее
|
|
487
|
+
// переименование ушло БЕЗ окна — 18.1.1 экспортировал только старые имена,
|
|
488
|
+
// 19.0.0 только новые, пересечения ноль. У потребителя (репа знаний, 12
|
|
489
|
+
// скомпилированных хуков) откат контейнера вернул старый `node_modules` под
|
|
490
|
+
// новые исходники, `PreToolUse` перестал загружаться, а не загрузившийся
|
|
491
|
+
// PreToolUse отбивает ЛЮБУЮ Bash-команду — включая ту, которой это чинится.
|
|
492
|
+
// Репа встала колом на час. Одно окно в один мажор делает этот отказ
|
|
493
|
+
// невыразимым: любая пара (лок, исходники) в пределах мажора совместима.
|
|
494
|
+
//
|
|
495
|
+
// 🔴 «ОДИН МАЖОР» СЧИТАЕТСЯ ОТ ТОГО, КОТОРЫЙ АЛИАСЫ ВВОДИТ, а не до него.
|
|
496
|
+
// Мажор, выпускающий этот PR, — ПЕРВЫЙ, где старые и новые имена сосуществуют;
|
|
497
|
+
// именно он и есть обещанное окно. Убирать алиасы можно в СЛЕДУЮЩЕМ за ним.
|
|
498
|
+
// Формулировка «Removed next major» была двусмысленной ровно здесь (её так и
|
|
499
|
+
// прочитал ревьюер: как «удалить в том же релизе, который их вводит»), и по
|
|
500
|
+
// такому расписанию окна не существовало бы вовсе — то есть отказ выше
|
|
501
|
+
// воспроизвёлся бы дословно, при живом абзаце, который его запрещает.
|
|
502
|
+
/**
|
|
503
|
+
* @deprecated Renamed to {@link instructionFile}. The builder compiles to
|
|
504
|
+
* `CLAUDE.md` **and** `AGENTS.md` (see `InstructionTarget`), so a name taken from
|
|
505
|
+
* one of the two harnesses was never right. Removed one major AFTER the one that introduces it.
|
|
506
|
+
*/
|
|
507
|
+
exports.claude = instructionFile;
|
|
508
|
+
/**
|
|
509
|
+
* @deprecated Renamed to {@link prose}. It builds a prose FRAGMENT with typed
|
|
510
|
+
* refs; the plural read as "the instruction file", which is what
|
|
511
|
+
* {@link instructionFile} builds. Removed one major AFTER the one that introduces it.
|
|
512
|
+
*/
|
|
513
|
+
exports.instructions = prose;
|
|
514
|
+
/**
|
|
515
|
+
* @deprecated Renamed to {@link experimental_agent} — the shape is not settled.
|
|
516
|
+
* Removed one major AFTER the one that introduces it.
|
|
517
|
+
*
|
|
518
|
+
* @experimental
|
|
519
|
+
* vigiles:experimental-name-ok this IS the old spelling — prefixing a deprecated
|
|
520
|
+
* alias would defeat the alias, which exists precisely so code written against
|
|
521
|
+
* the unprefixed name keeps compiling for one major. It carries the tag because
|
|
522
|
+
* it is the same function, and the tag is what the deprecation notice points at.
|
|
523
|
+
*/
|
|
524
|
+
exports.agent = experimental_agent;
|
|
471
525
|
//# sourceMappingURL=spec.js.map
|
package/dist/eval-surface.d.ts
CHANGED
|
@@ -13,9 +13,14 @@
|
|
|
13
13
|
* The import path warns ONCE, at the top of the file. The name warns EVERY time,
|
|
14
14
|
* at the call site. Reading `await judged(trace, "did it refuse?")` on line 140,
|
|
15
15
|
* the import line is long out of view — `await paid_judged(...)` still says what
|
|
16
|
-
* it costs. This is not a new idiom in this package: `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* it costs. This is not a new idiom in this package: the `experimental_` prefix
|
|
17
|
+
* says the same kind of thing on a second axis, at the same place.
|
|
18
|
+
*
|
|
19
|
+
* That comparison used to read "`vigiles/experimental` already pairs a
|
|
20
|
+
* quarantined subpath WITH a name prefix". The subpath was deleted 2026-08-21
|
|
21
|
+
* and the prefix kept, on the argument this paragraph makes: of the two, only
|
|
22
|
+
* the name is present where the reader is. Which is also why THIS surface has
|
|
23
|
+
* no `vigiles/paid` subpath and never needed one.
|
|
19
24
|
*
|
|
20
25
|
* ⚠️ **The prefix slightly OVERSTATES the cost, and that is a deliberate trade
|
|
21
26
|
* rather than an oversight.** `paid_judged` takes an injectable judge:
|
package/dist/eval-surface.js
CHANGED
|
@@ -14,9 +14,14 @@
|
|
|
14
14
|
* The import path warns ONCE, at the top of the file. The name warns EVERY time,
|
|
15
15
|
* at the call site. Reading `await judged(trace, "did it refuse?")` on line 140,
|
|
16
16
|
* the import line is long out of view — `await paid_judged(...)` still says what
|
|
17
|
-
* it costs. This is not a new idiom in this package: `
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* it costs. This is not a new idiom in this package: the `experimental_` prefix
|
|
18
|
+
* says the same kind of thing on a second axis, at the same place.
|
|
19
|
+
*
|
|
20
|
+
* That comparison used to read "`vigiles/experimental` already pairs a
|
|
21
|
+
* quarantined subpath WITH a name prefix". The subpath was deleted 2026-08-21
|
|
22
|
+
* and the prefix kept, on the argument this paragraph makes: of the two, only
|
|
23
|
+
* the name is present where the reader is. Which is also why THIS surface has
|
|
24
|
+
* no `vigiles/paid` subpath and never needed one.
|
|
20
25
|
*
|
|
21
26
|
* ⚠️ **The prefix slightly OVERSTATES the cost, and that is a deliberate trade
|
|
22
27
|
* rather than an oversight.** `paid_judged` takes an injectable judge:
|
package/dist/harness-assert.js
CHANGED
|
@@ -176,7 +176,7 @@ function assertHookAllowed(r) {
|
|
|
176
176
|
* in-process rather than loaded from disk has no file to name, so nothing is
|
|
177
177
|
* recorded and nothing is invented:
|
|
178
178
|
*
|
|
179
|
-
* const h =
|
|
179
|
+
* const h = experimental_defineHook({…}); assertHookDenies(h, e); → surfacesRecorded() === []
|
|
180
180
|
*
|
|
181
181
|
* …and a direct `runHookProgram(hook, event)` call (the pure evaluator, public
|
|
182
182
|
* via `vigiles/hook`) records nothing either, for the reason above. Both cost a
|
package/dist/hook-install.js
CHANGED
|
@@ -83,9 +83,41 @@ function normalizeHookRef(hookPath, cwd = process.cwd()) {
|
|
|
83
83
|
*/
|
|
84
84
|
function managesHook(entry, hookPath) {
|
|
85
85
|
const ref = normalizeHookRef(hookPath);
|
|
86
|
-
return entry.hooks.some((h) => h.command
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
return entry.hooks.some((h) => h.command.split(/\s+/).some((token) => {
|
|
87
|
+
const bare = bareToken(token);
|
|
88
|
+
return bare !== "" && normalizeHookRef(bare) === ref;
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A command token reduced to the PATH it names, so two spellings of the same
|
|
93
|
+
* hook file compare equal.
|
|
94
|
+
*
|
|
95
|
+
* 🔴 BOTH STRIPS ARE REGRESSIONS, MEASURED IN A CONSUMER REPO 2026-08-21, and
|
|
96
|
+
* they compound: a settings.json wired the Claude-Code-recommended way carries
|
|
97
|
+
* BOTH a quote and the project-dir variable, so `managesHook` saw
|
|
98
|
+
* `"$CLAUDE_PROJECT_DIR/.claude/hooks/x.hook.ts"`, canonicalized it to
|
|
99
|
+
* something under the cwd that resembles nothing, and reported "not managed by
|
|
100
|
+
* this hook". Recompiling then APPENDED its own block beside the existing one.
|
|
101
|
+
* Twelve hooks, twelve duplicates, and the duplicate is the WORSE of the two:
|
|
102
|
+
* it spells the path relative to the cwd, so it dies with exit 2 the moment the
|
|
103
|
+
* agent runs from a subdirectory — while the healthy copy beside it keeps
|
|
104
|
+
* working, which is why this survived a full day unnoticed.
|
|
105
|
+
*
|
|
106
|
+
* - QUOTES. A path with a space MUST be quoted, so the quoted form is not an
|
|
107
|
+
* exotic spelling — it is the correct one. Comparing it raw could never match.
|
|
108
|
+
* - `$CLAUDE_PROJECT_DIR`. It is defined as the project root, which is exactly
|
|
109
|
+
* what `normalizeHookRef` resolves relative paths against, so stripping the
|
|
110
|
+
* prefix makes the two spellings the same path by definition rather than by
|
|
111
|
+
* guess. `${CLAUDE_PROJECT_DIR}` is the same variable in brace syntax.
|
|
112
|
+
*
|
|
113
|
+
* Nothing else is stripped. A token this function does not recognise is left
|
|
114
|
+
* alone and simply fails to match, which is the pre-existing behaviour: the
|
|
115
|
+
* cost of a miss here is a duplicate block, and the cost of an over-match is
|
|
116
|
+
* deleting a hook the user wrote themselves.
|
|
117
|
+
*/
|
|
118
|
+
function bareToken(token) {
|
|
119
|
+
const unquoted = token.replace(/^["']/, "").replace(/["']$/, "");
|
|
120
|
+
return unquoted.replace(/^\$\{?CLAUDE_PROJECT_DIR\}?[/\\]/, "");
|
|
89
121
|
}
|
|
90
122
|
/**
|
|
91
123
|
* Idempotently merge a compiled hook's block into an existing `settings.json`
|
package/dist/hook.d.ts
CHANGED
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
*
|
|
12
12
|
* The roles, each with its own output type so a category mistake is a `tsc`
|
|
13
13
|
* error, not a silent no-op:
|
|
14
|
-
* - `
|
|
14
|
+
* - `experimental_defineHook` / `experimental_defineFileGate` — a **gate** returns a `Decision`
|
|
15
15
|
* (`allow`/`deny`/`ask`); `deny` is the only thing that blocks.
|
|
16
|
-
* - `
|
|
16
|
+
* - `experimental_definePromptGate` — a **prompt gate** (UserPromptSubmit) sees the prompt
|
|
17
17
|
* TEXT and may `deny` to block it (a security filter).
|
|
18
|
-
* - `
|
|
18
|
+
* - `experimental_defineStopGate` — a **stop gate** (Stop/SubagentStop) may `deny` to keep
|
|
19
19
|
* the agent going (gate-until-tests-pass).
|
|
20
|
-
* - `
|
|
20
|
+
* - `experimental_defineInject` — an **inject** returns an `Injection` (context text); it
|
|
21
21
|
* has no `deny`, so "block on a SessionStart hook" won't compile.
|
|
22
|
-
* - `
|
|
22
|
+
* - `experimental_defineReact` — a **react** (PostToolUse) returns a `Reaction`; it sees the
|
|
23
23
|
* tool RESPONSE, its `run(cmd)` is effect-classified at construction, and it
|
|
24
24
|
* can't block (the tool already ran).
|
|
25
25
|
*
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* tool calls. A gate is a strong default, never an unbypassable wall. See
|
|
46
46
|
* `docs/compiled-hooks.md`.
|
|
47
47
|
*/
|
|
48
|
-
export {
|
|
48
|
+
export { experimental_defineHook, experimental_defineFileGate, experimental_definePromptGate, experimental_defineStopGate, tool, tools, allow, deny, ask, commandView, pathView, gateAction, hookMode, experimental_defineInject, inject, experimental_defineReact, run, notice, nothing, responseView, decideProgram, decideFileGate, decidePromptGate, decideStopGate, runInject, runReact, runHookProgram, decisionExitCode, dispatchKind, hookRouting, hookNeeds, injectionOf, outcomeWrites, matchesTool, invalidToolPatterns, compileHookProgram, checkHookImports, stampHook, verifyHookStamp, HookCompileError, } from "./core/hook-program.js";
|
|
49
49
|
export type { Decision, HookMode, GateAction, CommandView, PathView, ResponseView, BashToolEvent, FileToolEvent, PromptEvent, StopEvent, ReactEvent, SessionEvent, HookProgram, FileGateHook, PromptGateHook, StopGateHook, InjectHook, ReactHook, AnyHook, DispatchKind, Injection, Reaction, RunReaction, CompiledHookProgram, CompileHookOptions, RawHookEvent, HookProgramOutcome, } from "./core/hook-program.js";
|
|
50
50
|
export { provide, dangerously, defineProvider, provider, } from "./core/hook-providers.js";
|
|
51
51
|
export { state, record, stateFact, isValidStateKey, isStateNeed, isStateWrite, admissibleWrites, durationSeconds, HookStateError, } from "./core/hook-state.js";
|