vigiles 5.2.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +99 -48
  2. package/dist/action-gate.js +1 -1
  3. package/dist/adapters/claude-code/agent-runtime.d.ts +64 -4
  4. package/dist/adapters/claude-code/agent-runtime.js +131 -17
  5. package/dist/adapters/claude-code/dialect.d.ts +34 -0
  6. package/dist/adapters/claude-code/dialect.js +46 -33
  7. package/dist/adapters/claude-code/effect-region.js +1 -1
  8. package/dist/adapters/claude-code/skill-runtime.d.ts +1 -1
  9. package/dist/adapters/claude-code/skill-runtime.js +1 -9
  10. package/dist/adapters/claude-code/typed-spec.d.ts +58 -0
  11. package/dist/adapters/claude-code/typed-spec.js +55 -0
  12. package/dist/adapters/codex/hook-protocol.js +3 -0
  13. package/dist/adapters/codex/mock-model.js +1 -1
  14. package/dist/claude-code.d.ts +1 -0
  15. package/dist/claude-code.js +8 -1
  16. package/dist/cli-commands.d.ts +19 -0
  17. package/dist/cli-commands.js +51 -0
  18. package/dist/cli.js +735 -76
  19. package/dist/core/bash-effects.d.ts +12 -0
  20. package/dist/core/bash-effects.js +31 -0
  21. package/dist/core/capability-diff.d.ts +46 -0
  22. package/dist/core/capability-diff.js +97 -0
  23. package/dist/core/compile.d.ts +1 -1
  24. package/dist/core/compile.js +14 -0
  25. package/dist/core/generate-harness.d.ts +187 -0
  26. package/dist/core/generate-harness.js +337 -0
  27. package/dist/core/guards.d.ts +126 -0
  28. package/dist/core/guards.js +309 -0
  29. package/dist/core/harness-driver.d.ts +1 -1
  30. package/dist/core/hook-program.d.ts +459 -0
  31. package/dist/core/hook-program.js +468 -0
  32. package/dist/core/hook-protocol.d.ts +7 -0
  33. package/dist/core/hook-providers.d.ts +138 -0
  34. package/dist/core/hook-providers.js +155 -0
  35. package/dist/core/hook-spec.d.ts +74 -0
  36. package/dist/core/hook-spec.js +130 -0
  37. package/dist/core/inline.js +1 -1
  38. package/dist/core/mcp-tool.d.ts +12 -0
  39. package/dist/core/mcp-tool.js +20 -0
  40. package/dist/core/mcp.d.ts +13 -0
  41. package/dist/core/mcp.js +67 -0
  42. package/dist/core/spec.d.ts +290 -8
  43. package/dist/core/spec.js +118 -3
  44. package/dist/core/types.d.ts +8 -0
  45. package/dist/dialect-drift.d.ts +65 -0
  46. package/dist/dialect-drift.js +216 -0
  47. package/dist/eval.d.ts +40 -5
  48. package/dist/eval.js +59 -5
  49. package/dist/guardrail-check.d.ts +85 -0
  50. package/dist/guardrail-check.js +152 -0
  51. package/dist/harness-assert.d.ts +10 -0
  52. package/dist/harness-assert.js +30 -0
  53. package/dist/hook-install.d.ts +43 -0
  54. package/dist/hook-install.js +91 -0
  55. package/dist/hook.d.ts +52 -0
  56. package/dist/hook.js +98 -0
  57. package/dist/leaderboard.d.ts +6 -0
  58. package/dist/leaderboard.js +43 -1
  59. package/dist/linting.d.ts +9 -5
  60. package/dist/linting.js +17 -5
  61. package/dist/optimize.js +1 -1
  62. package/dist/scaffold-test.d.ts +28 -0
  63. package/dist/scaffold-test.js +134 -15
  64. package/dist/scan-behavioral.d.ts +60 -0
  65. package/dist/scan-behavioral.js +239 -1
  66. package/dist/scan.d.ts +14 -0
  67. package/dist/scan.js +33 -1
  68. package/dist/score-explainer.js +1 -1
  69. package/dist/self-command-refs.d.ts +21 -0
  70. package/dist/self-command-refs.js +125 -0
  71. package/dist/testing.d.ts +5 -3
  72. package/dist/testing.js +37 -23
  73. package/dist/tool-intercept.d.ts +4 -4
  74. package/dist/tool-intercept.js +5 -5
  75. package/dist/unit.d.ts +2 -0
  76. package/dist/unit.js +8 -1
  77. package/hooks/refs-nudge.sh +1 -1
  78. package/package.json +5 -3
@@ -50,6 +50,42 @@ export type StrictLinterRule = [keyof KnownLinterRules] extends [never] ? Linter
50
50
  */
51
51
  export type StrictFile = [keyof KnownProjectFiles] extends [never] ? string : KnownProjectFiles[keyof KnownProjectFiles] & string;
52
52
  export type StrictCmd = [keyof KnownNpmScripts] extends [never] ? string : `npm run ${KnownNpmScripts[keyof KnownNpmScripts] & string}` | `npm ${KnownNpmScripts[keyof KnownNpmScripts] & string}` | (string & {});
