vigiles 7.0.0 → 9.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 (58) hide show
  1. package/README.md +207 -88
  2. package/dist/adoptability.d.ts +55 -0
  3. package/dist/adoptability.js +196 -0
  4. package/dist/audit-html.d.ts +20 -0
  5. package/dist/audit-html.js +61 -0
  6. package/dist/audit-prompts.d.ts +46 -0
  7. package/dist/audit-prompts.js +90 -0
  8. package/dist/audit-report.d.ts +70 -0
  9. package/dist/audit-report.js +51 -0
  10. package/dist/audit-report.template.html +110 -0
  11. package/dist/audit-score.d.ts +44 -0
  12. package/dist/audit-score.js +221 -0
  13. package/dist/cli-commands.d.ts +1 -1
  14. package/dist/cli-commands.js +3 -7
  15. package/dist/cli.d.ts +1 -1
  16. package/dist/cli.js +749 -180
  17. package/dist/core/adopt.d.ts +65 -0
  18. package/dist/core/adopt.js +199 -0
  19. package/dist/core/compose.d.ts +1 -1
  20. package/dist/core/compose.js +1 -1
  21. package/dist/core/evolve.d.ts +4 -0
  22. package/dist/core/evolve.js +4 -0
  23. package/dist/core/frontmatter.d.ts +8 -7
  24. package/dist/core/frontmatter.js +8 -7
  25. package/dist/core/generate-harness.d.ts +1 -1
  26. package/dist/core/generate-harness.js +3 -3
  27. package/dist/core/generate-schema.js +1 -1
  28. package/dist/core/inline.d.ts +6 -6
  29. package/dist/core/inline.js +17 -7
  30. package/dist/core/integrity.d.ts +31 -0
  31. package/dist/core/integrity.js +45 -0
  32. package/dist/core/orphans.js +1 -1
  33. package/dist/core/spec.d.ts +40 -2
  34. package/dist/core/spec.js +16 -1
  35. package/dist/core/types.d.ts +42 -6
  36. package/dist/core/validate.js +26 -26
  37. package/dist/dialect-drift.js +1 -1
  38. package/dist/eval.d.ts +1 -1
  39. package/dist/eval.js +1 -1
  40. package/dist/guardrail-check.d.ts +1 -1
  41. package/dist/guardrail-check.js +1 -1
  42. package/dist/optimize.d.ts +12 -5
  43. package/dist/optimize.js +27 -5
  44. package/dist/scan-behavioral.d.ts +8 -2
  45. package/dist/scan-behavioral.js +6 -4
  46. package/dist/scan-trigger-suggest.d.ts +91 -0
  47. package/dist/scan-trigger-suggest.js +103 -0
  48. package/dist/scan.d.ts +53 -12
  49. package/dist/scan.js +92 -16
  50. package/dist/score-explainer.d.ts +1 -1
  51. package/dist/setup-plan.d.ts +59 -1
  52. package/dist/setup-plan.js +103 -5
  53. package/hooks/post-edit.sh +1 -1
  54. package/package.json +4 -2
  55. package/skills/adopt-spec/SKILL.md +7 -7
  56. package/skills/linter-docs/eslint.md +1 -1
  57. package/skills/strengthen/SKILL.md +1 -1
  58. package/skills/test-harness/SKILL.md +1 -1
@@ -135,7 +135,7 @@ export declare function guidance(text: string): GuidanceRule;
135
135
  * Declare a reactive guard: runs a command when watched files change.
136
136
  *
137
137
  * guard({ watch: "*.spec.ts", run: "npx vigiles compile" }, "Recompile on spec change")
138
- * guard({ watch: ["eslint.config.*", "package.json"], run: "npx vigiles generate-types" }, "Regen types")
138
+ * guard({ watch: ["eslint.config.*", "package.json"], run: "npx vigiles generate types" }, "Regen types")
139
139
  */
