@mainahq/core 0.2.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/README.md +31 -0
  2. package/package.json +37 -0
  3. package/src/ai/__tests__/ai.test.ts +207 -0
  4. package/src/ai/__tests__/design-approaches.test.ts +192 -0
  5. package/src/ai/__tests__/spec-questions.test.ts +191 -0
  6. package/src/ai/__tests__/tiers.test.ts +110 -0
  7. package/src/ai/commit-msg.ts +28 -0
  8. package/src/ai/design-approaches.ts +76 -0
  9. package/src/ai/index.ts +205 -0
  10. package/src/ai/pr-summary.ts +60 -0
  11. package/src/ai/spec-questions.ts +74 -0
  12. package/src/ai/tiers.ts +52 -0
  13. package/src/ai/try-generate.ts +89 -0
  14. package/src/ai/validate.ts +66 -0
  15. package/src/benchmark/__tests__/reporter.test.ts +525 -0
  16. package/src/benchmark/__tests__/runner.test.ts +113 -0
  17. package/src/benchmark/__tests__/story-loader.test.ts +152 -0
  18. package/src/benchmark/reporter.ts +332 -0
  19. package/src/benchmark/runner.ts +91 -0
  20. package/src/benchmark/story-loader.ts +88 -0
  21. package/src/benchmark/types.ts +95 -0
  22. package/src/cache/__tests__/keys.test.ts +97 -0
  23. package/src/cache/__tests__/manager.test.ts +312 -0
  24. package/src/cache/__tests__/ttl.test.ts +94 -0
  25. package/src/cache/keys.ts +44 -0
  26. package/src/cache/manager.ts +231 -0
  27. package/src/cache/ttl.ts +77 -0
  28. package/src/config/__tests__/config.test.ts +376 -0
  29. package/src/config/index.ts +198 -0
  30. package/src/context/__tests__/budget.test.ts +179 -0
  31. package/src/context/__tests__/engine.test.ts +163 -0
  32. package/src/context/__tests__/episodic.test.ts +291 -0
  33. package/src/context/__tests__/relevance.test.ts +323 -0
  34. package/src/context/__tests__/retrieval.test.ts +143 -0
  35. package/src/context/__tests__/selector.test.ts +174 -0
  36. package/src/context/__tests__/semantic.test.ts +252 -0
  37. package/src/context/__tests__/treesitter.test.ts +229 -0
  38. package/src/context/__tests__/working.test.ts +236 -0
  39. package/src/context/budget.ts +130 -0
  40. package/src/context/engine.ts +394 -0
  41. package/src/context/episodic.ts +251 -0
  42. package/src/context/relevance.ts +325 -0
  43. package/src/context/retrieval.ts +325 -0
  44. package/src/context/selector.ts +93 -0
  45. package/src/context/semantic.ts +331 -0
  46. package/src/context/treesitter.ts +216 -0
  47. package/src/context/working.ts +192 -0
  48. package/src/db/__tests__/db.test.ts +151 -0
  49. package/src/db/index.ts +211 -0
  50. package/src/db/schema.ts +84 -0
  51. package/src/design/__tests__/design.test.ts +310 -0
  52. package/src/design/__tests__/generate-hld-lld.test.ts +109 -0
  53. package/src/design/__tests__/review.test.ts +561 -0
  54. package/src/design/index.ts +297 -0
  55. package/src/design/review.ts +327 -0
  56. package/src/explain/__tests__/explain.test.ts +173 -0
  57. package/src/explain/index.ts +181 -0
  58. package/src/features/__tests__/analyzer.test.ts +358 -0
  59. package/src/features/__tests__/checklist.test.ts +454 -0
  60. package/src/features/__tests__/numbering.test.ts +319 -0
  61. package/src/features/__tests__/quality.test.ts +295 -0
  62. package/src/features/__tests__/traceability.test.ts +147 -0
  63. package/src/features/analyzer.ts +445 -0
  64. package/src/features/checklist.ts +366 -0
  65. package/src/features/index.ts +18 -0
  66. package/src/features/numbering.ts +404 -0
  67. package/src/features/quality.ts +349 -0
  68. package/src/features/test-stubs.ts +157 -0
  69. package/src/features/traceability.ts +260 -0
  70. package/src/feedback/__tests__/async-feedback.test.ts +52 -0
  71. package/src/feedback/__tests__/collector.test.ts +219 -0
  72. package/src/feedback/__tests__/compress.test.ts +150 -0
  73. package/src/feedback/__tests__/preferences.test.ts +169 -0
  74. package/src/feedback/collector.ts +135 -0
  75. package/src/feedback/compress.ts +92 -0
  76. package/src/feedback/preferences.ts +108 -0
  77. package/src/git/__tests__/git.test.ts +62 -0
  78. package/src/git/index.ts +110 -0
  79. package/src/hooks/__tests__/runner.test.ts +266 -0
  80. package/src/hooks/index.ts +8 -0
  81. package/src/hooks/runner.ts +130 -0
  82. package/src/index.ts +356 -0
  83. package/src/init/__tests__/init.test.ts +228 -0
  84. package/src/init/index.ts +364 -0
  85. package/src/language/__tests__/detect.test.ts +77 -0
  86. package/src/language/__tests__/profile.test.ts +51 -0
  87. package/src/language/detect.ts +70 -0
  88. package/src/language/profile.ts +110 -0
  89. package/src/prompts/__tests__/defaults.test.ts +52 -0
  90. package/src/prompts/__tests__/engine.test.ts +183 -0
  91. package/src/prompts/__tests__/evolution-resolve.test.ts +169 -0
  92. package/src/prompts/__tests__/evolution.test.ts +187 -0
  93. package/src/prompts/__tests__/loader.test.ts +105 -0
  94. package/src/prompts/candidates/review-v2.md +55 -0
  95. package/src/prompts/defaults/ai-review.md +49 -0
  96. package/src/prompts/defaults/commit.md +30 -0
  97. package/src/prompts/defaults/context.md +26 -0
  98. package/src/prompts/defaults/design-approaches.md +57 -0
  99. package/src/prompts/defaults/design-hld-lld.md +55 -0
  100. package/src/prompts/defaults/design.md +53 -0
  101. package/src/prompts/defaults/explain.md +31 -0
  102. package/src/prompts/defaults/fix.md +32 -0
  103. package/src/prompts/defaults/index.ts +38 -0
  104. package/src/prompts/defaults/review.md +41 -0
  105. package/src/prompts/defaults/spec-questions.md +59 -0
  106. package/src/prompts/defaults/tests.md +72 -0
  107. package/src/prompts/engine.ts +137 -0
  108. package/src/prompts/evolution.ts +409 -0
  109. package/src/prompts/loader.ts +71 -0
  110. package/src/review/__tests__/review.test.ts +288 -0
  111. package/src/review/comprehensive.ts +362 -0
  112. package/src/review/index.ts +417 -0
  113. package/src/stats/__tests__/tracker.test.ts +323 -0
  114. package/src/stats/index.ts +11 -0
  115. package/src/stats/tracker.ts +492 -0
  116. package/src/ticket/__tests__/ticket.test.ts +273 -0
  117. package/src/ticket/index.ts +185 -0
  118. package/src/utils.ts +87 -0
  119. package/src/verify/__tests__/ai-review.test.ts +242 -0
  120. package/src/verify/__tests__/coverage.test.ts +83 -0
  121. package/src/verify/__tests__/detect.test.ts +175 -0
  122. package/src/verify/__tests__/diff-filter.test.ts +338 -0
  123. package/src/verify/__tests__/fix.test.ts +478 -0
  124. package/src/verify/__tests__/linters/clippy.test.ts +45 -0
  125. package/src/verify/__tests__/linters/go-vet.test.ts +27 -0
  126. package/src/verify/__tests__/linters/ruff.test.ts +64 -0
  127. package/src/verify/__tests__/mutation.test.ts +141 -0
  128. package/src/verify/__tests__/pipeline.test.ts +553 -0
  129. package/src/verify/__tests__/proof.test.ts +97 -0
  130. package/src/verify/__tests__/secretlint.test.ts +190 -0
  131. package/src/verify/__tests__/semgrep.test.ts +217 -0
  132. package/src/verify/__tests__/slop.test.ts +366 -0
  133. package/src/verify/__tests__/sonar.test.ts +113 -0
  134. package/src/verify/__tests__/syntax-guard.test.ts +227 -0
  135. package/src/verify/__tests__/trivy.test.ts +191 -0
  136. package/src/verify/__tests__/visual.test.ts +139 -0
  137. package/src/verify/ai-review.ts +276 -0
  138. package/src/verify/coverage.ts +134 -0
  139. package/src/verify/detect.ts +171 -0
  140. package/src/verify/diff-filter.ts +183 -0
  141. package/src/verify/fix.ts +317 -0
  142. package/src/verify/linters/clippy.ts +52 -0
  143. package/src/verify/linters/go-vet.ts +32 -0
  144. package/src/verify/linters/ruff.ts +47 -0
  145. package/src/verify/mutation.ts +143 -0
  146. package/src/verify/pipeline.ts +328 -0
  147. package/src/verify/proof.ts +277 -0
  148. package/src/verify/secretlint.ts +168 -0
  149. package/src/verify/semgrep.ts +170 -0
  150. package/src/verify/slop.ts +493 -0
  151. package/src/verify/sonar.ts +146 -0
  152. package/src/verify/syntax-guard.ts +251 -0
  153. package/src/verify/trivy.ts +161 -0
  154. package/src/verify/visual.ts +460 -0
  155. package/src/workflow/__tests__/context.test.ts +110 -0
  156. package/src/workflow/context.ts +81 -0
