@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.
- package/README.md +31 -0
- package/package.json +37 -0
- package/src/ai/__tests__/ai.test.ts +207 -0
- package/src/ai/__tests__/design-approaches.test.ts +192 -0
- package/src/ai/__tests__/spec-questions.test.ts +191 -0
- package/src/ai/__tests__/tiers.test.ts +110 -0
- package/src/ai/commit-msg.ts +28 -0
- package/src/ai/design-approaches.ts +76 -0
- package/src/ai/index.ts +205 -0
- package/src/ai/pr-summary.ts +60 -0
- package/src/ai/spec-questions.ts +74 -0
- package/src/ai/tiers.ts +52 -0
- package/src/ai/try-generate.ts +89 -0
- package/src/ai/validate.ts +66 -0
- package/src/benchmark/__tests__/reporter.test.ts +525 -0
- package/src/benchmark/__tests__/runner.test.ts +113 -0
- package/src/benchmark/__tests__/story-loader.test.ts +152 -0
- package/src/benchmark/reporter.ts +332 -0
- package/src/benchmark/runner.ts +91 -0
- package/src/benchmark/story-loader.ts +88 -0
- package/src/benchmark/types.ts +95 -0
- package/src/cache/__tests__/keys.test.ts +97 -0
- package/src/cache/__tests__/manager.test.ts +312 -0
- package/src/cache/__tests__/ttl.test.ts +94 -0
- package/src/cache/keys.ts +44 -0
- package/src/cache/manager.ts +231 -0
- package/src/cache/ttl.ts +77 -0
- package/src/config/__tests__/config.test.ts +376 -0
- package/src/config/index.ts +198 -0
- package/src/context/__tests__/budget.test.ts +179 -0
- package/src/context/__tests__/engine.test.ts +163 -0
- package/src/context/__tests__/episodic.test.ts +291 -0
- package/src/context/__tests__/relevance.test.ts +323 -0
- package/src/context/__tests__/retrieval.test.ts +143 -0
- package/src/context/__tests__/selector.test.ts +174 -0
- package/src/context/__tests__/semantic.test.ts +252 -0
- package/src/context/__tests__/treesitter.test.ts +229 -0
- package/src/context/__tests__/working.test.ts +236 -0
- package/src/context/budget.ts +130 -0
- package/src/context/engine.ts +394 -0
- package/src/context/episodic.ts +251 -0
- package/src/context/relevance.ts +325 -0
- package/src/context/retrieval.ts +325 -0
- package/src/context/selector.ts +93 -0
- package/src/context/semantic.ts +331 -0
- package/src/context/treesitter.ts +216 -0
- package/src/context/working.ts +192 -0
- package/src/db/__tests__/db.test.ts +151 -0
- package/src/db/index.ts +211 -0
- package/src/db/schema.ts +84 -0
- package/src/design/__tests__/design.test.ts +310 -0
- package/src/design/__tests__/generate-hld-lld.test.ts +109 -0
- package/src/design/__tests__/review.test.ts +561 -0
- package/src/design/index.ts +297 -0
- package/src/design/review.ts +327 -0
- package/src/explain/__tests__/explain.test.ts +173 -0
- package/src/explain/index.ts +181 -0
- package/src/features/__tests__/analyzer.test.ts +358 -0
- package/src/features/__tests__/checklist.test.ts +454 -0
- package/src/features/__tests__/numbering.test.ts +319 -0
- package/src/features/__tests__/quality.test.ts +295 -0
- package/src/features/__tests__/traceability.test.ts +147 -0
- package/src/features/analyzer.ts +445 -0
- package/src/features/checklist.ts +366 -0
- package/src/features/index.ts +18 -0
- package/src/features/numbering.ts +404 -0
- package/src/features/quality.ts +349 -0
- package/src/features/test-stubs.ts +157 -0
- package/src/features/traceability.ts +260 -0
- package/src/feedback/__tests__/async-feedback.test.ts +52 -0
- package/src/feedback/__tests__/collector.test.ts +219 -0
- package/src/feedback/__tests__/compress.test.ts +150 -0
- package/src/feedback/__tests__/preferences.test.ts +169 -0
- package/src/feedback/collector.ts +135 -0
- package/src/feedback/compress.ts +92 -0
- package/src/feedback/preferences.ts +108 -0
- package/src/git/__tests__/git.test.ts +62 -0
- package/src/git/index.ts +110 -0
- package/src/hooks/__tests__/runner.test.ts +266 -0
- package/src/hooks/index.ts +8 -0
- package/src/hooks/runner.ts +130 -0
- package/src/index.ts +356 -0
- package/src/init/__tests__/init.test.ts +228 -0
- package/src/init/index.ts +364 -0
- package/src/language/__tests__/detect.test.ts +77 -0
- package/src/language/__tests__/profile.test.ts +51 -0
- package/src/language/detect.ts +70 -0
- package/src/language/profile.ts +110 -0
- package/src/prompts/__tests__/defaults.test.ts +52 -0
- package/src/prompts/__tests__/engine.test.ts +183 -0
- package/src/prompts/__tests__/evolution-resolve.test.ts +169 -0
- package/src/prompts/__tests__/evolution.test.ts +187 -0
- package/src/prompts/__tests__/loader.test.ts +105 -0
- package/src/prompts/candidates/review-v2.md +55 -0
- package/src/prompts/defaults/ai-review.md +49 -0
- package/src/prompts/defaults/commit.md +30 -0
- package/src/prompts/defaults/context.md +26 -0
- package/src/prompts/defaults/design-approaches.md +57 -0
- package/src/prompts/defaults/design-hld-lld.md +55 -0
- package/src/prompts/defaults/design.md +53 -0
- package/src/prompts/defaults/explain.md +31 -0
- package/src/prompts/defaults/fix.md +32 -0
- package/src/prompts/defaults/index.ts +38 -0
- package/src/prompts/defaults/review.md +41 -0
- package/src/prompts/defaults/spec-questions.md +59 -0
- package/src/prompts/defaults/tests.md +72 -0
- package/src/prompts/engine.ts +137 -0
- package/src/prompts/evolution.ts +409 -0
- package/src/prompts/loader.ts +71 -0
- package/src/review/__tests__/review.test.ts +288 -0
- package/src/review/comprehensive.ts +362 -0
- package/src/review/index.ts +417 -0
- package/src/stats/__tests__/tracker.test.ts +323 -0
- package/src/stats/index.ts +11 -0
- package/src/stats/tracker.ts +492 -0
- package/src/ticket/__tests__/ticket.test.ts +273 -0
- package/src/ticket/index.ts +185 -0
- package/src/utils.ts +87 -0
- package/src/verify/__tests__/ai-review.test.ts +242 -0
- package/src/verify/__tests__/coverage.test.ts +83 -0
- package/src/verify/__tests__/detect.test.ts +175 -0
- package/src/verify/__tests__/diff-filter.test.ts +338 -0
- package/src/verify/__tests__/fix.test.ts +478 -0
- package/src/verify/__tests__/linters/clippy.test.ts +45 -0
- package/src/verify/__tests__/linters/go-vet.test.ts +27 -0
- package/src/verify/__tests__/linters/ruff.test.ts +64 -0
- package/src/verify/__tests__/mutation.test.ts +141 -0
- package/src/verify/__tests__/pipeline.test.ts +553 -0
- package/src/verify/__tests__/proof.test.ts +97 -0
- package/src/verify/__tests__/secretlint.test.ts +190 -0
- package/src/verify/__tests__/semgrep.test.ts +217 -0
- package/src/verify/__tests__/slop.test.ts +366 -0
- package/src/verify/__tests__/sonar.test.ts +113 -0
- package/src/verify/__tests__/syntax-guard.test.ts +227 -0
- package/src/verify/__tests__/trivy.test.ts +191 -0
- package/src/verify/__tests__/visual.test.ts +139 -0
- package/src/verify/ai-review.ts +276 -0
- package/src/verify/coverage.ts +134 -0
- package/src/verify/detect.ts +171 -0
- package/src/verify/diff-filter.ts +183 -0
- package/src/verify/fix.ts +317 -0
- package/src/verify/linters/clippy.ts +52 -0
- package/src/verify/linters/go-vet.ts +32 -0
- package/src/verify/linters/ruff.ts +47 -0
- package/src/verify/mutation.ts +143 -0
- package/src/verify/pipeline.ts +328 -0
- package/src/verify/proof.ts +277 -0
- package/src/verify/secretlint.ts +168 -0
- package/src/verify/semgrep.ts +170 -0
- package/src/verify/slop.ts +493 -0
- package/src/verify/sonar.ts +146 -0
- package/src/verify/syntax-guard.ts +251 -0
- package/src/verify/trivy.ts +161 -0
- package/src/verify/visual.ts +460 -0
- package/src/workflow/__tests__/context.test.ts +110 -0
- 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
|
+
});
|