vigiles 38.2.0 → 39.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 (102) hide show
  1. package/dist/adapters/claude-code/event-capability.js +24 -8
  2. package/dist/adapters/claude-code/hook-protocol.js +2 -1
  3. package/dist/adapters/codex/hook-protocol.js +2 -1
  4. package/dist/cli-flag-check.d.ts +36 -0
  5. package/dist/cli-flag-check.js +72 -1
  6. package/dist/cli-main.d.ts +2 -2
  7. package/dist/cli-main.js +548 -379
  8. package/dist/core/bash-effects.d.ts +0 -57
  9. package/dist/core/bash-effects.js +0 -147
  10. package/dist/core/compile-generator.d.ts +6 -6
  11. package/dist/core/compile-generator.js +4 -4
  12. package/dist/core/compile.d.ts +72 -8
  13. package/dist/core/compile.js +276 -133
  14. package/dist/core/config-schema.d.ts +40 -10
  15. package/dist/core/config-schema.js +139 -16
  16. package/dist/core/coverage.d.ts +16 -0
  17. package/dist/core/coverage.js +22 -0
  18. package/dist/core/doc-refs.d.ts +3 -2
  19. package/dist/core/doc-refs.js +2 -2
  20. package/dist/core/enforce-marker.d.ts +28 -0
  21. package/dist/core/enforce-marker.js +34 -0
  22. package/dist/core/eslint-inventory.d.ts +96 -0
  23. package/dist/core/eslint-inventory.js +251 -0
  24. package/dist/core/event-capability.d.ts +5 -1
  25. package/dist/core/frame.d.ts +43 -2
  26. package/dist/core/frame.js +85 -2
  27. package/dist/core/generate-schema.d.ts +3 -2
  28. package/dist/core/generate-schema.js +2 -2
  29. package/dist/core/generate-types.d.ts +11 -2
  30. package/dist/core/generate-types.js +12 -5
  31. package/dist/core/hook-command.d.ts +172 -0
  32. package/dist/core/hook-command.js +277 -0
  33. package/dist/core/hook-delivery.d.ts +133 -0
  34. package/dist/core/hook-delivery.js +150 -0
  35. package/dist/core/hook-paths.d.ts +14 -0
  36. package/dist/core/hook-paths.js +22 -0
  37. package/dist/core/hook-program.d.ts +4 -4
  38. package/dist/core/hook-program.js +13 -25
  39. package/dist/core/hook-state.d.ts +1 -1
  40. package/dist/core/hook-state.js +1 -1
  41. package/dist/core/instruction-remedies.d.ts +35 -0
  42. package/dist/core/instruction-remedies.js +70 -1
  43. package/dist/core/integrity-header.d.ts +54 -0
  44. package/dist/core/integrity-header.js +90 -0
  45. package/dist/core/integrity.d.ts +1 -25
  46. package/dist/core/integrity.js +16 -74
  47. package/dist/core/linter-adapter.d.ts +27 -3
  48. package/dist/core/linters.d.ts +8 -1
  49. package/dist/core/linters.js +116 -81
  50. package/dist/core/orphans.d.ts +3 -5
  51. package/dist/core/orphans.js +9 -10
  52. package/dist/core/presets.d.ts +19 -7
  53. package/dist/core/presets.js +19 -6
  54. package/dist/core/rule-catalog.d.ts +11 -12
  55. package/dist/core/rule-catalog.js +25 -94
  56. package/dist/core/rule-meta.d.ts +12 -0
  57. package/dist/core/rule-meta.js +47 -10
  58. package/dist/core/source-refs.d.ts +3 -23
  59. package/dist/core/source-refs.js +3 -27
  60. package/dist/core/test-utils.d.ts +13 -0
  61. package/dist/core/test-utils.js +40 -1
  62. package/dist/core/types.d.ts +18 -26
  63. package/dist/core/validate.d.ts +1 -2
  64. package/dist/core/validate.js +60 -47
  65. package/dist/coverage-evidence.js +2 -3
  66. package/dist/deprecated-key-files.d.ts +6 -0
  67. package/dist/deprecated-key-files.js +7 -6
  68. package/dist/exclude.d.ts +3 -1
  69. package/dist/exclude.js +9 -33
  70. package/dist/harness-assert.d.ts +18 -0
  71. package/dist/harness-assert.js +38 -0
  72. package/dist/hook-check-install.js +15 -6
  73. package/dist/hook-check.d.ts +1 -3
  74. package/dist/hook-check.js +3 -5
  75. package/dist/hook-install.d.ts +7 -41
  76. package/dist/hook-install.js +29 -96
  77. package/dist/hook-runtime.js +2 -1
  78. package/dist/load-hook.d.ts +3 -2
  79. package/dist/load-hook.js +28 -6
  80. package/dist/overrides.d.ts +25 -0
  81. package/dist/overrides.js +51 -0
  82. package/dist/plugin-loader.d.ts +23 -0
  83. package/dist/plugin-loader.js +42 -12
  84. package/dist/root-globs.d.ts +9 -0
  85. package/dist/root-globs.js +48 -0
  86. package/dist/rule-routing.js +6 -3
  87. package/dist/run-hook.d.ts +26 -2
  88. package/dist/run-hook.js +48 -12
  89. package/dist/scan-core.d.ts +96 -20
  90. package/dist/scan-core.js +171 -176
  91. package/dist/scan-files.d.ts +0 -8
  92. package/dist/scan-files.js +78 -20
  93. package/dist/scan.d.ts +12 -12
  94. package/dist/scan.js +33 -10
  95. package/dist/setup-plan.d.ts +15 -9
  96. package/dist/setup-plan.js +26 -18
  97. package/dist/test-coverage.d.ts +3 -8
  98. package/dist/test-coverage.js +6 -6
  99. package/dist/test.d.ts +1 -1
  100. package/dist/vigilesrc.schema.json +1329 -118
  101. package/package.json +1 -1
  102. package/skills/adopt-spec/SKILL.md +2 -1
