vigiles 2.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 (156) hide show
  1. package/.claude/settings.json +46 -0
  2. package/.claude/settings.local.json +8 -0
  3. package/.claude-plugin/hooks/post-edit.sh +34 -0
  4. package/.claude-plugin/hooks/pre-edit.sh +40 -0
  5. package/.claude-plugin/hooks/session-start.sh +38 -0
  6. package/.claude-plugin/marketplace.json +14 -0
  7. package/.claude-plugin/plugin.json +47 -0
  8. package/.github/workflows/ci.yml +81 -0
  9. package/.prettierignore +1 -0
  10. package/.vigiles/generated.d.ts +205 -0
  11. package/CLAUDE.md +95 -0
  12. package/CLAUDE.md.spec.ts +142 -0
  13. package/CONTRIBUTING.md +121 -0
  14. package/LICENSE +21 -0
  15. package/README.md +377 -0
  16. package/action.yml +25 -0
  17. package/dist/action.d.ts +7 -0
  18. package/dist/action.d.ts.map +1 -0
  19. package/dist/action.js +180 -0
  20. package/dist/action.js.map +1 -0
  21. package/dist/cli.d.ts +12 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +1267 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/cli.test.d.ts +2 -0
  26. package/dist/cli.test.d.ts.map +1 -0
  27. package/dist/cli.test.js +650 -0
  28. package/dist/cli.test.js.map +1 -0
  29. package/dist/compile.d.ts +101 -0
  30. package/dist/compile.d.ts.map +1 -0
  31. package/dist/compile.js +503 -0
  32. package/dist/compile.js.map +1 -0
  33. package/dist/evolve.d.ts +132 -0
  34. package/dist/evolve.d.ts.map +1 -0
  35. package/dist/evolve.js +599 -0
  36. package/dist/evolve.js.map +1 -0
  37. package/dist/freshness.d.ts +67 -0
  38. package/dist/freshness.d.ts.map +1 -0
  39. package/dist/freshness.js +244 -0
  40. package/dist/freshness.js.map +1 -0
  41. package/dist/freshness.test.d.ts +2 -0
  42. package/dist/freshness.test.d.ts.map +1 -0
  43. package/dist/freshness.test.js +356 -0
  44. package/dist/freshness.test.js.map +1 -0
  45. package/dist/generate-types.d.ts +34 -0
  46. package/dist/generate-types.d.ts.map +1 -0
  47. package/dist/generate-types.js +381 -0
  48. package/dist/generate-types.js.map +1 -0
  49. package/dist/inline.d.ts +58 -0
  50. package/dist/inline.d.ts.map +1 -0
  51. package/dist/inline.js +142 -0
  52. package/dist/inline.js.map +1 -0
  53. package/dist/inline.test.d.ts +5 -0
  54. package/dist/inline.test.d.ts.map +1 -0
  55. package/dist/inline.test.js +152 -0
  56. package/dist/inline.test.js.map +1 -0
  57. package/dist/linters.d.ts +38 -0
  58. package/dist/linters.d.ts.map +1 -0
  59. package/dist/linters.js +588 -0
  60. package/dist/linters.js.map +1 -0
  61. package/dist/proofs.d.ts +272 -0
  62. package/dist/proofs.d.ts.map +1 -0
  63. package/dist/proofs.js +622 -0
  64. package/dist/proofs.js.map +1 -0
  65. package/dist/proofs.test.d.ts +9 -0
  66. package/dist/proofs.test.d.ts.map +1 -0
  67. package/dist/proofs.test.js +952 -0
  68. package/dist/proofs.test.js.map +1 -0
  69. package/dist/spec.d.ts +258 -0
  70. package/dist/spec.d.ts.map +1 -0
  71. package/dist/spec.js +113 -0
  72. package/dist/spec.js.map +1 -0
  73. package/dist/spec.test.d.ts +2 -0
  74. package/dist/spec.test.d.ts.map +1 -0
  75. package/dist/spec.test.js +1222 -0
  76. package/dist/spec.test.js.map +1 -0
  77. package/dist/types.d.ts +101 -0
  78. package/dist/types.d.ts.map +1 -0
  79. package/dist/types.js +3 -0
  80. package/dist/types.js.map +1 -0
  81. package/dist/validate.d.ts +10 -0
  82. package/dist/validate.d.ts.map +1 -0
  83. package/dist/validate.js +286 -0
  84. package/dist/validate.js.map +1 -0
  85. package/dist/validate.test.d.ts +2 -0
  86. package/dist/validate.test.d.ts.map +1 -0
  87. package/dist/validate.test.js +531 -0
  88. package/dist/validate.test.js.map +1 -0
  89. package/docs/agent-setup.md +85 -0
  90. package/docs/agent-workflows.md +103 -0
  91. package/docs/comparison.md +71 -0
  92. package/docs/freshness.md +124 -0
  93. package/docs/inline-mode.md +119 -0
  94. package/docs/linter-support.md +166 -0
  95. package/docs/spec-format.md +194 -0
  96. package/eslint.config.mjs +79 -0
  97. package/examples/CLAUDE.md +54 -0
  98. package/examples/CLAUDE.md.spec.ts +65 -0
  99. package/examples/SKILL.md +50 -0
  100. package/examples/SKILL.md.spec.ts +57 -0
  101. package/fixtures/example-project/CLAUDE.md +11 -0
  102. package/fixtures/example-project/package.json +9 -0
  103. package/fixtures/example-project/src/index.ts +3 -0
  104. package/fixtures/example-project/src/utils.test.ts +2 -0
  105. package/fixtures/example-project/src/utils.ts +3 -0
  106. package/logo.png +0 -0
  107. package/package.json +42 -0
  108. package/research/adoption-strategy.md +111 -0
  109. package/research/agent-integration.md +145 -0
  110. package/research/ai-code-quality.md +197 -0
  111. package/research/code-search-for-agents.md +313 -0
  112. package/research/competitive-landscape.md +163 -0
  113. package/research/doc-freshness.md +516 -0
  114. package/research/executable-specs.md +368 -0
  115. package/research/feature-ideas.md +464 -0
  116. package/research/formal-proofs-for-agents.md +338 -0
  117. package/research/fp-for-agent-harness.md +150 -0
  118. package/research/fp-for-deterministic-ai.md +131 -0
  119. package/research/self-evolving-specs.md +298 -0
  120. package/schemas/claude-md-strict.yml +18 -0
  121. package/schemas/claude-md.yml +6 -0
  122. package/schemas/skill-strict.yml +12 -0
  123. package/schemas/skill.yml +5 -0
  124. package/skills/audit-feedback-loop/SKILL.md +76 -0
  125. package/skills/edit-spec/SKILL.md +131 -0
  126. package/skills/enforce-rules-format/SKILL.md +71 -0
  127. package/skills/generate-logo/SKILL.md +102 -0
  128. package/skills/generate-rule/SKILL.md +90 -0
  129. package/skills/linter-docs/clippy.md +241 -0
  130. package/skills/linter-docs/eslint.md +384 -0
  131. package/skills/linter-docs/pylint.md +288 -0
  132. package/skills/linter-docs/rubocop.md +277 -0
  133. package/skills/linter-docs/ruff.md +187 -0
  134. package/skills/linter-docs/stylelint.md +247 -0
  135. package/skills/migrate-to-spec/SKILL.md +124 -0
  136. package/skills/pr-to-lint-rule/SKILL.md +97 -0
  137. package/skills/strengthen/SKILL.md +168 -0
  138. package/src/action.ts +214 -0
  139. package/src/cli.test.ts +914 -0
  140. package/src/cli.ts +1631 -0
  141. package/src/compile.ts +691 -0
  142. package/src/evolve.ts +781 -0
  143. package/src/freshness.test.ts +449 -0
  144. package/src/freshness.ts +299 -0
  145. package/src/generate-types.ts +448 -0
  146. package/src/inline.test.ts +206 -0
  147. package/src/inline.ts +164 -0
  148. package/src/linters.ts +739 -0
  149. package/src/proofs.test.ts +1314 -0
  150. package/src/proofs.ts +849 -0
  151. package/src/spec.test.ts +1471 -0
  152. package/src/spec.ts +427 -0
  153. package/src/types.ts +117 -0
  154. package/src/validate.test.ts +701 -0
  155. package/src/validate.ts +381 -0
  156. package/tsconfig.json +23 -0
