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,85 @@
1
+ # Agent Setup Guide
2
+
3
+ How to set up vigiles when an AI agent is doing the installation (non-interactive).
4
+
5
+ ## What an Agent Can Do
6
+
7
+ | Action | Agent can do it? | How |
8
+ | ------------------------- | ---------------- | ------------------------------------------- |
9
+ | Create spec file | Yes | `npx vigiles init` (non-interactive wizard) |
10
+ | Generate types | Yes | `npx vigiles generate-types` |
11
+ | Compile specs | Yes | `npx vigiles compile` |
12
+ | Add CI step | Yes | Edit `.github/workflows/*.yml` directly |
13
+ | Install hooks | Yes | Write to `.claude/settings.json` directly |
14
+ | Install plugin via skills | **No** | Requires user to run `npx skills add` |
15
+
16
+ The `skills add` command requires user action — an agent can't install plugins for itself. But it CAN write the hook configuration directly to `.claude/settings.json`, which achieves the same result.
17
+
18
+ ## Non-Interactive Setup
19
+
20
+ ### Step 1: Run the wizard
21
+
22
+ ```bash
23
+ npx vigiles init
24
+ ```
25
+
26
+ This works non-interactively. It auto-detects the project, creates a spec, generates types, compiles, and adds a CI step. No prompts.
27
+
28
+ ### Step 2: Install hooks directly
29
+
30
+ Instead of `npx skills add zernie/vigiles`, the agent can write the hooks to `.claude/settings.json`:
31
+
32
+ ```json
33
+ {
34
+ "hooks": {
35
+ "PreToolUse": [
36
+ {
37
+ "matcher": "Edit|Write",
38
+ "command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && case \"$FILE\" in *.md) [ -f \"$FILE\" ] && head -1 \"$FILE\" | grep -q 'vigiles:sha256:' && { echo \"BLOCKED: Edit the .spec.ts source instead.\" >&2; exit 2; } ;; esac; exit 0"
39
+ }
40
+ ],
41
+ "PostToolUse": [
42
+ {
43
+ "matcher": "Edit|Write",
44
+ "command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && case \"$(basename \"$FILE\")\" in eslint.config.*|.eslintrc*|package.json|pyproject.toml|Cargo.toml) npx vigiles generate-types 2>&1 || true ;; esac && case \"$FILE\" in *.spec.ts) npx vigiles compile 2>&1 || true ;; esac"
45
+ }
46
+ ]
47
+ }
48
+ }
49
+ ```
50
+
51
+ This is equivalent to what the plugin installs, but written directly without the skills system.
52
+
53
+ ### Step 3: Edit the spec
54
+
55
+ The agent should read the generated `.spec.ts` file and fill in the project's actual conventions — sections, key files, commands, and rules. Use the `edit-spec` skill instructions as a guide for the spec format.
56
+
57
+ ### Step 4: Compile and verify
58
+
59
+ ```bash
60
+ npx vigiles compile
61
+ npx vigiles audit
62
+ ```
63
+
64
+ ## Recommended Agent Prompt
65
+
66
+ If you want an agent to set up vigiles in a project, use this prompt:
67
+
68
+ ```
69
+ Set up vigiles for this project:
70
+ 1. Run `npx vigiles init`
71
+ 2. Read the generated .spec.ts file
72
+ 3. Fill in the project's actual conventions based on the codebase
73
+ 4. Add hooks to .claude/settings.json for auto-compilation
74
+ 5. Run `npx vigiles compile` to verify everything works
75
+ 6. Commit the .spec.ts, compiled .md, .vigiles/generated.d.ts, and settings changes
76
+ ```
77
+
78
+ ## What the Agent Gets Wrong
79
+
80
+ Common issues when agents set up vigiles:
81
+
82
+ - **Editing CLAUDE.md directly** — the PreToolUse hook prevents this if installed
83
+ - **Using wrong rule names** — `enforce("no-console")` instead of `enforce("eslint/no-console")`. The compiler catches this.
84
+ - **Forgetting to compile** — the PostToolUse hook handles this automatically
85
+ - **Adding headers inside sections** — the compiler catches `#`/`##` headers in section content
@@ -0,0 +1,103 @@
1
+ # Agent Workflows
2
+
3
+ vigiles compiles typed specs to markdown instruction files. Different AI agents read different files, but the compilation and validation pipeline is the same.
4
+
5
+ ## Auto-Detection
6
+
7
+ `vigiles init` scans your project and auto-detects:
8
+
9
+ | Signal | What it means |
10
+ | ------------------------------------------ | ------------------------------------------------- |
11
+ | `CLAUDE.md` exists | Claude Code in use — suggest migration if no spec |
12
+ | `AGENTS.md` exists | Codex / GitHub Copilot in use |
13
+ | `.claude/` directory | Claude Code project config |
14
+ | `.cursorrules` | Cursor in use — suggest rule-porter |
15
+ | `.github/copilot-instructions.md` | GitHub Copilot custom instructions |
16
+ | `.windsurfrules` | Windsurf in use |
17
+ | `rule-porter` / `rulesync` in package.json | Sync tool already installed |
18
+ | Symlinked instruction files | Notes them in output |
19
+
20
+ The wizard creates specs for detected targets, generates types, compiles, and adds a CI step. No `--target` flag needed unless you want to override the auto-detection.
21
+
22
+ ## Claude Code
23
+
24
+ **Instruction file:** `CLAUDE.md`
25
+
26
+ **Setup:**
27
+
28
+ ```bash
29
+ npx vigiles init
30
+ npx skills add zernie/vigiles
31
+ ```
32
+
33
+ **What the plugin does:**
34
+
35
+ | Hook | Trigger | Action |
36
+ | ----------- | ----------------------------------------------- | ---------------------------------------- |
37
+ | PreToolUse | Agent tries to Edit/Write a compiled `.md` file | Blocks the edit, redirects to `.spec.ts` |
38
+ | PostToolUse | Agent edits a `.spec.ts` file | Auto-runs `vigiles compile` |
39
+ | PostToolUse | Agent edits linter config or `package.json` | Auto-runs `vigiles generate-types` |
40
+
41
+ **Without the plugin**, you must run `vigiles compile` manually after editing specs. CI still catches stale files.
42
+
43
+ ## Codex / GitHub Copilot
44
+
45
+ **Instruction file:** `AGENTS.md`
46
+
47
+ **Setup:**
48
+
49
+ ```bash
50
+ npx vigiles init --target=AGENTS.md
51
+ ```
52
+
53
+ Codex and GitHub Copilot read `AGENTS.md` directly. There is no plugin or hook system — these agents don't support it. The enforcement path is:
54
+
55
+ 1. Edit `AGENTS.md.spec.ts` (the source of truth)
56
+ 2. Run `npx vigiles compile` to regenerate `AGENTS.md`
57
+ 3. CI verifies freshness: `npx vigiles audit && npx vigiles generate-types --check`
58
+
59
+ If you also use Claude Code, install the plugin (`npx skills add zernie/vigiles`) to get auto-recompilation.
60
+
61
+ ## Multi-Agent (Claude + Codex)
62
+
63
+ Use a single spec with multiple targets:
64
+
65
+ ```typescript
66
+ export default claude({
67
+ target: ["CLAUDE.md", "AGENTS.md"],
68
+ rules: { ... },
69
+ });
70
+ ```
71
+
72
+ One spec, two outputs. Both files are compiled from the same source of truth with the same linter verification.
73
+
74
+ ```bash
75
+ npx vigiles init # for CLAUDE.md (primary)
76
+ npx vigiles init --target=AGENTS.md # adds AGENTS.md target
77
+ ```
78
+
79
+ Or just set `target: ["CLAUDE.md", "AGENTS.md"]` in your spec directly.
80
+
81
+ ## Cursor / Windsurf / Other Formats
82
+
83
+ vigiles compiles to **markdown only** (CLAUDE.md, AGENTS.md). For non-markdown formats (`.cursorrules`, `.github/copilot-instructions.md`, Windsurf), use a sync tool to convert from the compiled markdown:
84
+
85
+ - [rule-porter](https://github.com/nichochar/rule-porter) — bidirectional conversion between agent formats
86
+ - [rulesync](https://github.com/dyoshikawa/rulesync) — unified rule management across 10+ tools
87
+
88
+ vigiles is the source of truth compiler. Sync tools handle the last mile.
89
+
90
+ ## CI Pipeline
91
+
92
+ All agents share the same CI step:
93
+
94
+ ```yaml
95
+ - name: Verify specs
96
+ run: npx vigiles audit && npx vigiles generate-types --check
97
+ ```
98
+
99
+ This catches:
100
+
101
+ - Hash mismatches (someone edited the compiled `.md` directly)
102
+ - Missing specs (`require-spec` rule — every `.md` should have a `.spec.ts`)
103
+ - Stale generated types (linter config changed but types weren't regenerated)
@@ -0,0 +1,71 @@
1
+ # What Changes With vigiles
2
+
3
+ ## Claude Code
4
+
5
+ | | Without vigiles | With vigiles |
6
+ | ----------------------------------- | ---------------------------- | -------------------------------------------------------------- |
7
+ | **Instructions** | Hand-written CLAUDE.md | Compiled from `.spec.ts` (build artifact) |
8
+ | **Linter rule references** | Trust-based (nobody checks) | Verified at compile time against real config |
9
+ | **File paths** | Rot silently when renamed | `file()` references checked against filesystem |
10
+ | **Commands** | Stale scripts go unnoticed | `cmd()` references checked against package.json |
11
+ | **Direct edits to CLAUDE.md** | Anyone can, nobody knows | PreToolUse hook blocks edits, redirects to spec |
12
+ | **Linter config changes** | CLAUDE.md drifts out of sync | PostToolUse hook auto-regenerates types |
13
+ | **Spec edits** | N/A | PostToolUse hook auto-compiles to markdown |
14
+ | **guidance → enforce upgrades** | Manual guesswork | `/strengthen` reads per-linter docs, suggests upgrades |
15
+ | **New lint rules from PR feedback** | Copy-paste from review | `/pr-to-lint-rule` generates rule + tests + spec entry |
16
+ | **CI** | Nothing to verify | `vigiles audit` catches hash drift, disabled rules, stale refs |
17
+
18
+ ## Codex
19
+
20
+ | | Without vigiles | With vigiles |
21
+ | ----------------------------- | -------------------------------- | ------------------------------------------------------- |
22
+ | **Instructions** | Hand-written AGENTS.md | Compiled from `.spec.ts` |
23
+ | **Linter rule references** | Trust-based | Verified at compile time |
24
+ | **File paths / commands** | Rot silently | Checked at compile time |
25
+ | **Direct edits to AGENTS.md** | Undetected | CI catches hash mismatch |
26
+ | **Hooks / auto-compile** | Not available (no plugin system) | Not available — run `vigiles compile` manually or in CI |
27
+ | **CI** | Nothing to verify | Same `vigiles audit` pipeline as Claude |
28
+
29
+ Codex has no hook or plugin system. The compile-time verification and CI enforcement still work — the difference is there's no auto-recompilation on edit. You run `vigiles compile` before committing, and CI catches drift.
30
+
31
+ ## What's Deterministic vs What's Not
32
+
33
+ | Check | Deterministic? | How |
34
+ | -------------------------------- | -------------- | ------------------------------------------------------------------------------------ |
35
+ | Linter rule exists in catalog | Yes | Node API (`builtinRules`) or CLI (`ruff rule`, `rubocop --show-cops`) |
36
+ | Linter rule is enabled in config | Yes | `calculateConfigForFile` (ESLint), `--show-settings` (Ruff), `--show-cops` (RuboCop) |
37
+ | File path exists | Yes | `fs.existsSync` |
38
+ | npm script exists | Yes | Parsed from `package.json` |
39
+ | SHA-256 hash matches | Yes | Recompute and compare |
40
+ | Duplicate rule detection | Yes | Normalized Compression Distance (NCD) with fixed threshold |
41
+ | guidance → enforce suggestion | **No** | Agent reads linter docs, reasons about intent — `/strengthen` skill |
42
+ | PR comment → lint rule | **No** | Agent generates custom rule code — `/pr-to-lint-rule` skill |
43
+ | Spec content authoring | **No** | Agent or human writes the spec — vigiles verifies it |
44
+
45
+ Everything vigiles compiles and audits is deterministic — same input, same output, no LLM in the loop. The non-deterministic parts (authoring specs, suggesting upgrades, writing custom rules) are agent skills that run outside the compilation pipeline.
46
+
47
+ ## Flow
48
+
49
+ ```
50
+ DETERMINISTIC AGENT-ASSISTED
51
+ ┌─────────────────────────┐ ┌──────────────────────────┐
52
+ │ │ │ │
53
+ .spec.ts ──────┤ vigiles compile │ │ /strengthen │
54
+ │ │ ✓ linter rules exist │ │ guidance → enforce │
55
+ │ │ ✓ rules enabled │ │ │
56
+ │ │ ✓ file paths valid │ │ /pr-to-lint-rule │
57
+ │ │ ✓ commands valid │ │ PR comment → rule │
58
+ │ │ → CLAUDE.md + hash │ │ │
59
+ │ └─────────────────────────┘ │ /edit-spec │
60
+ │ │ agent edits .spec.ts │
61
+ │ ┌─────────────────────────┐ └──────────────────────────┘
62
+ └────────▶│ vigiles audit │ │
63
+ │ ✓ hash integrity │ │
64
+ │ ✓ inline rule checks │ ▼
65
+ │ ✓ duplicate detection │ ┌──────────────────────────┐
66
+ │ ✓ coverage gaps │ │ hooks (Claude Code) │
67
+ └─────────────────────────┘ │ auto-compile on edit │
68
+ │ auto-regen types │
69
+ │ block direct md edits │
70
+ └──────────────────────────┘
71
+ ```
@@ -0,0 +1,124 @@
1
+ # Freshness Detection
2
+
3
+ vigiles detects when compiled instruction files are out of date. The `freshness` validation rule catches drift between your specs and the compiled markdown.
4
+
5
+ ## Configuration
6
+
7
+ In `.vigilesrc.json`:
8
+
9
+ ```json
10
+ {
11
+ "rules": {
12
+ "freshness": "error"
13
+ },
14
+ "freshnessMode": "strict"
15
+ }
16
+ ```
17
+
18
+ ### Severity
19
+
20
+ | Value | Behavior |
21
+ | ------------------ | ----------------------------------------- |
22
+ | `"error"` | `vigiles audit` exits non-zero (CI fails) |
23
+ | `"warn"` (default) | Prints warning, exits 0 |
24
+ | `false` | Skip freshness checks entirely |
25
+
26
+ ### Mode
27
+
28
+ | Mode | What it checks | Cost | False positives | False negatives |
29
+ | -------------------- | -------------------------------------------------------------- | ------------- | --------------------------------------- | ----------------------------- |
30
+ | `"strict"` (default) | Recompiles in memory, diffs against existing output | 2-5s per spec | Zero | Zero |
31
+ | `"input-hash"` | Compares stored input fingerprint against current file state | <100ms | Possible (whitespace changes in config) | Possible (transitive deps) |
32
+ | `"output-hash"` | Only checks if the `.md` was hand-edited (existing hash check) | <1ms | Zero | Many (misses all input drift) |
33
+
34
+ **Strict mode** is the correct default. It catches every kind of staleness with zero false positives. The cost is running a full recompile in memory on every `vigiles audit`.
35
+
36
+ **Input-hash mode** is faster. Use it when compilation is slow (many specs, heavy linter config loading). It tracks a fingerprint of all input files (spec source, linter configs, lock files, keyFiles, generated types). When any input changes, audit flags the output as stale. To use it, set `freshnessMode: "input-hash"` — `vigiles compile` will embed the input fingerprint in the compiled markdown.
37
+
38
+ **Output-hash mode** is the minimal option. It only detects hand-edits to the compiled markdown (the pre-existing hash check). It won't catch disabled linter rules, deleted files, or spec changes.
39
+
40
+ ## What Counts as an Input
41
+
42
+ In input-hash mode, vigiles tracks these files:
43
+
44
+ | Category | Files | Why |
45
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
46
+ | Spec source | `CLAUDE.md.spec.ts` | Any spec change should force recompile |
47
+ | Linter configs | `eslint.config.*`, `.eslintrc.*`, `pyproject.toml`, `ruff.toml`, `Cargo.toml`, `clippy.toml`, `.pylintrc`, `.rubocop.yml`, `.stylelintrc.*`, `setup.cfg` | Disabling a rule makes `enforce()` claims stale |
48
+ | Package manifest | `package.json` | Scripts (`cmd()` refs) and deps (linter plugins) |
49
+ | Lock files | `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `bun.lockb`, `Gemfile.lock`, `poetry.lock`, `uv.lock`, `pdm.lock`, `Cargo.lock`, `go.sum`, `composer.lock`, `packages.lock.json`, `Package.resolved`, `mix.lock`, `requirements.txt` | Dependency version changes can add/remove linter rules |
50
+ | Referenced files | Every `file()` path in `keyFiles` | Deletion or rename makes the reference stale |
51
+ | Generated types | `.vigiles/generated.d.ts` | If types are stale, rule references may be invalid |
52
+ | Extra inputs | Configured via `freshnessInputs` | For non-standard files (e.g., monorepo root lock file) |
53
+
54
+ All files are auto-detected by checking existence at `basePath`. Lock files and linter configs cover 6+ ecosystems (Node.js, Ruby, Python, Rust, Go, PHP, .NET, Swift, Elixir).
55
+
56
+ ## Extra Inputs
57
+
58
+ For monorepos or non-standard layouts, add extra files to track:
59
+
60
+ ```json
61
+ {
62
+ "freshnessMode": "input-hash",
63
+ "freshnessInputs": ["../../yarn.lock", "shared/eslint-config/index.js"]
64
+ }
65
+ ```
66
+
67
+ ## How the Input Hash Works
68
+
69
+ At compile time (`vigiles compile`), when `freshnessMode` is `"input-hash"`:
70
+
71
+ 1. Discover all input files (spec, configs, lock files, keyFiles, etc.)
72
+ 2. Compute SHA-256 of each file's contents (missing files hash to `MISSING:<path>`)
73
+ 3. Combine all file hashes into a single fingerprint (SHA-256 of sorted hashes)
74
+ 4. Embed the fingerprint in the compiled markdown:
75
+
76
+ ```html
77
+ <!-- vigiles:sha256:a1b2c3d4e5f6g7h8 compiled from CLAUDE.md.spec.ts -->
78
+ <!-- vigiles:inputs:f9e8d7c6b5a49382 -->
79
+
80
+ # CLAUDE.md ...
81
+ ```
82
+
83
+ At audit time (`vigiles audit`):
84
+
85
+ 1. Extract the stored input fingerprint
86
+ 2. Recompute the fingerprint from current file state
87
+ 3. If they differ → stale
88
+
89
+ ## Audit Output
90
+
91
+ ```
92
+ Freshness check:
93
+
94
+ ✓ CLAUDE.md — fresh (strict)
95
+ ✗ AGENTS.md — Output would differ if recompiled — run `vigiles compile`
96
+ ```
97
+
98
+ With `--summary`:
99
+
100
+ ```
101
+ vigiles: 1 stale (run vigiles compile)
102
+ ```
103
+
104
+ ## Lock File Detection
105
+
106
+ vigiles auto-detects lock files for every major ecosystem. No configuration needed — if the file exists, it's tracked.
107
+
108
+ | Lock file | Ecosystem |
109
+ | -------------------- | --------------- |
110
+ | `package-lock.json` | Node.js (npm) |
111
+ | `yarn.lock` | Node.js (Yarn) |
112
+ | `pnpm-lock.yaml` | Node.js (pnpm) |
113
+ | `bun.lockb` | Node.js (Bun) |
114
+ | `Gemfile.lock` | Ruby (Bundler) |
115
+ | `poetry.lock` | Python (Poetry) |
116
+ | `uv.lock` | Python (uv) |
117
+ | `pdm.lock` | Python (PDM) |
118
+ | `requirements.txt` | Python (pip) |
119
+ | `Cargo.lock` | Rust (Cargo) |
120
+ | `go.sum` | Go |
121
+ | `composer.lock` | PHP (Composer) |
122
+ | `packages.lock.json` | .NET (NuGet) |
123
+ | `Package.resolved` | Swift (SPM) |
124
+ | `mix.lock` | Elixir (Mix) |
@@ -0,0 +1,119 @@
1
+ # Inline mode
2
+
3
+ Inline mode lets you adopt vigiles **one rule at a time**, without committing
4
+ to a `.spec.ts` compile step. You add `<!-- vigiles:enforce ... -->` HTML
5
+ comments directly to your existing `CLAUDE.md` / `AGENTS.md`, and
6
+ `vigiles audit` verifies them the same way it verifies spec-declared rules:
7
+ linter-backed existence check, closest-match typo suggestions, disabled-rule
8
+ detection, and GitHub Actions annotations.
9
+
10
+ It's the vigiles equivalent of `// eslint-disable-next-line` — minimum
11
+ commitment, maximum incrementalism.
12
+
13
+ ## When to use it
14
+
15
+ - You already have a `CLAUDE.md` and don't want to port it to `.spec.ts`
16
+ - You want to experiment with a single rule before committing to the full
17
+ vigiles workflow
18
+ - Your project isn't a TypeScript project at all and the build step feels
19
+ like dead weight
20
+ - Hesitant teammates want to see the verification work before accepting a
21
+ new file type in the repo
22
+
23
+ If you already have a real TypeScript project and you want the strongest
24
+ guarantees (editor-time type safety, programmatic rule composition, the
25
+ `generate-types` moat), use spec mode instead — see the main README.
26
+
27
+ ## Format
28
+
29
+ A single HTML comment per rule:
30
+
31
+ ```md
32
+ <!-- vigiles:enforce eslint/no-console "Use structured logger for app output" -->
33
+ ```
34
+
35
+ Three required pieces:
36
+
37
+ 1. `vigiles:enforce` — only `enforce` is supported inline. Guidance rules
38
+ are just paragraphs in the surrounding prose, so a `guidance` comment
39
+ would be a tautology.
40
+ 2. `<linter>/<rule>` — the same reference format as `enforce()` in spec
41
+ mode. Supports all six linters (ESLint, Stylelint, Ruff, Clippy, Pylint,
42
+ RuboCop) and scoped plugin names (`eslint/@typescript-eslint/...`).
43
+ 3. `"<why>"` — a simple double-quoted string. No newlines, no embedded
44
+ quotes. If you need either, move to spec mode.
45
+
46
+ ## Example
47
+
48
+ ```md
49
+ # My Project
50
+
51
+ <!-- vigiles:enforce eslint/no-console "Route output through logger.ts" -->
52
+ <!-- vigiles:enforce eslint/@typescript-eslint/no-floating-promises "Await or explicitly void" -->
53
+ <!-- vigiles:enforce ruff/F401 "No unused imports" -->
54
+
55
+ ## Logging
56
+
57
+ All application output must go through the shared logger module.
58
+ Do not use `console.log` directly in src/.
59
+
60
+ ## Async
61
+
62
+ Every promise must be awaited or explicitly voided. The ESLint rule
63
+ enforces this automatically.
64
+ ```
65
+
66
+ ## What audit catches
67
+
68
+ Running `vigiles audit CLAUDE.md` on the above file will:
69
+
70
+ - Verify each `eslint/…`, `ruff/…` reference against your actual linter
71
+ config
72
+ - Emit closest-match suggestions on typos: `"no-consol"` →
73
+ `did you mean "eslint/no-console"?`
74
+ - Emit `::error` annotations when running inside GitHub Actions
75
+ - Exit with code 2 (hard error) on any failed rule, so CI fails fast
76
+
77
+ ## What audit does NOT do in inline mode
78
+
79
+ - **No type safety at edit time.** The `.spec.ts` path gets TypeScript
80
+ squiggles in the editor because `StrictLinterRule` is a type union of
81
+ every rule in your linters. Inline mode is strings-in-markdown, so
82
+ typos only surface at `vigiles audit` time. Still catches them before
83
+ CI, just not in the editor.
84
+ - **No programmatic composition.** You can't reuse a batch of rules from
85
+ a helper. Each comment is its own line.
86
+ - **No rule deduplication via NCD.** Duplicate-rule detection runs on
87
+ spec-mode files; inline rules are ungrouped.
88
+
89
+ All of this is fine for the adoption-onramp use case. When you outgrow it,
90
+ port to spec mode.
91
+
92
+ ## Mixing inline and spec mode
93
+
94
+ Spec mode wins. If a file has both `CLAUDE.md.spec.ts` and inline
95
+ comments inside `CLAUDE.md`, the spec compiler will overwrite the markdown
96
+ on the next compile, and your inline comments will be gone. Pick one per
97
+ file.
98
+
99
+ ## Ignoring `require-spec`
100
+
101
+ The built-in `require-spec` validation rule demands a `.spec.ts` sibling
102
+ for every `CLAUDE.md` / `AGENTS.md`. Inline mode satisfies it — any file
103
+ with at least one `<!-- vigiles:enforce ... -->` comment is treated as
104
+ spec-equivalent, so you do not need to add a `vigiles-disable require-spec`
105
+ comment.
106
+
107
+ ## Graduating to spec mode
108
+
109
+ When you've accumulated a dozen or so inline rules and the prose is
110
+ starting to feel crowded, run:
111
+
112
+ ```bash
113
+ npx vigiles init --target=CLAUDE.md
114
+ ```
115
+
116
+ That scaffolds a `CLAUDE.md.spec.ts` next to your existing `CLAUDE.md`.
117
+ Copy the inline enforce rules into the `rules:` block, delete the inline
118
+ comments, and run `vigiles compile`. The markdown output will be rebuilt
119
+ with a `sha256` hash header, and future edits flow through the spec.
@@ -0,0 +1,166 @@
1
+ # Linter Cross-Referencing
2
+
3
+ vigiles verifies that every `enforce()` rule in your spec actually exists and is enabled in your project. This is the core differentiator -- no other tool resolves rules against 6 linter APIs and checks config-enabled status.
4
+
5
+ ## How It Works
6
+
7
+ When you write:
8
+
9
+ ```ts
10
+ enforce("eslint/no-console", "Use the project logger instead.");
11
+ ```
12
+
13
+ vigiles does two things at compile time:
14
+
15
+ 1. **Existence check** -- verifies the rule `no-console` exists in ESLint (via `builtinRules` or plugin resolution).
16
+ 2. **Config check** -- loads your ESLint flat config, calls `calculateConfigForFile`, and confirms the rule's severity is not `0` / `"off"`.
17
+
18
+ If either check fails, compilation fails with a clear error:
19
+
20
+ ```
21
+ Error: Rule "no-consloe" not found in eslint
22
+ Error: Rule "no-console" is disabled in eslint config
23
+ ```
24
+
25
+ The reference format is `<linter>/<rule>` -- e.g., `eslint/no-console`, `ruff/F401`, `clippy/needless_return`.
26
+
27
+ ## Supported Linters
28
+
29
+ | Linter | Detection | Existence Check | Config Check |
30
+ | --------- | -------------- | --------------------------------------------- | ----------------------------------------------------- |
31
+ | ESLint | `node_modules` | Node API (`builtinRules` + plugin resolution) | Loads flat config, checks severity > 0 |
32
+ | Stylelint | `node_modules` | Node API (`rules` export) | Loads config, checks rule value is not `null` |
33
+ | Ruff | `PATH` | CLI (`ruff rule <name>`) | Parses `ruff check --show-settings` for enabled codes |
34
+ | Clippy | `PATH` | CLI (`cargo clippy --explain <name>`) | Parses `Cargo.toml` `[lints.clippy]` section |
35
+ | Pylint | `PATH` | CLI (`pylint --help-msg=<name>`) | Runs `pylint --list-msgs-enabled` |
36
+ | RuboCop | `PATH` | CLI (`rubocop --show-cops <name>`) | Parses `Enabled: true/false` from output |
37
+
38
+ Node-based linters (ESLint, Stylelint) are resolved via `createRequire` from the project's `node_modules`. CLI-based linters (Ruff, Clippy, Pylint, RuboCop) must be available on `PATH`.
39
+
40
+ ## ESLint Plugin Support
41
+
42
+ Plugin rules use the format `eslint/<plugin>/<rule>`. vigiles resolves the plugin package and checks its exported `rules` object.
43
+
44
+ **Scoped plugins** (`@scope/plugin`):
45
+
46
+ ```ts
47
+ // Resolves @typescript-eslint/eslint-plugin, checks its "no-explicit-any" rule
48
+ enforce("eslint/@typescript-eslint/no-explicit-any", "Use `unknown` instead.");
49
+ ```
50
+
51
+ Resolution order for `@scope/name`:
52
+
53
+ 1. `@scope/eslint-plugin-name`
54
+ 2. `@scope/eslint-plugin`
55
+
56
+ For bare scopes like `@typescript-eslint`, it resolves `@typescript-eslint/eslint-plugin`.
57
+
58
+ **Unscoped plugins**:
59
+
60
+ ```ts
61
+ // Resolves eslint-plugin-import, checks its "no-unresolved" rule
62
+ enforce("eslint/import/no-unresolved", "Fix the import path.");
63
+ ```
64
+
65
+ Resolution: `import` becomes `eslint-plugin-import`.
66
+
67
+ Config-enabled checks for plugin rules go through ESLint's `calculateConfigForFile` with the full qualified name (e.g., `@typescript-eslint/no-explicit-any`), so they respect your flat config.
68
+
69
+ ## Custom Linters
70
+
71
+ For tools not built in, use the `linters` config option in `vigiles.json` (or `package.json` under `"vigiles"`):
72
+
73
+ ```json
74
+ {
75
+ "linters": {
76
+ "my-tool": {
77
+ "rulesDir": "tools/my-tool/rules/"
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ vigiles checks if a file matching the rule name exists in the specified directory. For example, `enforce("my-tool/no-foo")` passes if `tools/my-tool/rules/no-foo.*` exists (any extension).
84
+
85
+ Multiple directories are supported via an array:
86
+
87
+ ```json
88
+ {
89
+ "linters": {
90
+ "my-tool": {
91
+ "rulesDir": ["tools/my-tool/rules/", "tools/my-tool/extra-rules/"]
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ Custom linters only support existence checks. Config-enabled status is always `"unknown"`.
98
+
99
+ ## generate-types
100
+
101
+ `vigiles generate-types` scans all 6 linter APIs, `package.json`, and project files, then emits `.vigiles/generated.d.ts` with type unions derived from your actual project state.
102
+
103
+ ```sh
104
+ npx vigiles generate-types
105
+ ```
106
+
107
+ This produces a `.d.ts` file like:
108
+
109
+ ```ts
110
+ /**
111
+ * Auto-generated by `vigiles generate-types`.
112
+ * DO NOT EDIT -- re-run `vigiles generate-types` to update.
113
+ */
114
+
115
+ declare module "vigiles/generated" {
116
+ /** 42 enabled eslint rules (via flat config). */
117
+ export type EslintRule =
118
+ | "no-console"
119
+ | "no-debugger"
120
+ | "@typescript-eslint/no-explicit-any"
121
+ // ...
122
+
123
+ /** 5 enabled ruff rules (via CLI). */
124
+ export type RuffRule = "E501" | "F401" | "F841" | "I001" | "UP006";
125
+
126
+ /** All enabled linter rules across all detected linters. */
127
+ export type LinterRule = EslintRule | RuffRule;
128
+
129
+ /** 8 npm scripts from package.json. */
130
+ export type NpmScript = "build" | "test" | "lint" | "fmt" | "fmt:check" | ...;
131
+
132
+ /** 23 project files. */
133
+ export type ProjectFile = "src/cli.ts" | "src/compile.ts" | "src/linters.ts" | ...;
134
+ }
135
+ ```
136
+
137
+ The TypeScript compiler then proves references are valid at authoring time. A typo like `enforce("eslint/no-consloe")` becomes a type error in your editor before you ever run `vigiles compile`. This shifts rule-reference validation from runtime to authoring time.
138
+
139
+ Discovery methods per linter:
140
+
141
+ - **ESLint** -- loads flat config via `calculateConfigForFile`, collects rules with severity > 0
142
+ - **Stylelint** -- loads config via `createLinter` + `getConfigForFile`, collects non-null rules
143
+ - **Ruff** -- parses `ruff check --show-settings` for `linter.rules.enabled`
144
+ - **Pylint** -- parses `pylint --list-msgs-enabled`, extracts both IDs (`C0114`) and symbolic names
145
+ - **RuboCop** -- parses `rubocop --show-cops` output for cop names
146
+ - **Clippy** -- reads `Cargo.toml` `[lints.clippy]` section, excludes rules set to `"allow"`
147
+
148
+ Project files default to `src/**/*` but can be configured via `fileGlobs`.
149
+
150
+ ## Catalog-Only Mode
151
+
152
+ Set `catalogOnly: true` to skip config-enabled checks and only verify that rules exist in the linter's catalog:
153
+
154
+ ```json
155
+ {
156
+ "catalogOnly": true
157
+ }
158
+ ```
159
+
160
+ In this mode, `checkLinterRule` returns `"unknown"` for the `enabled` field instead of loading linter configs. This is useful when:
161
+
162
+ - Full config loading is slow in CI (ESLint/Stylelint config resolution can take seconds)
163
+ - You only care that referenced rules are real, not that they are currently turned on
164
+ - The linter config is not available in the build environment
165
+
166
+ The option can be set in `vigiles.json`, `package.json` under `"vigiles"`, or via the `INPUT_CATALOG_ONLY` environment variable in the GitHub Action.