@@ -42,6 +42,8 @@
42
42
  * requires, against which 45 ms is ~13%.
43
43
  */
44
44
  import { z } from "zod";
45
+ /** The config file; the directory holding it is a project root. */
46
+ export declare const PROJECT_CONFIG_FILE = ".vigilesrc.json";
45
47
  /** The rule names, for the "did you mean" on an unknown one. */
46
48
  export declare const RULE_NAMES: readonly string[];
47
49
  /**
@@ -84,38 +86,30 @@ export declare const vigilesConfigSchema: z.ZodObject<{
84
86
  }, z.core.$strict>], null>]>>;
85
87
  "untested-skill": z.ZodDefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>, z.ZodTuple<[z.ZodLiteral<"warn">, z.ZodObject<{
86
88
  include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
87
- exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
88
89
  testExtension: z.ZodOptional<z.ZodString>;
89
90
  }, z.core.$strict>], null>, z.ZodTuple<[z.ZodLiteral<"error">, z.ZodObject<{
90
91
  include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
91
- exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
92
92
  testExtension: z.ZodOptional<z.ZodString>;
93
93
  }, z.core.$strict>], null>]>>;
94
94
  "untested-subagent": z.ZodDefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>, z.ZodTuple<[z.ZodLiteral<"warn">, z.ZodObject<{
95
95
  include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
96
- exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
97
96
  testExtension: z.ZodOptional<z.ZodString>;
98
97
  }, z.core.$strict>], null>, z.ZodTuple<[z.ZodLiteral<"error">, z.ZodObject<{
99
98
  include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
100
- exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
101
99
  testExtension: z.ZodOptional<z.ZodString>;
102
100
  }, z.core.$strict>], null>]>>;
103
101
  "untested-hook": z.ZodDefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>, z.ZodTuple<[z.ZodLiteral<"warn">, z.ZodObject<{
104
102
  include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
105
- exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
106
103
  testExtension: z.ZodOptional<z.ZodString>;
107
104
  }, z.core.$strict>], null>, z.ZodTuple<[z.ZodLiteral<"error">, z.ZodObject<{
108
105
  include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
109
- exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
110
106
  testExtension: z.ZodOptional<z.ZodString>;
111
107
  }, z.core.$strict>], null>]>>;
112
108
  "untested-output-style": z.ZodDefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>, z.ZodTuple<[z.ZodLiteral<"warn">, z.ZodObject<{
113
109
  include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
114
- exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
115
110
  testExtension: z.ZodOptional<z.ZodString>;
116
111
  }, z.core.$strict>], null>, z.ZodTuple<[z.ZodLiteral<"error">, z.ZodObject<{
117
112
  include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
118
- exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
119
113
  testExtension: z.ZodOptional<z.ZodString>;
120
114
  }, z.core.$strict>], null>]>>;
121
115
  "unmarked-refs": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
@@ -126,7 +120,7 @@ export declare const vigilesConfigSchema: z.ZodObject<{
126
120
  "skill-frontmatter": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
127
121
  "mcp-tool-resolves": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
128
122
  "hook-script-exists": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
129
- "prefer-compiled-hooks": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
123
+ "hand-written-hook": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
130
124
  "disallowed-tools-contract": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
131
125
  "description-overlap": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
132
126
  "skill-description-budget": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
@@ -170,9 +164,14 @@ export declare const vigilesConfigSchema: z.ZodObject<{
170
164
  }>>;
171
165
  orphans: z.ZodOptional<z.ZodObject<{
172
166
  include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
173
- exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
174
167
  }, z.core.$strict>>;
175
168
  exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
169
+ overrides: z.ZodOptional<z.ZodArray<z.ZodObject<{
170
+ files: z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>;
171
+ rules: z.ZodObject<{
172
+ [x: string]: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "error" | "warn", boolean | 0 | 1 | 2 | "error" | "warn" | "off">>>;
173
+ }, z.core.$strict>;
174
+ }, z.core.$strict>>>;
176
175
  sharedDirs: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
177
176
  harnesses: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
178
177
  roots: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
@@ -253,6 +252,37 @@ export declare const REPLACED_KEYS: ReadonlyArray<{
253
252
  * it shows the reader their own keys instead of somebody else's.
254
253
  */
