@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,361 @@
|
|
|
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
|
+
import { createPlan } from '@eddacraft/anvil-core';
|
|
12
|
+
import { BaseAdapter } from '../common/types.js';
|
|
13
|
+
import { SpecParser } from './parsers/spec-parser.js';
|
|
14
|
+
import { PlanParser } from './parsers/plan-parser.js';
|
|
15
|
+
import { TasksParser } from './parsers/tasks-parser.js';
|
|
16
|
+
export class SpecKitImportAdapterV2 extends BaseAdapter {
|
|
17
|
+
name = 'speckit-import-v2';
|
|
18
|
+
version = '2.0.0';
|
|
19
|
+
supportedFormats = ['speckit', 'spec-kit', 'spec.md', 'plan.md', 'tasks.md'];
|
|
20
|
+
specParser;
|
|
21
|
+
planParser;
|
|
22
|
+
tasksParser;
|
|
23
|
+
constructor(config = {}) {
|
|
24
|
+
super(config);
|
|
25
|
+
this.specParser = new SpecParser();
|
|
26
|
+
this.planParser = new PlanParser();
|
|
27
|
+
this.tasksParser = new TasksParser();
|
|
28
|
+
}
|
|
29
|
+
async generateSpec(intent, context) {
|
|
30
|
+
const provenance = {
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
source: 'cli',
|
|
33
|
+
version: this.version,
|
|
34
|
+
author: context.author,
|
|
35
|
+
repository: context.repositoryPath,
|
|
36
|
+
branch: context.branch,
|
|
37
|
+
commit: context.commit,
|
|
38
|
+
};
|
|
39
|
+
// Generate a simple spec.md file creation change
|
|
40
|
+
const changes = [
|
|
41
|
+
{
|
|
42
|
+
type: 'file_create',
|
|
43
|
+
path: 'specs/new-feature/spec.md',
|
|
44
|
+
description: 'Create specification file following spec-kit format',
|
|
45
|
+
content: this.generateSpecTemplate(intent),
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
const planId = `aps-${Date.now().toString(16).substring(0, 8)}`;
|
|
49
|
+
return {
|
|
50
|
+
...createPlan({
|
|
51
|
+
id: planId,
|
|
52
|
+
intent,
|
|
53
|
+
provenance,
|
|
54
|
+
changes,
|
|
55
|
+
}),
|
|
56
|
+
schema_version: '0.1.0',
|
|
57
|
+
hash: '0'.repeat(64),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async validateSpec(spec) {
|
|
61
|
+
const errors = [];
|
|
62
|
+
const warnings = [];
|
|
63
|
+
if (spec.proposed_changes.length === 0) {
|
|
64
|
+
warnings.push({
|
|
65
|
+
field: 'proposed_changes',
|
|
66
|
+
message: 'No changes specified in the plan',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const issues = [
|
|
70
|
+
...errors.map((e) => ({
|
|
71
|
+
path: e.field,
|
|
72
|
+
message: e.message,
|
|
73
|
+
code: 'VALIDATION_ERROR',
|
|
74
|
+
severity: 'error',
|
|
75
|
+
})),
|
|
76
|
+
...warnings.map((w) => ({
|
|
77
|
+
path: w.field,
|
|
78
|
+
message: w.message,
|
|
79
|
+
code: 'VALIDATION_WARNING',
|
|
80
|
+
severity: 'warning',
|
|
81
|
+
})),
|
|
82
|
+
];
|
|
83
|
+
return {
|
|
84
|
+
valid: errors.length === 0,
|
|
85
|
+
data: spec,
|
|
86
|
+
issues: issues.length > 0 ? issues : undefined,
|
|
87
|
+
summary: errors.length === 0 ? 'Validation passed' : `Found ${errors.length} error(s)`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async convertToAPS(spec) {
|
|
91
|
+
const errors = [];
|
|
92
|
+
const warnings = [];
|
|
93
|
+
if (!this.canImport(spec.format)) {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
errors: [
|
|
97
|
+
{
|
|
98
|
+
code: 'UNSUPPORTED_FORMAT',
|
|
99
|
+
message: `Format '${spec.format}' is not supported by this adapter`,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const docs = spec.content;
|
|
106
|
+
// Parse all available documents
|
|
107
|
+
if (docs.spec?.content) {
|
|
108
|
+
docs.spec.parsed = this.specParser.parseSpec(docs.spec.content);
|
|
109
|
+
}
|
|
110
|
+
if (docs.plan?.content) {
|
|
111
|
+
docs.plan.parsed = this.planParser.parsePlan(docs.plan.content);
|
|
112
|
+
}
|
|
113
|
+
if (docs.tasks?.content) {
|
|
114
|
+
docs.tasks.parsed = this.tasksParser.parseTasks(docs.tasks.content);
|
|
115
|
+
}
|
|
116
|
+
// At minimum, we need spec.md
|
|
117
|
+
if (!docs.spec?.parsed) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
errors: [
|
|
121
|
+
{
|
|
122
|
+
code: 'MISSING_SPEC',
|
|
123
|
+
message: 'spec.md content is required',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Build APS plan from parsed documents
|
|
129
|
+
const apsResult = this.buildAPSFromDocs(docs, spec.metadata, errors, warnings);
|
|
130
|
+
if (errors.length > 0) {
|
|
131
|
+
return { success: false, errors, warnings };
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
success: true,
|
|
135
|
+
data: apsResult,
|
|
136
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
errors.push({
|
|
141
|
+
code: 'CONVERSION_ERROR',
|
|
142
|
+
message: error instanceof Error ? error.message : 'Unknown conversion error',
|
|
143
|
+
});
|
|
144
|
+
return { success: false, errors };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async convertFromAPS(_spec) {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
errors: [
|
|
151
|
+
{
|
|
152
|
+
code: 'NOT_IMPLEMENTED',
|
|
153
|
+
message: 'Export to SpecKit format is handled by speckit-export adapter',
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
buildAPSFromDocs(docs, metadata, errors, warnings) {
|
|
159
|
+
const parsedSpec = docs.spec.parsed;
|
|
160
|
+
const parsedPlan = docs.plan?.parsed;
|
|
161
|
+
const parsedTasks = docs.tasks?.parsed;
|
|
162
|
+
// Build intent from spec user scenarios and metadata
|
|
163
|
+
const intent = this.buildIntent(parsedSpec);
|
|
164
|
+
// Build proposed changes from user scenarios + plan details + tasks
|
|
165
|
+
const changes = this.buildProposedChanges(parsedSpec, parsedPlan, parsedTasks, warnings);
|
|
166
|
+
// Build provenance
|
|
167
|
+
const provenance = {
|
|
168
|
+
timestamp: metadata?.['timestamp'] || new Date().toISOString(),
|
|
169
|
+
source: 'cli',
|
|
170
|
+
version: this.version,
|
|
171
|
+
author: metadata?.['author'] || parsedSpec.metadata.branch,
|
|
172
|
+
repository: metadata?.['repository'],
|
|
173
|
+
branch: parsedSpec.metadata.branch,
|
|
174
|
+
commit: metadata?.['commit'],
|
|
175
|
+
};
|
|
176
|
+
const planId = `aps-${Date.now().toString(16).substring(0, 8)}`;
|
|
177
|
+
return {
|
|
178
|
+
...createPlan({
|
|
179
|
+
id: planId,
|
|
180
|
+
intent,
|
|
181
|
+
provenance,
|
|
182
|
+
changes,
|
|
183
|
+
}),
|
|
184
|
+
schema_version: '0.1.0',
|
|
185
|
+
hash: '0'.repeat(64),
|
|
186
|
+
metadata: {
|
|
187
|
+
source_format: 'speckit-v2',
|
|
188
|
+
feature: parsedSpec.metadata.feature,
|
|
189
|
+
// From spec.md
|
|
190
|
+
userScenarios: parsedSpec.userScenarios,
|
|
191
|
+
requirements: parsedSpec.requirements,
|
|
192
|
+
successCriteria: parsedSpec.successCriteria,
|
|
193
|
+
clarifications: parsedSpec.clarifications,
|
|
194
|
+
// From plan.md
|
|
195
|
+
technicalContext: parsedPlan?.technicalContext,
|
|
196
|
+
constitutionCheck: parsedPlan?.constitutionCheck,
|
|
197
|
+
projectStructure: parsedPlan?.projectStructure,
|
|
198
|
+
implementationDetails: parsedPlan?.implementationDetails
|
|
199
|
+
? Object.fromEntries(parsedPlan.implementationDetails)
|
|
200
|
+
: undefined,
|
|
201
|
+
complexityDecisions: parsedPlan?.complexityDecisions,
|
|
202
|
+
// From tasks.md
|
|
203
|
+
phases: parsedTasks?.phases,
|
|
204
|
+
taskDependencies: parsedTasks?.dependencies,
|
|
205
|
+
implementationStrategies: parsedTasks?.strategies,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
buildIntent(parsedSpec) {
|
|
210
|
+
// Build intent from feature name and P1 user scenarios
|
|
211
|
+
const feature = parsedSpec.metadata.feature || 'Feature';
|
|
212
|
+
const p1Scenarios = parsedSpec.userScenarios.filter((s) => s.priority === 'P1');
|
|
213
|
+
if (p1Scenarios.length === 0) {
|
|
214
|
+
return `Implement ${feature}`;
|
|
215
|
+
}
|
|
216
|
+
const scenarioDescriptions = p1Scenarios
|
|
217
|
+
.map((s) => `${s.asA} wants to ${s.iWantTo} so that ${s.soThat}`)
|
|
218
|
+
.join('. ');
|
|
219
|
+
return `${feature}: ${scenarioDescriptions}`.substring(0, 500);
|
|
220
|
+
}
|
|
221
|
+
buildProposedChanges(parsedSpec, parsedPlan, parsedTasks, warnings) {
|
|
222
|
+
const changes = [];
|
|
223
|
+
// Strategy: Convert user scenarios to proposed changes
|
|
224
|
+
// Each scenario represents a feature to implement
|
|
225
|
+
for (const scenario of parsedSpec.userScenarios) {
|
|
226
|
+
// Create a change for each P1/P2 user scenario
|
|
227
|
+
if (scenario.priority === 'P1' || scenario.priority === 'P2') {
|
|
228
|
+
const change = {
|
|
229
|
+
type: 'file_create',
|
|
230
|
+
path: this.inferPathFromScenario(scenario, parsedPlan),
|
|
231
|
+
description: `Implement ${scenario.title}: ${scenario.iWantTo}`,
|
|
232
|
+
metadata: {
|
|
233
|
+
priority: scenario.priority,
|
|
234
|
+
userStory: {
|
|
235
|
+
asA: scenario.asA,
|
|
236
|
+
iWantTo: scenario.iWantTo,
|
|
237
|
+
soThat: scenario.soThat,
|
|
238
|
+
},
|
|
239
|
+
acceptanceScenarios: scenario.acceptanceScenarios,
|
|
240
|
+
edgeCases: scenario.edgeCases,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
changes.push(change);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// If we have plan details, add implementation-specific changes
|
|
247
|
+
if (parsedPlan) {
|
|
248
|
+
// Add database migrations if mentioned
|
|
249
|
+
if (parsedPlan.implementationDetails.has('Database Schema')) {
|
|
250
|
+
changes.push({
|
|
251
|
+
type: 'file_create',
|
|
252
|
+
path: 'database/migrations/',
|
|
253
|
+
description: 'Create database migrations for required tables',
|
|
254
|
+
metadata: {
|
|
255
|
+
section: 'Database Schema',
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// Add API endpoints if mentioned
|
|
260
|
+
if (parsedPlan.implementationDetails.has('API Endpoints')) {
|
|
261
|
+
changes.push({
|
|
262
|
+
type: 'file_create',
|
|
263
|
+
path: this.inferAPIPath(parsedPlan),
|
|
264
|
+
description: 'Implement API endpoints',
|
|
265
|
+
metadata: {
|
|
266
|
+
section: 'API Endpoints',
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Add dependency installation if we have technical context
|
|
272
|
+
if (parsedPlan?.technicalContext.dependencies &&
|
|
273
|
+
parsedPlan.technicalContext.dependencies.length > 0) {
|
|
274
|
+
changes.push({
|
|
275
|
+
type: 'dependency_add',
|
|
276
|
+
path: 'package.json',
|
|
277
|
+
description: `Install dependencies: ${parsedPlan.technicalContext.dependencies.slice(0, 3).join(', ')}${parsedPlan.technicalContext.dependencies.length > 3 ? '...' : ''}`,
|
|
278
|
+
metadata: {
|
|
279
|
+
dependencies: parsedPlan.technicalContext.dependencies,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (changes.length === 0) {
|
|
284
|
+
warnings.push({
|
|
285
|
+
code: 'NO_CHANGES',
|
|
286
|
+
message: 'No actionable changes could be derived from spec',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return changes;
|
|
290
|
+
}
|
|
291
|
+
inferPathFromScenario(scenario, plan) {
|
|
292
|
+
const title = scenario.title.toLowerCase().replace(/\s+/g, '-');
|
|
293
|
+
// Use plan structure if available
|
|
294
|
+
if (plan?.projectStructure.sourceCode) {
|
|
295
|
+
// Try to extract common patterns
|
|
296
|
+
if (plan.projectStructure.sourceCode.includes('src/modules/')) {
|
|
297
|
+
return `src/modules/${title}/`;
|
|
298
|
+
}
|
|
299
|
+
if (plan.projectStructure.sourceCode.includes('src/features/')) {
|
|
300
|
+
return `src/features/${title}/`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return `src/${title}/`;
|
|
304
|
+
}
|
|
305
|
+
inferAPIPath(plan) {
|
|
306
|
+
if (plan.projectStructure.sourceCode?.includes('controllers')) {
|
|
307
|
+
return 'src/controllers/';
|
|
308
|
+
}
|
|
309
|
+
if (plan.projectStructure.sourceCode?.includes('routes')) {
|
|
310
|
+
return 'src/routes/';
|
|
311
|
+
}
|
|
312
|
+
return 'src/api/';
|
|
313
|
+
}
|
|
314
|
+
generateSpecTemplate(intent) {
|
|
315
|
+
return `# Feature: ${intent}
|
|
316
|
+
|
|
317
|
+
**Branch**: \`feature/xxx-feature-name\`
|
|
318
|
+
**Date**: ${new Date().toISOString().split('T')[0]}
|
|
319
|
+
**Status**: Draft
|
|
320
|
+
|
|
321
|
+
## User Scenarios & Testing
|
|
322
|
+
|
|
323
|
+
### P1: [User Scenario Title]
|
|
324
|
+
**As a** [user type]
|
|
325
|
+
**I want to** [action]
|
|
326
|
+
**So that** [benefit]
|
|
327
|
+
|
|
328
|
+
**Acceptance Scenarios:**
|
|
329
|
+
- [Scenario 1]
|
|
330
|
+
- [Scenario 2]
|
|
331
|
+
|
|
332
|
+
**Edge Cases:**
|
|
333
|
+
- [Edge case 1]
|
|
334
|
+
- [NEEDS CLARIFICATION: Question?]
|
|
335
|
+
|
|
336
|
+
## Requirements
|
|
337
|
+
|
|
338
|
+
### Functional Requirements
|
|
339
|
+
|
|
340
|
+
**FR-001**: System MUST [requirement]
|
|
341
|
+
**FR-002**: [NEEDS CLARIFICATION: What should happen when...]
|
|
342
|
+
|
|
343
|
+
### Key Entities
|
|
344
|
+
|
|
345
|
+
**EntityName**
|
|
346
|
+
- Represents: [What this entity represents]
|
|
347
|
+
- Key Attributes: [attribute1, attribute2]
|
|
348
|
+
- Relationships: [relationships to other entities]
|
|
349
|
+
|
|
350
|
+
## Success Criteria
|
|
351
|
+
|
|
352
|
+
### Quantitative Metrics
|
|
353
|
+
- [Metric 1]
|
|
354
|
+
- [Metric 2]
|
|
355
|
+
|
|
356
|
+
### Qualitative Metrics
|
|
357
|
+
- [Metric 1]
|
|
358
|
+
- [Metric 2]
|
|
359
|
+
`;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type APSPlan } from '@eddacraft/anvil-core';
|
|
2
|
+
import type { AdapterConfig, ConversionResult, ExternalSpec, SpecContext } from '../common/types.js';
|
|
3
|
+
import { BaseAdapter } from '../common/types.js';
|
|
4
|
+
export declare class SpecKitImportAdapter extends BaseAdapter {
|
|
5
|
+
readonly name = "speckit-import";
|
|
6
|
+
readonly version = "1.0.0";
|
|
7
|
+
readonly supportedFormats: readonly ["speckit", "spec.md"];
|
|
8
|
+
private parser;
|
|
9
|
+
constructor(config?: AdapterConfig);
|
|
10
|
+
generateSpec(intent: string, context: SpecContext): Promise<APSPlan>;
|
|
11
|
+
validateSpec(spec: APSPlan): Promise<import('@eddacraft/anvil-core').ValidationResult>;
|
|
12
|
+
convertToAPS(spec: ExternalSpec): Promise<ConversionResult<APSPlan>>;
|
|
13
|
+
convertFromAPS(_spec: APSPlan): Promise<ConversionResult<ExternalSpec>>;
|
|
14
|
+
private convertChangesToAPS;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/speckit/import.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,OAAO,EAAgC,MAAM,uBAAuB,CAAC;AAC/F,OAAO,KAAK,EACV,aAAa,EAEb,gBAAgB,EAEhB,YAAY,EACZ,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAUjD,qBAAa,oBAAqB,SAAQ,WAAW;IACnD,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,OAAO,WAAW;IAC3B,QAAQ,CAAC,gBAAgB,kCAAmC;IAE5D,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,GAAE,aAAkB;IAKhC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAmCpE,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,uBAAuB,EAAE,gBAAgB,CAAC;IAwDtF,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAoGpE,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;IAY7E,OAAO,CAAC,mBAAmB;CAsE5B"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { createPlan } from '@eddacraft/anvil-core';
|
|
2
|
+
import { BaseAdapter } from '../common/types.js';
|
|
3
|
+
import { SpecKitParser } from './parser.js';
|
|
4
|
+
export class SpecKitImportAdapter extends BaseAdapter {
|
|
5
|
+
name = 'speckit-import';
|
|
6
|
+
version = '1.0.0';
|
|
7
|
+
supportedFormats = ['speckit', 'spec.md'];
|
|
8
|
+
parser;
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super(config);
|
|
11
|
+
this.parser = new SpecKitParser();
|
|
12
|
+
}
|
|
13
|
+
async generateSpec(intent, context) {
|
|
14
|
+
const provenance = {
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
source: 'cli',
|
|
17
|
+
version: this.version,
|
|
18
|
+
author: context.author,
|
|
19
|
+
repository: context.repositoryPath,
|
|
20
|
+
branch: context.branch,
|
|
21
|
+
commit: context.commit,
|
|
22
|
+
};
|
|
23
|
+
const changes = [
|
|
24
|
+
{
|
|
25
|
+
type: 'file_create',
|
|
26
|
+
path: 'spec.md',
|
|
27
|
+
description: 'Create initial specification file',
|
|
28
|
+
content: `# Specification\n\n## Intent\n\n${intent}\n\n## Overview\n\n[Describe the overall approach]\n\n## Requirements\n\n- [List prerequisites]\n\n## Changes\n\n- [List proposed changes]\n`,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
const planId = `aps-${Date.now().toString(16).substring(0, 8)}`;
|
|
32
|
+
const plan = {
|
|
33
|
+
...createPlan({
|
|
34
|
+
id: planId,
|
|
35
|
+
intent,
|
|
36
|
+
provenance,
|
|
37
|
+
changes,
|
|
38
|
+
}),
|
|
39
|
+
schema_version: '0.1.0',
|
|
40
|
+
hash: '0'.repeat(64), // Placeholder hash
|
|
41
|
+
};
|
|
42
|
+
return plan;
|
|
43
|
+
}
|
|
44
|
+
async validateSpec(spec) {
|
|
45
|
+
const errors = [];
|
|
46
|
+
const warnings = [];
|
|
47
|
+
if (spec.proposed_changes.length === 0) {
|
|
48
|
+
warnings.push({
|
|
49
|
+
field: 'proposed_changes',
|
|
50
|
+
message: 'No changes specified in the plan',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
for (let i = 0; i < spec.proposed_changes.length; i++) {
|
|
54
|
+
const change = spec.proposed_changes[i];
|
|
55
|
+
if (!change.description || change.description.length < 10) {
|
|
56
|
+
warnings.push({
|
|
57
|
+
field: `proposed_changes[${i}].description`,
|
|
58
|
+
message: 'Change description is too short or missing',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (!change.path && change.type !== 'script_execute') {
|
|
62
|
+
errors.push({
|
|
63
|
+
field: `proposed_changes[${i}].path`,
|
|
64
|
+
message: `Path is required for change type '${change.type}'`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const issues = [
|
|
69
|
+
...errors.map((e) => ({
|
|
70
|
+
path: e.field,
|
|
71
|
+
message: e.message,
|
|
72
|
+
code: 'VALIDATION_ERROR',
|
|
73
|
+
severity: 'error',
|
|
74
|
+
})),
|
|
75
|
+
...warnings.map((w) => ({
|
|
76
|
+
path: w.field,
|
|
77
|
+
message: w.message,
|
|
78
|
+
code: 'VALIDATION_WARNING',
|
|
79
|
+
severity: 'warning',
|
|
80
|
+
})),
|
|
81
|
+
];
|
|
82
|
+
return {
|
|
83
|
+
valid: errors.length === 0,
|
|
84
|
+
data: spec,
|
|
85
|
+
issues: issues.length > 0 ? issues : undefined,
|
|
86
|
+
summary: errors.length === 0 ? 'Validation passed' : `Found ${errors.length} error(s)`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async convertToAPS(spec) {
|
|
90
|
+
const errors = [];
|
|
91
|
+
const warnings = [];
|
|
92
|
+
if (!this.canImport(spec.format)) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
errors: [
|
|
96
|
+
{
|
|
97
|
+
code: 'UNSUPPORTED_FORMAT',
|
|
98
|
+
message: `Format '${spec.format}' is not supported by this adapter`,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const specKitSpec = spec.content;
|
|
105
|
+
if (!specKitSpec.specContent) {
|
|
106
|
+
errors.push({
|
|
107
|
+
code: 'MISSING_SPEC_CONTENT',
|
|
108
|
+
message: 'spec.md content is required',
|
|
109
|
+
});
|
|
110
|
+
return { success: false, errors };
|
|
111
|
+
}
|
|
112
|
+
const parsed = this.parser.parseSpecMarkdown(specKitSpec.specContent);
|
|
113
|
+
if (!parsed.intent && !parsed.overview) {
|
|
114
|
+
errors.push({
|
|
115
|
+
code: 'MISSING_INTENT',
|
|
116
|
+
message: 'No intent or overview section found in spec.md',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else if (!parsed.intent) {
|
|
120
|
+
warnings.push({
|
|
121
|
+
code: 'MISSING_INTENT',
|
|
122
|
+
message: 'No intent section found, using overview as fallback',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const changes = this.convertChangesToAPS(parsed.changes || [], errors, warnings);
|
|
126
|
+
if (errors.length > 0) {
|
|
127
|
+
return { success: false, errors, warnings };
|
|
128
|
+
}
|
|
129
|
+
const intent = parsed.intent || parsed.overview || 'Specification from SpecKit';
|
|
130
|
+
const provenance = {
|
|
131
|
+
timestamp: spec.metadata?.['timestamp'] || new Date().toISOString(),
|
|
132
|
+
source: 'cli',
|
|
133
|
+
version: this.version,
|
|
134
|
+
author: spec.metadata?.['author'],
|
|
135
|
+
repository: spec.metadata?.['repository'],
|
|
136
|
+
branch: spec.metadata?.['branch'],
|
|
137
|
+
commit: spec.metadata?.['commit'],
|
|
138
|
+
};
|
|
139
|
+
const planId = `aps-${Date.now().toString(16).substring(0, 8)}`;
|
|
140
|
+
try {
|
|
141
|
+
const plan = {
|
|
142
|
+
...createPlan({
|
|
143
|
+
id: planId,
|
|
144
|
+
intent: intent.substring(0, 500),
|
|
145
|
+
provenance,
|
|
146
|
+
changes,
|
|
147
|
+
}),
|
|
148
|
+
schema_version: '0.1.0',
|
|
149
|
+
hash: '0'.repeat(64), // Placeholder hash
|
|
150
|
+
metadata: {
|
|
151
|
+
...parsed.metadata,
|
|
152
|
+
source_format: 'speckit',
|
|
153
|
+
goals: parsed.goals,
|
|
154
|
+
requirements: parsed.requirements,
|
|
155
|
+
overview: parsed.overview,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
data: plan,
|
|
161
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
errors.push({
|
|
166
|
+
code: 'APS_CREATION_FAILED',
|
|
167
|
+
message: error instanceof Error ? error.message : 'Failed to create APS plan',
|
|
168
|
+
});
|
|
169
|
+
return { success: false, errors };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
errors.push({
|
|
174
|
+
code: 'CONVERSION_ERROR',
|
|
175
|
+
message: error instanceof Error ? error.message : 'Unknown conversion error',
|
|
176
|
+
});
|
|
177
|
+
return { success: false, errors };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async convertFromAPS(_spec) {
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
errors: [
|
|
184
|
+
{
|
|
185
|
+
code: 'NOT_IMPLEMENTED',
|
|
186
|
+
message: 'Export to SpecKit format is handled by speckit-export adapter',
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
convertChangesToAPS(changes, errors, warnings) {
|
|
192
|
+
const apsChanges = [];
|
|
193
|
+
for (let i = 0; i < changes.length; i++) {
|
|
194
|
+
const change = changes[i];
|
|
195
|
+
if (!change.description) {
|
|
196
|
+
warnings.push({
|
|
197
|
+
code: 'EMPTY_DESCRIPTION',
|
|
198
|
+
message: `Change ${i + 1} has no description`,
|
|
199
|
+
path: `changes[${i}]`,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const validTypes = [
|
|
204
|
+
'file_create',
|
|
205
|
+
'file_update',
|
|
206
|
+
'file_delete',
|
|
207
|
+
'config_update',
|
|
208
|
+
'dependency_add',
|
|
209
|
+
'dependency_remove',
|
|
210
|
+
'dependency_update',
|
|
211
|
+
'script_execute',
|
|
212
|
+
];
|
|
213
|
+
if (!validTypes.includes(change.type)) {
|
|
214
|
+
warnings.push({
|
|
215
|
+
code: 'UNKNOWN_CHANGE_TYPE',
|
|
216
|
+
message: `Unknown change type '${change.type}', defaulting to 'script_execute'`,
|
|
217
|
+
path: `changes[${i}].type`,
|
|
218
|
+
});
|
|
219
|
+
change.type = 'script_execute';
|
|
220
|
+
}
|
|
221
|
+
const apsChange = {
|
|
222
|
+
type: change.type,
|
|
223
|
+
path: change.path || '',
|
|
224
|
+
description: change.description,
|
|
225
|
+
};
|
|
226
|
+
if (change.content) {
|
|
227
|
+
apsChange.content = change.content;
|
|
228
|
+
}
|
|
229
|
+
if (!apsChange.path && apsChange.type !== 'script_execute') {
|
|
230
|
+
warnings.push({
|
|
231
|
+
code: 'MISSING_PATH',
|
|
232
|
+
message: `Path not specified for ${apsChange.type}, using placeholder`,
|
|
233
|
+
path: `changes[${i}].path`,
|
|
234
|
+
});
|
|
235
|
+
apsChange.path = '<path-to-be-specified>';
|
|
236
|
+
}
|
|
237
|
+
apsChanges.push(apsChange);
|
|
238
|
+
}
|
|
239
|
+
if (apsChanges.length === 0) {
|
|
240
|
+
warnings.push({
|
|
241
|
+
code: 'NO_CHANGES',
|
|
242
|
+
message: 'No valid changes found in specification',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return apsChanges;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SpecKitImportAdapter } from './import.js';
|
|
2
|
+
export { SpecKitExportAdapter } from './export.js';
|
|
3
|
+
export { SpecKitParser } from './parser.js';
|
|
4
|
+
export { SpecKitFormatAdapter, createSpecKitAdapter } from './format-adapter.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/speckit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface ParsedSpecKit {
|
|
2
|
+
intent?: string;
|
|
3
|
+
overview?: string;
|
|
4
|
+
goals?: string[];
|
|
5
|
+
requirements?: string[];
|
|
6
|
+
changes?: Array<{
|
|
7
|
+
type: string;
|
|
8
|
+
description: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
content?: string;
|
|
11
|
+
}>;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export declare class SpecKitParser {
|
|
15
|
+
private static readonly SPEC_SECTIONS;
|
|
16
|
+
/** Maximum input size for SpecKit parsing (2MB) */
|
|
17
|
+
private static readonly MAX_INPUT_SIZE;
|
|
18
|
+
parseSpecMarkdown(content: string): ParsedSpecKit;
|
|
19
|
+
private parseMarkdownSections;
|
|
20
|
+
private extractSectionData;
|
|
21
|
+
private extractParagraphText;
|
|
22
|
+
private parseListItems;
|
|
23
|
+
private parseChanges;
|
|
24
|
+
private parseChangeSection;
|
|
25
|
+
private parseChangeFromListItem;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/speckit/parser.ts"],"names":[],"mappings":"AASA,UAAU,aAAa;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAeD,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAM1B;IAEX,mDAAmD;IACnD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAmB;IAEzD,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa;IAgBjD,OAAO,CAAC,qBAAqB;IAgD7B,OAAO,CAAC,kBAAkB;IA6C1B,OAAO,CAAC,oBAAoB;IA0B5B,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,YAAY;IAsDpB,OAAO,CAAC,kBAAkB;IA+C1B,OAAO,CAAC,uBAAuB;CAwChC"}
|