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
@@ -0,0 +1,194 @@
1
+ # Spec Format Reference
2
+
3
+ vigiles specs are TypeScript files (`*.spec.ts`) that compile to markdown instruction files. The spec is the source of truth; the markdown is a build artifact.
4
+
5
+ ## CLAUDE.md Specs
6
+
7
+ Use `claude()` to define a CLAUDE.md spec. Export it as the default export.
8
+
9
+ ```ts
10
+ import { claude, enforce, guidance, file, cmd, ref, instructions } from "vigiles";
11
+
12
+ export default claude({
13
+ target: "CLAUDE.md", // or "AGENTS.md", or ["CLAUDE.md", "AGENTS.md"]
14
+ sections: { ... },
15
+ keyFiles: { ... },
16
+ commands: { ... },
17
+ rules: { ... },
18
+ maxSectionLines: 30, // optional: cap per-section line count
19
+ });
20
+ ```
21
+
22
+ ### `target`
23
+
24
+ `string | string[]` -- Output filename(s). Defaults to `"CLAUDE.md"`. Also used as the `# Heading` in compiled output. Pass an array to compile one spec to multiple targets:
25
+
26
+ ```ts
27
+ target: ["CLAUDE.md", "AGENTS.md"], // emits both from one spec
28
+ ```
29
+
30
+ ### `sections`
31
+
32
+ `Record<string, string | InstructionFragment[]>` -- Named prose sections. Each key becomes a `## Heading` in the compiled output (first letter uppercased). Values are either plain strings or tagged templates via `instructions` with embedded `file()`, `cmd()`, and `ref()` references.
33
+
34
+ ```ts
35
+ sections: {
36
+ architecture: `Two rule types: enforce() and guidance().`,
37
+ setup: instructions`See ${file("docs/setup.md")} and run ${cmd("npm install")}.`,
38
+ }
39
+ ```
40
+
41
+ ### `keyFiles`
42
+
43
+ `Record<string, string>` -- File paths mapped to descriptions. Each path is verified via `existsSync` at compile time. Compiles to a bullet list under `## Key Files`.
44
+
45
+ ```ts
46
+ keyFiles: {
47
+ "src/spec.ts": "Type system and builder functions",
48
+ "src/compile.ts": "Compiler: spec to markdown with SHA-256 hash",
49
+ }
50
+ ```
51
+
52
+ ### `commands`
53
+
54
+ `Record<string, string>` -- Commands mapped to descriptions. `npm run <script>` and `npm <lifecycle>` commands are verified against `package.json` scripts at compile time. Compiles to a bullet list under `## Commands`.
55
+
56
+ ```ts
57
+ commands: {
58
+ "npm run build": "Compile TypeScript to dist/",
59
+ "npm test": "Build and run all tests",
60
+ }
61
+ ```
62
+
63
+ ### `rules`
64
+
65
+ `Record<string, Rule>` -- Rule ID mapped to an `enforce()` or `guidance()` rule. The ID is kebab-cased by convention and is converted to a Title Case `### Heading` in compiled output. See [Rule Types](#rule-types) below.
66
+
67
+ ## SKILL.md Specs
68
+
69
+ Use `skill()` to define a SKILL.md spec. Compiles to markdown with YAML frontmatter.
70
+
71
+ ```ts
72
+ import { skill, file, cmd, ref, instructions } from "vigiles";
73
+
74
+ export default skill({
75
+ name: "pr-to-lint-rule",
76
+ description: "Convert PR feedback into an automated lint rule",
77
+ argumentHint: "<description of recurring PR feedback>",
78
+ disableModelInvocation: true,
79
+ body: instructions`
80
+ Check ${file("eslint.config.ts")} for existing rules.
81
+ Run ${cmd("npm test")} to verify.
82
+ See ${ref("skills/other/SKILL.md")} for format.
83
+ `,
84
+ });
85
+ ```
86
+
87
+ | Field | Type | Required | Description |
88
+ | ------------------------ | --------------------------------- | -------- | --------------------------------------------------- |
89
+ | `name` | `string` | yes | Skill name (used in YAML frontmatter) |
90
+ | `description` | `string` | yes | Short description (frontmatter) |
91
+ | `argumentHint` | `string` | no | Hint for the argument (frontmatter) |
92
+ | `disableModelInvocation` | `boolean` | no | Disable model invocation flag (frontmatter) |
93
+ | `body` | `string \| InstructionFragment[]` | yes | Instruction body -- plain string or tagged template |
94
+
95
+ ## Reference Helpers
96
+
97
+ Reference helpers create branded types that the compiler validates at compile time.
98
+
99
+ ### `file(path)`
100
+
101
+ Returns a `FileRef` containing a `VerifiedPath`. The path is verified to exist via `existsSync` at compile time. Compiles to a backtick path in markdown: `` `path/to/file.ts` ``.
102
+
103
+ ### `cmd(command)`
104
+
105
+ Returns a `CmdRef` containing a `VerifiedCmd`. For npm commands, the script is verified against `package.json` at compile time. Compiles to a backtick command: `` `npm run build` ``.
106
+
107
+ ### `ref(path)`
108
+
109
+ Returns a `SkillRef` containing a `VerifiedRef`. The path is verified to exist at compile time. Compiles to a markdown link: `[dirname](path)`.
110
+
111
+ ### `instructions`
112
+
113
+ Tagged template literal that interleaves strings and refs. Use it for `sections` values in `claude()` or the `body` of `skill()`.
114
+
115
+ ```ts
116
+ instructions`Check ${file("tsconfig.json")} then run ${cmd("npm test")}.`;
117
+ // Returns InstructionFragment[] -- the compiler renders and validates each ref.
118
+ ```
119
+
120
+ ## Rule Types
121
+
122
+ ### `enforce(linterRule, why)`
123
+
124
+ Declares a rule delegated to an external linter. The `linterRule` accepts template literal types:
125
+
126
+ - `${BuiltinLinter}/${string}` where BuiltinLinter is `eslint`, `stylelint`, `ruff`, `clippy`, `pylint`, or `rubocop`
127
+ - `@${scope}/${rule}` for scoped ESLint plugins (e.g., `@typescript-eslint/no-explicit-any`)
128
+
129
+ At compile time, vigiles verifies the rule exists in the linter's catalog and is enabled in the project's config. Compiles to `**Enforced by:** ` followed by the linter rule in backticks.
130
+
131
+ ```ts
132
+ rules: {
133
+ "no-console-log": enforce("eslint/no-console", "Use structured logger."),
134
+ "no-print": enforce("ruff/T201", "Use logging module."),
135
+ }
136
+ ```
137
+
138
+ ### `guidance(text)`
139
+
140
+ Declares a prose-only rule with no mechanical enforcement. Guidance rules still participate in the monotonicity proof system: once a rule exists, it can be strengthened ( `guidance` → `enforce` ) but never weakened or removed without an explicit allowlist.
141
+
142
+ ```ts
143
+ rules: {
144
+ "prefer-composition": guidance("Prefer composition over inheritance."),
145
+ }
146
+ ```
147
+
148
+ Compiles to: `**Guidance only** -- <text>`.
149
+
150
+ ## Branded Types
151
+
152
+ `VerifiedPath`, `VerifiedCmd`, and `VerifiedRef` are branded string types (`string & { readonly [__brand]: "..." }`). They distinguish compiler-verified references from raw strings.
153
+
154
+ - `file()` produces `FileRef` containing `VerifiedPath`
155
+ - `cmd()` produces `CmdRef` containing `VerifiedCmd`
156
+ - `ref()` produces `SkillRef` containing `VerifiedRef`
157
+
158
+ The compiler only accepts these branded types in path-sensitive positions. This prevents passing unverified strings where a verified reference is expected -- the TypeScript compiler catches the error at authoring time.
159
+
160
+ ## Configuration
161
+
162
+ Create `vigiles.config.ts` with `defineConfig()`:
163
+
164
+ ```ts
165
+ import { defineConfig } from "vigiles";
166
+
167
+ export default defineConfig({
168
+ specs: "**/*.spec.ts", // glob pattern for spec discovery (default: "**/*.spec.ts")
169
+ discover: true, // auto-discover linter rules for coverage reporting
170
+ maxRules: 50, // maximum rules per spec file
171
+ maxTokens: 2000, // maximum estimated tokens for compiled output (~4 chars/token)
172
+ });
173
+ ```
174
+
175
+ | Option | Type | Description |
176
+ | ----------- | --------- | ------------------------------------------------------- |
177
+ | `specs` | `string` | Glob pattern to discover spec files |
178
+ | `discover` | `boolean` | Auto-discover linter rules for coverage reporting |
179
+ | `maxRules` | `number` | Compilation fails if a spec exceeds this rule count |
180
+ | `maxTokens` | `number` | Compilation fails if estimated tokens exceed this limit |
181
+
182
+ ## Hash Verification
183
+
184
+ Every compiled file starts with a SHA-256 integrity hash comment:
185
+
186
+ ```
187
+ <!-- vigiles:sha256:a1b2c3d4e5f67890 compiled from CLAUDE.md.spec.ts -->
188
+ ```
189
+
190
+ The hash covers the full compiled content (excluding the hash line itself), truncated to 16 hex characters.
191
+
192
+ ### `vigiles audit`
193
+
194
+ Verifies that each compiled file's hash matches its content, reports linter rule coverage gaps, and suggests guidance rules that could be upgraded to `enforce()`. If someone manually edits the markdown, the hash will no longer match, and `vigiles audit` reports the file as modified. This ensures the spec remains the source of truth.
@@ -0,0 +1,79 @@
1
+ import eslint from "@eslint/js";
2
+ import tseslint from "@typescript-eslint/eslint-plugin";
3
+ import tsparser from "@typescript-eslint/parser";
4
+ import sonarjs from "eslint-plugin-sonarjs";
5
+ import globals from "globals";
6
+
7
+ export default [
8
+ {
9
+ ignores: ["dist/", "node_modules/"],
10
+ },
11
+ eslint.configs.recommended,
12
+ {
13
+ files: ["src/**/*.ts"],
14
+ languageOptions: {
15
+ parser: tsparser,
16
+ globals: {
17
+ ...globals.node,
18
+ },
19
+ parserOptions: {
20
+ projectService: true,
21
+ tsconfigRootDir: import.meta.dirname,
22
+ },
23
+ },
24
+ plugins: {
25
+ "@typescript-eslint": tseslint,
26
+ sonarjs,
27
+ },
28
+ rules: {
29
+ ...tseslint.configs["strict-type-checked"]?.rules,
30
+ // TypeScript handles these better than ESLint
31
+ "no-undef": "off",
32
+ "no-unused-vars": "off",
33
+ // Allow unused vars prefixed with _
34
+ "@typescript-eslint/no-unused-vars": [
35
+ "error",
36
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
37
+ ],
38
+ // We use createRequire legitimately for linter detection
39
+ "@typescript-eslint/no-require-imports": "off",
40
+ // Relax some strict rules that are too noisy for this codebase
41
+ "@typescript-eslint/restrict-template-expressions": "off",
42
+ "@typescript-eslint/no-unnecessary-condition": "off",
43
+ // Ban non-null assertions — use proper narrowing instead
44
+ "@typescript-eslint/no-non-null-assertion": "error",
45
+
46
+ // --- Complexity rules ---
47
+ complexity: ["warn", { max: 15 }],
48
+ "max-depth": ["warn", { max: 4 }],
49
+ "max-lines-per-function": [
50
+ "warn",
51
+ { max: 80, skipBlankLines: true, skipComments: true },
52
+ ],
53
+ "max-params": ["warn", { max: 4 }],
54
+
55
+ // --- SonarJS ---
56
+ "sonarjs/cognitive-complexity": ["warn", 15],
57
+ "sonarjs/no-duplicate-string": ["warn", { threshold: 4 }],
58
+ "sonarjs/no-identical-functions": "warn",
59
+ "sonarjs/no-duplicated-branches": "warn",
60
+ "sonarjs/no-identical-conditions": "error",
61
+ "sonarjs/no-identical-expressions": "error",
62
+ "sonarjs/no-nested-conditional": "warn",
63
+ "sonarjs/nested-control-flow": ["warn", { maximumNestingLevel: 3 }],
64
+ },
65
+ },
66
+ // Test files: relax promise, assertion, and complexity rules
67
+ {
68
+ files: ["src/**/*.test.ts"],
69
+ rules: {
70
+ // node:test describe/it return promises that don't need to be awaited
71
+ "@typescript-eslint/no-floating-promises": "off",
72
+ "@typescript-eslint/no-unnecessary-type-assertion": "off",
73
+ // Tests are naturally longer and more repetitive
74
+ "max-lines-per-function": "off",
75
+ "sonarjs/no-duplicate-string": "off",
76
+ "sonarjs/no-identical-functions": "off",
77
+ },
78
+ },
79
+ ];
@@ -0,0 +1,54 @@
1
+ <!-- vigiles:sha256:f683550dbdfab721 compiled from examples/CLAUDE.md.spec.ts -->
2
+
3
+ # CLAUDE.md
4
+
5
+ ## Positioning
6
+
7
+ vigiles compiles `.spec.ts` files to instruction files (CLAUDE.md, AGENTS.md, or any markdown target). The spec is the source of truth. The markdown is a build artifact.
8
+
9
+ The linter cross-referencing engine is the core moat: `enforce("@typescript-eslint/no-floating-promises")` verifies the rule exists AND is enabled in your linter config. Same for ESLint, Ruff, Clippy, Pylint, RuboCop, and Stylelint.
10
+
11
+ `generate-types` is the second moat: scans all 6 linter APIs, package.json, and project files to emit a `.d.ts` with type unions. The TS compiler then PROVES references are valid at authoring time — typos become type errors, not runtime surprises.
12
+
13
+ ## Architecture
14
+
15
+ Three rule types in specs: `enforce()` (delegated to external tool), `check()` (vigiles-owned filesystem assertion), `guidance()` (prose only).
16
+
17
+ Core modules: `src/spec.ts` (types + builders), `src/compile.ts` (compiler), `src/linters.ts` (6-linter cross-referencing engine), `src/generate-types.ts` (type generator).
18
+
19
+ ## Key Files
20
+
21
+ - `src/spec.ts` — Type system and builder functions
22
+ - `src/compile.ts` — Compiler: spec → markdown with SHA-256 hash
23
+ - `src/linters.ts` — Linter cross-referencing engine (6 linters)
24
+ - `src/generate-types.ts` — Type generator: project state → .d.ts
25
+ - `src/cli.ts` — CLI: compile, check, init, generate-types, discover, adopt
26
+
27
+ ## Commands
28
+
29
+ - `npm run build` — Compile TypeScript to dist/
30
+ - `npm test` — Build and run all tests
31
+ - `npm run fmt` — Format with prettier
32
+ - `npm run fmt:check` — Check formatting
33
+
34
+ ## Rules
35
+
36
+ ### Zero Config By Default
37
+
38
+ **Guidance only** — vigiles compile should work with just a .spec.ts file. Config exists only for overrides (maxRules, maxTokens, catalogOnly).
39
+
40
+ ### Never Skip Tests
41
+
42
+ **Guidance only** — All tests must pass. If a test requires a CLI tool (pylint, rubocop, ruff, clippy), install the tool, don't skip the test.
43
+
44
+ ### Dont Reimplement Linters
45
+
46
+ **Guidance only** — Architectural linting belongs in ast-grep/Dependency Cruiser/Steiger. Per-file code rules belong in ESLint/Ruff/Clippy. vigiles owns: compilation, linter cross-referencing, type generation, filesystem assertions, and stale reference detection.
47
+
48
+ ### Format Before Commit
49
+
50
+ **Guidance only** — Run `npm run fmt:check` before committing. Inline code spans in markdown need surrounding spaces to render correctly.
51
+
52
+ ### No Session Links
53
+
54
+ **Guidance only** — This is a public repo. Claude Code session URLs are private and must not appear in commits or PRs.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Example: CLAUDE.md specification for the vigiles project itself.
3
+ *
4
+ * This is the source of truth. CLAUDE.md is a compiled build artifact.
5
+ * Run `vigiles compile` to generate CLAUDE.md from this spec.
6
+ */
7
+ import { claude, guidance } from "../src/spec.js";
8
+
9
+ export default claude({
10
+ sections: {
11
+ positioning: `vigiles compiles \`.spec.ts\` files to instruction files (CLAUDE.md, AGENTS.md, or any markdown target). The spec is the source of truth. The markdown is a build artifact.
12
+
13
+ The linter cross-referencing engine is the core moat: \`enforce("@typescript-eslint/no-floating-promises")\` verifies the rule exists AND is enabled in your linter config. Same for ESLint, Ruff, Clippy, Pylint, RuboCop, and Stylelint.
14
+
15
+ \`generate-types\` is the second moat: scans all 6 linter APIs, package.json, and project files to emit a \`.d.ts\` with type unions. The TS compiler then PROVES references are valid at authoring time — typos become type errors, not runtime surprises.`,
16
+
17
+ architecture: `Three rule types in specs: \`enforce()\` (delegated to external tool), \`check()\` (vigiles-owned filesystem assertion), \`guidance()\` (prose only).
18
+
19
+ Core modules: \`src/spec.ts\` (types + builders), \`src/compile.ts\` (compiler), \`src/linters.ts\` (6-linter cross-referencing engine), \`src/generate-types.ts\` (type generator).`,
20
+ },
21
+
22
+ keyFiles: {
23
+ "src/spec.ts": "Type system and builder functions",
24
+ "src/compile.ts": "Compiler: spec → markdown with SHA-256 hash",
25
+ "src/linters.ts": "Linter cross-referencing engine (6 linters)",
26
+ "src/generate-types.ts": "Type generator: project state → .d.ts",
27
+ "src/cli.ts": "CLI: compile, check, init, generate-types, discover, adopt",
28
+ },
29
+
30
+ commands: {
31
+ "npm run build": "Compile TypeScript to dist/",
32
+ "npm test": "Build and run all tests",
33
+ "npm run fmt": "Format with prettier",
34
+ "npm run fmt:check": "Check formatting",
35
+ },
36
+
37
+ rules: {
38
+ "zero-config-by-default": guidance(
39
+ "vigiles compile should work with just a .spec.ts file. Config exists only for overrides (maxRules, maxTokens, catalogOnly).",
40
+ ),
41
+
42
+ "never-skip-tests": guidance(
43
+ "All tests must pass. If a test requires a CLI tool (pylint, rubocop, ruff, clippy), install the tool, don't skip the test.",
44
+ ),
45
+
46
+ "dont-reimplement-linters": guidance(
47
+ "Architectural linting belongs in ast-grep/Dependency Cruiser/Steiger. Per-file code rules belong in ESLint/Ruff/Clippy. vigiles owns: compilation, linter cross-referencing, type generation, filesystem assertions, and stale reference detection.",
48
+ ),
49
+
50
+ "format-before-commit": guidance(
51
+ "Run `npm run fmt:check` before committing. Inline code spans in markdown need surrounding spaces to render correctly.",
52
+ ),
53
+
54
+ "no-session-links": guidance(
55
+ "This is a public repo. Claude Code session URLs are private and must not appear in commits or PRs.",
56
+ ),
57
+
58
+ // Example: check() rule (commented out because this is a demo spec,
59
+ // not the project's actual spec — see root CLAUDE.md.spec.ts)
60
+ // "test-file-pairing": check(
61
+ // every("src/**/!(*test|*spec).ts").has("{name}.test.ts"),
62
+ // "Every source module should have a corresponding test file.",
63
+ // ),
64
+ },
65
+ });
@@ -0,0 +1,50 @@
1
+ <!-- vigiles:sha256:da271d5e41e8e90e compiled from examples/SKILL.md.spec.ts -->
2
+
3
+ ---
4
+
5
+ name: pr-to-lint-rule
6
+ description: Convert a recurring PR review comment into an automated lint rule with tests and CLAUDE.md annotation
7
+ disable-model-invocation: true
8
+ argument-hint: <description of recurring PR feedback>
9
+
10
+ ---
11
+
12
+ Convert a recurring PR review comment into an automated lint rule.
13
+
14
+ ## Arguments
15
+
16
+ $ARGUMENTS — A natural language description of the pattern to enforce. Examples:
17
+
18
+ - "we keep telling people not to import directly from antd, use our design system barrel file instead"
19
+ - "people forget to use our custom logger instead of console.log"
20
+ - "don't use unwrap() in production code, use expect() or proper error handling"
21
+ - "API route handlers must use the withAuth wrapper"
22
+
23
+ ## Instructions
24
+
25
+ You are generating an automated lint rule from a recurring code review pattern. Follow these steps:
26
+
27
+ ### Step 1: Detect the Project Language and Toolchain
28
+
29
+ Look at the repository to determine:
30
+
31
+ - **Primary language** (JS/TS, Python, Rust, Go, Ruby, etc.)
32
+ - **Linter in use** (ESLint, Ruff, Clippy, golangci-lint, RuboCop, etc.)
33
+ - **Testing framework** (Vitest, Jest, pytest, cargo test, etc.)
34
+ - **Existing custom rules** (to match conventions)
35
+
36
+ **If the language or linter cannot be confidently detected**, **ask the user** which language and linter to target before generating anything.
37
+
38
+ ### Step 2: Generate the Lint Rule
39
+
40
+ Based on the detected (or user-specified) language, generate the appropriate rule type.
41
+
42
+ For JavaScript/TypeScript, generate an ESLint rule using the AST visitor pattern with `meta` and `create(context)`.
43
+
44
+ ### Step 3: Update `CLAUDE.md`
45
+
46
+ Add the annotation block. See [enforce-rules-format](skills/enforce-rules-format/SKILL.md) for the correct format.
47
+
48
+ ### Step 4: Verify
49
+
50
+ Run `npm test` to ensure the new rule and tests pass.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Example: SKILL.md specification for the pr-to-lint-rule skill.
3
+ *
4
+ * This is the source of truth. SKILL.md is a compiled build artifact.
5
+ * Run `vigiles compile` to generate SKILL.md from this spec.
6
+ */
7
+ import { skill, file, cmd, ref, instructions } from "../src/spec.js";
8
+
9
+ export default skill({
10
+ name: "pr-to-lint-rule",
11
+ description:
12
+ "Convert a recurring PR review comment into an automated lint rule with tests and CLAUDE.md annotation",
13
+ disableModelInvocation: true,
14
+ argumentHint: "<description of recurring PR feedback>",
15
+
16
+ body: instructions`
17
+ Convert a recurring PR review comment into an automated lint rule.
18
+
19
+ ## Arguments
20
+
21
+ $ARGUMENTS — A natural language description of the pattern to enforce. Examples:
22
+
23
+ - "we keep telling people not to import directly from antd, use our design system barrel file instead"
24
+ - "people forget to use our custom logger instead of console.log"
25
+ - "don't use unwrap() in production code, use expect() or proper error handling"
26
+ - "API route handlers must use the withAuth wrapper"
27
+
28
+ ## Instructions
29
+
30
+ You are generating an automated lint rule from a recurring code review pattern. Follow these steps:
31
+
32
+ ### Step 1: Detect the Project Language and Toolchain
33
+
34
+ Look at the repository to determine:
35
+
36
+ - **Primary language** (JS/TS, Python, Rust, Go, Ruby, etc.)
37
+ - **Linter in use** (ESLint, Ruff, Clippy, golangci-lint, RuboCop, etc.)
38
+ - **Testing framework** (Vitest, Jest, pytest, cargo test, etc.)
39
+ - **Existing custom rules** (to match conventions)
40
+
41
+ **If the language or linter cannot be confidently detected**, **ask the user** which language and linter to target before generating anything.
42
+
43
+ ### Step 2: Generate the Lint Rule
44
+
45
+ Based on the detected (or user-specified) language, generate the appropriate rule type.
46
+
47
+ For JavaScript/TypeScript, generate an ESLint rule using the AST visitor pattern with \`meta\` and \`create(context)\`.
48
+
49
+ ### Step 3: Update ${file("CLAUDE.md")}
50
+
51
+ Add the annotation block. See ${ref("skills/enforce-rules-format/SKILL.md")} for the correct format.
52
+
53
+ ### Step 4: Verify
54
+
55
+ Run ${cmd("npm test")} to ensure the new rule and tests pass.
56
+ `,
57
+ });
@@ -0,0 +1,11 @@
1
+ <!-- vigiles-disable require-spec -->
2
+
3
+ # CLAUDE.md
4
+
5
+ ## Code Style
6
+
7
+ Use TypeScript strict mode. Never use `any`.
8
+
9
+ ## Testing
10
+
11
+ Run `npm test` before submitting. Every file in src/ should have tests.
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "example-project",
3
+ "version": "1.0.0",
4
+ "scripts": {
5
+ "build": "echo build",
6
+ "test": "echo test",
7
+ "lint": "echo lint"
8
+ }
9
+ }
@@ -0,0 +1,3 @@
1
+ export function greet(name: string): string {
2
+ return `Hello, ${name}!`;
3
+ }
@@ -0,0 +1,2 @@
1
+ import { capitalize } from "./utils";
2
+ console.assert(capitalize("hello") === "Hello");
@@ -0,0 +1,3 @@
1
+ export function capitalize(s: string): string {
2
+ return s.charAt(0).toUpperCase() + s.slice(1);
3
+ }
package/logo.png ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "vigiles",
3
+ "version": "2.0.0",
4
+ "description": "Compile .spec.ts files to instruction files (CLAUDE.md, AGENTS.md) with linter cross-referencing",
5
+ "bin": {
6
+ "vigiles": "dist/cli.js"
7
+ },
8
+ "main": "./dist/spec.js",
9
+ "types": "./dist/spec.d.ts",
10
+ "exports": {
11
+ ".": "./dist/spec.js",
12
+ "./spec": "./dist/spec.js",
13
+ "./compile": "./dist/compile.js",
14
+ "./linters": "./dist/linters.js"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "test": "npm run build && node --test dist/spec.test.js dist/validate.test.js dist/cli.test.js dist/proofs.test.js dist/inline.test.js",
19
+ "lint": "eslint src/",
20
+ "fmt": "prettier --write .",
21
+ "fmt:check": "prettier --check ."
22
+ },
23
+ "devDependencies": {
24
+ "@eslint/js": "^10.0.1",
25
+ "@types/minimatch": "^5.1.2",
26
+ "@types/node": "^20.19.39",
27
+ "@typescript-eslint/eslint-plugin": "^8.58.0",
28
+ "@typescript-eslint/parser": "^8.58.0",
29
+ "eslint": "^10.1.0",
30
+ "eslint-plugin-sonarjs": "^4.0.2",
31
+ "globals": "^17.4.0",
32
+ "prettier": "^3.8.1",
33
+ "tsx": "^4.21.0",
34
+ "typescript": "^5.9.3"
35
+ },
36
+ "dependencies": {
37
+ "@jackchuka/mdschema": "^0.12.8",
38
+ "cosmiconfig": "^9.0.1",
39
+ "glob": "^13.0.6",
40
+ "minimatch": "^10.0.1"
41
+ }
42
+ }