255
254
  export declare function replacedKeyMessage(present: ReadonlyArray<(typeof REPLACED_KEYS)[number]>, names: readonly string[]): string;
255
+ /**
256
+ * The per-rule `exclude` options that became `overrides`, and the rules an
257
+ * equivalent override turns off. `untested-*` shared ONE merged option set, so
258
+ * an `exclude` on any of them skipped those paths for all four.
259
+ */
260
+ export declare const MOVED_TO_OVERRIDES: ReadonlyArray<{
261
+ /** Where the option sat: a rule name, or the `orphans` block. */
262
+ readonly owner: string;
263
+ /** The rules the equivalent override turns off. */
264
+ readonly rules: readonly string[];
265
+ }>;
266
+ /**
267
+ * The message an old config with a per-rule `exclude` gets — the user's OWN
268
+ * globs, written as the `overrides` entry that does what the option did.
269
+ *
270
+ * Read BEFORE the schema, like {@link REPLACED_KEYS}: the reader's config used
271
+ * to work, so "unknown key exclude" would be true and useless.
272
+ */
273
+ export declare function movedToOverridesMessages(raw: unknown): readonly string[];
274
+ /** Rules whose findings another rule now reports, and what changed. */
275
+ export declare const FOLDED_RULES: ReadonlyArray<{
276
+ readonly rule: string;
277
+ readonly into: string;
278
+ readonly what: string;
279
+ }>;
280
+ /**
281
+ * The message a config naming a folded rule gets — in `rules` or in an
282
+ * `overrides` entry. Read BEFORE the schema, like {@link REPLACED_KEYS}: the
283
+ * reader's config used to work, so "unknown key" would be true and useless.
284
+ */
285
+ export declare function foldedRuleMessages(raw: unknown): readonly string[];
256
286
  /**
257
287
  * Turn a Zod failure into the lines a human acts on — ONE per real problem.
258
288
  *
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.REPLACED_KEYS = exports.VigilesConfigError = exports.vigilesConfigSchema = exports.RULE_NAMES = void 0;
3
+ exports.FOLDED_RULES = exports.MOVED_TO_OVERRIDES = exports.REPLACED_KEYS = exports.VigilesConfigError = exports.vigilesConfigSchema = exports.RULE_NAMES = exports.PROJECT_CONFIG_FILE = void 0;
4
4
  exports.parseVigilesConfig = parseVigilesConfig;
5
5
  exports.replacedKeyMessage = replacedKeyMessage;
6
+ exports.movedToOverridesMessages = movedToOverridesMessages;
7
+ exports.foldedRuleMessages = foldedRuleMessages;
6
8
  exports.formatConfigIssues = formatConfigIssues;
7
9
  /**
8
10
  * `.vigilesrc.json`, AS A SCHEMA — the one place the config's shape, its
@@ -50,6 +52,8 @@ exports.formatConfigIssues = formatConfigIssues;
50
52
  const zod_1 = require("zod");
51
53
  const edit_distance_js_1 = require("./edit-distance.js");
52
54
  const presets_js_1 = require("./presets.js");
55
+ /** The config file; the directory holding it is a project root. */
56
+ exports.PROJECT_CONFIG_FILE = ".vigilesrc.json";
53
57
  // ---------------------------------------------------------------------------
54
58
  // Primitives
55
59
  // ---------------------------------------------------------------------------
@@ -114,11 +118,14 @@ const coverageThresholds = zod_1.z
114
118
  scripts: zod_1.z.number().optional(),
115
119
  })
116
120
  .strict();
117
- /** Discovery options shared by the three `untested-*` rules. */
121
+ /**
122
+ * Discovery options shared by the `untested-*` rules. `include` is where tests
123
+ * live — a real option of the rule. Skipping paths is not: that is `overrides`
124
+ * with the rule off (see {@link MOVED_TO_OVERRIDES}).
125
+ */
118
126
  const testCoverageConfig = zod_1.z
119
127
  .object({
120
128
  include: stringList.optional(),
121
- exclude: stringList.optional(),
122
129
  testExtension: zod_1.z.string().optional(),
123
130
  })