53
+ /**
54
+ * A harness's tool vocabulary, split by the purity floor that admits it. The
55
+ * read-only/side-effecting split is harness-specific (it comes from the
56
+ * dialect's `builtinAgentTools` − `sideEffectingTools`), so the concrete unions
57
+ * are supplied by the adapter; core only describes the SHAPE.
58
+ *
59
+ * - `readOnly`: tools a `pure` unit may declare (observation only).
60
+ * - `bounded`: tools a `bounded` unit may declare — the read-only set PLUS the
61
+ * decidable side-effecting tools (Write/Edit/NotebookEdit) AND `Bash` (its
62
+ * command is decided at RUNTIME by the gate; the floor admits the tool). Bars
63
+ * MCP / unknown / wildcard.
64
+ *
65
+ * The default (`string` at both) imposes no constraint — any tool is accepted at
66
+ * every level, the historical behaviour of an untyped `agent()`/`skill()`.
67
+ */
68
+ export interface ToolVocabulary {
69
+ /** Union of tool names allowed under `purity: "pure"`. */
70
+ readonly readOnly: string;
71
+ /** Union of tool names allowed under `purity: "bounded"`. */
72
+ readonly bounded: string;
73
+ }
74
+ /** The fully-open default vocabulary — no constraint at any purity level. */
75
+ export interface OpenToolVocabulary extends ToolVocabulary {
76
+ readonly readOnly: string;
77
+ readonly bounded: string;
78
+ }
79
+ /**
80
+ * The tool names ALLOWED at a declared `purity`, given a vocabulary `V`:
81
+ * - `"pure"` → only `V["readOnly"]`.
82
+ * - `"bounded"` → `V["bounded"]` (read-only ∪ decidable side-effecting ∪ `Bash`).
83
+ * - `"dangerously-unrestricted"` (or no purity) → `string` (anything).
84
+ *
85
+ * With the open default vocabulary every branch widens to `string`, so an
86
+ * untyped surface accepts any tools.
87
+ */
88
+ export type AllowedAt<P extends AuthoredPurity | undefined, V extends ToolVocabulary> = P extends "pure" ? V["readOnly"] : P extends "bounded" ? V["bounded"] : string;
53
89
  export type ClaudeTool = "Read" | "Write" | "Edit" | "Bash" | "Grep" | "Glob" | "Agent" | "TodoWrite" | "WebSearch" | "WebFetch" | "NotebookEdit";
54
90
  export type HookEvent = "PreToolUse" | "PostToolUse" | "PreSession" | "PostSession" | "Notification";
55
91
  /** A rule delegated to an external tool (linter, ast-grep, dependency-cruiser, etc.) or to a vigiles-internal check. */
@@ -430,13 +466,27 @@ export interface SkillSpec {
430
466
  */
431
467
  readonly maxInlineCodeLines?: number;
432
468
  }
469
+ /**
470
+ * The input to `skill()` — `SkillSpec` minus `_specType`, with `tools`
471
+ * constrained by the declared `purity` and vocabulary `V` (same mechanism as
472
+ * `AgentSpecInput`). The open default vocabulary leaves it unconstrained.
473
+ */
474
+ export type SkillSpecInput<P extends AuthoredPurity | undefined, V extends ToolVocabulary> = Omit<SkillSpec, "_specType" | "tools" | "purity"> & {
475
+ readonly purity?: P;
476
+ readonly tools?: readonly AllowedAt<P, V>[];
477
+ };
433
478
  /**
434
479
  * Define a SKILL.md specification.
435
480
  *
436
481
  * // skills/my-skill/SKILL.md.spec.ts
437
482
  * export default skill({ name: "my-skill", description: "...", body: "..." });
483
+ *
484
+ * Generic over a tool `Vocabulary` (default `OpenToolVocabulary` — no
485
+ * constraint), exactly like `agent()`: a vocabulary-bound `skill` (e.g.
486
+ * `vigiles/claude-code`) makes `purity: "pure"` + a side-effecting tool a `tsc`
487
+ * error; the bare core `skill()` accepts any tools, as before.
438
488
  */
439
- export declare function skill(spec: Omit<SkillSpec, "_specType">): SkillSpec;
489
+ export declare function skill<const P extends AuthoredPurity | undefined = undefined, V extends ToolVocabulary = OpenToolVocabulary>(spec: SkillSpecInput<P, V>): SkillSpec;
440
490
  /**
441
491
  * A subagent definition (compiles to `agents/<name>.md`). Unlike a skill —
442
492
  * reference material the model reads on activation — a subagent is a *delegated
@@ -507,6 +557,46 @@ export interface AgentSpec {
507
557
  */
508
558
  readonly purity?: AuthoredPurity;
509
559
  }
