@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,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
+ });