@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,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic plan verification checklist.
|
|
3
|
+
*
|
|
4
|
+
* Verifies a plan.md against its spec.md without any AI involvement:
|
|
5
|
+
* 1. Spec criterion coverage — every acceptance criterion has a matching task
|
|
6
|
+
* 2. No TODO/TBD/PLACEHOLDER/FIXME markers — [NEEDS CLARIFICATION] is allowed
|
|
7
|
+
* 3. Function/type name consistency — backtick-quoted identifiers are consistent
|
|
8
|
+
* 4. Test-first ordering — test tasks appear before implementation tasks
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import type { Result } from "../db/index";
|
|
13
|
+
import { extractAcceptanceCriteria } from "../utils";
|
|
14
|
+
|
|
15
|
+
export interface VerificationReport {
|
|
16
|
+
passed: boolean;
|
|
17
|
+
checks: CheckResult[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CheckResult {
|
|
21
|
+
name: string;
|
|
22
|
+
passed: boolean;
|
|
23
|
+
details: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extract task descriptions from a plan file's `## Tasks` section.
|
|
28
|
+
* Returns the full task line text (after the leading `- ` and optional task id).
|
|
29
|
+
*/
|
|
30
|
+
function extractTasks(planContent: string): string[] {
|
|
31
|
+
const lines = planContent.split("\n");
|
|
32
|
+
const tasks: string[] = [];
|
|
33
|
+
let inSection = false;
|
|
34
|
+
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
|
|
38
|
+
if (/^##\s+tasks/i.test(trimmed)) {
|
|
39
|
+
inSection = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (inSection && /^##\s/.test(trimmed)) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (inSection && trimmed.startsWith("-")) {
|
|
48
|
+
const content = trimmed.replace(/^-\s*(\[.\]\s*)?/, "").trim();
|
|
49
|
+
if (content.length > 0) {
|
|
50
|
+
tasks.push(content);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return tasks;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check 1: Spec criterion coverage.
|
|
60
|
+
*
|
|
61
|
+
* Every acceptance criterion keyword should appear in at least one task.
|
|
62
|
+
* We extract significant words (3+ chars, lowercase) from each criterion
|
|
63
|
+
* and check that the majority appear in the combined task text.
|
|
64
|
+
*/
|
|
65
|
+
function checkSpecCoverage(
|
|
66
|
+
specContent: string,
|
|
67
|
+
planContent: string,
|
|
68
|
+
): CheckResult {
|
|
69
|
+
const criteria = extractAcceptanceCriteria(specContent);
|
|
70
|
+
const tasks = extractTasks(planContent);
|
|
71
|
+
const allTasksText = tasks.join(" ").toLowerCase();
|
|
72
|
+
|
|
73
|
+
const details: string[] = [];
|
|
74
|
+
|
|
75
|
+
for (const criterion of criteria) {
|
|
76
|
+
const keywords = criterion
|
|
77
|
+
.toLowerCase()
|
|
78
|
+
.split(/\s+/)
|
|
79
|
+
.filter((w) => w.length >= 3)
|
|
80
|
+
// Filter out very common words that don't indicate coverage
|
|
81
|
+
.filter(
|
|
82
|
+
(w) =>
|
|
83
|
+
![
|
|
84
|
+
"the",
|
|
85
|
+
"and",
|
|
86
|
+
"for",
|
|
87
|
+
"are",
|
|
88
|
+
"but",
|
|
89
|
+
"not",
|
|
90
|
+
"you",
|
|
91
|
+
"all",
|
|
92
|
+
"can",
|
|
93
|
+
"has",
|
|
94
|
+
"her",
|
|
95
|
+
"was",
|
|
96
|
+
"one",
|
|
97
|
+
"our",
|
|
98
|
+
"out",
|
|
99
|
+
"with",
|
|
100
|
+
"that",
|
|
101
|
+
"this",
|
|
102
|
+
"from",
|
|
103
|
+
"have",
|
|
104
|
+
"will",
|
|
105
|
+
"should",
|
|
106
|
+
].includes(w),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (keywords.length === 0) continue;
|
|
110
|
+
|
|
111
|
+
const matchedCount = keywords.filter((kw) =>
|
|
112
|
+
allTasksText.includes(kw),
|
|
113
|
+
).length;
|
|
114
|
+
const coverage = matchedCount / keywords.length;
|
|
115
|
+
|
|
116
|
+
// Require at least 50% keyword coverage for a criterion to be considered covered
|
|
117
|
+
if (coverage < 0.5) {
|
|
118
|
+
details.push(`Criterion not covered in tasks: "${criterion}"`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
name: "spec-coverage",
|
|
124
|
+
passed: details.length === 0,
|
|
125
|
+
details,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check 2: No TODO/TBD/PLACEHOLDER/FIXME markers.
|
|
131
|
+
*
|
|
132
|
+
* Scans plan content for forbidden markers (case-insensitive).
|
|
133
|
+
* [NEEDS CLARIFICATION] is explicitly allowed and excluded.
|
|
134
|
+
*/
|
|
135
|
+
function checkNoPlaceholders(planContent: string): CheckResult {
|
|
136
|
+
const lines = planContent.split("\n");
|
|
137
|
+
const details: string[] = [];
|
|
138
|
+
|
|
139
|
+
// Remove [NEEDS CLARIFICATION] before scanning so it doesn't trigger
|
|
140
|
+
const forbiddenPattern = /\b(TODO|TBD|PLACEHOLDER|FIXME)\b/i;
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < lines.length; i++) {
|
|
143
|
+
const line = lines[i] ?? "";
|
|
144
|
+
// Strip out [NEEDS CLARIFICATION] markers before checking
|
|
145
|
+
const sanitized = line.replace(/\[NEEDS CLARIFICATION\]/gi, "");
|
|
146
|
+
const match = forbiddenPattern.exec(sanitized);
|
|
147
|
+
if (match) {
|
|
148
|
+
const marker = match[1] ?? "";
|
|
149
|
+
details.push(
|
|
150
|
+
`Line ${i + 1}: Found "${marker.toUpperCase()}" marker — "${line.trim()}"`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
name: "no-placeholders",
|
|
157
|
+
passed: details.length === 0,
|
|
158
|
+
details,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Check 3: Function/type name consistency.
|
|
164
|
+
*
|
|
165
|
+
* Extract all backtick-quoted identifiers from the plan's Tasks section.
|
|
166
|
+
* Identifiers used multiple times should be spelled identically.
|
|
167
|
+
* This check verifies no identifier appears with inconsistent casing
|
|
168
|
+
* (e.g., `createUser` vs `CreateUser` would be flagged).
|
|
169
|
+
*/
|
|
170
|
+
function checkNameConsistency(planContent: string): CheckResult {
|
|
171
|
+
const tasks = extractTasks(planContent);
|
|
172
|
+
const details: string[] = [];
|
|
173
|
+
|
|
174
|
+
// Extract all backtick-quoted identifiers from task lines
|
|
175
|
+
const identifierOccurrences = new Map<string, string[]>();
|
|
176
|
+
|
|
177
|
+
for (const task of tasks) {
|
|
178
|
+
const matches = task.matchAll(/`([^`]+)`/g);
|
|
179
|
+
for (const match of matches) {
|
|
180
|
+
const name = match[1];
|
|
181
|
+
if (!name) continue;
|
|
182
|
+
const lower = name.toLowerCase();
|
|
183
|
+
if (!identifierOccurrences.has(lower)) {
|
|
184
|
+
identifierOccurrences.set(lower, []);
|
|
185
|
+
}
|
|
186
|
+
identifierOccurrences.get(lower)?.push(name);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check for inconsistent casing among identifiers with the same lowercase form
|
|
191
|
+
for (const [lower, occurrences] of identifierOccurrences) {
|
|
192
|
+
if (occurrences.length < 2) continue;
|
|
193
|
+
const canonical = occurrences[0];
|
|
194
|
+
for (let i = 1; i < occurrences.length; i++) {
|
|
195
|
+
if (occurrences[i] !== canonical) {
|
|
196
|
+
details.push(
|
|
197
|
+
`Inconsistent identifier: "${canonical}" vs "${occurrences[i]}" (lowercase: "${lower}")`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
name: "name-consistency",
|
|
205
|
+
passed: details.length === 0,
|
|
206
|
+
details,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check 4: Test-first ordering.
|
|
212
|
+
*
|
|
213
|
+
* If a task mentions "test" in its description, it should appear before
|
|
214
|
+
* corresponding implementation tasks for the same component.
|
|
215
|
+
*
|
|
216
|
+
* We identify component keywords (significant words shared between test and
|
|
217
|
+
* implementation tasks) and verify test tasks come first.
|
|
218
|
+
*/
|
|
219
|
+
function checkTestFirstOrdering(planContent: string): CheckResult {
|
|
220
|
+
const tasks = extractTasks(planContent);
|
|
221
|
+
const details: string[] = [];
|
|
222
|
+
|
|
223
|
+
interface TaskInfo {
|
|
224
|
+
index: number;
|
|
225
|
+
text: string;
|
|
226
|
+
isTest: boolean;
|
|
227
|
+
keywords: Set<string>;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const stopWords = new Set([
|
|
231
|
+
"the",
|
|
232
|
+
"and",
|
|
233
|
+
"for",
|
|
234
|
+
"are",
|
|
235
|
+
"but",
|
|
236
|
+
"not",
|
|
237
|
+
"you",
|
|
238
|
+
"all",
|
|
239
|
+
"can",
|
|
240
|
+
"has",
|
|
241
|
+
"was",
|
|
242
|
+
"one",
|
|
243
|
+
"our",
|
|
244
|
+
"out",
|
|
245
|
+
"with",
|
|
246
|
+
"that",
|
|
247
|
+
"this",
|
|
248
|
+
"from",
|
|
249
|
+
"have",
|
|
250
|
+
"will",
|
|
251
|
+
"should",
|
|
252
|
+
"write",
|
|
253
|
+
"test",
|
|
254
|
+
"tests",
|
|
255
|
+
"implement",
|
|
256
|
+
"implementation",
|
|
257
|
+
"create",
|
|
258
|
+
"add",
|
|
259
|
+
"update",
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
const taskInfos: TaskInfo[] = tasks.map((text, index) => {
|
|
263
|
+
const isTest = /\btest/i.test(text);
|
|
264
|
+
const keywords = new Set(
|
|
265
|
+
text
|
|
266
|
+
.toLowerCase()
|
|
267
|
+
.replace(/`[^`]+`/g, "") // Remove backtick identifiers
|
|
268
|
+
.replace(/^T\d+:\s*/i, "") // Remove task ids
|
|
269
|
+
.split(/\s+/)
|
|
270
|
+
.filter((w) => w.length >= 3)
|
|
271
|
+
.filter((w) => !stopWords.has(w)),
|
|
272
|
+
);
|
|
273
|
+
return { index, text, isTest, keywords };
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const testTasks = taskInfos.filter((t) => t.isTest);
|
|
277
|
+
const implTasks = taskInfos.filter((t) => !t.isTest);
|
|
278
|
+
|
|
279
|
+
for (const testTask of testTasks) {
|
|
280
|
+
// Find implementation tasks that share keywords with this test task
|
|
281
|
+
for (const implTask of implTasks) {
|
|
282
|
+
const shared = [...testTask.keywords].filter((kw) =>
|
|
283
|
+
implTask.keywords.has(kw),
|
|
284
|
+
);
|
|
285
|
+
// If they share significant keywords, the test should come first
|
|
286
|
+
if (shared.length > 0 && testTask.index > implTask.index) {
|
|
287
|
+
details.push(
|
|
288
|
+
`Test task appears after implementation: "${testTask.text}" (task ${testTask.index + 1}) should come before "${implTask.text}" (task ${implTask.index + 1}) [shared: ${shared.join(", ")}]`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
name: "test-first",
|
|
296
|
+
passed: details.length === 0,
|
|
297
|
+
details,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Verify a plan.md against its spec.md using deterministic checks.
|
|
303
|
+
*
|
|
304
|
+
* Runs four checks:
|
|
305
|
+
* 1. spec-coverage — acceptance criteria covered by tasks
|
|
306
|
+
* 2. no-placeholders — no TODO/TBD/PLACEHOLDER/FIXME markers
|
|
307
|
+
* 3. name-consistency — backtick identifiers are consistent
|
|
308
|
+
* 4. test-first — test tasks appear before implementation tasks
|
|
309
|
+
*
|
|
310
|
+
* Returns a Result with VerificationReport on success, or an error string
|
|
311
|
+
* if the files cannot be read.
|
|
312
|
+
*/
|
|
313
|
+
export function verifyPlan(
|
|
314
|
+
planPath: string,
|
|
315
|
+
specPath: string,
|
|
316
|
+
): Result<VerificationReport, string> {
|
|
317
|
+
// Validate files exist
|
|
318
|
+
if (!existsSync(specPath)) {
|
|
319
|
+
return {
|
|
320
|
+
ok: false,
|
|
321
|
+
error: `Spec file not found: ${specPath}`,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!existsSync(planPath)) {
|
|
326
|
+
return {
|
|
327
|
+
ok: false,
|
|
328
|
+
error: `Plan file not found: ${planPath}`,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let specContent: string;
|
|
333
|
+
let planContent: string;
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
specContent = readFileSync(specPath, "utf-8");
|
|
337
|
+
} catch (e) {
|
|
338
|
+
return {
|
|
339
|
+
ok: false,
|
|
340
|
+
error: `Failed to read spec file: ${e instanceof Error ? e.message : String(e)}`,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
planContent = readFileSync(planPath, "utf-8");
|
|
346
|
+
} catch (e) {
|
|
347
|
+
return {
|
|
348
|
+
ok: false,
|
|
349
|
+
error: `Failed to read plan file: ${e instanceof Error ? e.message : String(e)}`,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const checks: CheckResult[] = [
|
|
354
|
+
checkSpecCoverage(specContent, planContent),
|
|
355
|
+
checkNoPlaceholders(planContent),
|
|
356
|
+
checkNameConsistency(planContent),
|
|
357
|
+
checkTestFirstOrdering(planContent),
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
const passed = checks.every((c) => c.passed);
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
ok: true,
|
|
364
|
+
value: { passed, checks },
|
|
365
|
+
};
|
|
366
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type { AnalysisFinding, AnalysisReport } from "./analyzer";
|
|
2
|
+
export { analyze } from "./analyzer";
|
|
3
|
+
export type { CheckResult, VerificationReport } from "./checklist";
|
|
4
|
+
export { verifyPlan } from "./checklist";
|
|
5
|
+
export {
|
|
6
|
+
createFeatureDir,
|
|
7
|
+
type DesignChoices,
|
|
8
|
+
getNextFeatureNumber,
|
|
9
|
+
scaffoldFeature,
|
|
10
|
+
scaffoldFeatureWithContext,
|
|
11
|
+
} from "./numbering";
|
|
12
|
+
export { generateTestStubs } from "./test-stubs";
|
|
13
|
+
export type {
|
|
14
|
+
TaskTrace,
|
|
15
|
+
TraceabilityReport,
|
|
16
|
+
TraceDeps,
|
|
17
|
+
} from "./traceability";
|
|
18
|
+
export { traceFeature } from "./traceability";
|