@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,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature numbering and directory management.
|
|
3
|
+
*
|
|
4
|
+
* Handles auto-numbering of features, creating feature directories,
|
|
5
|
+
* and scaffolding template files (spec.md, plan.md, tasks.md).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import type { Result } from "../db/index";
|
|
11
|
+
import { toKebabCase } from "../utils";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract numeric prefix from a feature directory name.
|
|
15
|
+
* Returns the number if the name matches NNN-... pattern, or null.
|
|
16
|
+
*/
|
|
17
|
+
function extractNumber(name: string): number | null {
|
|
18
|
+
const match = name.match(/^(\d{3})-/);
|
|
19
|
+
if (!match?.[1]) return null;
|
|
20
|
+
return Number.parseInt(match[1], 10);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Scan `.maina/features/` directory, find the highest existing number prefix,
|
|
25
|
+
* and return the next one zero-padded to 3 digits.
|
|
26
|
+
*
|
|
27
|
+
* Empty dir -> "001". Existing 001, 002 -> "003".
|
|
28
|
+
* If .maina/features/ does not exist, creates it and returns "001".
|
|
29
|
+
*/
|
|
30
|
+
export async function getNextFeatureNumber(
|
|
31
|
+
mainaDir: string,
|
|
32
|
+
): Promise<Result<string>> {
|
|
33
|
+
try {
|
|
34
|
+
const featuresDir = join(mainaDir, ".maina", "features");
|
|
35
|
+
|
|
36
|
+
if (!existsSync(featuresDir)) {
|
|
37
|
+
mkdirSync(featuresDir, { recursive: true });
|
|
38
|
+
return { ok: true, value: "001" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const entries = readdirSync(featuresDir);
|
|
42
|
+
let maxNumber = 0;
|
|
43
|
+
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const fullPath = join(featuresDir, entry);
|
|
46
|
+
// Only consider directories
|
|
47
|
+
try {
|
|
48
|
+
if (!statSync(fullPath).isDirectory()) continue;
|
|
49
|
+
} catch {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const num = extractNumber(entry);
|
|
54
|
+
if (num !== null && num > maxNumber) {
|
|
55
|
+
maxNumber = num;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const next = (maxNumber + 1).toString().padStart(3, "0");
|
|
60
|
+
return { ok: true, value: next };
|
|
61
|
+
} catch (e) {
|
|
62
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
error: `Failed to get next feature number: ${message}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create `.maina/features/{number}-{name}/` directory.
|
|
72
|
+
* Name is converted to kebab-case.
|
|
73
|
+
* Returns the full path to the created directory.
|
|
74
|
+
*/
|
|
75
|
+
export async function createFeatureDir(
|
|
76
|
+
mainaDir: string,
|
|
77
|
+
number: string,
|
|
78
|
+
name: string,
|
|
79
|
+
): Promise<Result<string>> {
|
|
80
|
+
try {
|
|
81
|
+
const kebabName = toKebabCase(name);
|
|
82
|
+
const dirName = `${number}-${kebabName}`;
|
|
83
|
+
const fullPath = join(mainaDir, ".maina", "features", dirName);
|
|
84
|
+
|
|
85
|
+
if (existsSync(fullPath)) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: `Feature directory already exists: ${fullPath}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
mkdirSync(fullPath, { recursive: true });
|
|
93
|
+
return { ok: true, value: fullPath };
|
|
94
|
+
} catch (e) {
|
|
95
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
error: `Failed to create feature directory: ${message}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Design Choices ──────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Represents user's design decisions collected during interactive planning.
|
|
107
|
+
* When provided, these enrich the scaffolded templates with concrete choices
|
|
108
|
+
* instead of generic [NEEDS CLARIFICATION] markers.
|
|
109
|
+
*/
|
|
110
|
+
export interface DesignChoices {
|
|
111
|
+
/** Brief description of what the feature does */
|
|
112
|
+
description?: string;
|
|
113
|
+
/** Architecture pattern chosen (e.g., "repository", "service-layer", "event-driven") */
|
|
114
|
+
pattern?: string;
|
|
115
|
+
/** Key libraries or tools selected */
|
|
116
|
+
libraries?: string[];
|
|
117
|
+
/** Tradeoff decisions made (e.g., "Chose simplicity over performance") */
|
|
118
|
+
tradeoffs?: string[];
|
|
119
|
+
/** Resolved clarifications — questions the user already answered */
|
|
120
|
+
clarifications?: Array<{ question: string; answer: string }>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─── Spec Template (Product Manager perspective) ─────────────────────────────
|
|
124
|
+
// Think as a PM: What problem are we solving? For whom? How will we know it works?
|
|
125
|
+
// Inspired by Superpowers brainstorming: purpose, constraints, success criteria.
|
|
126
|
+
|
|
127
|
+
const SPEC_TEMPLATE = `# Feature: [Name]
|
|
128
|
+
|
|
129
|
+
## Problem Statement
|
|
130
|
+
|
|
131
|
+
What specific problem does this solve? Who experiences it? What happens if we don't solve it?
|
|
132
|
+
|
|
133
|
+
- [NEEDS CLARIFICATION] Define the problem clearly.
|
|
134
|
+
|
|
135
|
+
## Target User
|
|
136
|
+
|
|
137
|
+
Who benefits? What is their current workflow? What frustrates them about it?
|
|
138
|
+
|
|
139
|
+
- Primary: [NEEDS CLARIFICATION]
|
|
140
|
+
- Secondary: [NEEDS CLARIFICATION]
|
|
141
|
+
|
|
142
|
+
## User Stories
|
|
143
|
+
|
|
144
|
+
- As a [role], I want [capability] so that [benefit].
|
|
145
|
+
|
|
146
|
+
## Success Criteria
|
|
147
|
+
|
|
148
|
+
How do we know this works? Every criterion must be testable — if you can't write
|
|
149
|
+
an assertion for it, the requirement isn't clear enough.
|
|
150
|
+
|
|
151
|
+
- [ ] [NEEDS CLARIFICATION] Define measurable, testable criteria.
|
|
152
|
+
|
|
153
|
+
## Scope
|
|
154
|
+
|
|
155
|
+
### In Scope
|
|
156
|
+
|
|
157
|
+
- [NEEDS CLARIFICATION] What this feature does.
|
|
158
|
+
|
|
159
|
+
### Out of Scope
|
|
160
|
+
|
|
161
|
+
- [NEEDS CLARIFICATION] What this feature explicitly does NOT do (prevents over-building).
|
|
162
|
+
|
|
163
|
+
## Design Decisions
|
|
164
|
+
|
|
165
|
+
Key choices made and WHY. Record tradeoffs — future you will thank you.
|
|
166
|
+
|
|
167
|
+
- [NEEDS CLARIFICATION] What alternatives were considered? Why was this one chosen?
|
|
168
|
+
|
|
169
|
+
## Open Questions
|
|
170
|
+
|
|
171
|
+
- [NEEDS CLARIFICATION] List ambiguities. Every question here must be resolved before implementation.
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
// ─── Plan Template (Technical Architect perspective) ─────────────────────────
|
|
175
|
+
// Think as an architect: What's the simplest approach? What are the failure modes?
|
|
176
|
+
// How does this fit into the existing system? Where are the integration points?
|
|
177
|
+
|
|
178
|
+
const PLAN_TEMPLATE = `# Implementation Plan
|
|
179
|
+
|
|
180
|
+
> HOW only — see spec.md for WHAT and WHY.
|
|
181
|
+
|
|
182
|
+
## Architecture
|
|
183
|
+
|
|
184
|
+
What is the technical approach? How does it fit into existing architecture?
|
|
185
|
+
Where are the integration points with existing code?
|
|
186
|
+
|
|
187
|
+
- Pattern: [NEEDS CLARIFICATION]
|
|
188
|
+
- Integration points: [NEEDS CLARIFICATION]
|
|
189
|
+
|
|
190
|
+
## Key Technical Decisions
|
|
191
|
+
|
|
192
|
+
What libraries, patterns, or approaches? WHY these and not alternatives?
|
|
193
|
+
|
|
194
|
+
- [NEEDS CLARIFICATION]
|
|
195
|
+
|
|
196
|
+
## Files
|
|
197
|
+
|
|
198
|
+
| File | Purpose | New/Modified |
|
|
199
|
+
|------|---------|-------------|
|
|
200
|
+
| [NEEDS CLARIFICATION] | | |
|
|
201
|
+
|
|
202
|
+
## Tasks
|
|
203
|
+
|
|
204
|
+
TDD: every implementation task must have a preceding test task.
|
|
205
|
+
|
|
206
|
+
- [ ] [NEEDS CLARIFICATION] Break down into small, testable tasks.
|
|
207
|
+
|
|
208
|
+
## Failure Modes
|
|
209
|
+
|
|
210
|
+
What can go wrong? How do we handle it gracefully?
|
|
211
|
+
|
|
212
|
+
- [NEEDS CLARIFICATION]
|
|
213
|
+
|
|
214
|
+
## Testing Strategy
|
|
215
|
+
|
|
216
|
+
Unit tests, integration tests, or both? What mocks are needed?
|
|
217
|
+
|
|
218
|
+
- [NEEDS CLARIFICATION]
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
// ─── Tasks Template ──────────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
const TASKS_TEMPLATE = `# Task Breakdown
|
|
224
|
+
|
|
225
|
+
## Tasks
|
|
226
|
+
|
|
227
|
+
Each task should be completable in one commit. Test tasks precede implementation tasks.
|
|
228
|
+
|
|
229
|
+
- [ ] [NEEDS CLARIFICATION] Define tasks.
|
|
230
|
+
|
|
231
|
+
## Dependencies
|
|
232
|
+
|
|
233
|
+
Which tasks block which? Draw the critical path.
|
|
234
|
+
|
|
235
|
+
- [NEEDS CLARIFICATION]
|
|
236
|
+
|
|
237
|
+
## Definition of Done
|
|
238
|
+
|
|
239
|
+
How do we know this feature is complete?
|
|
240
|
+
|
|
241
|
+
- [ ] All tests pass
|
|
242
|
+
- [ ] Biome lint clean
|
|
243
|
+
- [ ] TypeScript compiles
|
|
244
|
+
- [ ] maina analyze shows no errors
|
|
245
|
+
- [ ] [NEEDS CLARIFICATION] Feature-specific criteria
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Create three template files inside the feature directory:
|
|
250
|
+
* - spec.md — WHAT and WHY only
|
|
251
|
+
* - plan.md — HOW only
|
|
252
|
+
* - tasks.md — Task breakdown
|
|
253
|
+
*/
|
|
254
|
+
export async function scaffoldFeature(
|
|
255
|
+
featureDir: string,
|
|
256
|
+
): Promise<Result<void>> {
|
|
257
|
+
try {
|
|
258
|
+
if (!existsSync(featureDir)) {
|
|
259
|
+
return {
|
|
260
|
+
ok: false,
|
|
261
|
+
error: `Feature directory does not exist: ${featureDir}`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await Bun.write(join(featureDir, "spec.md"), SPEC_TEMPLATE);
|
|
266
|
+
await Bun.write(join(featureDir, "plan.md"), PLAN_TEMPLATE);
|
|
267
|
+
await Bun.write(join(featureDir, "tasks.md"), TASKS_TEMPLATE);
|
|
268
|
+
|
|
269
|
+
return { ok: true, value: undefined };
|
|
270
|
+
} catch (e) {
|
|
271
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
272
|
+
return { ok: false, error: `Failed to scaffold feature: ${message}` };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Build a spec.md from design choices, filling in concrete details
|
|
278
|
+
* instead of generic [NEEDS CLARIFICATION] markers.
|
|
279
|
+
*/
|
|
280
|
+
function buildEnrichedSpec(name: string, choices: DesignChoices): string {
|
|
281
|
+
const lines: string[] = [];
|
|
282
|
+
lines.push(`# Feature: ${name}`);
|
|
283
|
+
lines.push("");
|
|
284
|
+
|
|
285
|
+
if (choices.description) {
|
|
286
|
+
lines.push("## Problem Statement");
|
|
287
|
+
lines.push("");
|
|
288
|
+
lines.push(choices.description);
|
|
289
|
+
lines.push("");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
lines.push("## User Stories");
|
|
293
|
+
lines.push("");
|
|
294
|
+
lines.push("- As a [role], I want [capability] so that [benefit].");
|
|
295
|
+
lines.push("");
|
|
296
|
+
|
|
297
|
+
lines.push("## Success Criteria");
|
|
298
|
+
lines.push("");
|
|
299
|
+
lines.push(
|
|
300
|
+
"- [ ] [NEEDS CLARIFICATION] Define measurable, testable criteria.",
|
|
301
|
+
);
|
|
302
|
+
lines.push("");
|
|
303
|
+
|
|
304
|
+
if (choices.tradeoffs && choices.tradeoffs.length > 0) {
|
|
305
|
+
lines.push("## Design Decisions");
|
|
306
|
+
lines.push("");
|
|
307
|
+
for (const tradeoff of choices.tradeoffs) {
|
|
308
|
+
lines.push(`- ${tradeoff}`);
|
|
309
|
+
}
|
|
310
|
+
lines.push("");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (choices.clarifications && choices.clarifications.length > 0) {
|
|
314
|
+
lines.push("## Resolved Questions");
|
|
315
|
+
lines.push("");
|
|
316
|
+
for (const c of choices.clarifications) {
|
|
317
|
+
lines.push(`- **Q:** ${c.question}`);
|
|
318
|
+
lines.push(` **A:** ${c.answer}`);
|
|
319
|
+
}
|
|
320
|
+
lines.push("");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
lines.push("## Open Questions");
|
|
324
|
+
lines.push("");
|
|
325
|
+
lines.push("- [NEEDS CLARIFICATION] Resolve before implementation.");
|
|
326
|
+
lines.push("");
|
|
327
|
+
return lines.join("\n");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Build a plan.md from design choices, pre-filling architecture
|
|
332
|
+
* and library selections.
|
|
333
|
+
*/
|
|
334
|
+
function buildEnrichedPlan(choices: DesignChoices): string {
|
|
335
|
+
const lines: string[] = [];
|
|
336
|
+
lines.push("# Implementation Plan");
|
|
337
|
+
lines.push("");
|
|
338
|
+
lines.push("> HOW only — see spec.md for WHAT and WHY.");
|
|
339
|
+
lines.push("");
|
|
340
|
+
|
|
341
|
+
lines.push("## Architecture");
|
|
342
|
+
lines.push("");
|
|
343
|
+
if (choices.pattern) {
|
|
344
|
+
lines.push(`- Pattern: **${choices.pattern}**`);
|
|
345
|
+
} else {
|
|
346
|
+
lines.push("- [NEEDS CLARIFICATION] Describe the technical approach.");
|
|
347
|
+
}
|
|
348
|
+
lines.push("");
|
|
349
|
+
|
|
350
|
+
if (choices.libraries && choices.libraries.length > 0) {
|
|
351
|
+
lines.push("## Key Technical Decisions");
|
|
352
|
+
lines.push("");
|
|
353
|
+
for (const lib of choices.libraries) {
|
|
354
|
+
lines.push(`- ${lib}`);
|
|
355
|
+
}
|
|
356
|
+
lines.push("");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
lines.push("## Tasks");
|
|
360
|
+
lines.push("");
|
|
361
|
+
lines.push("TDD: every implementation task must have a preceding test task.");
|
|
362
|
+
lines.push("");
|
|
363
|
+
lines.push(
|
|
364
|
+
"- [ ] [NEEDS CLARIFICATION] Break down into small, testable tasks.",
|
|
365
|
+
);
|
|
366
|
+
lines.push("");
|
|
367
|
+
|
|
368
|
+
lines.push("## Failure Modes");
|
|
369
|
+
lines.push("");
|
|
370
|
+
lines.push("- [NEEDS CLARIFICATION] What can go wrong?");
|
|
371
|
+
lines.push("");
|
|
372
|
+
return lines.join("\n");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Scaffold feature files enriched with user's design choices.
|
|
377
|
+
* Falls back to generic templates for any missing choices.
|
|
378
|
+
*/
|
|
379
|
+
export async function scaffoldFeatureWithContext(
|
|
380
|
+
featureDir: string,
|
|
381
|
+
name: string,
|
|
382
|
+
choices: DesignChoices,
|
|
383
|
+
): Promise<Result<void>> {
|
|
384
|
+
try {
|
|
385
|
+
if (!existsSync(featureDir)) {
|
|
386
|
+
return {
|
|
387
|
+
ok: false,
|
|
388
|
+
error: `Feature directory does not exist: ${featureDir}`,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const spec = buildEnrichedSpec(name, choices);
|
|
393
|
+
const plan = buildEnrichedPlan(choices);
|
|
394
|
+
|
|
395
|
+
await Bun.write(join(featureDir, "spec.md"), spec);
|
|
396
|
+
await Bun.write(join(featureDir, "plan.md"), plan);
|
|
397
|
+
await Bun.write(join(featureDir, "tasks.md"), TASKS_TEMPLATE);
|
|
398
|
+
|
|
399
|
+
return { ok: true, value: undefined };
|
|
400
|
+
} catch (e) {
|
|
401
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
402
|
+
return { ok: false, error: `Failed to scaffold feature: ${message}` };
|
|
403
|
+
}
|
|
404
|
+
}
|