560
+ /**
561
+ * The input to `agent()` — `AgentSpec` minus the internal `_specType`, with the
562
+ * `tools` list constrained by the declared `purity` and the tool vocabulary `V`.
563
+ * `P` is inferred from the literal `purity` field (`const` inference), and
564
+ * `tools` is then typed `AllowedAt<P, V>[]`:
565
+ * - `purity: "pure"` → `tools` may list only `V["readOnly"]` tools.
566
+ * - `purity: "bounded"`→ `tools` may list `V["bounded"]` tools (admits `Bash`).
567
+ * - no `purity` / `"dangerously-unrestricted"` → `tools` is `string[]` (open).
568
+ *
569
+ * With the open default vocabulary (core `agent()`) every level widens to
570
+ * `string`, so any tools compile — backwards-compatible.
571
+ */
572
+ export type AgentSpecInput<P extends AuthoredPurity | undefined, V extends ToolVocabulary, Ok extends Shape = Shape, Err extends Shape = Shape> = Omit<AgentSpec, "_specType" | "tools" | "purity" | "output"> & {
573
+ readonly purity?: P;
574
+ readonly tools?: readonly AllowedAt<P, V>[];
575
+ /** The typed result contract — `result(ok, err)`. Its literal field shapes are
576
+ * captured into the returned `TypedAgentSpec` so a typed `pipe` can check them. */
577
+ readonly output?: OutputContract<Ok, Err>;
578
+ };
579
+ declare const __outcome: unique symbol;
580
+ /** Phantom carrier of an agent's success/error result shapes (type-level only). */
581
+ export interface TypedOutcome<Ok extends Shape, Err extends Shape> {
582
+ readonly [__outcome]: {
583
+ readonly ok: Ok;
584
+ readonly err: Err;
585
+ };
586
+ }
587
+ /** An `AgentSpec` that REMEMBERS its `result()` ok/err shapes at the type level
588
+ * (via a phantom field). Still an `AgentSpec`, so it flows everywhere one does.
589
+ * The input to a typed `pipe` / `then`. */
590
+ export type TypedAgentSpec<Ok extends Shape, Err extends Shape> = AgentSpec & TypedOutcome<Ok, Err>;
591
+ /**
592
+ * Extract a typed agent's success (`result().ok`) SHAPE at the type level. The
593
+ * phantom `__outcome` symbol is module-private, so this is the exported reader
594
+ * the whole-harness registry uses: `OkOf<typeof registry["planner"]>` is the
595
+ * literal `ok` shape `planner` produces, the producer side of a cross-file
596
+ * `Handoff<>` check. A plain `AgentSpec` (no `result()` contract) carries no
597
+ * phantom, so `OkOf` widens to the erased `Shape` — a no-op handoff, additive.
598
+ */
599
+ export type OkOf<T> = T extends TypedOutcome<infer Ok, Shape> ? Ok : Shape;
510
600
  /**
511
601
  * Define a subagent specification (compiles to `agents/<name>.md`).
512
602
  *
@@ -521,21 +611,41 @@ export interface AgentSpec {
521
611
  * "no-floating": enforce("@typescript-eslint/no-floating-promises", "Await promises."),
522
612
  * },
523
613
  * });
614
+ *
615
+ * Generic over a tool `Vocabulary` (default `OpenToolVocabulary` — no
616
+ * constraint). A harness adapter re-exports a vocabulary-bound `agent` (e.g.
617
+ * `vigiles/claude-code`) so `purity: "pure"` + a side-effecting tool is a `tsc`
618
+ * error at edit time; the bare core `agent()` accepts any tools, as before.
619
+ *
620
+ * Also generic over the result's `Ok`/`Err` shapes, inferred from `output:
621
+ * result(...)`. The returned value is a `TypedAgentSpec<Ok, Err>` — an
622
+ * `AgentSpec` that carries those shapes at the type level, so a typed `pipe`
623
+ * can cross-reference the handoff. With no `output` the shapes default to the
624
+ * erased `Shape`, and the value is still a plain `AgentSpec` — backwards-compatible.
524
625
  */
525
- export declare function agent(spec: Omit<AgentSpec, "_specType">): AgentSpec;
626
+ export declare function 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>;
526
627
  /** The field types a result contract can declare (kept tiny + dependency-free). */
527
628
  export type OutputFieldType = "string" | "number" | "boolean" | "string[]";
629
+ /** A field SHAPE — a record of field-name → field-type, kept in the TYPE so a
630
+ * typed pipeline can cross-reference one agent's `ok` against the next agent's
631
+ * `needs`. The erased runtime form is `Record<string, OutputFieldType>`. */
632
+ export type Shape = Readonly<Record<string, OutputFieldType>>;
528
633
  /**
529
634
  * A subagent's typed result contract: the shape it must return on success
530
635
  * (`ok`) and on failure (`err`). Rich on both tracks — an error is structured
531
636
  * detail, not a bare pass/fail bit. Compiles into the worker's system prompt
532
637
  * (the `vigiles:ok` / `vigiles:err` block it must emit) and is the schema the
533
638
  * `parseAgentResult` parser + the `assertAgentOk/Err` test helpers validate.
639
+ *
640
+ * Generic over its `ok`/`err` field shapes so a typed value REMEMBERS them at
641
+ * the type level (the basis of typed composition — see `pipe`). The default
642
+ * type parameters widen to the historical erased `Shape`, so an `OutputContract`
643
+ * named with no arguments behaves exactly as before — backwards-compatible.
534
644
  */