124
131
  .strict();
@@ -177,7 +184,8 @@ const rulesSchema = zod_1.z
177
184
  "skill-frontmatter": severitySchema.default("warn"),
178
185
  "mcp-tool-resolves": severitySchema.default("warn"),
179
186
  "hook-script-exists": severitySchema.default("warn"),
180
- "prefer-compiled-hooks": severitySchema.default(false),
187
+ // OFF: its only cure is a compiled hook, an experimental API (docs/experimental.md).
188
+ "hand-written-hook": severitySchema.default(false),
181
189
  "disallowed-tools-contract": severitySchema.default("warn"),
182
190
  "description-overlap": severitySchema.default("warn"),
183
191
  "skill-description-budget": severitySchema.default("warn"),
@@ -199,6 +207,35 @@ const rulesSchema = zod_1.z
199
207
  /** The rule names, for the "did you mean" on an unknown one. */
200
208
  exports.RULE_NAMES = Object.keys(rulesSchema.shape);
201
209
  // ---------------------------------------------------------------------------
210
+ // Overrides — per-path severities (`./overrides.ts`)
211
+ // ---------------------------------------------------------------------------
212
+ /**
213
+ * The rule set as an override may spell it: every rule, a SEVERITY only, no
214
+ * default. Options stay in `rules` — one place — so `["error", { … }]` here is
215
+ * refused rather than accepted and never read. Derived from `rulesSchema`'s
216
+ * keys, so a rule added there is overridable with no second list, and an
217
+ * unknown name gets the same "did you mean" as in `rules`.
218
+ */
219
+ const overrideRulesSchema = zod_1.z
220
+ .object(Object.fromEntries(Object.keys(rulesSchema.shape).map((name) => [
221
+ name,
222
+ severitySchema.optional(),
223
+ ])))
224
+ .strict();
225
+ /**
226
+ * One `overrides` entry. `files` are root-relative globs with top-level
227
+ * `exclude`'s semantics; an entry with none would apply nowhere, so it is
228
+ * refused rather than kept as a line that does nothing.
229
+ */
230
+ const overrideSchema = zod_1.z
231
+ .object({
232
+ files: stringList.refine((globs) => globs.length > 0, {
233
+ message: "needs at least one glob",
234
+ }),
235
+ rules: overrideRulesSchema,
236
+ })
237
+ .strict();
238
+ // ---------------------------------------------------------------------------
202
239
  // Harnesses (#240)
203
240
  // ---------------------------------------------------------------------------
204
241
  /**
@@ -251,11 +288,16 @@ exports.vigilesConfigSchema = zod_1.z
251
288
  orphans: zod_1.z
252
289
  .object({
253
290
  include: stringList.optional(),
254
- exclude: stringList.optional(),
255
291
  })
256
292
  .strict()
257
293
  .optional(),
258
294
  exclude: stringList.optional(),
295
+ /**
296
+ * Per-path rule severities, ESLint's `{ files, rules }` list: a later entry
297
+ * wins over an earlier one, any entry over `rules`, `rules` over `extends`.
298
+ * Resolved by `ruleResolver` (`./overrides.ts`).
299
+ */
300
+ overrides: zod_1.z.array(overrideSchema).optional(),
259
301
  sharedDirs: stringList.optional(),
260
302
  harnesses: zod_1.z.record(zod_1.z.string(), harnessDeclarationSchema).optional(),
261
303
  audit: zod_1.z.object({ measure: zod_1.z.boolean().optional() }).strict().optional(),
@@ -396,6 +438,83 @@ function replacedKeyMessage(present, names) {
396
438
  `skills and read no instruction file, the other read the instruction file and found no ` +
397
439
  `skills. Scoping each root under the harness that reads it removes the order.`);
398
440
  }
