@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.
Files changed (183) hide show
  1. package/AGENTS.md +180 -0
  2. package/BMAD_ADAPTER_SPEC.md +489 -0
  3. package/LICENSE +14 -0
  4. package/README.md +500 -0
  5. package/dist/aps-markdown/adapter.d.ts +102 -0
  6. package/dist/aps-markdown/adapter.d.ts.map +1 -0
  7. package/dist/aps-markdown/adapter.js +351 -0
  8. package/dist/aps-markdown/index.d.ts +8 -0
  9. package/dist/aps-markdown/index.d.ts.map +1 -0
  10. package/dist/aps-markdown/index.js +7 -0
  11. package/dist/base/file-discovery.d.ts +63 -0
  12. package/dist/base/file-discovery.d.ts.map +1 -0
  13. package/dist/base/file-discovery.js +246 -0
  14. package/dist/base/index.d.ts +10 -0
  15. package/dist/base/index.d.ts.map +1 -0
  16. package/dist/base/index.js +9 -0
  17. package/dist/base/registry.d.ts +155 -0
  18. package/dist/base/registry.d.ts.map +1 -0
  19. package/dist/base/registry.js +227 -0
  20. package/dist/base/testing.d.ts +102 -0
  21. package/dist/base/testing.d.ts.map +1 -0
  22. package/dist/base/testing.js +221 -0
  23. package/dist/base/types.d.ts +255 -0
  24. package/dist/base/types.d.ts.map +1 -0
  25. package/dist/base/types.js +78 -0
  26. package/dist/base/utils.d.ts +127 -0
  27. package/dist/base/utils.d.ts.map +1 -0
  28. package/dist/base/utils.js +254 -0
  29. package/dist/bmad/format-adapter.d.ts +76 -0
  30. package/dist/bmad/format-adapter.d.ts.map +1 -0
  31. package/dist/bmad/format-adapter.js +186 -0
  32. package/dist/bmad/index.d.ts +12 -0
  33. package/dist/bmad/index.d.ts.map +1 -0
  34. package/dist/bmad/index.js +10 -0
  35. package/dist/bmad/parser.d.ts +12 -0
  36. package/dist/bmad/parser.d.ts.map +1 -0
  37. package/dist/bmad/parser.js +181 -0
  38. package/dist/bmad/serializer.d.ts +16 -0
  39. package/dist/bmad/serializer.d.ts.map +1 -0
  40. package/dist/bmad/serializer.js +170 -0
  41. package/dist/bmad/types.d.ts +127 -0
  42. package/dist/bmad/types.d.ts.map +1 -0
  43. package/dist/bmad/types.js +47 -0
  44. package/dist/bmad/utils.d.ts +120 -0
  45. package/dist/bmad/utils.d.ts.map +1 -0
  46. package/dist/bmad/utils.js +480 -0
  47. package/dist/common/index.d.ts +3 -0
  48. package/dist/common/index.d.ts.map +1 -0
  49. package/dist/common/index.js +2 -0
  50. package/dist/common/registry.d.ts +18 -0
  51. package/dist/common/registry.d.ts.map +1 -0
  52. package/dist/common/registry.js +58 -0
  53. package/dist/common/types.d.ts +68 -0
  54. package/dist/common/types.d.ts.map +1 -0
  55. package/dist/common/types.js +12 -0
  56. package/dist/generic/format-adapter.d.ts +64 -0
  57. package/dist/generic/format-adapter.d.ts.map +1 -0
  58. package/dist/generic/format-adapter.js +159 -0
  59. package/dist/generic/index.d.ts +10 -0
  60. package/dist/generic/index.d.ts.map +1 -0
  61. package/dist/generic/index.js +9 -0
  62. package/dist/generic/parser.d.ts +11 -0
  63. package/dist/generic/parser.d.ts.map +1 -0
  64. package/dist/generic/parser.js +106 -0
  65. package/dist/generic/serializer.d.ts +11 -0
  66. package/dist/generic/serializer.d.ts.map +1 -0
  67. package/dist/generic/serializer.js +118 -0
  68. package/dist/generic/types.d.ts +52 -0
  69. package/dist/generic/types.d.ts.map +1 -0
  70. package/dist/generic/types.js +6 -0
  71. package/dist/generic/utils.d.ts +51 -0
  72. package/dist/generic/utils.d.ts.map +1 -0
  73. package/dist/generic/utils.js +232 -0
  74. package/dist/index.d.ts +15 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +31 -0
  77. package/dist/speckit/export.d.ts +22 -0
  78. package/dist/speckit/export.d.ts.map +1 -0
  79. package/dist/speckit/export.js +384 -0
  80. package/dist/speckit/format-adapter.d.ts +104 -0
  81. package/dist/speckit/format-adapter.d.ts.map +1 -0
  82. package/dist/speckit/format-adapter.js +488 -0
  83. package/dist/speckit/import-v2.d.ts +33 -0
  84. package/dist/speckit/import-v2.d.ts.map +1 -0
  85. package/dist/speckit/import-v2.js +361 -0
  86. package/dist/speckit/import.d.ts +16 -0
  87. package/dist/speckit/import.d.ts.map +1 -0
  88. package/dist/speckit/import.js +247 -0
  89. package/dist/speckit/index.d.ts +5 -0
  90. package/dist/speckit/index.d.ts.map +1 -0
  91. package/dist/speckit/index.js +4 -0
  92. package/dist/speckit/parser.d.ts +28 -0
  93. package/dist/speckit/parser.d.ts.map +1 -0
  94. package/dist/speckit/parser.js +283 -0
  95. package/dist/speckit/parsers/plan-parser.d.ts +71 -0
  96. package/dist/speckit/parsers/plan-parser.d.ts.map +1 -0
  97. package/dist/speckit/parsers/plan-parser.js +216 -0
  98. package/dist/speckit/parsers/spec-parser.d.ts +67 -0
  99. package/dist/speckit/parsers/spec-parser.d.ts.map +1 -0
  100. package/dist/speckit/parsers/spec-parser.js +255 -0
  101. package/dist/speckit/parsers/tasks-parser.d.ts +57 -0
  102. package/dist/speckit/parsers/tasks-parser.d.ts.map +1 -0
  103. package/dist/speckit/parsers/tasks-parser.js +157 -0
  104. package/package.json +23 -0
  105. package/project.json +29 -0
  106. package/src/__tests__/adapter-edge-cases.test.ts +937 -0
  107. package/src/__tests__/bmad-format-adapter.test.ts +1470 -0
  108. package/src/__tests__/fixtures/aps/expected-output.json +83 -0
  109. package/src/__tests__/fixtures/bmad/invalid-malformed-yaml.md +16 -0
  110. package/src/__tests__/fixtures/bmad/invalid-no-requirements.md +23 -0
  111. package/src/__tests__/fixtures/bmad/invalid-only-yaml.md +16 -0
  112. package/src/__tests__/fixtures/bmad/invalid-too-short.md +3 -0
  113. package/src/__tests__/fixtures/bmad/invalid-wrong-format.md +40 -0
  114. package/src/__tests__/fixtures/bmad/valid-agent.md +27 -0
  115. package/src/__tests__/fixtures/bmad/valid-architecture.md +116 -0
  116. package/src/__tests__/fixtures/bmad/valid-complex-prd.md +161 -0
  117. package/src/__tests__/fixtures/bmad/valid-epic.md +73 -0
  118. package/src/__tests__/fixtures/bmad/valid-minimal-prd.md +19 -0
  119. package/src/__tests__/fixtures/bmad/valid-prd.md +107 -0
  120. package/src/__tests__/fixtures/bmad/valid-story.md +107 -0
  121. package/src/__tests__/fixtures/bmad/valid-task.md +79 -0
  122. package/src/__tests__/fixtures/bmad/valid-v6-prd.md +35 -0
  123. package/src/__tests__/fixtures/generic/plan-detailed.md +39 -0
  124. package/src/__tests__/fixtures/generic/prd-simple.md +27 -0
  125. package/src/__tests__/fixtures/generic/rfc-example.md +26 -0
  126. package/src/__tests__/fixtures/generic/todo-list.md +23 -0
  127. package/src/__tests__/fixtures/speckit/sample-plan.md +63 -0
  128. package/src/__tests__/fixtures/speckit/sample-spec-namespaced.md +50 -0
  129. package/src/__tests__/fixtures/speckit/sample-spec.md +105 -0
  130. package/src/__tests__/fixtures/speckit/sample-tasks.md +87 -0
  131. package/src/__tests__/fixtures/speckit-official/auth-feature/plan.md +272 -0
  132. package/src/__tests__/fixtures/speckit-official/auth-feature/spec.md +149 -0
  133. package/src/__tests__/fixtures/speckit-official/auth-feature/tasks.md +169 -0
  134. package/src/__tests__/generic-format-adapter.test.ts +398 -0
  135. package/src/__tests__/speckit-export.test.ts +233 -0
  136. package/src/__tests__/speckit-format-adapter.test.ts +832 -0
  137. package/src/__tests__/speckit-import-v2.test.ts +253 -0
  138. package/src/__tests__/speckit-import.test.ts +209 -0
  139. package/src/__tests__/speckit-parser.test.ts +219 -0
  140. package/src/__tests__/speckit-spec-parser.test.ts +120 -0
  141. package/src/aps-markdown/__tests__/__fixtures__/simple-leaf.aps.md +17 -0
  142. package/src/aps-markdown/__tests__/adapter.test.ts +393 -0
  143. package/src/aps-markdown/adapter.ts +455 -0
  144. package/src/aps-markdown/index.ts +8 -0
  145. package/src/base/__tests__/registry.test.ts +515 -0
  146. package/src/base/file-discovery.ts +305 -0
  147. package/src/base/index.ts +10 -0
  148. package/src/base/registry.ts +263 -0
  149. package/src/base/testing.ts +334 -0
  150. package/src/base/types.ts +342 -0
  151. package/src/base/utils.ts +306 -0
  152. package/src/bmad/format-adapter.ts +227 -0
  153. package/src/bmad/index.ts +21 -0
  154. package/src/bmad/parser.ts +224 -0
  155. package/src/bmad/serializer.ts +206 -0
  156. package/src/bmad/types.ts +135 -0
  157. package/src/bmad/utils.ts +575 -0
  158. package/src/common/index.ts +2 -0
  159. package/src/common/registry.ts +72 -0
  160. package/src/common/types.ts +84 -0
  161. package/src/generic/__tests__/serializer.test.ts +167 -0
  162. package/src/generic/format-adapter.ts +200 -0
  163. package/src/generic/index.ts +11 -0
  164. package/src/generic/parser.ts +129 -0
  165. package/src/generic/serializer.ts +134 -0
  166. package/src/generic/types.ts +53 -0
  167. package/src/generic/utils.ts +270 -0
  168. package/src/index.ts +48 -0
  169. package/src/speckit/export.ts +489 -0
  170. package/src/speckit/format-adapter.ts +595 -0
  171. package/src/speckit/import-v2.ts +445 -0
  172. package/src/speckit/import.ts +305 -0
  173. package/src/speckit/index.ts +4 -0
  174. package/src/speckit/parser.ts +351 -0
  175. package/src/speckit/parsers/plan-parser.ts +342 -0
  176. package/src/speckit/parsers/spec-parser.ts +379 -0
  177. package/src/speckit/parsers/tasks-parser.ts +246 -0
  178. package/tsconfig.json +26 -0
  179. package/tsconfig.lib.json +21 -0
  180. package/tsconfig.lib.tsbuildinfo +1 -0
  181. package/tsconfig.spec.json +9 -0
  182. package/tsconfig.tsbuildinfo +1 -0
  183. 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
+ }