535
- export interface OutputContract {
645
+ export interface OutputContract<Ok extends Shape = Shape, Err extends Shape = Shape> {
536
646
  readonly _ref: "output";
537
- readonly ok: Readonly<Record<string, OutputFieldType>>;
538
- readonly err: Readonly<Record<string, OutputFieldType>>;
647
+ readonly ok: Ok;
648
+ readonly err: Err;
539
649
  }
540
650
  /**
541
651
  * Declare a subagent's success/error result contract.
@@ -547,8 +657,14 @@ export interface OutputContract {
547
657
  *
548
658
  * (Distinct from a skill's `result:` postcondition gate — this types a
549
659
  * subagent's *return value*, the success/error tracks of the railway.)
660
+ *
661
+ * The literal field shapes are PRESERVED in the return type (`const` inference),
662
+ * not erased to `Record<string, OutputFieldType>` — this is what lets `pipe`
663
+ * cross-reference one agent's `ok` against the next agent's needs at `tsc` time.
664
+ * The return is still an `OutputContract`, so every existing consumer (the
665
+ * `output:` field, `renderOutputContract`, `parseAgentResult`) is unchanged.
550
666
  */
551
- export declare function result(ok: Record<string, OutputFieldType>, err: Record<string, OutputFieldType>): OutputContract;
667
+ export declare function result<const Ok extends Shape, const Err extends Shape>(ok: Ok, err: Err): OutputContract<Ok, Err>;
552
668
  /** One step on a railway: dispatch a flat subagent (the "activity"). */
553
669
  export interface RailwayStep {
554
670
  readonly _step: "delegate";
@@ -556,9 +672,31 @@ export interface RailwayStep {
556
672
  readonly agent: string;
557
673
  /** Optional task hint passed to the worker. */
558
674
  readonly task?: string;
675
+ /**
676
+ * Optional input contract the step reads from its predecessor's `result().ok`.
677
+ * When present, the whole-harness registry (`generate-harness`) emits a
678
+ * per-edge `Handoff<>` assertion so a CROSS-FILE handoff mismatch (a missing
679
+ * field or wrong type vs the prior step's `ok`) is a `tsc` error naming the
680
+ * field. Absent `needs` = no handoff check (today's behavior, the string-path
681
+ * backstop). Built by `needs(...)`, the same builder a typed `pipeStep` uses.
682
+ */
683
+ readonly needs?: Shape;
559
684
  }
560
- /** Build a railway step that dispatches `agent` (optionally with a task hint). */
561
- export declare function delegate(agent: string, task?: string): RailwayStep;
685
+ /**
686
+ * Build a railway step that dispatches `agent`.
687
+ *
688
+ * delegate("planner") // no task, no handoff
689
+ * delegate("implementer", "implement the plan") // task hint only
690
+ * delegate("reviewer", undefined, needs({ diff: "string" })) // + handoff check
691
+ *
692
+ * The optional 3rd argument carries the step's input `needs` (built by
693
+ * `needs(...)`). When present, the whole-harness registry asserts that the
694
+ * PREVIOUS success-track step's `result().ok` SUPPLIES it — a cross-file
695
+ * handoff that doesn't line up is a `tsc` error naming the offending field.
696
+ * Omitting it (the historical 1-/2-arg call) keeps the exact string-path
697
+ * behavior — fully backwards-compatible.
698
+ */
699
+ export declare function delegate(agent: string, task?: string, needsContract?: Shape): RailwayStep;
562
700
  /**
563
701
  * A railway over flat subagents. `steps` run in order on the success track; the
564
702
  * first step that returns an error short-circuits to `onError`. `recover`
@@ -589,6 +727,150 @@ export interface Railway {
589
727
  * })
590
728
  */
591
729
  export declare function railway(spec: Omit<Railway, "_specType">): Railway;
730
+ /**
731
+ * A subagent's INPUT contract — the fields it reads from the prior step's `ok`.
732
+ * Declared via `needs(...)` and threaded into the typed agent so `pipe` can
733
+ * cross-reference it. Independent of `result()` (the output) — an agent both
734
+ * `needs` an input shape and produces an `ok`/`err` output shape.
735
+ */
736
+ export type NeedsContract<N extends Shape> = N;
737
+ /**
738
+ * Declare the input fields a step reads from its predecessor's success payload.
739
+ * Pass it as `needs:` on a typed pipeline step. `needs({})` (the default) is a
740
+ * step with no upstream requirement — valid as the FIRST step of a pipeline.
741
+ *
742
+ * needs({ plan: "string", files: "string[]" })
743
+ */
744
+ export declare function needs<const N extends Shape>(shape: N): NeedsContract<N>;
745
+ /**
746
+ * A typed pipeline step: a `TypedAgentSpec` paired with the input `needs` it
747
+ * reads from the prior step's `ok`. `step()` builds one; `pipe` checks that the
748
+ * prior step's `ok` shape supplies this step's `needs`.
749
+ */
750
+ export interface PipeStep<Needs extends Shape, Ok extends Shape, Err extends Shape> {
751
+ readonly _step: "typed-delegate";
752
+ readonly agent: TypedAgentSpec<Ok, Err>;
753
+ readonly needs: Needs;
754
+ }
755
+ /**
756
+ * Pair a typed agent with the input it `needs` from the previous step. The first
757
+ * argument is an `agent()` VALUE (which carries its `result()` shape); the
758
+ * second is the `needs(...)` input contract.
759
+ *
760
+ * pipeStep(implementer, needs({ plan: "string", files: "string[]" }))
761
+ */
762
+ export declare function pipeStep<Needs extends Shape, Ok extends Shape, Err extends Shape>(a: TypedAgentSpec<Ok, Err>, needsContract?: Needs): PipeStep<Needs, Ok, Err>;
763
+ /**
764
+ * True iff `Producer` provides EVERY field `Consumer` needs, with matching
765
+ * field types. When satisfiable it is `true`; otherwise it collapses to a
766
+ * descriptive error object naming the offending field (`__missing` /
767
+ * `__mismatch`), which surfaces at the mismatched call. Shallow (a per-field
768
+ * mapped type, not a recursion) to avoid TS2589.
769
+ */
770
+ export type Supplies<Producer extends Shape, Consumer extends Shape> = {
771
+ [K in keyof Consumer]: K extends keyof Producer ? Producer[K] extends Consumer[K] ? true : {
772
+ readonly __mismatch: K;
773
+ readonly expected: Consumer[K];
774
+ readonly got: Producer[K];
775
+ } : {
776
+ readonly __missing: K;
777
+ readonly required: Consumer[K];
778
+ };
779
+ }[keyof Consumer];
780
+ /**
781
+ * Per-edge CROSS-FILE handoff check — the registry-scale form of `Supplies<>`,
782
+ * mirroring `KnownAgentName` (the dangling-delegate per-edge check). `Producer`
783
+ * is the prior success-track step's `result().ok` shape (read off the registry
784
+ * via `OkOf`); `Consumer` is THIS step's `needs(...)` input contract. Collapses
785
+ * to `true` when the producer supplies every field the consumer needs (matching
786
+ * types), else to a descriptive error object naming the offending field
787
+ * (`__handoff_error` wrapping `Supplies`'s `__missing`/`__mismatch`), so
788
+ * assigning `true` to it is a `tsc` error at edit time. Shallow (one wrap over
789
+ * the per-field `Supplies` mapped type, no recursion); the generator emits one
790
+ * assertion per consecutive step pair (O(N)), keeping clear of TS2589.
791
+ */
792
+ export type Handoff<Producer extends Shape, Consumer extends Shape> = Supplies<Producer, Consumer> extends true ? true : {
793
+ readonly __handoff_error: Supplies<Producer, Consumer>;
794
+ };
795
+ /** A typed pipeline value — carries the LAST step's `ok` and the UNION of every
796
+ * step's `err` (any step can short-circuit to the error track). */
797
+ export interface Pipeline<Ok extends Shape, Err extends Shape> {
798
+ readonly _specType: "pipeline";
799
+ /** Ordered agent names — the resolved compose order. */
800
+ readonly agents: readonly string[];
801
+ /** The final step's success shape. */
802
+ readonly ok: Ok;
803
+ /** The union of every step's error shape. */
804
+ readonly err: Err;
805
+ /** The underlying string-path railway, for `compileRailway` reuse. */
806
+ readonly railway: Railway;
807
+ }
808
+ /**
809
+ * Begin a typed pipeline from its first step. The first step has no upstream, so
810
+ * its `needs` must be empty (`needs({})` or omitted). Returns a `Pipeline`
811
+ * carrying that step's `ok`/`err` forward.
812
+ */
813
+ export declare function start<Ok extends Shape, Err extends Shape>(first: PipeStep<Record<string, never>, Ok, Err> | TypedAgentSpec<Ok, Err>): Pipeline<Ok, Err>;
814
+ /**
815
+ * Append a step to a typed pipeline. The handoff is CHECKED: the constraint
816
+ * `Supplies<PriorOk, Needs>` must be `true`, else the `next` parameter's type
817
+ * collapses to a `__HANDOFF_ERROR` object and `tsc` rejects the call, naming the
818
+ * missing/mismatched field. Carries the new step's `ok` forward and accumulates
819
+ * the error track. Shallow per-call check — no recursive chain type.
820
+ *
821
+ * Named `andThen` (Wlaschin's railway `bind`/`andThen`), NOT `then`: a module
822
+ * exporting a function called `then` becomes a thenable, so `await import()` of
823
+ * any barrel re-exporting it would invoke it — a footgun the rename avoids.
824
+ */
825
+ export declare function andThen<PriorOk extends Shape, PriorErr extends Shape, Needs extends Shape, Ok extends Shape, Err extends Shape>(prior: Pipeline<PriorOk, PriorErr>, next: Supplies<PriorOk, Needs> extends true ? PipeStep<Needs, Ok, Err> : {
826
+ readonly __HANDOFF_ERROR: Supplies<PriorOk, Needs>;
827
+ }): Pipeline<Ok, PriorErr | Err>;
828
+ /**
829
+ * Compose a typed pipeline in one call — the ergonomic form of
830
+ * `andThen(andThen(start(a), b), c)`. Each adjacent handoff is checked
831
+ * left-to-right: the FIRST step is the producer, the rest are
832
+ * `pipeStep(agent, needs(...))` consumers, and the compiler rejects the whole
833
+ * expression if ANY handoff's producer `ok` does not supply the consumer's
834
+ * `needs` (variadic chains hit TS2589 quickly, so `pipe` is a fixed set of
835
+ * overloads over the shallow `start`/`andThen` fold rather than a recursive
836
+ * variadic type — keep chains to a handful of steps; for longer ones, fold
837
+ * `andThen` explicitly).
838
+ *
839
+ * pipe(
840
+ * planner, // produces ok
841
+ * pipeStep(implementer, needs({ plan: "string", files: "string[]" })),
842
+ * pipeStep(reviewer, needs({ diff: "string" })),
843
+ * ) // ← won't compile if a handoff doesn't line up
844
+ */
845
+ export declare function pipe<A extends Shape, AE extends Shape>(a: TypedAgentSpec<A, AE>): Pipeline<A, AE>;
846
+ export declare function pipe<A extends Shape, AE extends Shape, BN extends Shape, B extends Shape, BE extends Shape>(a: TypedAgentSpec<A, AE>, b: Supplies<A, BN> extends true ? PipeStep<BN, B, BE> : {
847
+ readonly __HANDOFF_ERROR: Supplies<A, BN>;
848
+ }): Pipeline<B, AE | BE>;
849
+ export declare function pipe<A extends Shape, AE extends Shape, BN extends Shape, B extends Shape, BE extends Shape, CN extends Shape, C extends Shape, CE extends Shape>(a: TypedAgentSpec<A, AE>, b: Supplies<A, BN> extends true ? PipeStep<BN, B, BE> : {
850
+ readonly __HANDOFF_ERROR: Supplies<A, BN>;
851
+ }, c: Supplies<B, CN> extends true ? PipeStep<CN, C, CE> : {
852
+ readonly __HANDOFF_ERROR: Supplies<B, CN>;
853
+ }): Pipeline<C, AE | BE | CE>;
854
+ export declare function pipe<A extends Shape, AE extends Shape, BN extends Shape, B extends Shape, BE extends Shape, CN extends Shape, C extends Shape, CE extends Shape, DN extends Shape, D extends Shape, DE extends Shape>(a: TypedAgentSpec<A, AE>, b: Supplies<A, BN> extends true ? PipeStep<BN, B, BE> : {
855
+ readonly __HANDOFF_ERROR: Supplies<A, BN>;
856
+ }, c: Supplies<B, CN> extends true ? PipeStep<CN, C, CE> : {
857
+ readonly __HANDOFF_ERROR: Supplies<B, CN>;
858
+ }, d: Supplies<C, DN> extends true ? PipeStep<DN, D, DE> : {
859
+ readonly __HANDOFF_ERROR: Supplies<C, DN>;
860
+ }): Pipeline<D, AE | BE | CE | DE>;
861
+ /**
862
+ * Per-edge dangling-`delegate` check. `Target` is a delegate target NAME (a
863
+ * string literal the generator reads off a `railway()` value); `Names` is the
864
+ * literal union of every agent name in the harness (emitted by the generator).
865
+ * Collapses to `true` when the target resolves, else to a descriptive error
866
+ * object naming the dangling target + the railway it came from — so assigning
867
+ * `true` to it is a `tsc` error at edit time. Shallow (one conditional, no
868
+ * recursion); the generator emits one assertion per edge (O(N)).
869
+ */
870
+ export type KnownAgentName<Target extends string, Names extends string, From extends string = string> = [Target] extends [Names] ? true : {
871
+ readonly __dangling_delegate: Target;
872
+ readonly from: From;
873
+ };
592
874
  /** Derive the spec filename from an output filename. */
593
875
  export type SpecPath<Output extends `${string}.md`> = `${Output}.spec.ts`;
594
876
  /** Extract the output filename from a spec filename. */
package/dist/core/spec.js CHANGED
@@ -30,6 +30,11 @@ exports.agent = agent;
30
30
  exports.result = result;
31
31
  exports.delegate = delegate;
32
32
  exports.railway = railway;
33
+ exports.needs = needs;
34
+ exports.pipeStep = pipeStep;
35
+ exports.start = start;
36
+ exports.andThen = andThen;
37
+ exports.pipe = pipe;
33
38
  exports.defineConfig = defineConfig;
34
39
  // ---------------------------------------------------------------------------
35
40
  // Builder functions
@@ -202,6 +207,11 @@ function step(instr, opts = {}) {
202
207
  *
203
208
  * // skills/my-skill/SKILL.md.spec.ts
204
209
  * export default skill({ name: "my-skill", description: "...", body: "..." });
210
+ *
211
+ * Generic over a tool `Vocabulary` (default `OpenToolVocabulary` — no
212
+ * constraint), exactly like `agent()`: a vocabulary-bound `skill` (e.g.
213
+ * `vigiles/claude-code`) makes `purity: "pure"` + a side-effecting tool a `tsc`
214
+ * error; the bare core `skill()` accepts any tools, as before.
205
215
  */
206
216
  function skill(spec) {
207
217
  return { _specType: "skill", ...spec };
@@ -220,6 +230,17 @@ function skill(spec) {
220
230
  * "no-floating": enforce("@typescript-eslint/no-floating-promises", "Await promises."),
221
231
  * },
222
232
  * });
233
+ *
234
+ * Generic over a tool `Vocabulary` (default `OpenToolVocabulary` — no
235
+ * constraint). A harness adapter re-exports a vocabulary-bound `agent` (e.g.
236
+ * `vigiles/claude-code`) so `purity: "pure"` + a side-effecting tool is a `tsc`
237
+ * error at edit time; the bare core `agent()` accepts any tools, as before.
238
+ *
239
+ * Also generic over the result's `Ok`/`Err` shapes, inferred from `output:
240
+ * result(...)`. The returned value is a `TypedAgentSpec<Ok, Err>` — an
241
+ * `AgentSpec` that carries those shapes at the type level, so a typed `pipe`
242
+ * can cross-reference the handoff. With no `output` the shapes default to the
243
+ * erased `Shape`, and the value is still a plain `AgentSpec` — backwards-compatible.
223
244
  */
224
245
  function agent(spec) {
225
246
  return { _specType: "agent", ...spec };
@@ -234,15 +255,35 @@ function agent(spec) {
234
255
  *
235
256
  * (Distinct from a skill's `result:` postcondition gate — this types a
236
257
  * subagent's *return value*, the success/error tracks of the railway.)
258
+ *
259
+ * The literal field shapes are PRESERVED in the return type (`const` inference),
260
+ * not erased to `Record<string, OutputFieldType>` — this is what lets `pipe`
261
+ * cross-reference one agent's `ok` against the next agent's needs at `tsc` time.
262
+ * The return is still an `OutputContract`, so every existing consumer (the
263
+ * `output:` field, `renderOutputContract`, `parseAgentResult`) is unchanged.
237
264
  */
238
265
  function result(ok, err) {
239
266
  return { _ref: "output", ok, err };
240
267
  }
241
- /** Build a railway step that dispatches `agent` (optionally with a task hint). */
242
- function delegate(agent, task) {
243
- return task === undefined
268
+ /**
269
+ * Build a railway step that dispatches `agent`.
270
+ *
271
+ * delegate("planner") // no task, no handoff
272
+ * delegate("implementer", "implement the plan") // task hint only
273
+ * delegate("reviewer", undefined, needs({ diff: "string" })) // + handoff check
274
+ *
275
+ * The optional 3rd argument carries the step's input `needs` (built by
276
+ * `needs(...)`). When present, the whole-harness registry asserts that the
277
+ * PREVIOUS success-track step's `result().ok` SUPPLIES it — a cross-file
278
+ * handoff that doesn't line up is a `tsc` error naming the offending field.
279
+ * Omitting it (the historical 1-/2-arg call) keeps the exact string-path
280
+ * behavior — fully backwards-compatible.
281
+ */
282
+ function delegate(agent, task, needsContract) {
283
+ const base = task === undefined
244
284
  ? { _step: "delegate", agent }
245
285
  : { _step: "delegate", agent, task };
286
+ return needsContract === undefined ? base : { ...base, needs: needsContract };
246
287
  }
247
288
  /**
248
289
  * Compose flat subagents into a railway (compiles to an orchestrator command).
@@ -257,6 +298,80 @@ function delegate(agent, task) {
257
298
  function railway(spec) {
258
299
  return { _specType: "railway", ...spec };
259
300
  }
301
+ /**
302
+ * Declare the input fields a step reads from its predecessor's success payload.
303
+ * Pass it as `needs:` on a typed pipeline step. `needs({})` (the default) is a
304
+ * step with no upstream requirement — valid as the FIRST step of a pipeline.
305
+ *
306
+ * needs({ plan: "string", files: "string[]" })
307
+ */
308
+ function needs(shape) {
309
+ return shape;
310
+ }
311
+ /**
312
+ * Pair a typed agent with the input it `needs` from the previous step. The first
313
+ * argument is an `agent()` VALUE (which carries its `result()` shape); the
314
+ * second is the `needs(...)` input contract.
315
+ *
316
+ * pipeStep(implementer, needs({ plan: "string", files: "string[]" }))
317
+ */
318
+ function pipeStep(a, needsContract = {}) {
319
+ return { _step: "typed-delegate", agent: a, needs: needsContract };
320
+ }
321
+ /**
322
+ * Begin a typed pipeline from its first step. The first step has no upstream, so
323
+ * its `needs` must be empty (`needs({})` or omitted). Returns a `Pipeline`
324
+ * carrying that step's `ok`/`err` forward.
325
+ */
326
+ function start(first) {
327
+ const step = "_step" in first ? first : pipeStep(first, {});
328
+ const out = step.agent.output;
329
+ return {
330
+ _specType: "pipeline",
331
+ agents: [step.agent.name],
332
+ ok: (out ? out.ok : {}),
333
+ err: (out ? out.err : {}),
334
+ railway: railway({
335
+ name: step.agent.name,
336
+ steps: [delegate(step.agent.name)],
337
+ }),
338
+ };
339
+ }
340
+ /**
341
+ * Append a step to a typed pipeline. The handoff is CHECKED: the constraint
342
+ * `Supplies<PriorOk, Needs>` must be `true`, else the `next` parameter's type
343
+ * collapses to a `__HANDOFF_ERROR` object and `tsc` rejects the call, naming the
344
+ * missing/mismatched field. Carries the new step's `ok` forward and accumulates
345
+ * the error track. Shallow per-call check — no recursive chain type.
346
+ *
347
+ * Named `andThen` (Wlaschin's railway `bind`/`andThen`), NOT `then`: a module
348
+ * exporting a function called `then` becomes a thenable, so `await import()` of
349
+ * any barrel re-exporting it would invoke it — a footgun the rename avoids.
350
+ */
351
+ function andThen(prior, next) {
352
+ const real = next;
353
+ const out = real.agent.output;
354
+ const rw = railway({
355
+ name: prior.railway.name,
356
+ steps: [...prior.railway.steps, delegate(real.agent.name)],
357
+ });
358
+ return {
359
+ _specType: "pipeline",
360
+ agents: [...prior.agents, real.agent.name],
361
+ ok: (out ? out.ok : {}),
362
+ err: (out ? out.err : {}),
363
+ railway: rw,
364
+ };
365
+ }
366
+ function pipe(first, ...rest) {
367
+ // The overloads above enforce each handoff at the type level; the runtime body
368
+ // is the same left fold of start/andThen, untyped (the checks already happened).
369
+ let pipeline = start(first);
370
+ for (const s of rest) {
371
+ pipeline = andThen(pipeline, s);
372
+ }
373
+ return pipeline;
374
+ }
260
375
  function defineConfig(config) {
261
376
  return config;
262
377
  }
@@ -221,6 +221,14 @@ export interface VigilesConfig {
221
221
  }>;
222
222
  /** Orphan-docs check configuration. Include/exclude globs, tsconfig-style. */
223
223
  orphans?: OrphansConfig;
224
+ /**
225
+ * Glob patterns of instruction/skill files to EXCLUDE from `lint` discovery
226
+ * (tsconfig-style, relative to the repo root). Use it for vendored or
227
+ * benchmark fixtures the repo's own lint shouldn't police — e.g.
228
+ * `["bench/**"]` so a third-party `CLAUDE.md` injected verbatim as a benchmark
229
+ * arm isn't held to `require-spec`. `node_modules`/`dist` are always excluded.
230
+ */
231
+ exclude?: readonly string[];
224
232
  /**
225
233
  * The harness(es) this repo targets — selects the compile dialect / skill
226
234
  * frontmatter profile / instruction-file shape, instead of sniffing the cwd.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The Claude Code version `ACKNOWLEDGED_TOOL_INPUT_TYPES` + the dialect were last
3
+ * validated against. SINGLE SOURCE OF TRUTH for the pin: CI installs
4
+ * `@anthropic-ai/claude-code@<this>` (grepped from this line) in every job that
5
+ * drives the real binary, so the dialect-drift alarm fires only on a DELIBERATE
6
+ * bump — not on every unpinned CC release landing on an unrelated PR — and the
7
+ * real-`claude` harness/eval tests stay reproducible. Bump this together with
8
+ * `ACKNOWLEDGED_TOOL_INPUT_TYPES` (the gated test cross-checks them).
9
+ */
10
+ export declare const VALIDATED_CC_VERSION = "2.1.187";
11
+ /**
12
+ * The `<X>Input` interface names we've ACKNOWLEDGED from `sdk-tools.d.ts` (Claude
13
+ * Code 2.1.187). The drift test fails when the installed set differs — a loud nudge
14
+ * to re-check `claudeCodeDialect` (and update this set) when CC adds/removes a tool.
15
+ * NOT a redistribution of their file: a list of bare identifiers (facts), authored here.
16
+ *
17
+ * 2.1.187 added the agent-PLATFORM surface (cron/scheduling/worktrees/web-app):
18
+ * Artifact, Cron{Create,Delete,List}, Enter/ExitWorktree, EnterPlanMode, Monitor,
19
+ * Projects, PushNotification, REPL, ReadMcpResourceDir, RemoteTrigger,
20
+ * ScheduleWakeup, ShowOnboardingRolePicker, Task{Create,Get,List,Update}, Workflow;
21
+ * and removed Config. These are HOST/platform tools, NOT subagent-grantable, so
22
+ * `claudeCodeDialect.builtinAgentTools` (the `tools:` frontmatter catalog) is
23
+ * intentionally unchanged — they're acknowledged here as facts, nothing more.
24
+ */
25
+ export declare const ACKNOWLEDGED_TOOL_INPUT_TYPES: readonly string[];
26
+ /** Parse `export interface <X>Input {` names from sdk-tools.d.ts → sorted [<X>]. Pure. */
27
+ export declare function parseToolInputTypes(dts: string): string[];
28
+ /**
29
+ * Locate a READABLE JavaScript bundle inside the installed CC package, or null.
30
+ * Older CC shipped `cli.js` — a readable JS bundle whose hook-event names appear as
31
+ * string literals, greppable by `eventsMissingFromBundle`. CC ≥ ~2.1.18x switched to
32
+ * a NATIVE-BINARY distribution (`bin/claude.exe` copied from a platform
33
+ * `optionalDependencies` package) with NO readable JS bundle, so there is nothing to
34
+ * text-scan. Returns the bundle path when present, else null — callers then SKIP the
35
+ * event-drift check loudly rather than crash on a missing `cli.js`.
36
+ */
37
+ export declare function findClaudeCodeBundle(pkg: string): string | null;
38
+ /** Which of `events` do NOT appear as a whole-word literal in the bundle. Pure. */
39
+ export declare function eventsMissingFromBundle(bundle: string, events: readonly string[]): string[];
40
+ /**
41
+ * Locate the user's installed `@anthropic-ai/claude-code` package dir, or null.
42
+ * Tries the global npm root, then the `claude` binary's real path. Read-only —
43
+ * we only read files the user already installed under their own CC license.
44
+ */
45
+ export declare function findClaudeCodePackage(): string | null;
46
+ /** A runtime drift report: how the INSTALLED CC's tool surface compares to ours. */
47
+ export interface DialectDriftReport {
48
+ readonly installedVersion: string;
49
+ readonly validatedVersion: string;
50
+ /** Tool-input types present in the install but not in ACKNOWLEDGED (CC added). */
51
+ readonly newToolTypes: string[];
52
+ /** Acknowledged types absent from the install (CC removed/renamed). */
53
+ readonly removedToolTypes: string[];
54
+ }
55
+ /**
56
+ * Best-effort, read-local drift check for `scan` (and other runtime callers). Reads
57
+ * only the small `sdk-tools.d.ts` (fast — no `cli.js` bundle scan; events are the
58
+ * CI test's job). Returns null when CC isn't installed or anything is unreadable —
59
+ * NEVER throws, so it can't break the command. ToS-clean: reads the user's own
60
+ * install, ships nothing.
61
+ */
62
+ export declare function checkDialectDrift(): DialectDriftReport | null;
63
+ /** A one-line freshness warning if the dialect drifted from the install, else null. */
64
+ export declare function formatDialectDrift(r: DialectDriftReport | null): string | null;
65
+ //# sourceMappingURL=dialect-drift.d.ts.map