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,650 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ /**
7
+ * CLI integration tests — spawn the actual vigiles CLI and verify output.
8
+ *
9
+ * These test the full flow: CLI → init/compile/audit → filesystem output.
10
+ */
11
+ const node_test_1 = require("node:test");
12
+ const strict_1 = __importDefault(require("node:assert/strict"));
13
+ const node_fs_1 = require("node:fs");
14
+ const node_path_1 = require("node:path");
15
+ const node_os_1 = require("node:os");
16
+ const node_child_process_1 = require("node:child_process");
17
+ const CLI = (0, node_path_1.resolve)(__dirname, "..", "dist", "cli.js");
18
+ function run(args, cwd) {
19
+ try {
20
+ const stdout = (0, node_child_process_1.execSync)(`node ${CLI} ${args}`, {
21
+ cwd,
22
+ encoding: "utf-8",
23
+ stdio: ["pipe", "pipe", "pipe"],
24
+ timeout: 30000,
25
+ });
26
+ return { stdout, stderr: "", exitCode: 0 };
27
+ }
28
+ catch (e) {
29
+ const err = e;
30
+ return {
31
+ stdout: err.stdout ?? "",
32
+ stderr: err.stderr ?? "",
33
+ exitCode: err.status ?? 1,
34
+ };
35
+ }
36
+ }
37
+ // ---------------------------------------------------------------------------
38
+ // vigiles init
39
+ // ---------------------------------------------------------------------------
40
+ (0, node_test_1.describe)("CLI: vigiles init", () => {
41
+ let tmpDir;
42
+ (0, node_test_1.before)(() => {
43
+ tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-cli-init-"));
44
+ });
45
+ (0, node_test_1.after)(() => {
46
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
47
+ });
48
+ (0, node_test_1.it)("should create CLAUDE.md.spec.ts by default", () => {
49
+ const { stdout, exitCode } = run("init", tmpDir);
50
+ strict_1.default.equal(exitCode, 0);
51
+ strict_1.default.ok(stdout.includes("Created CLAUDE.md.spec.ts"));
52
+ strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md.spec.ts")));
53
+ });
54
+ (0, node_test_1.it)("should not overwrite existing spec", () => {
55
+ // Already created in previous test
56
+ const { stdout } = run("init", tmpDir);
57
+ strict_1.default.ok(stdout.includes("already exists"));
58
+ });
59
+ (0, node_test_1.it)("should create AGENTS.md.spec.ts with --target flag", () => {
60
+ const { stdout, exitCode } = run("init --target=AGENTS.md", tmpDir);
61
+ strict_1.default.equal(exitCode, 0);
62
+ strict_1.default.ok(stdout.includes("Created AGENTS.md.spec.ts"));
63
+ const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(tmpDir, "AGENTS.md.spec.ts"), "utf-8");
64
+ strict_1.default.ok(content.includes('target: "AGENTS.md"'));
65
+ });
66
+ (0, node_test_1.it)("should create custom target spec", () => {
67
+ const { exitCode } = run("init --target=CODEX.md", tmpDir);
68
+ strict_1.default.equal(exitCode, 0);
69
+ strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.join)(tmpDir, "CODEX.md.spec.ts")));
70
+ const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(tmpDir, "CODEX.md.spec.ts"), "utf-8");
71
+ strict_1.default.ok(content.includes('target: "CODEX.md"'));
72
+ });
73
+ });
74
+ // ---------------------------------------------------------------------------
75
+ // vigiles compile
76
+ // ---------------------------------------------------------------------------
77
+ (0, node_test_1.describe)("CLI: vigiles compile", () => {
78
+ let tmpDir;
79
+ (0, node_test_1.before)(() => {
80
+ tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-cli-compile-"));
81
+ });
82
+ (0, node_test_1.after)(() => {
83
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
84
+ });
85
+ (0, node_test_1.it)("should report when no specs are found", () => {
86
+ const { stdout, exitCode } = run("compile", tmpDir);
87
+ strict_1.default.equal(exitCode, 0);
88
+ strict_1.default.ok(stdout.includes("No .spec.ts files found"));
89
+ });
90
+ (0, node_test_1.it)("should compile a spec", () => {
91
+ const tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-compile-"));
92
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "package.json"), JSON.stringify({ name: "test", scripts: { test: "echo ok" } }));
93
+ const specSrc = (0, node_path_1.resolve)(process.cwd(), "dist", "spec.js");
94
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md.spec.ts"), `import { claude, guidance } from "${specSrc}";\nexport default claude({ rules: { r: guidance("test") } });\n`);
95
+ const { stdout, exitCode } = run("compile CLAUDE.md.spec.ts", tmpDir);
96
+ strict_1.default.equal(exitCode, 0, stdout);
97
+ strict_1.default.ok(stdout.includes("CLAUDE.md.spec.ts"));
98
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
99
+ });
100
+ (0, node_test_1.it)("should compile subdirectory spec to same directory", () => {
101
+ const tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-compile-subdir-"));
102
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "package.json"), JSON.stringify({ name: "test", scripts: { test: "echo ok" } }));
103
+ const subDir = (0, node_path_1.join)(tmpDir, "examples");
104
+ (0, node_fs_1.mkdirSync)(subDir);
105
+ const specSrc = (0, node_path_1.resolve)(process.cwd(), "dist", "spec.js");
106
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(subDir, "CLAUDE.md.spec.ts"), `import { claude, guidance } from "${specSrc}";\nexport default claude({ rules: { r: guidance("test") } });\n`);
107
+ const { stdout, exitCode } = run("compile examples/CLAUDE.md.spec.ts", tmpDir);
108
+ strict_1.default.equal(exitCode, 0, stdout);
109
+ // Output should be in examples/, not root
110
+ strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.join)(subDir, "CLAUDE.md")), "Expected examples/CLAUDE.md to exist");
111
+ strict_1.default.ok(!(0, node_fs_1.existsSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md")), "Root CLAUDE.md should NOT exist — spec in subdirectory must write to same directory");
112
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
113
+ });
114
+ (0, node_test_1.it)("should not clobber root spec output when compiling all specs", () => {
115
+ const tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-compile-noclobber-"));
116
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "package.json"), JSON.stringify({ name: "test", scripts: { test: "echo ok" } }));
117
+ const specSrc = (0, node_path_1.resolve)(process.cwd(), "dist", "spec.js");
118
+ // Root spec
119
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md.spec.ts"), `import { claude, guidance } from "${specSrc}";\nexport default claude({ rules: { "root-rule": guidance("from root") } });\n`);
120
+ // Subdirectory spec
121
+ const subDir = (0, node_path_1.join)(tmpDir, "examples");
122
+ (0, node_fs_1.mkdirSync)(subDir);
123
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(subDir, "CLAUDE.md.spec.ts"), `import { claude, guidance } from "${specSrc}";\nexport default claude({ rules: { "sub-rule": guidance("from subdir") } });\n`);
124
+ const { stdout, exitCode } = run("compile", tmpDir);
125
+ strict_1.default.equal(exitCode, 0, stdout);
126
+ // Root CLAUDE.md should come from root spec
127
+ const rootMd = (0, node_fs_1.readFileSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md"), "utf-8");
128
+ strict_1.default.ok(rootMd.includes("from root"), "Root CLAUDE.md should contain root spec content");
129
+ strict_1.default.ok(!rootMd.includes("from subdir"), "Root CLAUDE.md must not be overwritten by subdirectory spec");
130
+ // Subdirectory CLAUDE.md should come from subdirectory spec
131
+ const subMd = (0, node_fs_1.readFileSync)((0, node_path_1.join)(subDir, "CLAUDE.md"), "utf-8");
132
+ strict_1.default.ok(subMd.includes("from subdir"), "examples/CLAUDE.md should contain subdirectory spec content");
133
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
134
+ });
135
+ });
136
+ // ---------------------------------------------------------------------------
137
+ // vigiles audit
138
+ // ---------------------------------------------------------------------------
139
+ (0, node_test_1.describe)("CLI: vigiles audit", () => {
140
+ let tmpDir;
141
+ (0, node_test_1.before)(() => {
142
+ tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-cli-audit-"));
143
+ });
144
+ (0, node_test_1.after)(() => {
145
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
146
+ });
147
+ (0, node_test_1.it)("should report when no instruction files are found", () => {
148
+ const { stdout, exitCode } = run("audit", tmpDir);
149
+ strict_1.default.equal(exitCode, 0);
150
+ strict_1.default.ok(stdout.includes("No compiled instruction files found"));
151
+ });
152
+ (0, node_test_1.it)("should include coverage and strengthen output", () => {
153
+ const { stdout } = run("audit", tmpDir);
154
+ // audit runs discover + strengthen in addition to verification
155
+ strict_1.default.ok(stdout.includes("coverage") ||
156
+ stdout.includes("Linter") ||
157
+ stdout.includes("No .spec.ts"));
158
+ });
159
+ (0, node_test_1.it)("should detect duplicate rules via NCD", () => {
160
+ const dupDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-audit-dup-"));
161
+ try {
162
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dupDir, "package.json"), JSON.stringify({ name: "test", scripts: {} }));
163
+ const specSrc = (0, node_path_1.resolve)(process.cwd(), "dist", "spec.js");
164
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dupDir, "CLAUDE.md.spec.ts"), `import { claude, guidance } from "${specSrc}";
165
+ export default claude({
166
+ rules: {
167
+ "use-logger": guidance("Always use the structured logger instead of console.log for production output."),
168
+ "logger-over-console": guidance("Use the structured logger instead of console.log in production code."),
169
+ "unrelated": guidance("Prefer composition over inheritance in class hierarchies."),
170
+ },
171
+ });
172
+ `);
173
+ const { stdout } = run("audit", dupDir);
174
+ // Should detect the two logger rules as near-duplicates
175
+ strict_1.default.ok(stdout.includes("near-duplicate") || stdout.includes("duplicate"), `Expected duplicate detection, got: ${stdout.slice(0, 500)}`);
176
+ }
177
+ finally {
178
+ (0, node_fs_1.rmSync)(dupDir, { recursive: true, force: true });
179
+ }
180
+ });
181
+ (0, node_test_1.it)("should skip inline verification for spec-managed files", () => {
182
+ // A file with a sibling .spec.ts (and compiled-from header) must
183
+ // not run inline verification, so literal vigiles:enforce snippets
184
+ // in prose cannot trip audit when the file is spec-managed.
185
+ // We use a sibling-.spec.ts with the spec snippet embedded as a
186
+ // prose section — compile generates the valid hash, then audit
187
+ // must ignore the inline marker in the output.
188
+ const specDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-audit-spec-skip-"));
189
+ try {
190
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(specDir, "package.json"), JSON.stringify({ name: "test", scripts: {} }));
191
+ const specSrc = (0, node_path_1.resolve)(process.cwd(), "dist", "spec.js");
192
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(specDir, "CLAUDE.md.spec.ts"), `import { claude, guidance } from "${specSrc}";
193
+ export default claude({
194
+ sections: {
195
+ // A literal enforce marker embedded in prose — would be picked
196
+ // up as an inline rule if audit didn't skip spec-managed files.
197
+ example: 'Example: <!-- vigiles:enforce eslint/total-nonsense "prose" -->',
198
+ },
199
+ rules: {
200
+ "some-rule": guidance("Something."),
201
+ },
202
+ });
203
+ `);
204
+ // Compile first so CLAUDE.md has a valid hash.
205
+ const compileResult = run("compile", specDir);
206
+ strict_1.default.equal(compileResult.exitCode, 0, `compile failed: ${compileResult.stdout}`);
207
+ const { stdout, exitCode } = run("audit CLAUDE.md", specDir);
208
+ // Should NOT surface the bogus rule as an inline error.
209
+ strict_1.default.ok(!stdout.includes("total-nonsense"), `Spec-managed file should skip inline verification, got: ${stdout.slice(0, 800)}`);
210
+ // And should not have exited with the hard-error code.
211
+ strict_1.default.notEqual(exitCode, 2, `Expected no inline errors, got exit ${String(exitCode)}: ${stdout.slice(0, 600)}`);
212
+ }
213
+ finally {
214
+ (0, node_fs_1.rmSync)(specDir, { recursive: true, force: true });
215
+ }
216
+ });
217
+ });
218
+ // ---------------------------------------------------------------------------
219
+ // Inline mode E2E
220
+ // ---------------------------------------------------------------------------
221
+ (0, node_test_1.describe)("E2E: inline enforcement", () => {
222
+ let inlineDir;
223
+ (0, node_test_1.before)(() => {
224
+ inlineDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-inline-e2e-"));
225
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(inlineDir, "package.json"), JSON.stringify({ name: "test-inline", scripts: {} }));
226
+ // Symlink node_modules so checkLinterRule can find ESLint
227
+ // when the spawned CLI runs with cwd=inlineDir.
228
+ (0, node_fs_1.symlinkSync)((0, node_path_1.resolve)(process.cwd(), "node_modules"), (0, node_path_1.join)(inlineDir, "node_modules"));
229
+ // Also copy eslint config so the config checker can resolve rules
230
+ const eslintConfig = (0, node_path_1.resolve)(process.cwd(), "eslint.config.ts");
231
+ if ((0, node_fs_1.existsSync)(eslintConfig)) {
232
+ (0, node_fs_1.copyFileSync)(eslintConfig, (0, node_path_1.join)(inlineDir, "eslint.config.ts"));
233
+ }
234
+ });
235
+ (0, node_test_1.after)(() => {
236
+ (0, node_fs_1.rmSync)(inlineDir, { recursive: true, force: true });
237
+ });
238
+ (0, node_test_1.it)("verifies valid inline enforce rules and exits clean", () => {
239
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(inlineDir, "CLAUDE.md"), `# Project
240
+
241
+ <!-- vigiles:enforce eslint/no-console "Use structured logger" -->
242
+
243
+ All output goes through logger.ts.
244
+ `);
245
+ const { stdout, exitCode } = run("audit CLAUDE.md", inlineDir);
246
+ strict_1.default.ok(stdout.includes("eslint/no-console"), `Expected rule in output, got: ${stdout.slice(0, 600)}`);
247
+ // Exit code 0 means no hard errors (inline rule is valid)
248
+ strict_1.default.equal(exitCode, 0, `Expected clean exit, got ${String(exitCode)}`);
249
+ });
250
+ (0, node_test_1.it)("flags a typo'd inline rule with a closest-match suggestion", () => {
251
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(inlineDir, "CLAUDE.md"), `# Project
252
+
253
+ <!-- vigiles:enforce eslint/no-consol "Typo check" -->
254
+
255
+ Some prose.
256
+ `);
257
+ const { stdout, exitCode } = run("audit CLAUDE.md", inlineDir);
258
+ strict_1.default.ok(stdout.includes("no-consol"), `Expected typo'd rule in output, got: ${stdout.slice(0, 600)}`);
259
+ strict_1.default.ok(stdout.includes("Did you mean"), `Expected closest-match suggestion, got: ${stdout.slice(0, 600)}`);
260
+ strict_1.default.equal(exitCode, 2, `Expected exit 2 on inline error`);
261
+ });
262
+ (0, node_test_1.it)("ignores inline markers inside fenced code blocks", () => {
263
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(inlineDir, "CLAUDE.md"), `# Docs
264
+
265
+ Example usage:
266
+
267
+ \`\`\`md
268
+ <!-- vigiles:enforce eslint/totally-bogus "inside fence" -->
269
+ \`\`\`
270
+
271
+ Real rule:
272
+
273
+ <!-- vigiles:enforce eslint/no-console "outside fence" -->
274
+ `);
275
+ const { stdout, exitCode } = run("audit CLAUDE.md", inlineDir);
276
+ // The bogus rule inside the fence must NOT appear as an error
277
+ strict_1.default.ok(!stdout.includes("totally-bogus"), `Fenced marker should be skipped, got: ${stdout.slice(0, 600)}`);
278
+ strict_1.default.ok(stdout.includes("no-console"), `Real rule should be verified, got: ${stdout.slice(0, 600)}`);
279
+ strict_1.default.equal(exitCode, 0);
280
+ });
281
+ (0, node_test_1.it)("reports inline errors in --json output", () => {
282
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(inlineDir, "CLAUDE.md"), `<!-- vigiles:enforce eslint/fake-rule-xyz "bad" -->`);
283
+ const { stdout, exitCode } = run("audit --json CLAUDE.md", inlineDir);
284
+ const report = JSON.parse(stdout);
285
+ strict_1.default.ok(report.inlineErrors > 0, "Expected inlineErrors > 0");
286
+ strict_1.default.ok(report.inlineRules > 0, "Expected inlineRules > 0");
287
+ strict_1.default.equal(exitCode, 2);
288
+ });
289
+ (0, node_test_1.it)("reports inline rules in --summary output", () => {
290
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(inlineDir, "CLAUDE.md"), `<!-- vigiles:enforce eslint/fake-rule-xyz "bad" -->`);
291
+ const { stdout, exitCode } = run("audit --summary CLAUDE.md", inlineDir);
292
+ strict_1.default.ok(stdout.includes("inline"), `Expected 'inline' in summary, got: ${stdout}`);
293
+ strict_1.default.equal(exitCode, 2);
294
+ });
295
+ (0, node_test_1.it)("satisfies require-spec when inline rules are present", () => {
296
+ // A file with inline rules but no .spec.ts should NOT trigger
297
+ // the require-spec validation warning.
298
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(inlineDir, "CLAUDE.md"), `<!-- vigiles:enforce eslint/no-console "valid" -->
299
+
300
+ # Project
301
+ `);
302
+ const { stdout } = run("audit CLAUDE.md", inlineDir);
303
+ strict_1.default.ok(!stdout.includes("require-spec"), `require-spec should be satisfied by inline rules, got: ${stdout.slice(0, 600)}`);
304
+ });
305
+ });
306
+ // ---------------------------------------------------------------------------
307
+ // vigiles generate-types
308
+ // ---------------------------------------------------------------------------
309
+ (0, node_test_1.describe)("CLI: vigiles generate-types", () => {
310
+ (0, node_test_1.it)("should generate types", () => {
311
+ const tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-gen-"));
312
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "package.json"), JSON.stringify({ name: "test", scripts: { build: "echo ok" } }));
313
+ const { stdout, exitCode } = run("generate-types", tmpDir);
314
+ strict_1.default.equal(exitCode, 0);
315
+ strict_1.default.ok(stdout.includes("Generated"));
316
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
317
+ });
318
+ (0, node_test_1.it)("should verify freshness with --check", () => {
319
+ // Use a temp dir so we don't modify the project's generated types
320
+ const tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-types-check-"));
321
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "package.json"), JSON.stringify({ name: "test", scripts: { test: "echo ok" } }));
322
+ // Generate types in temp dir
323
+ run("generate-types", tmpDir);
324
+ // Then check — should pass
325
+ const { exitCode } = run("generate-types --check", tmpDir);
326
+ strict_1.default.equal(exitCode, 0);
327
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
328
+ });
329
+ });
330
+ // ---------------------------------------------------------------------------
331
+ // Multi-target compilation
332
+ // ---------------------------------------------------------------------------
333
+ (0, node_test_1.describe)("CLI: multi-target compile", () => {
334
+ let tmpDir;
335
+ (0, node_test_1.before)(() => {
336
+ tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-cli-multi-"));
337
+ // Create a spec with multiple targets
338
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md.spec.ts"), `import { claude, guidance } from "${(0, node_path_1.resolve)(process.cwd(), "src/spec.js")}";
339
+ export default claude({
340
+ target: ["CLAUDE.md", "AGENTS.md"],
341
+ rules: {
342
+ "test-rule": guidance("Test guidance."),
343
+ },
344
+ });
345
+ `);
346
+ });
347
+ (0, node_test_1.after)(() => {
348
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
349
+ });
350
+ (0, node_test_1.it)("should compile to multiple targets", () => {
351
+ const { stdout, exitCode } = run("compile CLAUDE.md.spec.ts", tmpDir);
352
+ strict_1.default.equal(exitCode, 0);
353
+ strict_1.default.ok(stdout.includes("CLAUDE.md"));
354
+ strict_1.default.ok(stdout.includes("AGENTS.md"));
355
+ // Both files should exist
356
+ strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md")));
357
+ strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.join)(tmpDir, "AGENTS.md")));
358
+ // Primary has CLAUDE.md heading
359
+ const claude = (0, node_fs_1.readFileSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md"), "utf-8");
360
+ strict_1.default.ok(claude.includes("# CLAUDE.md"));
361
+ // Secondary has AGENTS.md heading
362
+ const agents = (0, node_fs_1.readFileSync)((0, node_path_1.join)(tmpDir, "AGENTS.md"), "utf-8");
363
+ strict_1.default.ok(agents.includes("# AGENTS.md"));
364
+ });
365
+ });
366
+ // ---------------------------------------------------------------------------
367
+ // vigiles strengthen
368
+ // ---------------------------------------------------------------------------
369
+ // ---------------------------------------------------------------------------
370
+ // vigiles init (was: vigiles setup)
371
+ // ---------------------------------------------------------------------------
372
+ (0, node_test_1.describe)("CLI: vigiles init (full setup)", () => {
373
+ let tmpDir;
374
+ (0, node_test_1.before)(() => {
375
+ tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-cli-setup-"));
376
+ // Need a package.json for generate-types
377
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "package.json"), JSON.stringify({ name: "test", scripts: { test: "echo ok" } }));
378
+ });
379
+ (0, node_test_1.after)(() => {
380
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
381
+ });
382
+ (0, node_test_1.it)("should create spec, generate types, and compile", () => {
383
+ const { stdout, exitCode } = run("init", tmpDir);
384
+ strict_1.default.equal(exitCode, 0);
385
+ strict_1.default.ok(stdout.includes("Created CLAUDE.md.spec.ts"));
386
+ strict_1.default.ok(stdout.includes("Setup complete"));
387
+ strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md.spec.ts")));
388
+ strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.join)(tmpDir, ".vigiles/generated.d.ts")));
389
+ });
390
+ (0, node_test_1.it)("should support --target flag", () => {
391
+ const { stdout, exitCode } = run("init --target=AGENTS.md", tmpDir);
392
+ strict_1.default.equal(exitCode, 0);
393
+ strict_1.default.ok(stdout.includes("AGENTS.md.spec.ts"));
394
+ strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.join)(tmpDir, "AGENTS.md.spec.ts")));
395
+ });
396
+ });
397
+ (0, node_test_1.describe)("CLI: vigiles init auto-detection", () => {
398
+ let tmpDir;
399
+ (0, node_test_1.before)(() => {
400
+ tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-cli-detect-"));
401
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "package.json"), JSON.stringify({ name: "test", scripts: { test: "echo ok" } }));
402
+ });
403
+ (0, node_test_1.after)(() => {
404
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
405
+ });
406
+ (0, node_test_1.it)("should detect existing CLAUDE.md and suggest migration", () => {
407
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(tmpDir, "CLAUDE.md"), "# Hand-written\n");
408
+ const { stdout } = run("init", tmpDir);
409
+ strict_1.default.ok(stdout.includes("without a spec") || stdout.includes("migrate"));
410
+ });
411
+ (0, node_test_1.it)("should detect .cursorrules and suggest sync tool", () => {
412
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-detect-cursor-"));
413
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name: "test", scripts: {} }));
414
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, ".cursorrules"), "Use TypeScript.\n");
415
+ const { stdout } = run("init", dir);
416
+ strict_1.default.ok(stdout.includes("Cursor") || stdout.includes("Non-markdown"));
417
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
418
+ });
419
+ (0, node_test_1.it)("should detect .claude directory as Claude Code project", () => {
420
+ const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-detect-claude-"));
421
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name: "test", scripts: {} }));
422
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(dir, ".claude"), { recursive: true });
423
+ const { stdout } = run("init", dir);
424
+ strict_1.default.ok(stdout.includes("Claude Code"));
425
+ (0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
426
+ });
427
+ });
428
+ // ---------------------------------------------------------------------------
429
+ // Pre-edit hook (blocks compiled file edits)
430
+ // ---------------------------------------------------------------------------
431
+ (0, node_test_1.describe)("plugin hook: pre-edit.sh", () => {
432
+ let tmpDir;
433
+ (0, node_test_1.before)(() => {
434
+ tmpDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-pre-edit-"));
435
+ });
436
+ (0, node_test_1.after)(() => {
437
+ (0, node_fs_1.rmSync)(tmpDir, { recursive: true, force: true });
438
+ });
439
+ (0, node_test_1.it)("should be executable with valid bash syntax", () => {
440
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".claude-plugin/hooks/pre-edit.sh");
441
+ strict_1.default.ok((0, node_fs_1.existsSync)(hookPath));
442
+ (0, node_child_process_1.execSync)(`bash -n ${hookPath}`, { stdio: "pipe" });
443
+ });
444
+ (0, node_test_1.it)("should exit 0 for non-md files", () => {
445
+ const input = JSON.stringify({
446
+ tool_input: { file_path: (0, node_path_1.join)(tmpDir, "src/app.ts") },
447
+ });
448
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".claude-plugin/hooks/pre-edit.sh");
449
+ (0, node_child_process_1.execSync)(`echo '${input}' | bash ${hookPath}`, {
450
+ cwd: tmpDir,
451
+ encoding: "utf-8",
452
+ stdio: ["pipe", "pipe", "pipe"],
453
+ });
454
+ // Should exit 0 (no block)
455
+ strict_1.default.ok(true);
456
+ });
457
+ (0, node_test_1.it)("should exit 2 for compiled md files", () => {
458
+ // Create a compiled .md file with vigiles hash
459
+ const mdPath = (0, node_path_1.join)(tmpDir, "CLAUDE.md");
460
+ (0, node_fs_1.writeFileSync)(mdPath, "<!-- vigiles:sha256:abc123 compiled from CLAUDE.md.spec.ts -->\n# CLAUDE.md\n");
461
+ const input = JSON.stringify({ tool_input: { file_path: mdPath } });
462
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".claude-plugin/hooks/pre-edit.sh");
463
+ try {
464
+ (0, node_child_process_1.execSync)(`echo '${input}' | bash ${hookPath}`, {
465
+ cwd: tmpDir,
466
+ encoding: "utf-8",
467
+ stdio: ["pipe", "pipe", "pipe"],
468
+ });
469
+ strict_1.default.fail("Should have exited with code 2");
470
+ }
471
+ catch (e) {
472
+ const err = e;
473
+ strict_1.default.equal(err.status, 2);
474
+ const stderr = err.stderr ?? "";
475
+ strict_1.default.ok(stderr.includes("CLAUDE.md.spec.ts"), "Should mention the spec file");
476
+ strict_1.default.ok(stderr.includes("BLOCKED"), "Should clearly indicate the action was blocked");
477
+ strict_1.default.ok(stderr.includes("edit-spec"), "Should reference the edit-spec skill");
478
+ }
479
+ });
480
+ (0, node_test_1.it)("should exit 0 for non-compiled md files", () => {
481
+ const mdPath = (0, node_path_1.join)(tmpDir, "HANDWRITTEN.md");
482
+ (0, node_fs_1.writeFileSync)(mdPath, "# Hand-written\n\nNo vigiles hash.\n");
483
+ const input = JSON.stringify({ tool_input: { file_path: mdPath } });
484
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".claude-plugin/hooks/pre-edit.sh");
485
+ (0, node_child_process_1.execSync)(`echo '${input}' | bash ${hookPath}`, {
486
+ cwd: tmpDir,
487
+ encoding: "utf-8",
488
+ stdio: ["pipe", "pipe", "pipe"],
489
+ });
490
+ // Should exit 0 (no block)
491
+ strict_1.default.ok(true);
492
+ });
493
+ });
494
+ // ---------------------------------------------------------------------------
495
+ // Plugin hook: post-edit
496
+ // ---------------------------------------------------------------------------
497
+ (0, node_test_1.describe)("plugin hook: post-edit.sh", () => {
498
+ (0, node_test_1.it)("should be executable", () => {
499
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".claude-plugin/hooks/post-edit.sh");
500
+ strict_1.default.ok((0, node_fs_1.existsSync)(hookPath));
501
+ // Check it's parseable bash
502
+ try {
503
+ (0, node_child_process_1.execSync)(`bash -n ${hookPath}`, { stdio: "pipe" });
504
+ }
505
+ catch {
506
+ strict_1.default.fail("post-edit.sh has syntax errors");
507
+ }
508
+ });
509
+ (0, node_test_1.it)("should exit cleanly with empty input", () => {
510
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".claude-plugin/hooks/post-edit.sh");
511
+ try {
512
+ (0, node_child_process_1.execSync)(`echo '{}' | bash ${hookPath}`, {
513
+ cwd: process.cwd(),
514
+ stdio: "pipe",
515
+ timeout: 5000,
516
+ });
517
+ }
518
+ catch {
519
+ // Non-zero exit is ok — jq might fail on empty. Just shouldn't hang.
520
+ }
521
+ });
522
+ (0, node_test_1.it)("should match linter config files for type regeneration", () => {
523
+ // Test the case pattern matching by running a dry-run variant:
524
+ // Override npx to just echo, check the routing logic works.
525
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".claude-plugin/hooks/post-edit.sh");
526
+ const configFiles = [
527
+ "eslint.config.mjs",
528
+ ".eslintrc.json",
529
+ "pyproject.toml",
530
+ "Cargo.toml",
531
+ "package.json",
532
+ ".stylelintrc.json",
533
+ ".rubocop.yml",
534
+ ];
535
+ for (const filename of configFiles) {
536
+ const input = JSON.stringify({
537
+ tool_input: { file_path: `/tmp/${filename}` },
538
+ });
539
+ // The hook should match these files — we can't easily verify it runs
540
+ // generate-types without mocking npx, but we can verify it doesn't crash.
541
+ try {
542
+ (0, node_child_process_1.execSync)(`echo '${input}' | bash ${hookPath}`, {
543
+ cwd: process.cwd(),
544
+ encoding: "utf-8",
545
+ stdio: ["pipe", "pipe", "pipe"],
546
+ timeout: 5000,
547
+ env: { ...process.env, PATH: "/nonexistent" }, // npx won't be found, but routing still works
548
+ });
549
+ }
550
+ catch {
551
+ // Expected: npx not in PATH or vigiles not available in /tmp.
552
+ // The important thing is the script didn't error on the case match.
553
+ }
554
+ }
555
+ strict_1.default.ok(true, "All config files processed without crash");
556
+ });
557
+ (0, node_test_1.it)("should match .spec.ts files for compilation", () => {
558
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".claude-plugin/hooks/post-edit.sh");
559
+ const input = JSON.stringify({
560
+ tool_input: { file_path: "/tmp/CLAUDE.md.spec.ts" },
561
+ });
562
+ try {
563
+ (0, node_child_process_1.execSync)(`echo '${input}' | bash ${hookPath}`, {
564
+ cwd: process.cwd(),
565
+ encoding: "utf-8",
566
+ stdio: ["pipe", "pipe", "pipe"],
567
+ timeout: 5000,
568
+ env: { ...process.env, PATH: "/nonexistent" },
569
+ });
570
+ }
571
+ catch {
572
+ // Expected: npx not found. Routing logic still works.
573
+ }
574
+ strict_1.default.ok(true, ".spec.ts file processed without crash");
575
+ });
576
+ (0, node_test_1.it)("should not trigger for unrelated files", () => {
577
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".claude-plugin/hooks/post-edit.sh");
578
+ const input = JSON.stringify({
579
+ tool_input: { file_path: "/tmp/src/app.ts" },
580
+ });
581
+ // Should exit 0 quickly — no case match, no npx call.
582
+ (0, node_child_process_1.execSync)(`echo '${input}' | bash ${hookPath}`, {
583
+ cwd: process.cwd(),
584
+ encoding: "utf-8",
585
+ stdio: ["pipe", "pipe", "pipe"],
586
+ timeout: 5000,
587
+ });
588
+ strict_1.default.ok(true, "Unrelated file skipped without triggering any action");
589
+ });
590
+ });
591
+ // ---------------------------------------------------------------------------
592
+ // E2E: fixture project — full adoption flow
593
+ // ---------------------------------------------------------------------------
594
+ (0, node_test_1.describe)("E2E: fixture project adoption", () => {
595
+ const FIXTURE = (0, node_path_1.resolve)(__dirname, "..", "fixtures", "example-project");
596
+ let workDir;
597
+ (0, node_test_1.before)(() => {
598
+ // Copy fixture to a temp dir so tests don't pollute it
599
+ workDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "vigiles-e2e-"));
600
+ (0, node_child_process_1.execSync)(`cp -r ${FIXTURE}/* ${workDir}/`, { stdio: "pipe" });
601
+ });
602
+ (0, node_test_1.after)(() => {
603
+ (0, node_fs_1.rmSync)(workDir, { recursive: true, force: true });
604
+ });
605
+ (0, node_test_1.it)("setup detects existing hand-written CLAUDE.md", () => {
606
+ const { stdout } = run("init", workDir);
607
+ // Should detect CLAUDE.md without spec and suggest migration
608
+ strict_1.default.ok(stdout.includes("without a spec") || stdout.includes("migrate"), "Should detect hand-written CLAUDE.md");
609
+ });
610
+ (0, node_test_1.it)("audit detects CLAUDE.md has no vigiles hash", () => {
611
+ const { stdout } = run("audit", workDir);
612
+ // Hand-written CLAUDE.md has no hash — should report it
613
+ strict_1.default.ok(stdout.includes("no vigiles hash") ||
614
+ stdout.includes("require-spec") ||
615
+ stdout.includes("Verifying"));
616
+ });
617
+ (0, node_test_1.it)("generate-types works in fixture project", () => {
618
+ const { exitCode } = run("generate-types", workDir);
619
+ strict_1.default.equal(exitCode, 0);
620
+ strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.join)(workDir, ".vigiles/generated.d.ts")));
621
+ });
622
+ (0, node_test_1.it)("full flow: write spec → compile → audit passes", () => {
623
+ // Clean slate: remove any existing CLAUDE.md and spec
624
+ const mdPath = (0, node_path_1.join)(workDir, "CLAUDE.md");
625
+ const specPath = (0, node_path_1.join)(workDir, "CLAUDE.md.spec.ts");
626
+ if ((0, node_fs_1.existsSync)(mdPath))
627
+ (0, node_fs_1.rmSync)(mdPath);
628
+ if ((0, node_fs_1.existsSync)(specPath))
629
+ (0, node_fs_1.rmSync)(specPath);
630
+ // Write a spec that imports from vigiles dist (not node_modules)
631
+ const specSrc = (0, node_path_1.resolve)(process.cwd(), "dist", "spec.js");
632
+ (0, node_fs_1.writeFileSync)(specPath, `import { claude, guidance } from "${specSrc}";
633
+ export default claude({
634
+ commands: { "npm test": "Run tests" },
635
+ rules: { "be-nice": guidance("Be nice.") },
636
+ });
637
+ `);
638
+ // Compile
639
+ const compileResult = run("compile CLAUDE.md.spec.ts", workDir);
640
+ strict_1.default.equal(compileResult.exitCode, 0, compileResult.stdout);
641
+ strict_1.default.ok((0, node_fs_1.existsSync)(mdPath));
642
+ // Compiled file should have vigiles hash
643
+ const content = (0, node_fs_1.readFileSync)(mdPath, "utf-8");
644
+ strict_1.default.ok(content.includes("<!-- vigiles:sha256:"));
645
+ // Audit should pass (hash valid, spec exists)
646
+ const auditResult = run("audit", workDir);
647
+ strict_1.default.ok(auditResult.stdout.includes("hash valid") || auditResult.exitCode === 0);
648
+ });
649
+ });
650
+ //# sourceMappingURL=cli.test.js.map