@eddacraft/anvil-adapters 0.1.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/AGENTS.md +180 -0
- package/BMAD_ADAPTER_SPEC.md +489 -0
- package/LICENSE +14 -0
- package/README.md +500 -0
- package/dist/aps-markdown/adapter.d.ts +102 -0
- package/dist/aps-markdown/adapter.d.ts.map +1 -0
- package/dist/aps-markdown/adapter.js +351 -0
- package/dist/aps-markdown/index.d.ts +8 -0
- package/dist/aps-markdown/index.d.ts.map +1 -0
- package/dist/aps-markdown/index.js +7 -0
- package/dist/base/file-discovery.d.ts +63 -0
- package/dist/base/file-discovery.d.ts.map +1 -0
- package/dist/base/file-discovery.js +246 -0
- package/dist/base/index.d.ts +10 -0
- package/dist/base/index.d.ts.map +1 -0
- package/dist/base/index.js +9 -0
- package/dist/base/registry.d.ts +155 -0
- package/dist/base/registry.d.ts.map +1 -0
- package/dist/base/registry.js +227 -0
- package/dist/base/testing.d.ts +102 -0
- package/dist/base/testing.d.ts.map +1 -0
- package/dist/base/testing.js +221 -0
- package/dist/base/types.d.ts +255 -0
- package/dist/base/types.d.ts.map +1 -0
- package/dist/base/types.js +78 -0
- package/dist/base/utils.d.ts +127 -0
- package/dist/base/utils.d.ts.map +1 -0
- package/dist/base/utils.js +254 -0
- package/dist/bmad/format-adapter.d.ts +76 -0
- package/dist/bmad/format-adapter.d.ts.map +1 -0
- package/dist/bmad/format-adapter.js +186 -0
- package/dist/bmad/index.d.ts +12 -0
- package/dist/bmad/index.d.ts.map +1 -0
- package/dist/bmad/index.js +10 -0
- package/dist/bmad/parser.d.ts +12 -0
- package/dist/bmad/parser.d.ts.map +1 -0
- package/dist/bmad/parser.js +181 -0
- package/dist/bmad/serializer.d.ts +16 -0
- package/dist/bmad/serializer.d.ts.map +1 -0
- package/dist/bmad/serializer.js +170 -0
- package/dist/bmad/types.d.ts +127 -0
- package/dist/bmad/types.d.ts.map +1 -0
- package/dist/bmad/types.js +47 -0
- package/dist/bmad/utils.d.ts +120 -0
- package/dist/bmad/utils.d.ts.map +1 -0
- package/dist/bmad/utils.js +480 -0
- package/dist/common/index.d.ts +3 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +2 -0
- package/dist/common/registry.d.ts +18 -0
- package/dist/common/registry.d.ts.map +1 -0
- package/dist/common/registry.js +58 -0
- package/dist/common/types.d.ts +68 -0
- package/dist/common/types.d.ts.map +1 -0
- package/dist/common/types.js +12 -0
- package/dist/generic/format-adapter.d.ts +64 -0
- package/dist/generic/format-adapter.d.ts.map +1 -0
- package/dist/generic/format-adapter.js +159 -0
- package/dist/generic/index.d.ts +10 -0
- package/dist/generic/index.d.ts.map +1 -0
- package/dist/generic/index.js +9 -0
- package/dist/generic/parser.d.ts +11 -0
- package/dist/generic/parser.d.ts.map +1 -0
- package/dist/generic/parser.js +106 -0
- package/dist/generic/serializer.d.ts +11 -0
- package/dist/generic/serializer.d.ts.map +1 -0
- package/dist/generic/serializer.js +118 -0
- package/dist/generic/types.d.ts +52 -0
- package/dist/generic/types.d.ts.map +1 -0
- package/dist/generic/types.js +6 -0
- package/dist/generic/utils.d.ts +51 -0
- package/dist/generic/utils.d.ts.map +1 -0
- package/dist/generic/utils.js +232 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/speckit/export.d.ts +22 -0
- package/dist/speckit/export.d.ts.map +1 -0
- package/dist/speckit/export.js +384 -0
- package/dist/speckit/format-adapter.d.ts +104 -0
- package/dist/speckit/format-adapter.d.ts.map +1 -0
- package/dist/speckit/format-adapter.js +488 -0
- package/dist/speckit/import-v2.d.ts +33 -0
- package/dist/speckit/import-v2.d.ts.map +1 -0
- package/dist/speckit/import-v2.js +361 -0
- package/dist/speckit/import.d.ts +16 -0
- package/dist/speckit/import.d.ts.map +1 -0
- package/dist/speckit/import.js +247 -0
- package/dist/speckit/index.d.ts +5 -0
- package/dist/speckit/index.d.ts.map +1 -0
- package/dist/speckit/index.js +4 -0
- package/dist/speckit/parser.d.ts +28 -0
- package/dist/speckit/parser.d.ts.map +1 -0
- package/dist/speckit/parser.js +283 -0
- package/dist/speckit/parsers/plan-parser.d.ts +71 -0
- package/dist/speckit/parsers/plan-parser.d.ts.map +1 -0
- package/dist/speckit/parsers/plan-parser.js +216 -0
- package/dist/speckit/parsers/spec-parser.d.ts +67 -0
- package/dist/speckit/parsers/spec-parser.d.ts.map +1 -0
- package/dist/speckit/parsers/spec-parser.js +255 -0
- package/dist/speckit/parsers/tasks-parser.d.ts +57 -0
- package/dist/speckit/parsers/tasks-parser.d.ts.map +1 -0
- package/dist/speckit/parsers/tasks-parser.js +157 -0
- package/package.json +23 -0
- package/project.json +29 -0
- package/src/__tests__/adapter-edge-cases.test.ts +937 -0
- package/src/__tests__/bmad-format-adapter.test.ts +1470 -0
- package/src/__tests__/fixtures/aps/expected-output.json +83 -0
- package/src/__tests__/fixtures/bmad/invalid-malformed-yaml.md +16 -0
- package/src/__tests__/fixtures/bmad/invalid-no-requirements.md +23 -0
- package/src/__tests__/fixtures/bmad/invalid-only-yaml.md +16 -0
- package/src/__tests__/fixtures/bmad/invalid-too-short.md +3 -0
- package/src/__tests__/fixtures/bmad/invalid-wrong-format.md +40 -0
- package/src/__tests__/fixtures/bmad/valid-agent.md +27 -0
- package/src/__tests__/fixtures/bmad/valid-architecture.md +116 -0
- package/src/__tests__/fixtures/bmad/valid-complex-prd.md +161 -0
- package/src/__tests__/fixtures/bmad/valid-epic.md +73 -0
- package/src/__tests__/fixtures/bmad/valid-minimal-prd.md +19 -0
- package/src/__tests__/fixtures/bmad/valid-prd.md +107 -0
- package/src/__tests__/fixtures/bmad/valid-story.md +107 -0
- package/src/__tests__/fixtures/bmad/valid-task.md +79 -0
- package/src/__tests__/fixtures/bmad/valid-v6-prd.md +35 -0
- package/src/__tests__/fixtures/generic/plan-detailed.md +39 -0
- package/src/__tests__/fixtures/generic/prd-simple.md +27 -0
- package/src/__tests__/fixtures/generic/rfc-example.md +26 -0
- package/src/__tests__/fixtures/generic/todo-list.md +23 -0
- package/src/__tests__/fixtures/speckit/sample-plan.md +63 -0
- package/src/__tests__/fixtures/speckit/sample-spec-namespaced.md +50 -0
- package/src/__tests__/fixtures/speckit/sample-spec.md +105 -0
- package/src/__tests__/fixtures/speckit/sample-tasks.md +87 -0
- package/src/__tests__/fixtures/speckit-official/auth-feature/plan.md +272 -0
- package/src/__tests__/fixtures/speckit-official/auth-feature/spec.md +149 -0
- package/src/__tests__/fixtures/speckit-official/auth-feature/tasks.md +169 -0
- package/src/__tests__/generic-format-adapter.test.ts +398 -0
- package/src/__tests__/speckit-export.test.ts +233 -0
- package/src/__tests__/speckit-format-adapter.test.ts +832 -0
- package/src/__tests__/speckit-import-v2.test.ts +253 -0
- package/src/__tests__/speckit-import.test.ts +209 -0
- package/src/__tests__/speckit-parser.test.ts +219 -0
- package/src/__tests__/speckit-spec-parser.test.ts +120 -0
- package/src/aps-markdown/__tests__/__fixtures__/simple-leaf.aps.md +17 -0
- package/src/aps-markdown/__tests__/adapter.test.ts +393 -0
- package/src/aps-markdown/adapter.ts +455 -0
- package/src/aps-markdown/index.ts +8 -0
- package/src/base/__tests__/registry.test.ts +515 -0
- package/src/base/file-discovery.ts +305 -0
- package/src/base/index.ts +10 -0
- package/src/base/registry.ts +263 -0
- package/src/base/testing.ts +334 -0
- package/src/base/types.ts +342 -0
- package/src/base/utils.ts +306 -0
- package/src/bmad/format-adapter.ts +227 -0
- package/src/bmad/index.ts +21 -0
- package/src/bmad/parser.ts +224 -0
- package/src/bmad/serializer.ts +206 -0
- package/src/bmad/types.ts +135 -0
- package/src/bmad/utils.ts +575 -0
- package/src/common/index.ts +2 -0
- package/src/common/registry.ts +72 -0
- package/src/common/types.ts +84 -0
- package/src/generic/__tests__/serializer.test.ts +167 -0
- package/src/generic/format-adapter.ts +200 -0
- package/src/generic/index.ts +11 -0
- package/src/generic/parser.ts +129 -0
- package/src/generic/serializer.ts +134 -0
- package/src/generic/types.ts +53 -0
- package/src/generic/utils.ts +270 -0
- package/src/index.ts +48 -0
- package/src/speckit/export.ts +489 -0
- package/src/speckit/format-adapter.ts +595 -0
- package/src/speckit/import-v2.ts +445 -0
- package/src/speckit/import.ts +305 -0
- package/src/speckit/index.ts +4 -0
- package/src/speckit/parser.ts +351 -0
- package/src/speckit/parsers/plan-parser.ts +342 -0
- package/src/speckit/parsers/spec-parser.ts +379 -0
- package/src/speckit/parsers/tasks-parser.ts +246 -0
- package/tsconfig.json +26 -0
- package/tsconfig.lib.json +21 -0
- package/tsconfig.lib.tsbuildinfo +1 -0
- package/tsconfig.spec.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecKit Import Adapter v2 - Official Format
|
|
3
|
+
*
|
|
4
|
+
* Supports official GitHub spec-kit format:
|
|
5
|
+
* - spec.md: Requirements and user scenarios (WHAT and WHY)
|
|
6
|
+
* - plan.md: Technical implementation details (HOW)
|
|
7
|
+
* - tasks.md: Executable task breakdown
|
|
8
|
+
*
|
|
9
|
+
* Can import individual files or complete feature directories
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createPlan, type APSPlan, type Change, type Provenance } from '@eddacraft/anvil-core';
|
|
13
|
+
import type {
|
|
14
|
+
AdapterConfig,
|
|
15
|
+
ConversionError,
|
|
16
|
+
ConversionResult,
|
|
17
|
+
ConversionWarning,
|
|
18
|
+
ExternalSpec,
|
|
19
|
+
SpecContext,
|
|
20
|
+
} from '../common/types.js';
|
|
21
|
+
import { BaseAdapter } from '../common/types.js';
|
|
22
|
+
import { SpecParser, type ParsedSpec } from './parsers/spec-parser.js';
|
|
23
|
+
import { PlanParser, type ParsedPlan } from './parsers/plan-parser.js';
|
|
24
|
+
import { TasksParser, type ParsedTasks } from './parsers/tasks-parser.js';
|
|
25
|
+
|
|
26
|
+
interface SpecKitDocuments {
|
|
27
|
+
spec?: {
|
|
28
|
+
content: string;
|
|
29
|
+
parsed?: ParsedSpec;
|
|
30
|
+
};
|
|
31
|
+
plan?: {
|
|
32
|
+
content: string;
|
|
33
|
+
parsed?: ParsedPlan;
|
|
34
|
+
};
|
|
35
|
+
tasks?: {
|
|
36
|
+
content: string;
|
|
37
|
+
parsed?: ParsedTasks;
|
|
38
|
+
};
|
|
39
|
+
metadata?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class SpecKitImportAdapterV2 extends BaseAdapter {
|
|
43
|
+
readonly name = 'speckit-import-v2';
|
|
44
|
+
readonly version = '2.0.0';
|
|
45
|
+
readonly supportedFormats = ['speckit', 'spec-kit', 'spec.md', 'plan.md', 'tasks.md'] as const;
|
|
46
|
+
|
|
47
|
+
private specParser: SpecParser;
|
|
48
|
+
private planParser: PlanParser;
|
|
49
|
+
private tasksParser: TasksParser;
|
|
50
|
+
|
|
51
|
+
constructor(config: AdapterConfig = {}) {
|
|
52
|
+
super(config);
|
|
53
|
+
this.specParser = new SpecParser();
|
|
54
|
+
this.planParser = new PlanParser();
|
|
55
|
+
this.tasksParser = new TasksParser();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async generateSpec(intent: string, context: SpecContext): Promise<APSPlan> {
|
|
59
|
+
const provenance: Provenance = {
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
source: 'cli',
|
|
62
|
+
version: this.version,
|
|
63
|
+
author: context.author,
|
|
64
|
+
repository: context.repositoryPath,
|
|
65
|
+
branch: context.branch,
|
|
66
|
+
commit: context.commit,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Generate a simple spec.md file creation change
|
|
70
|
+
const changes: Change[] = [
|
|
71
|
+
{
|
|
72
|
+
type: 'file_create',
|
|
73
|
+
path: 'specs/new-feature/spec.md',
|
|
74
|
+
description: 'Create specification file following spec-kit format',
|
|
75
|
+
content: this.generateSpecTemplate(intent),
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const planId = `aps-${Date.now().toString(16).substring(0, 8)}`;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...createPlan({
|
|
83
|
+
id: planId,
|
|
84
|
+
intent,
|
|
85
|
+
provenance,
|
|
86
|
+
changes,
|
|
87
|
+
}),
|
|
88
|
+
schema_version: '0.1.0' as const,
|
|
89
|
+
hash: '0'.repeat(64),
|
|
90
|
+
} as APSPlan;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async validateSpec(spec: APSPlan): Promise<import('@eddacraft/anvil-core').ValidationResult> {
|
|
94
|
+
const errors: Array<{ field: string; message: string }> = [];
|
|
95
|
+
const warnings: Array<{ field: string; message: string }> = [];
|
|
96
|
+
|
|
97
|
+
if (spec.proposed_changes.length === 0) {
|
|
98
|
+
warnings.push({
|
|
99
|
+
field: 'proposed_changes',
|
|
100
|
+
message: 'No changes specified in the plan',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const issues: Array<{
|
|
105
|
+
path: string;
|
|
106
|
+
message: string;
|
|
107
|
+
code: string;
|
|
108
|
+
severity: 'error' | 'warning';
|
|
109
|
+
}> = [
|
|
110
|
+
...errors.map((e) => ({
|
|
111
|
+
path: e.field,
|
|
112
|
+
message: e.message,
|
|
113
|
+
code: 'VALIDATION_ERROR',
|
|
114
|
+
severity: 'error' as const,
|
|
115
|
+
})),
|
|
116
|
+
...warnings.map((w) => ({
|
|
117
|
+
path: w.field,
|
|
118
|
+
message: w.message,
|
|
119
|
+
code: 'VALIDATION_WARNING',
|
|
120
|
+
severity: 'warning' as const,
|
|
121
|
+
})),
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
valid: errors.length === 0,
|
|
126
|
+
data: spec,
|
|
127
|
+
issues: issues.length > 0 ? issues : undefined,
|
|
128
|
+
summary: errors.length === 0 ? 'Validation passed' : `Found ${errors.length} error(s)`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async convertToAPS(spec: ExternalSpec): Promise<ConversionResult<APSPlan>> {
|
|
133
|
+
const errors: ConversionError[] = [];
|
|
134
|
+
const warnings: ConversionWarning[] = [];
|
|
135
|
+
|
|
136
|
+
if (!this.canImport(spec.format)) {
|
|
137
|
+
return {
|
|
138
|
+
success: false,
|
|
139
|
+
errors: [
|
|
140
|
+
{
|
|
141
|
+
code: 'UNSUPPORTED_FORMAT',
|
|
142
|
+
message: `Format '${spec.format}' is not supported by this adapter`,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const docs = spec.content as SpecKitDocuments;
|
|
150
|
+
|
|
151
|
+
// Parse all available documents
|
|
152
|
+
if (docs.spec?.content) {
|
|
153
|
+
docs.spec.parsed = this.specParser.parseSpec(docs.spec.content);
|
|
154
|
+
}
|
|
155
|
+
if (docs.plan?.content) {
|
|
156
|
+
docs.plan.parsed = this.planParser.parsePlan(docs.plan.content);
|
|
157
|
+
}
|
|
158
|
+
if (docs.tasks?.content) {
|
|
159
|
+
docs.tasks.parsed = this.tasksParser.parseTasks(docs.tasks.content);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// At minimum, we need spec.md
|
|
163
|
+
if (!docs.spec?.parsed) {
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
errors: [
|
|
167
|
+
{
|
|
168
|
+
code: 'MISSING_SPEC',
|
|
169
|
+
message: 'spec.md content is required',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Build APS plan from parsed documents
|
|
176
|
+
const apsResult = this.buildAPSFromDocs(docs, spec.metadata, errors, warnings);
|
|
177
|
+
|
|
178
|
+
if (errors.length > 0) {
|
|
179
|
+
return { success: false, errors, warnings };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
success: true,
|
|
184
|
+
data: apsResult,
|
|
185
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
186
|
+
};
|
|
187
|
+
} catch (error) {
|
|
188
|
+
errors.push({
|
|
189
|
+
code: 'CONVERSION_ERROR',
|
|
190
|
+
message: error instanceof Error ? error.message : 'Unknown conversion error',
|
|
191
|
+
});
|
|
192
|
+
return { success: false, errors };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async convertFromAPS(_spec: APSPlan): Promise<ConversionResult<ExternalSpec>> {
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
errors: [
|
|
200
|
+
{
|
|
201
|
+
code: 'NOT_IMPLEMENTED',
|
|
202
|
+
message: 'Export to SpecKit format is handled by speckit-export adapter',
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private buildAPSFromDocs(
|
|
209
|
+
docs: SpecKitDocuments,
|
|
210
|
+
metadata: Record<string, unknown> | undefined,
|
|
211
|
+
errors: ConversionError[],
|
|
212
|
+
warnings: ConversionWarning[]
|
|
213
|
+
): APSPlan {
|
|
214
|
+
const parsedSpec = docs.spec!.parsed!;
|
|
215
|
+
const parsedPlan = docs.plan?.parsed;
|
|
216
|
+
const parsedTasks = docs.tasks?.parsed;
|
|
217
|
+
|
|
218
|
+
// Build intent from spec user scenarios and metadata
|
|
219
|
+
const intent = this.buildIntent(parsedSpec);
|
|
220
|
+
|
|
221
|
+
// Build proposed changes from user scenarios + plan details + tasks
|
|
222
|
+
const changes = this.buildProposedChanges(parsedSpec, parsedPlan, parsedTasks, warnings);
|
|
223
|
+
|
|
224
|
+
// Build provenance
|
|
225
|
+
const provenance: Provenance = {
|
|
226
|
+
timestamp: (metadata?.['timestamp'] as string) || new Date().toISOString(),
|
|
227
|
+
source: 'cli',
|
|
228
|
+
version: this.version,
|
|
229
|
+
author: (metadata?.['author'] as string) || parsedSpec.metadata.branch,
|
|
230
|
+
repository: metadata?.['repository'] as string,
|
|
231
|
+
branch: parsedSpec.metadata.branch as string,
|
|
232
|
+
commit: metadata?.['commit'] as string,
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const planId = `aps-${Date.now().toString(16).substring(0, 8)}`;
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
...createPlan({
|
|
239
|
+
id: planId,
|
|
240
|
+
intent,
|
|
241
|
+
provenance,
|
|
242
|
+
changes,
|
|
243
|
+
}),
|
|
244
|
+
schema_version: '0.1.0' as const,
|
|
245
|
+
hash: '0'.repeat(64),
|
|
246
|
+
metadata: {
|
|
247
|
+
source_format: 'speckit-v2',
|
|
248
|
+
feature: parsedSpec.metadata.feature,
|
|
249
|
+
// From spec.md
|
|
250
|
+
userScenarios: parsedSpec.userScenarios,
|
|
251
|
+
requirements: parsedSpec.requirements,
|
|
252
|
+
successCriteria: parsedSpec.successCriteria,
|
|
253
|
+
clarifications: parsedSpec.clarifications,
|
|
254
|
+
// From plan.md
|
|
255
|
+
technicalContext: parsedPlan?.technicalContext,
|
|
256
|
+
constitutionCheck: parsedPlan?.constitutionCheck,
|
|
257
|
+
projectStructure: parsedPlan?.projectStructure,
|
|
258
|
+
implementationDetails: parsedPlan?.implementationDetails
|
|
259
|
+
? Object.fromEntries(parsedPlan.implementationDetails)
|
|
260
|
+
: undefined,
|
|
261
|
+
complexityDecisions: parsedPlan?.complexityDecisions,
|
|
262
|
+
// From tasks.md
|
|
263
|
+
phases: parsedTasks?.phases,
|
|
264
|
+
taskDependencies: parsedTasks?.dependencies,
|
|
265
|
+
implementationStrategies: parsedTasks?.strategies,
|
|
266
|
+
},
|
|
267
|
+
} as APSPlan;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private buildIntent(parsedSpec: ParsedSpec): string {
|
|
271
|
+
// Build intent from feature name and P1 user scenarios
|
|
272
|
+
const feature = parsedSpec.metadata.feature || 'Feature';
|
|
273
|
+
const p1Scenarios = parsedSpec.userScenarios.filter((s) => s.priority === 'P1');
|
|
274
|
+
|
|
275
|
+
if (p1Scenarios.length === 0) {
|
|
276
|
+
return `Implement ${feature}`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const scenarioDescriptions = p1Scenarios
|
|
280
|
+
.map((s) => `${s.asA} wants to ${s.iWantTo} so that ${s.soThat}`)
|
|
281
|
+
.join('. ');
|
|
282
|
+
|
|
283
|
+
return `${feature}: ${scenarioDescriptions}`.substring(0, 500);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private buildProposedChanges(
|
|
287
|
+
parsedSpec: ParsedSpec,
|
|
288
|
+
parsedPlan: ParsedPlan | undefined,
|
|
289
|
+
parsedTasks: ParsedTasks | undefined,
|
|
290
|
+
warnings: ConversionWarning[]
|
|
291
|
+
): Change[] {
|
|
292
|
+
const changes: Change[] = [];
|
|
293
|
+
|
|
294
|
+
// Strategy: Convert user scenarios to proposed changes
|
|
295
|
+
// Each scenario represents a feature to implement
|
|
296
|
+
for (const scenario of parsedSpec.userScenarios) {
|
|
297
|
+
// Create a change for each P1/P2 user scenario
|
|
298
|
+
if (scenario.priority === 'P1' || scenario.priority === 'P2') {
|
|
299
|
+
const change: Change = {
|
|
300
|
+
type: 'file_create',
|
|
301
|
+
path: this.inferPathFromScenario(scenario, parsedPlan),
|
|
302
|
+
description: `Implement ${scenario.title}: ${scenario.iWantTo}`,
|
|
303
|
+
metadata: {
|
|
304
|
+
priority: scenario.priority,
|
|
305
|
+
userStory: {
|
|
306
|
+
asA: scenario.asA,
|
|
307
|
+
iWantTo: scenario.iWantTo,
|
|
308
|
+
soThat: scenario.soThat,
|
|
309
|
+
},
|
|
310
|
+
acceptanceScenarios: scenario.acceptanceScenarios,
|
|
311
|
+
edgeCases: scenario.edgeCases,
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
changes.push(change);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// If we have plan details, add implementation-specific changes
|
|
320
|
+
if (parsedPlan) {
|
|
321
|
+
// Add database migrations if mentioned
|
|
322
|
+
if (parsedPlan.implementationDetails.has('Database Schema')) {
|
|
323
|
+
changes.push({
|
|
324
|
+
type: 'file_create',
|
|
325
|
+
path: 'database/migrations/',
|
|
326
|
+
description: 'Create database migrations for required tables',
|
|
327
|
+
metadata: {
|
|
328
|
+
section: 'Database Schema',
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Add API endpoints if mentioned
|
|
334
|
+
if (parsedPlan.implementationDetails.has('API Endpoints')) {
|
|
335
|
+
changes.push({
|
|
336
|
+
type: 'file_create',
|
|
337
|
+
path: this.inferAPIPath(parsedPlan),
|
|
338
|
+
description: 'Implement API endpoints',
|
|
339
|
+
metadata: {
|
|
340
|
+
section: 'API Endpoints',
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Add dependency installation if we have technical context
|
|
347
|
+
if (
|
|
348
|
+
parsedPlan?.technicalContext.dependencies &&
|
|
349
|
+
parsedPlan.technicalContext.dependencies.length > 0
|
|
350
|
+
) {
|
|
351
|
+
changes.push({
|
|
352
|
+
type: 'dependency_add',
|
|
353
|
+
path: 'package.json',
|
|
354
|
+
description: `Install dependencies: ${parsedPlan.technicalContext.dependencies.slice(0, 3).join(', ')}${parsedPlan.technicalContext.dependencies.length > 3 ? '...' : ''}`,
|
|
355
|
+
metadata: {
|
|
356
|
+
dependencies: parsedPlan.technicalContext.dependencies,
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (changes.length === 0) {
|
|
362
|
+
warnings.push({
|
|
363
|
+
code: 'NO_CHANGES',
|
|
364
|
+
message: 'No actionable changes could be derived from spec',
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return changes;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private inferPathFromScenario(scenario: { title: string }, plan?: ParsedPlan): string {
|
|
372
|
+
const title = scenario.title.toLowerCase().replace(/\s+/g, '-');
|
|
373
|
+
|
|
374
|
+
// Use plan structure if available
|
|
375
|
+
if (plan?.projectStructure.sourceCode) {
|
|
376
|
+
// Try to extract common patterns
|
|
377
|
+
if (plan.projectStructure.sourceCode.includes('src/modules/')) {
|
|
378
|
+
return `src/modules/${title}/`;
|
|
379
|
+
}
|
|
380
|
+
if (plan.projectStructure.sourceCode.includes('src/features/')) {
|
|
381
|
+
return `src/features/${title}/`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return `src/${title}/`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private inferAPIPath(plan: ParsedPlan): string {
|
|
389
|
+
if (plan.projectStructure.sourceCode?.includes('controllers')) {
|
|
390
|
+
return 'src/controllers/';
|
|
391
|
+
}
|
|
392
|
+
if (plan.projectStructure.sourceCode?.includes('routes')) {
|
|
393
|
+
return 'src/routes/';
|
|
394
|
+
}
|
|
395
|
+
return 'src/api/';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private generateSpecTemplate(intent: string): string {
|
|
399
|
+
return `# Feature: ${intent}
|
|
400
|
+
|
|
401
|
+
**Branch**: \`feature/xxx-feature-name\`
|
|
402
|
+
**Date**: ${new Date().toISOString().split('T')[0]}
|
|
403
|
+
**Status**: Draft
|
|
404
|
+
|
|
405
|
+
## User Scenarios & Testing
|
|
406
|
+
|
|
407
|
+
### P1: [User Scenario Title]
|
|
408
|
+
**As a** [user type]
|
|
409
|
+
**I want to** [action]
|
|
410
|
+
**So that** [benefit]
|
|
411
|
+
|
|
412
|
+
**Acceptance Scenarios:**
|
|
413
|
+
- [Scenario 1]
|
|
414
|
+
- [Scenario 2]
|
|
415
|
+
|
|
416
|
+
**Edge Cases:**
|
|
417
|
+
- [Edge case 1]
|
|
418
|
+
- [NEEDS CLARIFICATION: Question?]
|
|
419
|
+
|
|
420
|
+
## Requirements
|
|
421
|
+
|
|
422
|
+
### Functional Requirements
|
|
423
|
+
|
|
424
|
+
**FR-001**: System MUST [requirement]
|
|
425
|
+
**FR-002**: [NEEDS CLARIFICATION: What should happen when...]
|
|
426
|
+
|
|
427
|
+
### Key Entities
|
|
428
|
+
|
|
429
|
+
**EntityName**
|
|
430
|
+
- Represents: [What this entity represents]
|
|
431
|
+
- Key Attributes: [attribute1, attribute2]
|
|
432
|
+
- Relationships: [relationships to other entities]
|
|
433
|
+
|
|
434
|
+
## Success Criteria
|
|
435
|
+
|
|
436
|
+
### Quantitative Metrics
|
|
437
|
+
- [Metric 1]
|
|
438
|
+
- [Metric 2]
|
|
439
|
+
|
|
440
|
+
### Qualitative Metrics
|
|
441
|
+
- [Metric 1]
|
|
442
|
+
- [Metric 2]
|
|
443
|
+
`;
|
|
444
|
+
}
|
|
445
|
+
}
|