@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,253 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { SpecKitImportAdapterV2 } from '../speckit/import-v2.js';
|
|
6
|
+
import type { ExternalSpec } from '../common/types.js';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const fixturesDir = join(__dirname, 'fixtures/speckit-official/auth-feature');
|
|
11
|
+
|
|
12
|
+
describe('SpecKitImportAdapterV2 - Official Format', () => {
|
|
13
|
+
let adapter: SpecKitImportAdapterV2;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
adapter = new SpecKitImportAdapterV2();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should have correct name and version', () => {
|
|
20
|
+
expect(adapter.name).toBe('speckit-import-v2');
|
|
21
|
+
expect(adapter.version).toBe('2.0.0');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should support spec-kit formats', () => {
|
|
25
|
+
expect(adapter.canImport('speckit')).toBe(true);
|
|
26
|
+
expect(adapter.canImport('spec-kit')).toBe(true);
|
|
27
|
+
expect(adapter.canImport('spec.md')).toBe(true);
|
|
28
|
+
expect(adapter.canImport('plan.md')).toBe(true);
|
|
29
|
+
expect(adapter.canImport('tasks.md')).toBe(true);
|
|
30
|
+
expect(adapter.canImport('unknown')).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should convert spec.md only to APS', async () => {
|
|
34
|
+
const specContent = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
35
|
+
|
|
36
|
+
const externalSpec: ExternalSpec = {
|
|
37
|
+
format: 'spec.md',
|
|
38
|
+
version: '2.0.0',
|
|
39
|
+
content: {
|
|
40
|
+
spec: { content: specContent },
|
|
41
|
+
metadata: {
|
|
42
|
+
timestamp: '2025-01-15T10:00:00Z',
|
|
43
|
+
author: 'test@example.com',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
49
|
+
|
|
50
|
+
expect(result.success).toBe(true);
|
|
51
|
+
expect(result.data).toBeDefined();
|
|
52
|
+
|
|
53
|
+
if (result.success && result.data) {
|
|
54
|
+
// Check intent was built from user scenarios
|
|
55
|
+
expect(result.data.intent).toContain('User Authentication System');
|
|
56
|
+
expect(result.data.intent).toContain('new user wants to');
|
|
57
|
+
|
|
58
|
+
// Check metadata preservation
|
|
59
|
+
expect(result.data.metadata?.feature).toBe('User Authentication System');
|
|
60
|
+
expect(result.data.metadata?.userScenarios).toBeDefined();
|
|
61
|
+
expect(result.data.metadata?.requirements).toBeDefined();
|
|
62
|
+
expect(result.data.metadata?.successCriteria).toBeDefined();
|
|
63
|
+
expect(result.data.metadata?.clarifications).toBeDefined();
|
|
64
|
+
|
|
65
|
+
// Check proposed changes generated from scenarios
|
|
66
|
+
expect(result.data.proposed_changes.length).toBeGreaterThan(0);
|
|
67
|
+
|
|
68
|
+
// P1 and P2 scenarios should become changes
|
|
69
|
+
const p1Changes = result.data.proposed_changes.filter((c) => c.metadata?.priority === 'P1');
|
|
70
|
+
expect(p1Changes.length).toBeGreaterThan(0);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should convert spec.md + plan.md to APS', async () => {
|
|
75
|
+
const specContent = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
76
|
+
const planContent = await readFile(join(fixturesDir, 'plan.md'), 'utf-8');
|
|
77
|
+
|
|
78
|
+
const externalSpec: ExternalSpec = {
|
|
79
|
+
format: 'speckit',
|
|
80
|
+
version: '2.0.0',
|
|
81
|
+
content: {
|
|
82
|
+
spec: { content: specContent },
|
|
83
|
+
plan: { content: planContent },
|
|
84
|
+
metadata: {
|
|
85
|
+
timestamp: '2025-01-15T10:00:00Z',
|
|
86
|
+
author: 'test@example.com',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
92
|
+
|
|
93
|
+
expect(result.success).toBe(true);
|
|
94
|
+
expect(result.data).toBeDefined();
|
|
95
|
+
|
|
96
|
+
if (result.success && result.data) {
|
|
97
|
+
// Check technical context from plan.md
|
|
98
|
+
expect(result.data.metadata?.technicalContext).toBeDefined();
|
|
99
|
+
expect(result.data.metadata?.technicalContext?.language).toContain('TypeScript');
|
|
100
|
+
expect(result.data.metadata?.technicalContext?.dependencies).toBeDefined();
|
|
101
|
+
expect(result.data.metadata?.technicalContext?.dependencies?.length).toBeGreaterThan(0);
|
|
102
|
+
|
|
103
|
+
// Check constitution check
|
|
104
|
+
expect(result.data.metadata?.constitutionCheck).toBeDefined();
|
|
105
|
+
|
|
106
|
+
// Check project structure
|
|
107
|
+
expect(result.data.metadata?.projectStructure).toBeDefined();
|
|
108
|
+
|
|
109
|
+
// Check implementation details
|
|
110
|
+
expect(result.data.metadata?.implementationDetails).toBeDefined();
|
|
111
|
+
|
|
112
|
+
// Should have more changes with plan details
|
|
113
|
+
expect(result.data.proposed_changes.length).toBeGreaterThan(2);
|
|
114
|
+
|
|
115
|
+
// Should have dependency installation change
|
|
116
|
+
const depChange = result.data.proposed_changes.find((c) => c.type === 'dependency_add');
|
|
117
|
+
expect(depChange).toBeDefined();
|
|
118
|
+
expect(depChange?.description).toContain('dependencies');
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should convert spec.md + plan.md + tasks.md to APS', async () => {
|
|
123
|
+
const specContent = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
124
|
+
const planContent = await readFile(join(fixturesDir, 'plan.md'), 'utf-8');
|
|
125
|
+
const tasksContent = await readFile(join(fixturesDir, 'tasks.md'), 'utf-8');
|
|
126
|
+
|
|
127
|
+
const externalSpec: ExternalSpec = {
|
|
128
|
+
format: 'speckit',
|
|
129
|
+
version: '2.0.0',
|
|
130
|
+
content: {
|
|
131
|
+
spec: { content: specContent },
|
|
132
|
+
plan: { content: planContent },
|
|
133
|
+
tasks: { content: tasksContent },
|
|
134
|
+
metadata: {
|
|
135
|
+
timestamp: '2025-01-15T10:00:00Z',
|
|
136
|
+
author: 'test@example.com',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
142
|
+
|
|
143
|
+
expect(result.success).toBe(true);
|
|
144
|
+
expect(result.data).toBeDefined();
|
|
145
|
+
|
|
146
|
+
if (result.success && result.data) {
|
|
147
|
+
// Check phases from tasks.md
|
|
148
|
+
expect(result.data.metadata?.phases).toBeDefined();
|
|
149
|
+
expect(result.data.metadata?.phases?.length).toBeGreaterThan(0);
|
|
150
|
+
|
|
151
|
+
// Check task dependencies
|
|
152
|
+
expect(result.data.metadata?.taskDependencies).toBeDefined();
|
|
153
|
+
|
|
154
|
+
// Check implementation strategies
|
|
155
|
+
expect(result.data.metadata?.implementationStrategies).toBeDefined();
|
|
156
|
+
|
|
157
|
+
// Verify complete metadata
|
|
158
|
+
expect(result.data.metadata?.source_format).toBe('speckit-v2');
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should handle missing spec.md', async () => {
|
|
163
|
+
const externalSpec: ExternalSpec = {
|
|
164
|
+
format: 'speckit',
|
|
165
|
+
version: '2.0.0',
|
|
166
|
+
content: {
|
|
167
|
+
plan: { content: '# Plan\nSome plan' },
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
172
|
+
|
|
173
|
+
expect(result.success).toBe(false);
|
|
174
|
+
expect(result.errors).toBeDefined();
|
|
175
|
+
expect(result.errors?.[0].code).toBe('MISSING_SPEC');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should preserve clarifications in metadata', async () => {
|
|
179
|
+
const specContent = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
180
|
+
|
|
181
|
+
const externalSpec: ExternalSpec = {
|
|
182
|
+
format: 'spec.md',
|
|
183
|
+
version: '2.0.0',
|
|
184
|
+
content: {
|
|
185
|
+
spec: { content: specContent },
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
190
|
+
|
|
191
|
+
expect(result.success).toBe(true);
|
|
192
|
+
if (result.success && result.data) {
|
|
193
|
+
expect(result.data.metadata?.clarifications).toBeDefined();
|
|
194
|
+
expect(Array.isArray(result.data.metadata?.clarifications)).toBe(true);
|
|
195
|
+
expect((result.data.metadata?.clarifications as string[]).length).toBeGreaterThan(0);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should map user scenarios to proposed changes', async () => {
|
|
200
|
+
const specContent = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
201
|
+
|
|
202
|
+
const externalSpec: ExternalSpec = {
|
|
203
|
+
format: 'spec.md',
|
|
204
|
+
version: '2.0.0',
|
|
205
|
+
content: {
|
|
206
|
+
spec: { content: specContent },
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
211
|
+
|
|
212
|
+
expect(result.success).toBe(true);
|
|
213
|
+
if (result.success && result.data) {
|
|
214
|
+
const changes = result.data.proposed_changes;
|
|
215
|
+
|
|
216
|
+
// Should have changes for user scenarios
|
|
217
|
+
const scenarioChanges = changes.filter((c) => c.metadata?.userStory);
|
|
218
|
+
expect(scenarioChanges.length).toBeGreaterThan(0);
|
|
219
|
+
|
|
220
|
+
// Check first scenario change
|
|
221
|
+
const firstChange = scenarioChanges[0];
|
|
222
|
+
expect(firstChange.metadata?.userStory?.asA).toBeDefined();
|
|
223
|
+
expect(firstChange.metadata?.userStory?.iWantTo).toBeDefined();
|
|
224
|
+
expect(firstChange.metadata?.userStory?.soThat).toBeDefined();
|
|
225
|
+
expect(firstChange.metadata?.acceptanceScenarios).toBeDefined();
|
|
226
|
+
expect(firstChange.metadata?.edgeCases).toBeDefined();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should preserve success criteria structure', async () => {
|
|
231
|
+
const specContent = await readFile(join(fixturesDir, 'spec.md'), 'utf-8');
|
|
232
|
+
|
|
233
|
+
const externalSpec: ExternalSpec = {
|
|
234
|
+
format: 'spec.md',
|
|
235
|
+
version: '2.0.0',
|
|
236
|
+
content: {
|
|
237
|
+
spec: { content: specContent },
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
242
|
+
|
|
243
|
+
expect(result.success).toBe(true);
|
|
244
|
+
if (result.success && result.data) {
|
|
245
|
+
const successCriteria = result.data.metadata?.successCriteria as Record<string, unknown>;
|
|
246
|
+
expect(successCriteria).toBeDefined();
|
|
247
|
+
expect(successCriteria.quantitative).toBeDefined();
|
|
248
|
+
expect(successCriteria.qualitative).toBeDefined();
|
|
249
|
+
expect(successCriteria.security).toBeDefined();
|
|
250
|
+
expect(Array.isArray(successCriteria.quantitative)).toBe(true);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { SpecKitImportAdapter } from '../speckit/import.js';
|
|
6
|
+
import type { ExternalSpec, SpecContext } from '../common/types.js';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const fixturesDir = join(__dirname, 'fixtures');
|
|
11
|
+
|
|
12
|
+
describe('SpecKitImportAdapter', () => {
|
|
13
|
+
let adapter: SpecKitImportAdapter;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
adapter = new SpecKitImportAdapter();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('basic properties', () => {
|
|
20
|
+
it('should have correct name and version', () => {
|
|
21
|
+
expect(adapter.name).toBe('speckit-import');
|
|
22
|
+
expect(adapter.version).toBe('1.0.0');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should support speckit and spec.md formats', () => {
|
|
26
|
+
expect(adapter.canImport('speckit')).toBe(true);
|
|
27
|
+
expect(adapter.canImport('spec.md')).toBe(true);
|
|
28
|
+
expect(adapter.canImport('unknown')).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('generateSpec', () => {
|
|
33
|
+
it('should generate a basic spec from intent', async () => {
|
|
34
|
+
const context: SpecContext = {
|
|
35
|
+
repositoryPath: '/test/repo',
|
|
36
|
+
branch: 'main',
|
|
37
|
+
author: 'test@example.com',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const result = await adapter.generateSpec('Test intent', context);
|
|
41
|
+
|
|
42
|
+
expect(result).toBeDefined();
|
|
43
|
+
expect(result.intent).toBe('Test intent');
|
|
44
|
+
expect(result.schema_version).toBe('0.1.0');
|
|
45
|
+
expect(result.proposed_changes).toHaveLength(1);
|
|
46
|
+
expect(result.proposed_changes[0].type).toBe('file_create');
|
|
47
|
+
expect(result.proposed_changes[0].path).toBe('spec.md');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('convertToAPS', () => {
|
|
52
|
+
it('should convert a valid spec.md to APS format', async () => {
|
|
53
|
+
const specContent = await readFile(join(fixturesDir, 'speckit/sample-spec.md'), 'utf-8');
|
|
54
|
+
|
|
55
|
+
const externalSpec: ExternalSpec = {
|
|
56
|
+
format: 'speckit',
|
|
57
|
+
version: '1.0.0',
|
|
58
|
+
content: {
|
|
59
|
+
specContent,
|
|
60
|
+
metadata: {
|
|
61
|
+
timestamp: '2024-01-15T10:00:00Z',
|
|
62
|
+
author: 'test@example.com',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
68
|
+
|
|
69
|
+
expect(result.success).toBe(true);
|
|
70
|
+
expect(result.data).toBeDefined();
|
|
71
|
+
|
|
72
|
+
if (result.success && result.data) {
|
|
73
|
+
console.log('Changes found:', result.data.proposed_changes.length);
|
|
74
|
+
console.log(
|
|
75
|
+
'Changes:',
|
|
76
|
+
result.data.proposed_changes.map((c) => ({
|
|
77
|
+
type: c.type,
|
|
78
|
+
path: c.path,
|
|
79
|
+
desc: c.description.substring(0, 50),
|
|
80
|
+
}))
|
|
81
|
+
);
|
|
82
|
+
expect(result.data.intent).toContain('JWT tokens');
|
|
83
|
+
expect(result.data.schema_version).toBe('0.1.0');
|
|
84
|
+
expect(result.data.proposed_changes.length).toBeGreaterThan(0);
|
|
85
|
+
|
|
86
|
+
// Check that goals and requirements are preserved in metadata
|
|
87
|
+
expect(result.data.metadata?.goals).toBeDefined();
|
|
88
|
+
expect(result.data.metadata?.requirements).toBeDefined();
|
|
89
|
+
expect(result.data.metadata?.source_format).toBe('speckit');
|
|
90
|
+
|
|
91
|
+
// Verify specific changes were parsed correctly
|
|
92
|
+
const fileCreateChanges = result.data.proposed_changes.filter(
|
|
93
|
+
(c) => c.type === 'file_create'
|
|
94
|
+
);
|
|
95
|
+
expect(fileCreateChanges.length).toBeGreaterThan(0);
|
|
96
|
+
|
|
97
|
+
const authControllerChange = fileCreateChanges.find(
|
|
98
|
+
(c) => c.path === 'src/controllers/auth.controller.ts'
|
|
99
|
+
);
|
|
100
|
+
expect(authControllerChange).toBeDefined();
|
|
101
|
+
expect(authControllerChange?.content).toContain('AuthController');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should handle missing spec content', async () => {
|
|
106
|
+
const externalSpec: ExternalSpec = {
|
|
107
|
+
format: 'speckit',
|
|
108
|
+
version: '1.0.0',
|
|
109
|
+
content: {
|
|
110
|
+
// Missing specContent
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
115
|
+
|
|
116
|
+
expect(result.success).toBe(false);
|
|
117
|
+
expect(result.errors).toBeDefined();
|
|
118
|
+
expect(result.errors?.[0].code).toBe('MISSING_SPEC_CONTENT');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should handle spec without intent section', async () => {
|
|
122
|
+
const externalSpec: ExternalSpec = {
|
|
123
|
+
format: 'speckit',
|
|
124
|
+
version: '1.0.0',
|
|
125
|
+
content: {
|
|
126
|
+
specContent: '# Specification\n\n## Overview\n\nSome overview without intent.',
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
131
|
+
|
|
132
|
+
expect(result.success).toBe(true);
|
|
133
|
+
expect(result.warnings).toBeDefined();
|
|
134
|
+
// Should use overview as fallback for intent
|
|
135
|
+
expect(result.data?.intent).toContain('Some overview');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should reject unsupported formats', async () => {
|
|
139
|
+
const externalSpec: ExternalSpec = {
|
|
140
|
+
format: 'unknown-format',
|
|
141
|
+
version: '1.0.0',
|
|
142
|
+
content: {},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const result = await adapter.convertToAPS(externalSpec);
|
|
146
|
+
|
|
147
|
+
expect(result.success).toBe(false);
|
|
148
|
+
expect(result.errors?.[0].code).toBe('UNSUPPORTED_FORMAT');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('validateSpec', () => {
|
|
153
|
+
it('should validate a valid APS spec', async () => {
|
|
154
|
+
const spec = {
|
|
155
|
+
id: 'aps-12345678',
|
|
156
|
+
hash: '0'.repeat(64),
|
|
157
|
+
intent: 'This is a valid intent for testing',
|
|
158
|
+
schema_version: '0.1.0' as const,
|
|
159
|
+
proposed_changes: [
|
|
160
|
+
{
|
|
161
|
+
type: 'file_create' as const,
|
|
162
|
+
path: 'test.ts',
|
|
163
|
+
description: 'Create a test file',
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
provenance: {
|
|
167
|
+
timestamp: new Date().toISOString(),
|
|
168
|
+
source: 'cli' as const,
|
|
169
|
+
version: '1.0.0',
|
|
170
|
+
},
|
|
171
|
+
validations: {
|
|
172
|
+
required_checks: [],
|
|
173
|
+
skip_checks: [],
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result = await adapter.validateSpec(spec);
|
|
178
|
+
|
|
179
|
+
expect(result.valid).toBe(true);
|
|
180
|
+
expect(result.issues).toBeUndefined();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should warn about empty changes', async () => {
|
|
184
|
+
const spec = {
|
|
185
|
+
id: 'aps-12345678',
|
|
186
|
+
hash: '0'.repeat(64),
|
|
187
|
+
intent: 'This is a valid intent for testing',
|
|
188
|
+
schema_version: '0.1.0' as const,
|
|
189
|
+
proposed_changes: [],
|
|
190
|
+
provenance: {
|
|
191
|
+
timestamp: new Date().toISOString(),
|
|
192
|
+
source: 'cli' as const,
|
|
193
|
+
version: '1.0.0',
|
|
194
|
+
},
|
|
195
|
+
validations: {
|
|
196
|
+
required_checks: [],
|
|
197
|
+
skip_checks: [],
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const result = await adapter.validateSpec(spec);
|
|
202
|
+
|
|
203
|
+
expect(result.valid).toBe(true);
|
|
204
|
+
expect(result.issues).toBeDefined();
|
|
205
|
+
expect(result.issues?.[0].severity).toBe('warning');
|
|
206
|
+
expect(result.issues?.[0].message).toContain('No changes');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { SpecKitParser } from '../speckit/parser.js';
|
|
3
|
+
|
|
4
|
+
describe('SpecKitParser', () => {
|
|
5
|
+
let parser: SpecKitParser;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
parser = new SpecKitParser();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('parseSpecMarkdown', () => {
|
|
12
|
+
it('should parse intent section', () => {
|
|
13
|
+
const markdown = `# Specification
|
|
14
|
+
|
|
15
|
+
## Intent
|
|
16
|
+
|
|
17
|
+
Build a REST API for user management with CRUD operations.
|
|
18
|
+
|
|
19
|
+
## Other Section
|
|
20
|
+
|
|
21
|
+
Some content`;
|
|
22
|
+
|
|
23
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
24
|
+
|
|
25
|
+
expect(result.intent).toBe('Build a REST API for user management with CRUD operations.');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should parse overview section', () => {
|
|
29
|
+
const markdown = `# Spec
|
|
30
|
+
|
|
31
|
+
## Overview
|
|
32
|
+
|
|
33
|
+
This system provides comprehensive user management capabilities including create, read, update, and delete operations.
|
|
34
|
+
|
|
35
|
+
## Details`;
|
|
36
|
+
|
|
37
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
38
|
+
|
|
39
|
+
expect(result.overview).toContain('comprehensive user management');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should parse goals as list items', () => {
|
|
43
|
+
const markdown = `# Specification
|
|
44
|
+
|
|
45
|
+
## Goals
|
|
46
|
+
|
|
47
|
+
- Implement secure authentication
|
|
48
|
+
- Support role-based access control
|
|
49
|
+
- Provide RESTful API endpoints
|
|
50
|
+
- Enable user profile management`;
|
|
51
|
+
|
|
52
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
53
|
+
|
|
54
|
+
expect(result.goals).toBeDefined();
|
|
55
|
+
expect(result.goals).toHaveLength(4);
|
|
56
|
+
expect(result.goals?.[0]).toBe('Implement secure authentication');
|
|
57
|
+
expect(result.goals?.[3]).toBe('Enable user profile management');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should parse numbered list items', () => {
|
|
61
|
+
const markdown = `# Spec
|
|
62
|
+
|
|
63
|
+
## Requirements
|
|
64
|
+
|
|
65
|
+
1. Node.js version 18 or higher
|
|
66
|
+
2. PostgreSQL database
|
|
67
|
+
3. Redis for caching
|
|
68
|
+
4. Docker for containerization`;
|
|
69
|
+
|
|
70
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
71
|
+
|
|
72
|
+
expect(result.requirements).toBeDefined();
|
|
73
|
+
expect(result.requirements).toHaveLength(4);
|
|
74
|
+
expect(result.requirements?.[0]).toBe('Node.js version 18 or higher');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should handle multi-line list items', () => {
|
|
78
|
+
const markdown = `# Spec
|
|
79
|
+
|
|
80
|
+
## Goals
|
|
81
|
+
|
|
82
|
+
- Implement authentication system
|
|
83
|
+
with JWT tokens and refresh mechanism
|
|
84
|
+
- Support multiple authentication providers
|
|
85
|
+
including OAuth2 and SAML`;
|
|
86
|
+
|
|
87
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
88
|
+
|
|
89
|
+
expect(result.goals).toHaveLength(2);
|
|
90
|
+
expect(result.goals?.[0]).toBe(
|
|
91
|
+
'Implement authentication system with JWT tokens and refresh mechanism'
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should parse changes section with subsections', () => {
|
|
96
|
+
const markdown = `# Specification
|
|
97
|
+
|
|
98
|
+
## Changes
|
|
99
|
+
|
|
100
|
+
### Create authentication controller
|
|
101
|
+
|
|
102
|
+
Create a new controller at \`src/auth.controller.ts\`
|
|
103
|
+
|
|
104
|
+
\`\`\`typescript
|
|
105
|
+
export class AuthController {
|
|
106
|
+
// Implementation
|
|
107
|
+
}
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
### Update main application
|
|
111
|
+
|
|
112
|
+
Modify \`src/app.ts\` to include auth routes`;
|
|
113
|
+
|
|
114
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
115
|
+
|
|
116
|
+
expect(result.changes).toBeDefined();
|
|
117
|
+
expect(result.changes).toHaveLength(2);
|
|
118
|
+
|
|
119
|
+
const createChange = result.changes?.[0];
|
|
120
|
+
expect(createChange?.type).toBe('file_create');
|
|
121
|
+
expect(createChange?.description).toContain('Create authentication controller');
|
|
122
|
+
expect(createChange?.path).toBe('src/auth.controller.ts');
|
|
123
|
+
expect(createChange?.content).toContain('export class AuthController');
|
|
124
|
+
|
|
125
|
+
const updateChange = result.changes?.[1];
|
|
126
|
+
expect(updateChange?.type).toBe('file_update');
|
|
127
|
+
expect(updateChange?.path).toBe('src/app.ts');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should parse changes from list items', () => {
|
|
131
|
+
const markdown = `# Specification
|
|
132
|
+
|
|
133
|
+
## Changes
|
|
134
|
+
|
|
135
|
+
- Create new file \`config/database.ts\` for database configuration
|
|
136
|
+
- Update \`package.json\` to add PostgreSQL dependency
|
|
137
|
+
- Delete obsolete \`src/old-db.ts\` file
|
|
138
|
+
- Install new dependency: express-validator
|
|
139
|
+
- Run migration script to update database schema`;
|
|
140
|
+
|
|
141
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
142
|
+
|
|
143
|
+
expect(result.changes).toHaveLength(5);
|
|
144
|
+
|
|
145
|
+
expect(result.changes?.[0].type).toBe('file_create');
|
|
146
|
+
expect(result.changes?.[0].path).toBe('config/database.ts');
|
|
147
|
+
|
|
148
|
+
expect(result.changes?.[1].type).toBe('file_update');
|
|
149
|
+
expect(result.changes?.[1].path).toBe('package.json');
|
|
150
|
+
|
|
151
|
+
expect(result.changes?.[2].type).toBe('file_delete');
|
|
152
|
+
expect(result.changes?.[2].path).toBe('src/old-db.ts');
|
|
153
|
+
|
|
154
|
+
expect(result.changes?.[3].type).toBe('dependency_add');
|
|
155
|
+
|
|
156
|
+
expect(result.changes?.[4].type).toBe('script_execute');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should handle empty sections gracefully', () => {
|
|
160
|
+
const markdown = `# Specification
|
|
161
|
+
|
|
162
|
+
## Intent
|
|
163
|
+
|
|
164
|
+
## Goals
|
|
165
|
+
|
|
166
|
+
## Requirements`;
|
|
167
|
+
|
|
168
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
169
|
+
|
|
170
|
+
expect(result.intent).toBe('');
|
|
171
|
+
expect(result.goals).toEqual([]);
|
|
172
|
+
expect(result.requirements).toEqual([]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should handle alternative section names', () => {
|
|
176
|
+
const markdown = `# Specification
|
|
177
|
+
|
|
178
|
+
## Purpose
|
|
179
|
+
|
|
180
|
+
Implement user authentication
|
|
181
|
+
|
|
182
|
+
## Objectives
|
|
183
|
+
|
|
184
|
+
- Secure the application
|
|
185
|
+
- Manage user sessions
|
|
186
|
+
|
|
187
|
+
## Prerequisites
|
|
188
|
+
|
|
189
|
+
- Database setup
|
|
190
|
+
- SSL certificates`;
|
|
191
|
+
|
|
192
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
193
|
+
|
|
194
|
+
expect(result.intent).toBe('Implement user authentication');
|
|
195
|
+
expect(result.goals).toContain('Secure the application');
|
|
196
|
+
expect(result.requirements).toContain('Database setup');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should extract code blocks with correct language', () => {
|
|
200
|
+
const markdown = `# Spec
|
|
201
|
+
|
|
202
|
+
## Changes
|
|
203
|
+
|
|
204
|
+
### Update TypeScript config
|
|
205
|
+
|
|
206
|
+
\`\`\`json
|
|
207
|
+
{
|
|
208
|
+
"compilerOptions": {
|
|
209
|
+
"strict": true
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
\`\`\``;
|
|
213
|
+
|
|
214
|
+
const result = parser.parseSpecMarkdown(markdown);
|
|
215
|
+
|
|
216
|
+
expect(result.changes?.[0].content).toContain('"strict": true');
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|