441
+ /**
442
+ * The per-rule `exclude` options that became `overrides`, and the rules an
443
+ * equivalent override turns off. `untested-*` shared ONE merged option set, so
444
+ * an `exclude` on any of them skipped those paths for all four.
445
+ */
446
+ exports.MOVED_TO_OVERRIDES = [
447
+ ...[
448
+ "untested-skill",
449
+ "untested-subagent",
450
+ "untested-hook",
451
+ "untested-output-style",
452
+ ].map((owner, _i, all) => ({ owner, rules: all })),
453
+ { owner: "orphans", rules: ["orphan-docs"] },
454
+ ];
455
+ /** A plain JSON object, narrowed without a cast. */
456
+ function isRecord(v) {
457
+ return typeof v === "object" && v !== null && !Array.isArray(v);
458
+ }
459
+ /** The `exclude` an old config wrote under `owner`, as a glob list, if any. */
460
+ function movedExclude(raw, owner) {
461
+ if (!isRecord(raw))
462
+ return null;
463
+ const value = isRecord(raw.rules) ? raw.rules[owner] : undefined;
464
+ const tuple = Array.isArray(value) ? value : [];
465
+ const holder = owner === "orphans" ? raw.orphans : tuple[1];
466
+ if (!isRecord(holder) || holder.exclude === undefined)
467
+ return null;
468
+ const globs = [holder.exclude].flat();
469
+ return globs.filter((g) => typeof g === "string");
470
+ }
471
+ /**
472
+ * The message an old config with a per-rule `exclude` gets — the user's OWN
473
+ * globs, written as the `overrides` entry that does what the option did.
474
+ *
475
+ * Read BEFORE the schema, like {@link REPLACED_KEYS}: the reader's config used
476
+ * to work, so "unknown key exclude" would be true and useless.
477
+ */
478
+ function movedToOverridesMessages(raw) {
479
+ return exports.MOVED_TO_OVERRIDES.flatMap(({ owner, rules }) => {
480
+ const globs = movedExclude(raw, owner);
481
+ if (globs === null)
482
+ return [];
483
+ const files = globs.map((g) => JSON.stringify(g)).join(", ");
484
+ const off = rules.map((r) => `"${r}": "off"`).join(", ");
485
+ return [
486
+ `.vigilesrc.json: "exclude" inside "${owner}" moved to "overrides" — skipping paths is not a rule option any more; an override turns the rule off for them.\n` +
487
+ ` Write: "overrides": [{ "files": [${files}], "rules": { ${off} } }]\n` +
488
+ ` and remove "exclude" from "${owner}". Paths top-level "exclude" removes are not read at all; an override only changes what a rule reports.`,
489
+ ];
490
+ });
491
+ }
492
+ /** Rules whose findings another rule now reports, and what changed. */
493
+ exports.FOLDED_RULES = [
494
+ {
495
+ rule: "prefer-compiled-hooks",
496
+ into: "hand-written-hook",
497
+ what: "every hook line vigiles did not write is now one hand-written-hook finding that names it",
498
+ },
499
+ ];
500
+ /**
501
+ * The message a config naming a folded rule gets — in `rules` or in an
502
+ * `overrides` entry. Read BEFORE the schema, like {@link REPLACED_KEYS}: the
503
+ * reader's config used to work, so "unknown key" would be true and useless.
504
+ */
505
+ function foldedRuleMessages(raw) {
506
+ if (!isRecord(raw))
507
+ return [];
508
+ const overrides = Array.isArray(raw.overrides)
509
+ ? raw.overrides
510
+ : [];
511
+ const ruleBlocks = [
512
+ raw.rules,
513
+ ...overrides.map((o) => (isRecord(o) ? o.rules : undefined)),
514
+ ];
515
+ return exports.FOLDED_RULES.filter(({ rule }) => ruleBlocks.some((b) => isRecord(b) && rule in b)).map(({ rule, into, what }) => `.vigilesrc.json: rule "${rule}" was folded into "${into}": ${what}.\n` +
516
+ ` Remove "${rule}"; set "${into}" to change how those findings report.`);
517
+ }
399
518
  // ---------------------------------------------------------------------------
400
519
  // Messages — the half a schema library does NOT give you
401
520
  // ---------------------------------------------------------------------------