package/src/spec.ts ADDED
@@ -0,0 +1,427 @@
1
+ /**
2
+ * vigiles v2 — Executable specification system.
3
+ *
4
+ * Specs are TypeScript files that compile to instruction files (CLAUDE.md, SKILL.md).
5
+ * The spec is the source of truth. The markdown is a build artifact.
6
+ *
7
+ * Two rule types:
8
+ * enforce() — delegated to an external linter (ESLint, Ruff, Clippy, etc.)
9
+ * guidance() — prose only, no mechanical enforcement
10
+ */
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Template literal types for type-safe linter references
14
+ // ---------------------------------------------------------------------------
15
+
16
+ /** Linters vigiles can cross-reference. */
17
+ type BuiltinLinter =
18
+ | "eslint"
19
+ | "stylelint"
20
+ | "ruff"
21
+ | "clippy"
22
+ | "pylint"
23
+ | "rubocop";
24
+
25
+ /** Scoped ESLint plugin prefix (e.g., @typescript-eslint). */
26
+ type ScopedPlugin = `@${string}/${string}`;
27
+
28
+ /** A linter/rule reference: "eslint/no-console", "ruff/T201", "@typescript-eslint/no-explicit-any". */
29
+ export type LinterRule = `${BuiltinLinter}/${string}` | ScopedPlugin;
30
+
31
+ /** Vigiles-proven rule reference: "vigiles/<assertion-id>". */
32
+ export type VigilesRef = `vigiles/${string}`;
33
+
34
+ /** Any enforcement reference. */
35
+ export type EnforcementRef = LinterRule | VigilesRef;
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Type augmentation points for generate-types (#1 + #6)
39
+ //
40
+ // When `vigiles generate-types` runs, it emits a .d.ts that populates these
41
+ // interfaces via declaration merging. This narrows enforce(), file(), and
42
+ // cmd() signatures from "any string" to "only known valid references."
43
+ //
44
+ // Without generated types: interfaces are empty, strict types fall back to
45
+ // broad LinterRule / string. No change in behavior.
46
+ //
47
+ // With generated types: enforce("eslint/no-consolee") → type error in editor.
48
+ // ---------------------------------------------------------------------------
49
+
50
+ /**
51
+ * Populated by generate-types with per-linter rule unions.
52
+ * Keys are linter prefixes ("eslint", "@typescript-eslint", "ruff", etc.),
53
+ * values are unions of enabled rule names.
54
+ */
55
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
56
+ export interface KnownLinterRules {}
57
+
58
+ /**
59
+ * Populated by generate-types with project file paths.
60
+ * Single key "files" maps to a union of relative paths.
61
+ */
62
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
63
+ export interface KnownProjectFiles {}
64
+
65
+ /**
66
+ * Populated by generate-types with npm script names.
67
+ * Single key "scripts" maps to a union of script names.
68
+ */
69
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
70
+ export interface KnownNpmScripts {}
71
+
72
+ /**
73
+ * When KnownLinterRules is populated, narrows to exact rule unions.
74
+ * Falls back to broad LinterRule when no generated types exist.
75
+ */
76
+ export type StrictLinterRule = [keyof KnownLinterRules] extends [never]
77
+ ? LinterRule
78
+ : {
79
+ [K in keyof KnownLinterRules]: `${K & string}/${KnownLinterRules[K] & string}`;
80
+ }[keyof KnownLinterRules];
81
+
82
+ /**
83
+ * When KnownProjectFiles is populated, narrows file() to known paths.
84
+ * Falls back to string when no generated types exist.
85
+ */
86
+ /* eslint-disable @typescript-eslint/no-redundant-type-constituents, @typescript-eslint/no-duplicate-type-constituents */
87
+ export type StrictFile = [keyof KnownProjectFiles] extends [never]
88
+ ? string
89
+ : KnownProjectFiles[keyof KnownProjectFiles] & string;
90
+
91
+ export type StrictCmd = [keyof KnownNpmScripts] extends [never]
92
+ ? string
93
+ :
94
+ | `npm run ${KnownNpmScripts[keyof KnownNpmScripts] & string}`
95
+ | `npm ${KnownNpmScripts[keyof KnownNpmScripts] & string}`
96
+ | (string & {}); // escape hatch for non-npm commands
97
+ /* eslint-enable @typescript-eslint/no-redundant-type-constituents, @typescript-eslint/no-duplicate-type-constituents */
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Claude Code tool types (for hook validation)
101
+ // ---------------------------------------------------------------------------
102
+
103
+ export type ClaudeTool =
104
+ | "Read"
105
+ | "Write"
106
+ | "Edit"
107
+ | "Bash"
108
+ | "Grep"
109
+ | "Glob"
110
+ | "Agent"
111
+ | "TodoWrite"
112
+ | "WebSearch"
113
+ | "WebFetch"
114
+ | "NotebookEdit";
115
+
116
+ export type HookEvent =
117
+ | "PreToolUse"
118
+ | "PostToolUse"
119
+ | "PreSession"
120
+ | "PostSession"
121
+ | "Notification";
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Rule types
125
+ // ---------------------------------------------------------------------------
126
+
127
+ /** A rule delegated to an external tool (linter, ast-grep, dependency-cruiser, etc.). */
128
+ export interface EnforceRule {
129
+ readonly _kind: "enforce";
130
+ readonly linterRule: LinterRule;
131
+ readonly why: string;
132
+ /** Skip linter verification for this rule. Default: true (verify). */
133
+ readonly verify: boolean;
134
+ }
135
+
136
+ /** A guidance-only rule (prose, no enforcement). */
137
+ export interface GuidanceRule {
138
+ readonly _kind: "guidance";
139
+ readonly text: string;
140
+ }
141
+
142
+ export type Rule = EnforceRule | GuidanceRule;
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Builder functions
146
+ // ---------------------------------------------------------------------------
147
+
148
+ /**
149
+ * Declare a rule enforced by an external tool.
150
+ *
151
+ * When generated types are present, the `linterRule` argument is narrowed
152
+ * to only accept rules that exist in your linter configs.
153
+ *
154
+ * enforce("eslint/no-console", "Use structured logger.")
155
+ * enforce("@typescript-eslint/no-floating-promises", "Always await.")
156
+ * enforce("ruff/T201", "Use logging module.")
157
+ */
158
+ export function enforce(
159
+ linterRule: NoInfer<StrictLinterRule>,
160
+ why: string,
161
+ options?: { verify?: boolean },
162
+ ): EnforceRule {
163
+ return {
164
+ _kind: "enforce",
165
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
166
+ linterRule: linterRule as LinterRule,
167
+ why,
168
+ verify: options?.verify ?? true,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Declare a guidance-only rule.
174
+ *
175
+ * guidance("Google unfamiliar APIs before implementing.")
176
+ */
177
+ export function guidance(text: string): GuidanceRule {
178
+ return { _kind: "guidance", text };
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Reference helpers for skill instructions
183
+ // ---------------------------------------------------------------------------
184
+
185
+ /**
186
+ * Branded string types — these prove a reference has gone through
187
+ * vigiles's verification. The compiler only accepts branded refs,
188
+ * not raw strings, for path-sensitive positions.
189
+ */
190
+ declare const __brand: unique symbol;
191
+ export type VerifiedPath = string & { readonly [__brand]: "VerifiedPath" };
192
+ export type VerifiedCmd = string & { readonly [__brand]: "VerifiedCmd" };
193
+ export type VerifiedRef = string & { readonly [__brand]: "VerifiedRef" };
194
+
195
+ /** A typed file reference — verified at compile time. */
196
+ export interface FileRef {
197
+ readonly _ref: "file";
198
+ readonly path: VerifiedPath;
199
+ }
200
+
201
+ /** A typed command reference — verified at compile time. */
202
+ export interface CmdRef {
203
+ readonly _ref: "cmd";
204
+ readonly command: VerifiedCmd;
205
+ }
206
+
207
+ /** A typed cross-reference to another instruction file/skill. */
208
+ export interface SkillRef {
209
+ readonly _ref: "skill";
210
+ readonly path: VerifiedRef;
211
+ }
212
+
213
+ export type Ref = FileRef | CmdRef | SkillRef;
214
+
215
+ /**
216
+ * Reference a file path — verified to exist at compile time.
217
+ * When generated types are present, narrowed to known project files.
218
+ */
219
+ export function file(path: NoInfer<StrictFile>): FileRef {
220
+ return { _ref: "file", path: path as VerifiedPath };
221
+ }
222
+
223
+ /**
224
+ * Reference a command — verified against package.json at compile time.
225
+ * When generated types are present, narrowed to known npm scripts.
226
+ */
227
+ export function cmd(command: NoInfer<StrictCmd>): CmdRef {
228
+ return { _ref: "cmd", command: command as VerifiedCmd };
229
+ }
230
+
231
+ /**
232
+ * Reference another skill or instruction file — verified to exist.
233
+ * Compiles to a markdown link: [skill name](path)
234
+ */
235
+ export function ref(path: string): SkillRef {
236
+ return { _ref: "skill", path: path as VerifiedRef };
237
+ }
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // Instruction template — process refs inside skill instructions
241
+ // ---------------------------------------------------------------------------
242
+
243
+ export type InstructionFragment = string | Ref;
244
+
245
+ /**
246
+ * Tagged template literal for skill instructions with typed references.
247
+ *
248
+ * instructions`
249
+ * Check ${file("eslint.config.ts")} for rules.
250
+ * Run ${cmd("npm test")} to verify.
251
+ * See ${ref("skills/other/SKILL.md")} for format.
252
+ * `
253
+ */
254
+ export function instructions(
255
+ strings: TemplateStringsArray,
256
+ ...values: InstructionFragment[]
257
+ ): InstructionFragment[] {
258
+ const result: InstructionFragment[] = [];
259
+ for (let i = 0; i < strings.length; i++) {
260
+ if (strings[i]) result.push(strings[i]);
261
+ if (i < values.length) result.push(values[i]);
262
+ }
263
+ return result;
264
+ }
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // CLAUDE.md spec (#5 — conditional maxSectionLines)
268
+ // ---------------------------------------------------------------------------
269
+
270
+ /** Known markdown instruction file targets. */
271
+ export type InstructionTarget = "CLAUDE.md" | "AGENTS.md" | (string & {}); // escape hatch for custom targets
272
+
273
+ export interface ClaudeSpec {
274
+ readonly _specType: "claude";
275
+ /**
276
+ * Output filename(s). Defaults to "CLAUDE.md". Also used as the h1 heading.
277
+ * Pass an array to compile one spec to multiple targets (e.g., CLAUDE.md + AGENTS.md).
278
+ */
279
+ readonly target?: InstructionTarget | InstructionTarget[];
280
+ /** npm scripts / shell commands → descriptions. Verified against package.json. */
281
+ readonly commands?: Record<string, string>;
282
+ /** File paths → descriptions. Verified via existsSync. */
283
+ readonly keyFiles?: Record<string, string>;
284
+ /** Named prose sections — plain strings or tagged templates with file()/cmd()/ref(). */
285
+ readonly sections?: Record<string, string | InstructionFragment[]>;
286
+ /** Maximum lines per prose section (per-spec override). */
287
+ readonly maxSectionLines?: number;
288
+ /**
289
+ * Maximum estimated tokens for the compiled output (~4 chars per token).
290
+ * Compile fails if exceeded. Matches ETH Zurich 2511.12884 finding that
291
+ * files over ~300 lines / ~2000 tokens degrade agent task success.
292
+ */
293
+ readonly maxTokens?: number;
294
+ /** Rules: enforce(), check(), or guidance(). */
295
+ readonly rules: Record<string, Rule>;
296
+ }
297
+
298
+ /**
299
+ * Input type for claude() — maxSectionLines is only valid when sections are provided.
300
+ * TypeScript errors if you set maxSectionLines without defining sections.
301
+ */
302
+ type ClaudeSpecBase = {
303
+ readonly target?: InstructionTarget | InstructionTarget[];
304
+ readonly commands?: Record<string, string>;
305
+ readonly keyFiles?: Record<string, string>;
306
+ readonly maxTokens?: number;
307
+ readonly rules: Record<string, Rule>;
308
+ };
309
+
310
+ type ClaudeSpecSections =
311
+ | {
312
+ readonly sections: Record<string, string | InstructionFragment[]>;
313
+ readonly maxSectionLines?: number;
314
+ }
315
+ | { readonly sections?: undefined; readonly maxSectionLines?: never };
316
+
317
+ type ClaudeSpecInput = ClaudeSpecBase & ClaudeSpecSections;
318
+
319
+ /**
320
+ * Define a CLAUDE.md specification.
321
+ *
322
+ * // CLAUDE.md.spec.ts
323
+ * export default claude({ commands: {...}, rules: {...} });
324
+ */
325
+ export function claude(spec: ClaudeSpecInput): ClaudeSpec {
326
+ return { _specType: "claude", ...spec } as ClaudeSpec;
327
+ }
328
+
329
+ // ---------------------------------------------------------------------------
330
+ // SKILL.md spec
331
+ // ---------------------------------------------------------------------------
332
+
333
+ export interface SkillSpec {
334
+ readonly _specType: "skill";
335
+ /** Skill name (used in frontmatter). */
336
+ readonly name: string;
337
+ /** Short description (used in frontmatter). */
338
+ readonly description: string;
339
+ /** Hint for the argument (used in frontmatter). */
340
+ readonly argumentHint?: string;
341
+ /** Whether to disable model invocation (frontmatter flag). */
342
+ readonly disableModelInvocation?: boolean;
343
+ /** Instruction body — string or tagged template with typed refs. */
344
+ readonly body: string | InstructionFragment[];
345
+ }
346
+
347
+ /**
348
+ * Define a SKILL.md specification.
349
+ *
350
+ * // skills/my-skill/SKILL.md.spec.ts
351
+ * export default skill({ name: "my-skill", description: "...", body: "..." });
352
+ */
353
+ export function skill(spec: Omit<SkillSpec, "_specType">): SkillSpec {
354
+ return { _specType: "skill", ...spec };
355
+ }
356
+
357
+ // ---------------------------------------------------------------------------
358
+ // Spec file naming convention (#11)
359
+ //
360
+ // Type-level proof that a spec filename maps to its output.
361
+ // SpecPath<"CLAUDE.md"> = "CLAUDE.md.spec.ts"
362
+ // ---------------------------------------------------------------------------
363
+
364
+ /** Derive the spec filename from an output filename. */
365
+ export type SpecPath<Output extends `${string}.md`> = `${Output}.spec.ts`;
366
+
367
+ /** Extract the output filename from a spec filename. */
368
+ export type OutputPath<Spec extends `${string}.md.spec.ts`> =
369
+ Spec extends `${infer Base}.spec.ts` ? Base : never;
370
+
371
+ // ---------------------------------------------------------------------------
372
+ // Compile pipeline phantom types (#7)
373
+ //
374
+ // Branded stages track which validations have been applied.
375
+ // The compiler can only emit markdown from a fully-validated spec.
376
+ // ---------------------------------------------------------------------------
377
+
378
+ declare const __stage: unique symbol;
379
+
380
+ /** A spec that hasn't been validated yet. */
381
+ export type RawSpec<T extends ClaudeSpec | SkillSpec = ClaudeSpec> = T & {
382
+ readonly [__stage]: "raw";
383
+ };
384
+
385
+ /** A spec whose file/cmd/ref references have been validated. */
386
+ export type RefsValidated<T extends ClaudeSpec | SkillSpec = ClaudeSpec> = T & {
387
+ readonly [__stage]: "refs-validated";
388
+ };
389
+
390
+ /** A spec whose linter rules have been cross-referenced. */
391
+ export type LintersVerified<T extends ClaudeSpec | SkillSpec = ClaudeSpec> =
392
+ T & {
393
+ readonly [__stage]: "linters-verified";
394
+ };
395
+
396
+ /** A fully validated spec, ready for markdown emission. */
397
+ export type ReadyToEmit<T extends ClaudeSpec | SkillSpec = ClaudeSpec> = T & {
398
+ readonly [__stage]: "ready";
399
+ };
400
+
401
+ // ---------------------------------------------------------------------------
402
+ // Project-level config
403
+ // ---------------------------------------------------------------------------
404
+
405
+ /** Per-linter verification mode. */
406
+ export type LinterMode = boolean | "catalog-only";
407
+
408
+ export interface VigilesV2Config {
409
+ /** Glob pattern to discover spec files. Default: "**\/*.spec.ts" */
410
+ readonly specs?: string;
411
+ /** Auto-discover linter rules for coverage reporting. */
412
+ readonly discover?: boolean;
413
+ /** Maximum rules per spec file. */
414
+ readonly maxRules?: number;
415
+ /** Maximum estimated tokens for compiled output. ~4 chars per token. */
416
+ readonly maxTokens?: number;
417
+ /** Maximum lines per prose section. Forces splitting into named sections. */
418
+ readonly maxSectionLines?: number;
419
+ /** Global kill switch: skip ALL linter verification during compile. */
420
+ readonly verifyLinters?: boolean;
421
+ /** Per-linter verification mode: true (full), "catalog-only", or false (skip). */
422
+ readonly linters?: Record<string, LinterMode>;
423
+ }
424
+
425
+ export function defineConfig(config: VigilesV2Config): VigilesV2Config {
426
+ return config;
427
+ }
package/src/types.ts ADDED
@@ -0,0 +1,117 @@
1
+ /** A parsed rule from a markdown instruction file. */
2
+ export interface ParsedRule {
3
+ title: string;
4
+ line: number;
5
+ enforcement: "enforced" | "guidance" | "disabled" | "missing";
6
+ enforcedBy: string | null;
7
+ }
8
+
9
+ /** A validation error produced by a rule check. */
10
+ export interface ValidationError {
11
+ rule: string;
12
+ message: string;
13
+ line: number;
14
+ }
15
+
16
+ /** Result of validating a single file's content. */
17
+ export interface ValidationResult {
18
+ rules: ParsedRule[];
19
+ enforced: number;
20
+ guidanceOnly: number;
21
+ disabled: number;
22
+ missing: number;
23
+ total: number;
24
+ errors: ValidationError[];
25
+ warnings: ValidationError[];
26
+ valid: boolean;
27
+ }
28
+
29
+ /** Result of reading a file (may be skipped due to symlinks). */
30
+ export interface ReadResult {
31
+ content: string | null;
32
+ skipped: boolean;
33
+ reason: string | null;
34
+ }
35
+
36
+ /** Result of validating a single file path. */
37
+ export interface FileResult {
38
+ path: string;
39
+ skipped: boolean;
40
+ reason: string | null;
41
+ result: ValidationResult | null;
42
+ }
43
+
44
+ /** Combined result of validating multiple file paths. */
45
+ export interface ValidatePathsResult {
46
+ fileResults: FileResult[];
47
+ valid: boolean;
48
+ }
49
+
50
+ /** Toggleable rule settings. */
51
+ /** Rule severity: "warn" prints but exits 0, "error" fails, false disables. */
52
+ export type RuleSeverity = "warn" | "error" | false;
53
+
54
+ /** Freshness detection mode for the `freshness` rule. */
55
+ export type FreshnessMode = "strict" | "input-hash" | "output-hash";
56
+
57
+ export interface RulesConfig {
58
+ /** Require .spec.ts for CLAUDE.md / AGENTS.md. Default: "warn". */
59
+ "require-spec"?: RuleSeverity;
60
+ /** Require .spec.ts for SKILL.md files. Default: false. */
61
+ "require-skill-spec"?: RuleSeverity;
62
+ /** Detect stale compiled output. Default: "warn". */
63
+ freshness?: RuleSeverity;
64
+ }
65
+
66
+ /** Full vigiles configuration. Loaded from .vigilesrc.json. */
67
+ export interface VigilesConfig {
68
+ // --- Validation ---
69
+ ruleMarkers: MarkerType[];
70
+ rules: Required<RulesConfig>;
71
+ files: string[];
72
+
73
+ // --- Compilation ---
74
+ /** Maximum number of rules allowed per spec. */
75
+ maxRules?: number;
76
+ /** Maximum estimated tokens for compiled output. */
77
+ maxTokens?: number;
78
+ /** Maximum lines per prose section. */
79
+ maxSectionLines?: number;
80
+ /** Skip config-enabled checks, only verify rule exists in catalog. */
81
+ catalogOnly?: boolean;
82
+ /** Custom linter configs (rulesDir). */
83
+ linters?: Record<string, { rulesDir?: string | string[] }>;
84
+
85
+ // --- Freshness ---
86
+ /** How to detect staleness. Default: "strict" (recompile and diff). */
87
+ freshnessMode?: FreshnessMode;
88
+ /** Extra files to track in input-hash mode (e.g., monorepo root lock file). */
89
+ freshnessInputs?: string[];
90
+ }
91
+
92
+ /** Valid marker types for rule detection. */
93
+ export type MarkerType = "headings" | "checkboxes";
94
+
95
+ /** Options for parseRules. */
96
+ export interface ParseOptions {
97
+ ruleMarkers?: MarkerType[];
98
+ }
99
+
100
+ /** Options for validate(). */
101
+ export interface ValidateOptions {
102
+ ruleMarkers?: MarkerType[];
103
+ rules?: RulesConfig;
104
+ filePath?: string;
105
+ }
106
+
107
+ /** Options for validatePaths(). */
108
+ export interface ValidatePathsOptions {
109
+ followSymlinks?: boolean;
110
+ ruleMarkers?: MarkerType[];
111
+ rules?: RulesConfig;
112
+ }
113
+
114
+ /** Options for readInstructionFile(). */
115
+ export interface ReadOptions {
116
+ followSymlinks?: boolean;
117
+ }