@@ -0,0 +1,454 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { verifyPlan } from "../checklist";
6
+
7
+ function makeTmpDir(): string {
8
+ const dir = join(
9
+ tmpdir(),
10
+ `maina-checklist-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
11
+ );
12
+ mkdirSync(dir, { recursive: true });
13
+ return dir;
14
+ }
15
+
16
+ const VALID_SPEC = `# Feature: User Auth
17
+
18
+ ## User stories
19
+ - As a user, I want to log in with email/password
20
+
21
+ ## Acceptance criteria
22
+ - Login form validates email format
23
+ - Failed login shows specific error message
24
+ - Password reset sends email within 30 seconds
25
+
26
+ ## [NEEDS CLARIFICATION]
27
+ - Should we support OAuth?
28
+ `;
29
+
30
+ const VALID_PLAN = `# Implementation Plan
31
+
32
+ ## Architecture
33
+ - JWT with RS256 signing
34
+
35
+ ## Tasks
36
+ - T001: Write tests for email validation and login form
37
+ - T002: Implement login endpoint with email format validation
38
+ - T003: Implement error messages for failed login attempts
39
+ - T004: Write tests for password reset flow
40
+ - T005: Implement password reset with email sending within 30 seconds
41
+ `;
42
+
43
+ describe("verifyPlan", () => {
44
+ let tmpDir: string;
45
+
46
+ beforeEach(() => {
47
+ tmpDir = makeTmpDir();
48
+ });
49
+
50
+ afterEach(() => {
51
+ rmSync(tmpDir, { recursive: true, force: true });
52
+ });
53
+
54
+ // --- Missing spec file ---
55
+ test("missing spec file returns error Result", () => {
56
+ const planPath = join(tmpDir, "plan.md");
57
+ const specPath = join(tmpDir, "spec.md");
58
+ writeFileSync(planPath, VALID_PLAN);
59
+
60
+ const result = verifyPlan(planPath, specPath);
61
+ expect(result.ok).toBe(false);
62
+ if (!result.ok) {
63
+ expect(result.error).toContain("spec");
64
+ }
65
+ });
66
+
67
+ // --- Missing plan file ---
68
+ test("missing plan file returns error Result", () => {
69
+ const planPath = join(tmpDir, "plan.md");
70
+ const specPath = join(tmpDir, "spec.md");
71
+ writeFileSync(specPath, VALID_SPEC);
72
+
73
+ const result = verifyPlan(planPath, specPath);
74
+ expect(result.ok).toBe(false);
75
+ if (!result.ok) {
76
+ expect(result.error).toContain("plan");
77
+ }
78
+ });
79
+
80
+ // --- Spec criterion coverage ---
81
+ test("plan missing a spec criterion fails with specific message", () => {
82
+ const specPath = join(tmpDir, "spec.md");
83
+ const planPath = join(tmpDir, "plan.md");
84
+
85
+ writeFileSync(specPath, VALID_SPEC);
86
+ // Plan only covers email validation — missing "error message" and "password reset"
87
+ writeFileSync(
88
+ planPath,
89
+ `# Implementation Plan
90
+
91
+ ## Architecture
92
+ - Simple approach
93
+
94
+ ## Tasks
95
+ - T001: Implement email format validation for login form
96
+ `,
97
+ );
98
+
99
+ const result = verifyPlan(planPath, specPath);
100
+ expect(result.ok).toBe(true);
101
+ if (!result.ok) return;
102
+
103
+ const coverage = result.value.checks.find(
104
+ (c) => c.name === "spec-coverage",
105
+ );
106
+ expect(coverage).toBeDefined();
107
+ expect(coverage?.passed).toBe(false);
108
+ // Should mention the missing criteria
109
+ expect(
110
+ coverage?.details.some((d) => d.toLowerCase().includes("error message")),
111
+ ).toBe(true);
112
+ expect(
113
+ coverage?.details.some((d) => d.toLowerCase().includes("password reset")),
114
+ ).toBe(true);
115
+ });
116
+
117
+ test("plan covering all spec criteria passes", () => {
118
+ const specPath = join(tmpDir, "spec.md");
119
+ const planPath = join(tmpDir, "plan.md");
120
+ writeFileSync(specPath, VALID_SPEC);
121
+ writeFileSync(planPath, VALID_PLAN);
122
+
123
+ const result = verifyPlan(planPath, specPath);
124
+ expect(result.ok).toBe(true);
125
+ if (!result.ok) return;
126
+
127
+ const coverage = result.value.checks.find(
128
+ (c) => c.name === "spec-coverage",
129
+ );
130
+ expect(coverage).toBeDefined();
131
+ expect(coverage?.passed).toBe(true);
132
+ expect(coverage?.details).toHaveLength(0);
133
+ });
134
+
135
+ // --- No TODO/TBD/PLACEHOLDER markers ---
136
+ test("plan with TODO marker fails and reports the line", () => {
137
+ const specPath = join(tmpDir, "spec.md");
138
+ const planPath = join(tmpDir, "plan.md");
139
+ writeFileSync(specPath, VALID_SPEC);
140
+ writeFileSync(
141
+ planPath,
142
+ `# Implementation Plan
143
+
144
+ ## Architecture
145
+ - JWT with RS256 signing
146
+
147
+ ## Tasks
148
+ - T001: Write tests for email validation and login form
149
+ - T002: Implement login endpoint with email format validation
150
+ - T003: TODO: Implement error messages for failed login attempts
151
+ - T004: Write tests for password reset flow
152
+ - T005: Implement password reset with email sending within 30 seconds
153
+ `,
154
+ );
155
+
156
+ const result = verifyPlan(planPath, specPath);
157
+ expect(result.ok).toBe(true);
158
+ if (!result.ok) return;
159
+
160
+ const placeholders = result.value.checks.find(
161
+ (c) => c.name === "no-placeholders",
162
+ );
163
+ expect(placeholders).toBeDefined();
164
+ expect(placeholders?.passed).toBe(false);
165
+ expect(placeholders?.details.some((d) => d.includes("TODO"))).toBe(true);
166
+ // Should report the line number
167
+ expect(placeholders?.details.some((d) => /line \d+/i.test(d))).toBe(true);
168
+ });
169
+
170
+ test("plan with TBD marker fails", () => {
171
+ const specPath = join(tmpDir, "spec.md");
172
+ const planPath = join(tmpDir, "plan.md");
173
+ writeFileSync(specPath, VALID_SPEC);
174
+ writeFileSync(
175
+ planPath,
176
+ `# Implementation Plan
177
+
178
+ ## Architecture
179
+ - TBD architecture decisions
180
+
181
+ ## Tasks
182
+ - T001: Write tests for email validation and login form
183
+ - T002: Implement login endpoint with email format validation
184
+ - T003: Implement error messages for failed login attempts
185
+ - T004: Write tests for password reset flow
186
+ - T005: Implement password reset with email sending within 30 seconds
187
+ `,
188
+ );
189
+
190
+ const result = verifyPlan(planPath, specPath);
191
+ expect(result.ok).toBe(true);
192
+ if (!result.ok) return;
193
+
194
+ const placeholders = result.value.checks.find(
195
+ (c) => c.name === "no-placeholders",
196
+ );
197
+ expect(placeholders).toBeDefined();
198
+ expect(placeholders?.passed).toBe(false);
199
+ expect(placeholders?.details.some((d) => d.includes("TBD"))).toBe(true);
200
+ });
201
+
202
+ test("plan with PLACEHOLDER marker fails", () => {
203
+ const specPath = join(tmpDir, "spec.md");
204
+ const planPath = join(tmpDir, "plan.md");
205
+ writeFileSync(specPath, VALID_SPEC);
206
+ writeFileSync(
207
+ planPath,
208
+ `# Implementation Plan
209
+
210
+ ## Architecture
211
+ - JWT with RS256 signing
212
+
213
+ ## Tasks
214
+ - T001: Write tests for email validation and login form
215
+ - T002: Implement login endpoint with email format validation
216
+ - T003: Implement error messages for failed login attempts
217
+ - T004: Write tests for password reset flow
218
+ - T005: Implement password reset with email sending within 30 seconds
219
+ - T006: PLACEHOLDER for future work
220
+ `,
221
+ );
222
+
223
+ const result = verifyPlan(planPath, specPath);
224
+ expect(result.ok).toBe(true);
225
+ if (!result.ok) return;
226
+
227
+ const placeholders = result.value.checks.find(
228
+ (c) => c.name === "no-placeholders",
229
+ );
230
+ expect(placeholders).toBeDefined();
231
+ expect(placeholders?.passed).toBe(false);
232
+ expect(placeholders?.details.some((d) => d.includes("PLACEHOLDER"))).toBe(
233
+ true,
234
+ );
235
+ });
236
+
237
+ test("plan with FIXME marker fails", () => {
238
+ const specPath = join(tmpDir, "spec.md");
239
+ const planPath = join(tmpDir, "plan.md");
240
+ writeFileSync(specPath, VALID_SPEC);
241
+ writeFileSync(
242
+ planPath,
243
+ `# Implementation Plan
244
+
245
+ ## Architecture
246
+ - JWT with RS256 signing
247
+
248
+ ## Tasks
249
+ - T001: Write tests for email validation and login form
250
+ - T002: Implement login endpoint with email format validation
251
+ - T003: Implement error messages for failed login attempts FIXME
252
+ - T004: Write tests for password reset flow
253
+ - T005: Implement password reset with email sending within 30 seconds
254
+ `,
255
+ );
256
+
257
+ const result = verifyPlan(planPath, specPath);
258
+ expect(result.ok).toBe(true);
259
+ if (!result.ok) return;
260
+
261
+ const placeholders = result.value.checks.find(
262
+ (c) => c.name === "no-placeholders",
263
+ );
264
+ expect(placeholders).toBeDefined();
265
+ expect(placeholders?.passed).toBe(false);
266
+ expect(placeholders?.details.some((d) => d.includes("FIXME"))).toBe(true);
267
+ });
268
+
269
+ test("plan with [NEEDS CLARIFICATION] passes (allowed marker)", () => {
270
+ const specPath = join(tmpDir, "spec.md");
271
+ const planPath = join(tmpDir, "plan.md");
272
+ writeFileSync(specPath, VALID_SPEC);
273
+ writeFileSync(
274
+ planPath,
275
+ `# Implementation Plan
276
+
277
+ ## Architecture
278
+ - JWT with RS256 signing
279
+ - [NEEDS CLARIFICATION] OAuth support decision pending
280
+
281
+ ## Tasks
282
+ - T001: Write tests for email validation and login form
283
+ - T002: Implement login endpoint with email format validation
284
+ - T003: Implement error messages for failed login attempts
285
+ - T004: Write tests for password reset flow
286
+ - T005: Implement password reset with email sending within 30 seconds
287
+ `,
288
+ );
289
+
290
+ const result = verifyPlan(planPath, specPath);
291
+ expect(result.ok).toBe(true);
292
+ if (!result.ok) return;
293
+
294
+ const placeholders = result.value.checks.find(
295
+ (c) => c.name === "no-placeholders",
296
+ );
297
+ expect(placeholders).toBeDefined();
298
+ expect(placeholders?.passed).toBe(true);
299
+ expect(placeholders?.details).toHaveLength(0);
300
+ });
301
+
302
+ // --- Function/type name consistency ---
303
+ test("consistent function names across tasks passes", () => {
304
+ const specPath = join(tmpDir, "spec.md");
305
+ const planPath = join(tmpDir, "plan.md");
306
+ writeFileSync(
307
+ specPath,
308
+ `# Feature: API
309
+
310
+ ## Acceptance criteria
311
+ - User creation works
312
+ `,
313
+ );
314
+ writeFileSync(
315
+ planPath,
316
+ `# Implementation Plan
317
+
318
+ ## Tasks
319
+ - T001: Write tests for \`createUser\` function
320
+ - T002: Implement \`createUser\` in user service
321
+ - T003: Wire \`createUser\` to the API route
322
+ `,
323
+ );
324
+
325
+ const result = verifyPlan(planPath, specPath);
326
+ expect(result.ok).toBe(true);
327
+ if (!result.ok) return;
328
+
329
+ const nameCheck = result.value.checks.find(
330
+ (c) => c.name === "name-consistency",
331
+ );
332
+ expect(nameCheck).toBeDefined();
333
+ expect(nameCheck?.passed).toBe(true);
334
+ expect(nameCheck?.details).toHaveLength(0);
335
+ });
336
+
337
+ test("identifiers used only once are tracked without issues", () => {
338
+ const specPath = join(tmpDir, "spec.md");
339
+ const planPath = join(tmpDir, "plan.md");
340
+ writeFileSync(
341
+ specPath,
342
+ `# Feature: API
343
+
344
+ ## Acceptance criteria
345
+ - User creation works
346
+ - User deletion works
347
+ `,
348
+ );
349
+ writeFileSync(
350
+ planPath,
351
+ `# Implementation Plan
352
+
353
+ ## Tasks
354
+ - T001: Implement \`createUser\` in user service
355
+ - T002: Implement \`deleteUser\` in user service
356
+ `,
357
+ );
358
+
359
+ const result = verifyPlan(planPath, specPath);
360
+ expect(result.ok).toBe(true);
361
+ if (!result.ok) return;
362
+
363
+ const nameCheck = result.value.checks.find(
364
+ (c) => c.name === "name-consistency",
365
+ );
366
+ expect(nameCheck).toBeDefined();
367
+ // Single-use names are fine — no inconsistency to detect
368
+ expect(nameCheck?.passed).toBe(true);
369
+ });
370
+
371
+ // --- Test-first ordering ---
372
+ test("test task before implementation passes", () => {
373
+ const specPath = join(tmpDir, "spec.md");
374
+ const planPath = join(tmpDir, "plan.md");
375
+ writeFileSync(
376
+ specPath,
377
+ `# Feature: Auth
378
+
379
+ ## Acceptance criteria
380
+ - Login works
381
+ `,
382
+ );
383
+ writeFileSync(
384
+ planPath,
385
+ `# Implementation Plan
386
+
387
+ ## Tasks
388
+ - T001: Write tests for login endpoint
389
+ - T002: Implement login endpoint
390
+ `,
391
+ );
392
+
393
+ const result = verifyPlan(planPath, specPath);
394
+ expect(result.ok).toBe(true);
395
+ if (!result.ok) return;
396
+
397
+ const testFirst = result.value.checks.find((c) => c.name === "test-first");
398
+ expect(testFirst).toBeDefined();
399
+ expect(testFirst?.passed).toBe(true);
400
+ expect(testFirst?.details).toHaveLength(0);
401
+ });
402
+
403
+ test("implementation task before test task fails with warning", () => {
404
+ const specPath = join(tmpDir, "spec.md");
405
+ const planPath = join(tmpDir, "plan.md");
406
+ writeFileSync(
407
+ specPath,
408
+ `# Feature: Auth
409
+
410
+ ## Acceptance criteria
411
+ - Login works
412
+ `,
413
+ );
414
+ writeFileSync(
415
+ planPath,
416
+ `# Implementation Plan
417
+
418
+ ## Tasks
419
+ - T001: Implement login endpoint
420
+ - T002: Write tests for login endpoint
421
+ `,
422
+ );
423
+
424
+ const result = verifyPlan(planPath, specPath);
425
+ expect(result.ok).toBe(true);
426
+ if (!result.ok) return;
427
+
428
+ const testFirst = result.value.checks.find((c) => c.name === "test-first");
429
+ expect(testFirst).toBeDefined();
430
+ expect(testFirst?.passed).toBe(false);
431
+ expect(testFirst?.details.length).toBeGreaterThan(0);
432
+ // Should mention which test task comes after implementation
433
+ expect(testFirst?.details.some((d) => d.includes("login"))).toBe(true);
434
+ });
435
+
436
+ // --- Fully valid plan ---
437
+ test("fully valid plan passes all checks", () => {
438
+ const specPath = join(tmpDir, "spec.md");
439
+ const planPath = join(tmpDir, "plan.md");
440
+ writeFileSync(specPath, VALID_SPEC);
441
+ writeFileSync(planPath, VALID_PLAN);
442
+
443
+ const result = verifyPlan(planPath, specPath);
444
+ expect(result.ok).toBe(true);
445
+ if (!result.ok) return;
446
+
447
+ expect(result.value.passed).toBe(true);
448
+ for (const check of result.value.checks) {
449
+ expect(check.passed).toBe(true);
450
+ expect(check.details).toHaveLength(0);
451
+ }
452
+ expect(result.value.checks).toHaveLength(4);
453
+ });
454
+ });