@@ -412,17 +531,7 @@ function replacedKeyMessage(present, names) {
412
531
  function knownKeysAt(path) {
413
532
  let node = exports.vigilesConfigSchema;
414
533
  for (const seg of path) {
415
- const def = node._zod
416
- ?.def;
417
- if (def?.type === "object") {
418
- node = def.shape?.[String(seg)];
419
- }
420
- else if (def?.type === "record") {
421
- node = def.valueType;
422
- }
423
- else {
424
- return [];
425
- }
534
+ node = childAt(node, seg);
426
535
  if (node === undefined)
427
536
  return [];
428
537
  node = unwrap(node);
@@ -432,6 +541,20 @@ function knownKeysAt(path) {
432
541
  ? Object.keys(def.shape)
433
542
  : [];
434
543
  }
544
+ /**
545
+ * One step down a schema node: an object's field `seg`, an array's element, a
546
+ * record's value. `undefined` where the node has no child to step into.
547
+ */
548
+ function childAt(node, seg) {
549
+ const def = node._zod?.def;
550
+ if (def?.type === "object")
551
+ return def.shape?.[String(seg)];
552
+ if (def?.type === "array")
553
+ return def.element;
554
+ if (def?.type === "record")
555
+ return def.valueType;
556
+ return undefined;
557
+ }
435
558
  /** Peel `.optional()` / `.default()` / `.prefault()` wrappers off a schema node. */
436
559
  function unwrap(node) {
437
560
  let cur = node;
@@ -54,4 +54,20 @@ export declare function checkCoverage(basePath: string, thresholds: CoverageThre
54
54
  * Format coverage report as human-readable text.
55
55
  */
56
56
  export declare function formatCoverageReport(report: CoverageReport): string;
57
+ /**
58
+ * What `vigiles lint` says under the linter-rule coverage line when some enabled
59
+ * rules are undocumented. Empty when nothing is.
60
+ *
61
+ * 🔴 IT MUST NOT TELL ANYONE TO WRITE MORE INTO THE INSTRUCTION FILE. This used
62
+ * to read "Consider adding enforce() rules for frequently-triggered undocumented
63
+ * rules", which turns a coverage percentage into a to-do list for CLAUDE.md —
64
+ * the one file whose every line is loaded into every request and every subagent.
65
+ * An enabled linter rule is already enforced: the agent meets it as a failing
66
+ * lint with the rule's own message. Documentation only earns its place where the
67
+ * WHY is not in that message, and then it belongs where it is read on demand.
68
+ */
69
+ export declare function undocumentedRulesAdvice(totals: Readonly<{
70
+ enabled: number;
71
+ documented: number;
72
+ }>): readonly string[];
57
73
  //# sourceMappingURL=coverage.d.ts.map
@@ -16,6 +16,7 @@ exports.computeScriptCoverage = computeScriptCoverage;
16
16
  exports.computeLinterRuleCoverage = computeLinterRuleCoverage;
17
17
  exports.checkCoverage = checkCoverage;
18
18
  exports.formatCoverageReport = formatCoverageReport;
19
+ exports.undocumentedRulesAdvice = undocumentedRulesAdvice;
19
20
  const node_fs_1 = require("node:fs");
20
21
  const integrity_js_1 = require("./integrity.js");
21
22
  const node_path_1 = require("node:path");
@@ -183,4 +184,25 @@ function formatCoverageReport(report) {
183
184
  }
184
185
  return lines.join("\n");
185
186
  }
187
+ /**
188
+ * What `vigiles lint` says under the linter-rule coverage line when some enabled
189
+ * rules are undocumented. Empty when nothing is.
190
+ *
191
+ * 🔴 IT MUST NOT TELL ANYONE TO WRITE MORE INTO THE INSTRUCTION FILE. This used
192
+ * to read "Consider adding enforce() rules for frequently-triggered undocumented
193
+ * rules", which turns a coverage percentage into a to-do list for CLAUDE.md —
194
+ * the one file whose every line is loaded into every request and every subagent.
195
+ * An enabled linter rule is already enforced: the agent meets it as a failing
196
+ * lint with the rule's own message. Documentation only earns its place where the
197
+ * WHY is not in that message, and then it belongs where it is read on demand.
198
+ */
199
+ function undocumentedRulesAdvice(totals) {
200
+ if (totals.documented >= totals.enabled)
201
+ return [];
202
+ return [
203
+ "Undocumented is not unenforced: the linter already enforces these rules, and the agent meets each one as a lint failure.",
204
+ "Every line added to CLAUDE.md is loaded into every request, so do not document a rule just to raise this number.",
205
+ "Where the WHY is not obvious from the rule's own message, put it in docs/ (linked, read on demand) or declare the rule with enforce() in the spec, which also verifies it stays enabled.",
206
+ ];
207
+ }
186
208
  //# sourceMappingURL=coverage.js.map
@@ -40,7 +40,8 @@ export interface DocRefReport {
40
40
  readonly placeholders: number;
41
41
  }
42
42
  export interface FindDocRefsOptions {
43
- readonly basePath?: string;
43
+ /** The repo root. Required: no cwd fallback. */
44
+ readonly basePath: string;
44
45
  /** The repo exclude (`ExcludeSet.globIgnore`), or patterns relative to `basePath`. */
45
46
  readonly ignore?: GlobIgnore;
46
47
  }
@@ -55,7 +56,7 @@ interface ExtractResult {
55
56
  * spec.ts. Honors `<!-- vigiles:ignore-file -->` (skip the whole file)
56
57
  * and `<!-- vigiles:ignore -->` (skip the next code block).
57
58
  */
58
- export declare function findDocRefs(options?: FindDocRefsOptions): DocRefReport;
59
+ export declare function findDocRefs(options: FindDocRefsOptions): DocRefReport;
59
60
  /** Format a DocRefReport as human-readable text. */
60
61
  export declare function formatDocRefReport(report: DocRefReport,
61
62
  /**
@@ -238,8 +238,8 @@ function validateRefs(refs, basePath) {
238
238
  * spec.ts. Honors `<!-- vigiles:ignore-file -->` (skip the whole file)
239
239
  * and `<!-- vigiles:ignore -->` (skip the next code block).
240
240
  */
241
- function findDocRefs(options = {}) {
242
- const basePath = options.basePath ?? process.cwd();
241
+ function findDocRefs(options) {
242
+ const basePath = options.basePath;
243
243
  const ignore = (0, glob_ignore_js_1.withIgnored)(DEFAULT_IGNORE, options.ignore);
244
244
  const files = (0, glob_1.globSync)("**/*.md", { cwd: basePath, ignore });
245
245
  const allRefs = [];
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The two ways a compiled instruction file can state an `enforce()` reference,
3
+ * spelled in ONE place so the renderer and every reader agree (#351).
4
+ *
5
+ * "**Enforced by:**" is the product's promise that a machine holds the rule, so
6
+ * `compile` writes it only for a reference it VERIFIED: the rule exists in the
7
+ * linter's catalog and the project's config switches it on. Anything less — the
8
+ * check was turned off (`verify: false`, `verifyLinters: false`, a per-linter
9
+ * `false`, catalog-only), the config does not set the rule, or the config could
10
+ * not be read — renders as "**Claimed (unverified):**". A reader of the compiled
11
+ * file can then tell the two apart without the compile log.
12
+ *
13
+ * Zero imports: `validate.ts`, `rule-routing.ts` and the CLI read these markers
14
+ * too, and some of them are browser-safe.
15
+ */
16
+ /** A reference `compile` verified: exists, and the project's config runs it. */
17
+ export declare const ENFORCED_MARKER = "**Enforced by:**";
18
+ /** A reference `compile` did not or could not verify. */
19
+ export declare const CLAIMED_MARKER = "**Claimed (unverified):**";
20
+ /**
21
+ * Either marker followed by its backticked reference, anywhere in a line.
22
+ * Group 1 is the marker's label (`Enforced by` / `Claimed (unverified)`),
23
+ * group 2 the reference.
24
+ */
25
+ export declare const RULE_MARKER_SOURCE: string;
26
+ /** Is the label captured by {@link RULE_MARKER_SOURCE} the verified one? */
27
+ export declare function isVerifiedLabel(label: string): boolean;
28
+ //# sourceMappingURL=enforce-marker.d.ts.map
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * The two ways a compiled instruction file can state an `enforce()` reference,
4
+ * spelled in ONE place so the renderer and every reader agree (#351).
5
+ *
6
+ * "**Enforced by:**" is the product's promise that a machine holds the rule, so
7
+ * `compile` writes it only for a reference it VERIFIED: the rule exists in the
8
+ * linter's catalog and the project's config switches it on. Anything less — the
9
+ * check was turned off (`verify: false`, `verifyLinters: false`, a per-linter
10
+ * `false`, catalog-only), the config does not set the rule, or the config could
11
+ * not be read — renders as "**Claimed (unverified):**". A reader of the compiled
12
+ * file can then tell the two apart without the compile log.
13
+ *
14
+ * Zero imports: `validate.ts`, `rule-routing.ts` and the CLI read these markers
15
+ * too, and some of them are browser-safe.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.RULE_MARKER_SOURCE = exports.CLAIMED_MARKER = exports.ENFORCED_MARKER = void 0;
19
+ exports.isVerifiedLabel = isVerifiedLabel;
20
+ /** A reference `compile` verified: exists, and the project's config runs it. */
21
+ exports.ENFORCED_MARKER = "**Enforced by:**";
22
+ /** A reference `compile` did not or could not verify. */
23
+ exports.CLAIMED_MARKER = "**Claimed (unverified):**";
24
+ /**
25
+ * Either marker followed by its backticked reference, anywhere in a line.
26
+ * Group 1 is the marker's label (`Enforced by` / `Claimed (unverified)`),
27
+ * group 2 the reference.
28
+ */
29
+ exports.RULE_MARKER_SOURCE = /\*\*(Enforced by|Claimed \(unverified\)):\*\*\s*`([^`]+)`/.source;
30
+ /** Is the label captured by {@link RULE_MARKER_SOURCE} the verified one? */
31
+ function isVerifiedLabel(label) {
32
+ return label === "Enforced by";
33
+ }
34
+ //# sourceMappingURL=enforce-marker.js.map
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The ONE answer to "which ESLint rules exist in this repo, which are enabled
3
+ * where, and did the config load" — read from ESLint itself.
4
+ *
5
+ * Three callers used to ask ESLint three different ways, and each saw a
6
+ * different repo: `generate types` probed one representative path per
7
+ * (directory, extension) (#352), `enforce()` verification probed one path per
8
+ * config block `files` glob (#351), and the routing catalog asked about
9
+ * `src/index.ts` alone. The same rule could be enabled for one caller and
10
+ * absent for another. This module owns the probe; every caller reads the same
11
+ * {@link EslintInventory}.
12
+ *
13
+ * This module is the pure half (the probe's script, its wire schema, and every
14
+ * question asked of the answer); `eslintInventory` in `linters.ts` runs the
15
+ * probe beside the other linter subprocesses, cached per project.
16
+ *
17
+ * HOW: a child `node -e` process runs in the project's directory, so `eslint`
18
+ * and the config's own imports resolve from the project exactly as `npx eslint`
19
+ * would, and nothing of the project's config loads into our process. The child
20
+ * lists the config's blocks (plugins they register, rules they mention, a probe
21
+ * path synthesized from each `files` glob), adds one representative path per
22
+ * (directory, name shape) of the project's files, and asks
23
+ * `calculateConfigForFile` for each. A rule is `enabled` if any probe has it on,
24
+ * `disabled` if every probe that sets it has it off.
25
+ *
26
+ * LIMITS, named: a block naming one exact file that shares directory and
27
+ * extension with siblings is reached only if its glob synthesizes that path; a
28
+ * config in a SUBDIRECTORY (v10 looks configs up per file) is not listed; a
29
+ * `files` entry given as a function is not probed. A rule that a block mentions
30
+ * but no probe reached reads `unknown`, never `unconfigured`.
31
+ */
32
+ import type { ConfigEnabledStatus } from "./linter-adapter.js";
33
+ /** What ESLint's resolved config says about the repo, when it loaded. */
34
+ export interface EslintConfigView {
35
+ /** ESLint's built-in rule ids. */
36
+ readonly core: ReadonlySet<string>;
37
+ /** plugin namespace → its rule names, from the plugin objects ESLint holds. */
38
+ readonly plugins: ReadonlyMap<string, ReadonlySet<string>>;
39
+ /** rule id → state, merged over every probe path. */
40
+ readonly rules: ReadonlyMap<string, "enabled" | "disabled">;
41
+ /** rule ids some block sets, whether or not a probe reached that block. */
42
+ readonly mentioned: ReadonlySet<string>;
43
+ /** every block of the config was listed, so an unmentioned rule is unset. */
44
+ readonly complete: boolean;
45
+ }
46
+ /**
47
+ * The inventory. Separate members because each is a different instruction to
48
+ * the user: no ESLint, no config, a config that throws, a config that loads and
49
+ * switches nothing on, a config with rules.
50
+ */
51
+ export type EslintInventory = {
52
+ readonly kind: "not-installed";
53
+ } | {
54
+ readonly kind: "no-config";
55
+ } | {
56
+ readonly kind: "failed";
57
+ readonly error: string;
58
+ } | {
59
+ readonly kind: "empty";
60
+ readonly view: EslintConfigView;
61
+ } | {
62
+ readonly kind: "loaded";
63
+ readonly view: EslintConfigView;
64
+ };
65
+ /**
66
+ * One representative path per distinct (directory, name shape) pair of the
67
+ * project's files. A flat-config `files` glob selects on the directory and the
68
+ * name's extension chain, so one file per pair sees every block any file of
69
+ * that pair could match — without linting a single file.
70
+ */
71
+ export declare function representativePaths(files: readonly string[]): readonly string[];
72
+ /** The files `representativePaths` picks from: everything but dependencies and git. */
73
+ export declare const PROJECT_FILES_GLOB: {
74
+ readonly pattern: "**/*";
75
+ readonly ignore: readonly ["**/node_modules/**", "**/.git/**"];
76
+ };
77
+ export declare const ESLINT_PROBE_SCRIPT: string;
78
+ /**
79
+ * Turn the child's stdout into an inventory. Pure, so every shape of answer is
80
+ * testable without spawning ESLint. @internal
81
+ */
82
+ export declare function parseEslintInventory(stdout: string): EslintInventory;
83
+ /** The view, when the config loaded (with or without enabled rules). */
84
+ export declare function configView(inv: EslintInventory): EslintConfigView | null;
85
+ /** Ids of every rule the config switches on somewhere, sorted. */
86
+ export declare function enabledRuleIds(view: EslintConfigView): readonly string[];
87
+ /** Enabled state of one rule id across every block of the config. */
88
+ export declare function eslintRuleState(inv: EslintInventory, ruleId: string): ConfigEnabledStatus;
89
+ /**
90
+ * Does the plugin the config REGISTERS under `ruleId`'s namespace define the
91
+ * rule? null when the config registers no plugin under that namespace (or did
92
+ * not load). The namespace is everything before the LAST slash — ESLint's own
93
+ * split, so `@scope/plugin/rule` works.
94
+ */
95
+ export declare function registeredPluginHasRule(inv: EslintInventory, ruleId: string): boolean | null;
96
+ //# sourceMappingURL=eslint-inventory.d.ts.map