140
140
  export declare function guard(options: {
141
141
  watch: string | readonly string[];
@@ -237,6 +237,9 @@ export declare function glob(pattern: string): GlobRef;
237
237
  * the region the unit is treated as read-only (the `"pure"` effective floor),
238
238
  * inside it the declared purity floor applies. The position-aware companion to
239
239
  * the per-call `purity` floor. See `research/effect-boundary-design.md`.
240
+ *
241
+ * @internal Experimental (parked P3) — NOT part of the frozen public surface;
242
+ * may change or be removed without a major bump pre-1.0.
240
243
  */
241
244
  export interface EffectRegion {
242
245
  readonly _ref: "effect";
@@ -269,6 +272,9 @@ export declare function instructions(strings: TemplateStringsArray, ...values: I
269
272
  * Returns an `EffectRegion` fragment; `compile` wraps its rendered body in
270
273
  * `<!-- vigiles:effect -->` markers. Independent of the `doc()` authoring
271
274
  * surface — it does not block on it.
275
+ *
276
+ * @internal Experimental (parked P3) — NOT part of the frozen public surface;
277
+ * may change or be removed without a major bump pre-1.0.
272
278
  */
273
279
  export declare function effect(strings: TemplateStringsArray, ...values: InstructionFragment[]): EffectRegion;
274
280
  /**
@@ -732,6 +738,9 @@ export declare function railway(spec: Omit<Railway, "_specType">): Railway;
732
738
  * Declared via `needs(...)` and threaded into the typed agent so `pipe` can
733
739
  * cross-reference it. Independent of `result()` (the output) — an agent both
734
740
  * `needs` an input shape and produces an `ok`/`err` output shape.
741
+ *
742
+ * @internal Experimental typed-composition surface — NOT part of the frozen
743
+ * public API (pre-1.0); may change without a major bump.
735
744
  */
736
745
  export type NeedsContract<N extends Shape> = N;
737
746
  /**
@@ -740,12 +749,18 @@ export type NeedsContract<N extends Shape> = N;
740
749
  * step with no upstream requirement — valid as the FIRST step of a pipeline.
741
750
  *
742
751
  * needs({ plan: "string", files: "string[]" })
752
+ *
753
+ * @internal Experimental typed-composition surface — NOT part of the frozen
754
+ * public API (pre-1.0); may change without a major bump.
743
755
  */
744
756
  export declare function needs<const N extends Shape>(shape: N): NeedsContract<N>;
745
757
  /**
746
758
  * A typed pipeline step: a `TypedAgentSpec` paired with the input `needs` it
747
759
  * reads from the prior step's `ok`. `step()` builds one; `pipe` checks that the
748
760
  * prior step's `ok` shape supplies this step's `needs`.
761
+ *
762
+ * @internal Experimental typed-composition surface — NOT part of the frozen
763
+ * public API (pre-1.0); may change without a major bump.
749
764
  */
750
765
  export interface PipeStep<Needs extends Shape, Ok extends Shape, Err extends Shape> {
751
766
  readonly _step: "typed-delegate";
@@ -758,6 +773,9 @@ export interface PipeStep<Needs extends Shape, Ok extends Shape, Err extends Sha
758
773
  * second is the `needs(...)` input contract.
759
774
  *
760
775
  * pipeStep(implementer, needs({ plan: "string", files: "string[]" }))
776
+ *
777
+ * @internal Experimental typed-composition surface — NOT part of the frozen
778
+ * public API (pre-1.0); may change without a major bump.
761
779
  */
762
780
  export declare function pipeStep<Needs extends Shape, Ok extends Shape, Err extends Shape>(a: TypedAgentSpec<Ok, Err>, needsContract?: Needs): PipeStep<Needs, Ok, Err>;
763
781
  /**
@@ -766,6 +784,9 @@ export declare function pipeStep<Needs extends Shape, Ok extends Shape, Err exte
766
784
  * descriptive error object naming the offending field (`__missing` /
767
785
  * `__mismatch`), which surfaces at the mismatched call. Shallow (a per-field
768
786
  * mapped type, not a recursion) to avoid TS2589.
787
+ *
788
+ * @internal Experimental typed-composition surface — NOT part of the frozen
789
+ * public API (pre-1.0); may change without a major bump.
769
790
  */
770
791
  export type Supplies<Producer extends Shape, Consumer extends Shape> = {
771
792
  [K in keyof Consumer]: K extends keyof Producer ? Producer[K] extends Consumer[K] ? true : {
@@ -788,12 +809,17 @@ export type Supplies<Producer extends Shape, Consumer extends Shape> = {
788
809
  * assigning `true` to it is a `tsc` error at edit time. Shallow (one wrap over
789
810
  * the per-field `Supplies` mapped type, no recursion); the generator emits one
790
811
  * assertion per consecutive step pair (O(N)), keeping clear of TS2589.
812
+ *
813
+ * @internal Experimental typed-composition surface — NOT part of the frozen
814
+ * public API (pre-1.0); may change without a major bump.
791
815
  */
792
816
  export type Handoff<Producer extends Shape, Consumer extends Shape> = Supplies<Producer, Consumer> extends true ? true : {
793
817
  readonly __handoff_error: Supplies<Producer, Consumer>;
794
818
  };
795
819
  /** 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). */
820
+ * step's `err` (any step can short-circuit to the error track).
821
+ * @internal Experimental typed-composition surface — NOT part of the frozen
822
+ * public API (pre-1.0); may change without a major bump. */
797
823
  export interface Pipeline<Ok extends Shape, Err extends Shape> {
798
824
  readonly _specType: "pipeline";
799
825
  /** Ordered agent names — the resolved compose order. */
@@ -809,6 +835,9 @@ export interface Pipeline<Ok extends Shape, Err extends Shape> {
809
835
  * Begin a typed pipeline from its first step. The first step has no upstream, so
810
836
  * its `needs` must be empty (`needs({})` or omitted). Returns a `Pipeline`
811
837
  * carrying that step's `ok`/`err` forward.
838
+ *
839
+ * @internal Experimental typed-composition surface — NOT part of the frozen
840
+ * public API (pre-1.0); may change without a major bump.
812
841
  */
813
842
  export declare function start<Ok extends Shape, Err extends Shape>(first: PipeStep<Record<string, never>, Ok, Err> | TypedAgentSpec<Ok, Err>): Pipeline<Ok, Err>;
814
843
  /**
@@ -821,6 +850,9 @@ export declare function start<Ok extends Shape, Err extends Shape>(first: PipeSt
821
850
  * Named `andThen` (Wlaschin's railway `bind`/`andThen`), NOT `then`: a module
822
851
  * exporting a function called `then` becomes a thenable, so `await import()` of
823
852
  * any barrel re-exporting it would invoke it — a footgun the rename avoids.
853
+ *
854
+ * @internal Experimental typed-composition surface — NOT part of the frozen
855
+ * public API (pre-1.0); may change without a major bump.
824
856
  */
825
857
  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
858
  readonly __HANDOFF_ERROR: Supplies<PriorOk, Needs>;
@@ -841,6 +873,9 @@ export declare function andThen<PriorOk extends Shape, PriorErr extends Shape, N
841
873
  * pipeStep(implementer, needs({ plan: "string", files: "string[]" })),
842
874
  * pipeStep(reviewer, needs({ diff: "string" })),
843
875
  * ) // ← won't compile if a handoff doesn't line up
876
+ *
877
+ * @internal Experimental typed-composition surface — NOT part of the frozen
878
+ * public API (pre-1.0); may change without a major bump.
844
879
  */
845
880
  export declare function pipe<A extends Shape, AE extends Shape>(a: TypedAgentSpec<A, AE>): Pipeline<A, AE>;
846
881
  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> : {
@@ -866,6 +901,9 @@ export declare function pipe<A extends Shape, AE extends Shape, BN extends Shape
866
901
  * object naming the dangling target + the railway it came from — so assigning
867
902
  * `true` to it is a `tsc` error at edit time. Shallow (one conditional, no
868
903
  * recursion); the generator emits one assertion per edge (O(N)).
904
+ *
905
+ * @internal Experimental whole-harness-codegen surface — NOT part of the frozen
906
+ * public API (pre-1.0); may change without a major bump.
869
907
  */
870
908
  export type KnownAgentName<Target extends string, Names extends string, From extends string = string> = [Target] extends [Names] ? true : {
871
909
  readonly __dangling_delegate: Target;
package/dist/core/spec.js CHANGED
@@ -73,7 +73,7 @@ function guidance(text) {
73
73
  * Declare a reactive guard: runs a command when watched files change.
74
74
  *
75
75
  * guard({ watch: "*.spec.ts", run: "npx vigiles compile" }, "Recompile on spec change")
76
- * guard({ watch: ["eslint.config.*", "package.json"], run: "npx vigiles generate-types" }, "Regen types")
76
+ * guard({ watch: ["eslint.config.*", "package.json"], run: "npx vigiles generate types" }, "Regen types")
77
77
  */
78
78
  function guard(options, description) {
79
79
  return {
@@ -166,6 +166,9 @@ function instructions(strings, ...values) {
166
166
  * Returns an `EffectRegion` fragment; `compile` wraps its rendered body in
167
167
  * `<!-- vigiles:effect -->` markers. Independent of the `doc()` authoring
168
168
  * surface — it does not block on it.
169
+ *
170
+ * @internal Experimental (parked P3) — NOT part of the frozen public surface;
171
+ * may change or be removed without a major bump pre-1.0.
169
172
  */
170
173
  function effect(strings, ...values) {
171
174
  const body = [];
@@ -304,6 +307,9 @@ function railway(spec) {
304
307
  * step with no upstream requirement — valid as the FIRST step of a pipeline.
305
308
  *
306
309
  * needs({ plan: "string", files: "string[]" })
310
+ *
311
+ * @internal Experimental typed-composition surface — NOT part of the frozen
312
+ * public API (pre-1.0); may change without a major bump.
307
313
  */
308
314
  function needs(shape) {
309
315
  return shape;
@@ -314,6 +320,9 @@ function needs(shape) {
314
320
  * second is the `needs(...)` input contract.
315
321
  *
316
322
  * pipeStep(implementer, needs({ plan: "string", files: "string[]" }))
323
+ *
324
+ * @internal Experimental typed-composition surface — NOT part of the frozen
325
+ * public API (pre-1.0); may change without a major bump.
317
326
  */
318
327
  function pipeStep(a, needsContract = {}) {
319
328
  return { _step: "typed-delegate", agent: a, needs: needsContract };
@@ -322,6 +331,9 @@ function pipeStep(a, needsContract = {}) {
322
331
  * Begin a typed pipeline from its first step. The first step has no upstream, so
323
332
  * its `needs` must be empty (`needs({})` or omitted). Returns a `Pipeline`
324
333
  * carrying that step's `ok`/`err` forward.
334
+ *
335
+ * @internal Experimental typed-composition surface — NOT part of the frozen
336
+ * public API (pre-1.0); may change without a major bump.
325
337
  */
326
338
  function start(first) {
327
339
  const step = "_step" in first ? first : pipeStep(first, {});
@@ -347,6 +359,9 @@ function start(first) {
347
359
  * Named `andThen` (Wlaschin's railway `bind`/`andThen`), NOT `then`: a module
348
360
  * exporting a function called `then` becomes a thenable, so `await import()` of
349
361
  * any barrel re-exporting it would invoke it — a footgun the rename avoids.
362
+ *
363
+ * @internal Experimental typed-composition surface — NOT part of the frozen
364
+ * public API (pre-1.0); may change without a major bump.
350
365
  */
351
366
  function andThen(prior, next) {
352
367
  const real = next;
@@ -82,12 +82,23 @@ export interface TestCoverageConfig {
82
82
  exclude?: readonly string[];
83
83
  }
84
84
  export interface RulesConfig {
85
- /** Require .spec.ts for CLAUDE.md / AGENTS.md. Default: "warn". */
86
- "require-spec"?: RuleSeverity;
87
85
  /**
88
- * @deprecated Skills are legitimately hand-written; use `untested-skill`
89
- * ("every skill ships with a test/eval") instead. Default: false (off). The
90
- * check still runs if you set this explicitly.
86
+ * Require a `.spec.ts` behind each instruction file (CLAUDE.md / AGENTS.md) —
87
+ * the file must be compiled from a typed spec, not hand-written. NARROW: only a
88
+ * `.spec.ts` sibling (or an explicit `<!-- vigiles-disable
89
+ * require-instructions-spec -->` marker) satisfies it; inline
90
+ * `<!-- vigiles:enforce -->` comments do NOT (the rule name says "spec"). Default:
91
+ * "warn". `vigiles init` auto-adopts every instruction file into a spec, so this
92
+ * is GREEN by construction after setup — a safety net for a NEW hand-added file,
93
+ * not a nag. The workflow-tier opt-in (gated under `--strict`).
94
+ */
95
+ "require-instructions-spec"?: RuleSeverity;
96
+ /**
97
+ * Require a `.spec.ts` behind each SKILL.md — the consistent
98
+ * `require-<surface>-spec` parallel to `require-instructions-spec`. Default:
99
+ * false (OFF): skills are legitimately hand-written, and the coverage that
100
+ * matters ("every skill ships with a test/eval") is the `untested-skill` rule.
101
+ * Set it explicitly if your team wants every skill spec-managed.
91
102
  */
92
103
  "require-skill-spec"?: RuleSeverity;
93
104
  /** Detect hand-edits to compiled markdown via SHA-256 hash. Default: "warn". */
@@ -161,6 +172,18 @@ export interface RulesConfig {
161
172
  * "warn"; "error" gates CI. Same detector as `scan` (hooks status "missing").
162
173
  */
163
174
  "hook-script-exists"?: RuleSeverity;
175
+ /**
176
+ * A single repo-level RECOMMENDATION (one finding regardless of hook count):
177
+ * when a plugin/repo ships hand-written hook commands that aren't compiled
178
+ * `vigiles/hook` artifacts, nudge toward compiled hooks — they make whole hook
179
+ * bug classes (exit-1-not-2, wrong decision field, matcher bypass) UNREPRESENTABLE
180
+ * at authoring time, and `guardrail-check` proves an existing one blocks. A
181
+ * discovery nudge, not a defect: the hand-written shell lane stays first-class,
182
+ * so it's opt-out and fires ONCE (never per-hook). The message links
183
+ * `docs/compiled-hooks.md`. Default "warn"; set "off" to silence or "error" to
184
+ * enforce. Same detector as `scan` (manualHookCount).
185
+ */
186
+ "prefer-compiled-hooks"?: RuleSeverity;
164
187
  /**
165
188
  * Cross-reference a subagent's `disallowedTools:` block-list against the
166
189
  * catalog — the deny-side mirror of `subagent-tool-contract`. A close typo there
@@ -226,7 +249,8 @@ export interface VigilesConfig {
226
249
  * (tsconfig-style, relative to the repo root). Use it for vendored or
227
250
  * benchmark fixtures the repo's own lint shouldn't police — e.g.
228
251
  * `["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.
252
+ * arm isn't held to `require-instructions-spec`. `node_modules`/`dist` are always
253
+ * excluded.
230
254
  */
231
255
  exclude?: readonly string[];
232
256
  /**
@@ -239,6 +263,18 @@ export interface VigilesConfig {
239
263
  * `"claude-code"`. See research/multi-harness-compile.md.
240
264
  */
241
265
  harness?: string | string[];
266
+ /**
267
+ * `vigiles audit` preferences. `measure` is the sticky remembered answer to the
268
+ * "run the executing checks against your harness?" prompt — at a TTY `audit`
269
+ * asks once, then records the choice here so it never asks again. `true` runs
270
+ * the executing checks (safety battery · live MCP · skill firing) on every
271
+ * interactive run, `false` keeps them off (edit this key to change). Written by
272
+ * the audit consent prompt, not `init`. Headless runs never execute regardless
273
+ * (audit is a local report, not a CI step — there is no execution flag).
274
+ */
275
+ audit?: {
276
+ measure?: boolean;
277
+ };
242
278
  }
243
279
  /** Valid marker types for rule detection. */
244
280
  export type MarkerType = "headings" | "checkboxes";
@@ -11,8 +11,6 @@ const node_fs_1 = require("node:fs");
11
11
  const glob_1 = require("glob");
12
12
  const node_path_1 = require("node:path");
13
13
  const cosmiconfig_1 = require("cosmiconfig");
14
- const inline_js_1 = require("./inline.js");
15
- const frontmatter_js_1 = require("./frontmatter.js");
16
14
  // ---------------------------------------------------------------------------
17
15
  // Constants & regex
18
16
  // ---------------------------------------------------------------------------
@@ -31,13 +29,13 @@ const INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md"];
31
29
  // The default instruction file to validate when no config names one.
32
30
  const DEFAULT_FILES = [INSTRUCTION_FILES[0]];
33
31
  const DEFAULT_RULES = {
34
- "require-spec": "warn",
35
- // DEPRECATEDdefault OFF. Skills are legitimately hand-written (Level 0/1),
36
- // so requiring a .spec.ts per SKILL.md was the wrong constraint and only added
37
- // noise (it also nagged about vendored/fixture/bench skills). Use the
38
- // `untested-*` rules instead — "every skill/agent/hook ships with a test or
39
- // eval" is the coverage that matters. The implementation is kept: setting
40
- // `require-skill-spec` explicitly still works for anyone who wants it.
32
+ "require-instructions-spec": "warn",
33
+ // Default OFF the consistent `require-<surface>-spec` parallel. Skills are
34
+ // legitimately hand-written, so requiring a .spec.ts per SKILL.md is the wrong
35
+ // default (it would nag about vendored/fixture/bench skills); the coverage that
36
+ // matters is the `untested-*` rules ("every skill/agent/hook ships with a test
37
+ // or eval"). Set `require-skill-spec` explicitly if your team wants every skill
38
+ // spec-managed.
41
39
  "require-skill-spec": false,
42
40
  integrity: "warn",
43
41
  coverage: false,
@@ -60,6 +58,10 @@ const DEFAULT_RULES = {
60
58
  "mcp-tool-resolves": "warn",
61
59
  // A hook script referenced but missing never runs — on by default at warn.
62
60
  "hook-script-exists": "warn",
61
+ // Discovery nudge toward compiled hooks (one finding) — default OFF: it's a
62
+ // recommendation, not a defect (the hand-written shell lane stays first-class),
63
+ // so it shouldn't fire unasked. Set "warn"/"error" to opt in.
64
+ "prefer-compiled-hooks": false,
63
65
  // High-precision (close-typo only) deny-list mirror of subagent-tool-contract.
64
66
  "disallowed-tools-contract": "warn",
65
67
  // Deterministic NCD precision proxy (near-identical skill descriptions) — warn.
@@ -176,26 +178,25 @@ function validate(content, { ruleMarkers, rules: rulesConfig, filePath, dialect
176
178
  const missingCount = parsedRules.filter((r) => r.enforcement === "missing").length;
177
179
  const errors = [];
178
180
  const warnings = [];
179
- const disableComment = /<!--\s*vigiles-disable\s+require-spec\s*-->/;
181
+ const disableComment = /<!--\s*vigiles-disable\s+require-instructions-spec\s*-->/;
180
182
  if (filePath) {
181
183
  const basename = (0, node_path_1.basename)(filePath);
182
184
  const recognized = dialect?.instructionTargets ?? INSTRUCTION_FILES;
183
185
  const isInstruction = recognized.includes(basename);
184
186
  const isSkill = basename === "SKILL.md";
185
- // --- require-spec (CLAUDE.md / AGENTS.md) ---
186
- const specSeverity = activeRules["require-spec"];
187
+ // --- require-instructions-spec (CLAUDE.md / AGENTS.md) ---
188
+ // NARROW: only a `.spec.ts` sibling satisfies it. The rule name says "spec",
189
+ // so inline `<!-- vigiles:enforce -->` / `vigiles:` frontmatter do NOT count
190
+ // (a user on inline mode keeps this rule off — it's a workflow-tier opt-in).
191
+ // `vigiles init` auto-adopts every instruction file into a spec, so this is
192
+ // green by construction after setup.
193
+ const specSeverity = activeRules["require-instructions-spec"];
187
194
  if (specSeverity && isInstruction && !disableComment.test(content)) {
188
195
  const specPath = filePath + ".spec.ts";
189
- // Inline mode counts as a spec — any parseable
190
- // `<!-- vigiles:enforce ... -->` comment means the file is
191
- // verified on `vigiles lint` even without a .spec.ts sibling.
192
- // Delegate to the real parser so a malformed marker can't
193
- // satisfy require-spec with a rule that lint can't verify.
194
- const hasInline = (0, inline_js_1.hasInlineRules)(content) || (0, frontmatter_js_1.hasFrontmatterRules)(content);
195
- if (!(0, node_fs_1.existsSync)(specPath) && !hasInline) {
196
+ if (!(0, node_fs_1.existsSync)(specPath)) {
196
197
  const msg = {
197
- rule: "require-spec",
198
- message: `No spec file found for "${filePath}". Expected "${specPath}". Run \`npx vigiles init --target=${filePath}\` to create one, add inline \`<!-- vigiles:enforce ... -->\` comments or a \`vigiles:\` frontmatter block, or disable with <!-- vigiles-disable require-spec -->.`,
198
+ rule: "require-instructions-spec",
199
+ message: `No spec file found for "${filePath}". Expected "${specPath}". Run \`npx vigiles init --target=${filePath}\` to adopt it into a spec, or disable with <!-- vigiles-disable require-instructions-spec -->.`,
199
200
  line: 1,
200
201
  };
201
202
  if (specSeverity === "error") {
@@ -206,9 +207,8 @@ function validate(content, { ruleMarkers, rules: rulesConfig, filePath, dialect
206
207
  }
207
208
  }
208
209
  }
209
- // --- require-skill-spec (SKILL.md) — DEPRECATED but still honored when a
210
- // user sets it explicitly, so reading the deprecated key here is intentional.
211
- // eslint-disable-next-line @typescript-eslint/no-deprecated
210
+ // --- require-skill-spec (SKILL.md) — the consistent require-<surface>-spec
211
+ // parallel, off by default; honored when a user sets it explicitly.
212
212
  const skillSeverity = activeRules["require-skill-spec"];
213
213
  if (skillSeverity && isSkill && !disableComment.test(content)) {
214
214
  const specPath = filePath + ".spec.ts";
@@ -302,7 +302,7 @@ function validatePaths(paths, { followSymlinks = false, ruleMarkers, rules: rule
302
302
  let allValid = true;
303
303
  // Maps a real (symlink-resolved) path → the first path validated for it, so a
304
304
  // symlinked/synced CLAUDE.md⇄AGENTS.md mirror is validated ONCE on the real
305
- // file instead of double-firing require-spec on the mirror's name (sync-tool-
305
+ // file instead of double-firing require-instructions-spec on the mirror's name (sync-tool-
306
306
  // compatibility.md req 7). Recorded only on a successful validation, so a
307
307
  // symlink seen first (and skipped) never shadows its real target.
308
308
  const seenReal = new Map();
@@ -338,7 +338,7 @@ function validatePaths(paths, { followSymlinks = false, ruleMarkers, rules: rule
338
338
  allValid = false;
339
339
  continue;
340
340
  }
341
- // Attribute require-spec/integrity to the REAL file when this path is a
341
+ // Attribute require-instructions-spec/integrity to the REAL file when this path is a
342
342
  // symlink, so a symlinked AGENTS.md resolves to CLAUDE.md's spec rather than
343
343
  // a nonexistent AGENTS.md.spec.ts. Non-symlinks keep the original path
344
344
  // verbatim (behaviour-preserving).
@@ -40,7 +40,7 @@ exports.formatDialectDrift = formatDialectDrift;
40
40
  *
41
41
  * Pure parsers (testable with fixtures) + a local-install locator. TWO consumers:
42
42
  * the gated CI test in `dialect-drift.test.ts` (fails loud on tool/event drift), and
43
- * `vigiles scan` at runtime via `checkDialectDrift`/`formatDialectDrift` (a best-effort,
43
+ * `vigiles audit` at runtime via `checkDialectDrift`/`formatDialectDrift` (a best-effort,
44
44
  * read-local freshness WARN when the installed CC's tool surface drifts from ours).
45
45
  */
46
46
  const node_fs_1 = require("node:fs");
package/dist/eval.d.ts CHANGED
@@ -251,7 +251,7 @@ export type AgentRunner = (args: AgentRunArgs) => Promise<RunOut>;
251
251
  */
252
252
  export declare function resolveSpawnEnv(a: Pick<AgentRunArgs, "env" | "replaceEnv">, base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
253
253
  /** The real `claude`-spawning runner (composition root). Exported so other
254
- * real-model entries (e.g. `scan --trigger`) bind the same runner. */
254
+ * real-model entries (e.g. the `audit` trigger tier) bind the same runner. */
255
255
  export declare function spawnAgent(a: AgentRunArgs): Promise<RunOut>;
256
256
  /**
257
257
  * Run the eval: every arm × every trial against the real `claude` CLI, with the
package/dist/eval.js CHANGED
@@ -96,7 +96,7 @@ function resolveSpawnEnv(a, base = process.env) {
96
96
  }
97
97
  /* v8 ignore start -- real claude subprocess; exercised by bench/, not the unit gate */
98
98
  /** The real `claude`-spawning runner (composition root). Exported so other
99
- * real-model entries (e.g. `scan --trigger`) bind the same runner. */
99
+ * real-model entries (e.g. the `audit` trigger tier) bind the same runner. */
100
100
  function spawnAgent(a) {
101
101
  return new Promise((resolvePromise) => {
102
102
  const args = [
@@ -75,7 +75,7 @@ export declare function unblockedDisasters(results: readonly GuardrailResult[]):
75
75
  */
76
76
  export declare function assertBlocksDisasters(hookCommand: string, opts?: VerifyGuardrailOptions): void;
77
77
  /**
78
- * Render a coverage report (Level 0 — informational, NEUTRAL). It reports what the
78
+ * Render a coverage report (informational, NEUTRAL). It reports what the
79
79
  * hook blocks WITHOUT judging it: a hook that allows these may simply not be a
80
80
  * bash-safety guard (our own pre-edit.sh blocks .md edits, not `rm -rf`). The
81
81
  * "false confidence" verdict only applies once intent is DECLARED — see
@@ -131,7 +131,7 @@ function assertBlocksDisasters(hookCommand, opts = {}) {
131
131
  throw new Error(`Guardrail \`${hookCommand}\` did NOT block ${misses.length} dangerous action(s):\n${lines.join("\n")}\nA hook that doesn't block these is false confidence — fix it (PreToolUse + exit 2).`);
132
132
  }
133
133
  /**
134
- * Render a coverage report (Level 0 — informational, NEUTRAL). It reports what the
134
+ * Render a coverage report (informational, NEUTRAL). It reports what the
135
135
  * hook blocks WITHOUT judging it: a hook that allows these may simply not be a
136
136
  * bash-safety guard (our own pre-edit.sh blocks .md edits, not `rm -rf`). The
137
137
  * "false confidence" verdict only applies once intent is DECLARED — see
@@ -1,9 +1,9 @@
1
1
  /**
2
- * The per-repo harness optimizer's DETERMINISTIC spine — shipped as the
3
- * `vigiles scan --fix-plan` lens (NOT its own `optimize` verb: until the measured
4
- * A/B half lands, an "optimizer" that only re-prints scan's findings doesn't earn
5
- * a separate command, so it's folded into scan as one more view on the same
6
- * report; see research/roadmap.md §P2 "reconsider an `optimize` verb").
2
+ * The per-repo harness optimizer's DETERMINISTIC spine — folded INLINE into the
3
+ * default `vigiles audit` report (each finding carries its fix; NOT its own
4
+ * `optimize` verb, and no longer a `--fix-plan` flag: until the measured A/B half
5
+ * lands, an "optimizer" that only re-prints audit's findings doesn't earn a
6
+ * separate surface; see research/roadmap.md §P2 "reconsider an `optimize` verb").
7
7
  *
8
8
  * A2 in the measurement-authority pivot is the ADOPTION product: measure a user's
9
9
  * own skills/model/rules on their tasks and recommend add/drop/swap with a MEASURED
@@ -69,6 +69,13 @@ export interface OptimizeReport {
69
69
  * before `possible` proxies, via explainScore's own ordering). Pure over the report.
70
70
  */
71
71
  export declare function optimize(report: ScanReport): OptimizeReport;
72
+ /**
73
+ * Just the deterministic fix list (no score header) — folded into the default
74
+ * `vigiles audit` report so every finding carries its fix inline (replaces the
75
+ * former `--fix-plan`/`--explain` flags). Empty string when there's nothing to
76
+ * fix (or no loadable surface), so the caller can skip the section entirely.
77
+ */
78
+ export declare function formatRecommendations(rep: OptimizeReport): string;
72
79
  /** Render an optimization plan for the CLI. */
73
80
  export declare function formatOptimize(rep: OptimizeReport): string;
74
81
  //# sourceMappingURL=optimize.d.ts.map
package/dist/optimize.js CHANGED
@@ -1,13 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.optimize = optimize;
4
+ exports.formatRecommendations = formatRecommendations;
4
5
  exports.formatOptimize = formatOptimize;
5
6
  /**
6
- * The per-repo harness optimizer's DETERMINISTIC spine — shipped as the
7
- * `vigiles scan --fix-plan` lens (NOT its own `optimize` verb: until the measured
8
- * A/B half lands, an "optimizer" that only re-prints scan's findings doesn't earn
9
- * a separate command, so it's folded into scan as one more view on the same
10
- * report; see research/roadmap.md §P2 "reconsider an `optimize` verb").
7
+ * The per-repo harness optimizer's DETERMINISTIC spine — folded INLINE into the
8
+ * default `vigiles audit` report (each finding carries its fix; NOT its own
9
+ * `optimize` verb, and no longer a `--fix-plan` flag: until the measured A/B half
10
+ * lands, an "optimizer" that only re-prints audit's findings doesn't earn a
11
+ * separate surface; see research/roadmap.md §P2 "reconsider an `optimize` verb").
11
12
  *
12
13
  * A2 in the measurement-authority pivot is the ADOPTION product: measure a user's
13
14
  * own skills/model/rules on their tasks and recommend add/drop/swap with a MEASURED
@@ -67,6 +68,27 @@ const ACTION_LABEL = {
67
68
  differentiate: "DIFFERENTIATE",
68
69
  };
69
70
  const measureHint = (dir) => `\`vigiles measure ${dir} --prompts=<file>\` — real-model, runs on your subscription`;
71
+ /**
72
+ * Just the deterministic fix list (no score header) — folded into the default
73
+ * `vigiles audit` report so every finding carries its fix inline (replaces the
74
+ * former `--fix-plan`/`--explain` flags). Empty string when there's nothing to
75
+ * fix (or no loadable surface), so the caller can skip the section entirely.
76
+ */
77
+ function formatRecommendations(rep) {
78
+ if (rep.empty || rep.recommendations.length === 0)
79
+ return "";
80
+ const lines = [
81
+ `${String(rep.recommendations.length)} deterministic fix(es) — free, no model:`,
82
+ "",
83
+ ];
84
+ for (const r of rep.recommendations) {
85
+ const mark = r.confidence === "likely" ? "✗" : "⚠";
86
+ lines.push(`${mark} [${ACTION_LABEL[r.action]}] ${r.surface}`);
87
+ lines.push(` why: ${r.rationale} [${r.detector}]`);
88
+ lines.push(` → ${r.fix}`);
89
+ }
90
+ return lines.join("\n");
91
+ }
70
92
  /** Render an optimization plan for the CLI. */
71
93
  function formatOptimize(rep) {
72
94
  const head = `Harness health: ${String(rep.score)}/100 (${rep.grade}) — ${rep.dir}`;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * `vigiles scan --trigger` — the BEHAVIORAL column of the scan report.
2
+ * `vigiles audit` model trigger tier — the BEHAVIORAL column of the audit report.
3
3
  *
4
4
  * Structural `scan`/`scanPlugin` is deterministic, no-model, CI-free — and stays
5
- * that way. This is the opt-in, model-gated column that stacks on top: for each
5
+ * that way. This is the model-gated column that stacks on top: for each
6
6
  * model-invocable skill in a plugin, it measures how reliably the description
7
7
  * actually FIRES (recall, + precision when irrelevant prompts are supplied),
8
8
  * reusing `measureTriggerRate`. It degrades honestly when the `claude` CLI / auth
@@ -12,6 +12,8 @@
12
12
  * path in prose is undecidable, and the deterministic-input discipline is what
13
13
  * makes the column trustworthy. See `research/plugin-behavioral-findings.md`.
14
14
  */
15
+ import type { PluginLayout } from "./core/layout.js";
16
+ import type { HarnessDialect } from "./core/dialect.js";
15
17
  import { type EvalDriver } from "./eval.js";
16
18
  import { type Trace } from "./harness-test.js";
17
19
  /** Which harness drives the behavioral column (default Claude Code). */
@@ -46,6 +48,10 @@ export interface ProbeOptions {
46
48
  readonly minDistance?: number;
47
49
  /** Which harness to drive (default `"claude-code"`). */
48
50
  readonly harness?: ProbeHarness;
51
+ /** Layout + dialect for candidate discovery — so a Codex repo's skills (under
52
+ * the Codex layout) are found, not silently missed by the default CC layout. */
53
+ readonly layout?: PluginLayout;
54
+ readonly dialect?: HarnessDialect;
49
55
  }
50
56
  /**
51
57
  * Per-harness probe wiring: the eval driver (runner+parse), how to build the
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  /**
3
- * `vigiles scan --trigger` — the BEHAVIORAL column of the scan report.
3
+ * `vigiles audit` model trigger tier — the BEHAVIORAL column of the audit report.
4
4
  *
5
5
  * Structural `scan`/`scanPlugin` is deterministic, no-model, CI-free — and stays
6
- * that way. This is the opt-in, model-gated column that stacks on top: for each
6
+ * that way. This is the model-gated column that stacks on top: for each
7
7
  * model-invocable skill in a plugin, it measures how reliably the description
8
8
  * actually FIRES (recall, + precision when irrelevant prompts are supplied),
9
9
  * reusing `measureTriggerRate`. It degrades honestly when the `claude` CLI / auth
@@ -131,8 +131,10 @@ async function probeSkill(ctx, name, ps) {
131
131
  async function probePluginTriggersWith(dir, promptSet, probe, opts = {}) {
132
132
  const ctx = { dir, opts, probe };
133
133
  // Only model-invocable, describable skills can auto-trigger; user-invoked and
134
- // description-less ones can't, so they're not behavioral candidates.
135
- const candidates = (0, scan_js_1.scanPlugin)(dir).skills.filter((s) => !s.userInvoked && s.hasDescription);
134
+ // description-less ones can't, so they're not behavioral candidates. Discover
135
+ // them with the resolved layout/dialect (default CC) so a Codex repo's skills
136
+ // aren't missed by the wrong layout.
137
+ const candidates = (0, scan_js_1.scanPlugin)(dir, opts.layout, opts.dialect).skills.filter((s) => !s.userInvoked && s.hasDescription);
136
138
  const results = [];
137
139
  for (const s of candidates) {
138
140
  const ps = promptSet[s.name];