@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,283 @@
1
+ import { validateRelativePath } from '@eddacraft/anvil-core';
2
+ /**
3
+ * Validate and sanitize a file path to prevent path traversal attacks.
4
+ * Returns undefined if the path is invalid (absolute, contains null bytes, or escapes parent directory).
5
+ */
6
+ function safePath(raw) {
7
+ if (!raw)
8
+ return undefined;
9
+ try {
10
+ return validateRelativePath(raw);
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ export class SpecKitParser {
17
+ static SPEC_SECTIONS = {
18
+ intent: ['intent', 'purpose', 'objective'],
19
+ overview: ['overview', 'summary', 'description'],
20
+ goals: ['goals', 'objectives', 'outcomes'],
21
+ requirements: ['requirements', 'prerequisites', 'dependencies'],
22
+ changes: ['changes', 'modifications', 'alterations', 'tasks'],
23
+ };
24
+ /** Maximum input size for SpecKit parsing (2MB) */
25
+ static MAX_INPUT_SIZE = 2 * 1024 * 1024;
26
+ parseSpecMarkdown(content) {
27
+ if (content.length > SpecKitParser.MAX_INPUT_SIZE) {
28
+ throw new Error(`Input exceeds maximum size of ${SpecKitParser.MAX_INPUT_SIZE} bytes`);
29
+ }
30
+ const sections = this.parseMarkdownSections(content);
31
+ const result = {
32
+ metadata: {},
33
+ };
34
+ for (const section of sections) {
35
+ this.extractSectionData(section, result);
36
+ }
37
+ return result;
38
+ }
39
+ parseMarkdownSections(content) {
40
+ const lines = content.split('\n');
41
+ const sections = [];
42
+ const stack = [];
43
+ let currentContent = [];
44
+ for (const line of lines) {
45
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
46
+ if (headerMatch) {
47
+ if (currentContent.length > 0 && stack.length > 0) {
48
+ stack[stack.length - 1].content = currentContent.join('\n').trim();
49
+ }
50
+ const level = headerMatch[1].length;
51
+ const title = headerMatch[2].trim();
52
+ const newSection = {
53
+ title,
54
+ level,
55
+ content: '',
56
+ subsections: [],
57
+ };
58
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) {
59
+ stack.pop();
60
+ }
61
+ if (stack.length === 0) {
62
+ sections.push(newSection);
63
+ }
64
+ else {
65
+ stack[stack.length - 1].subsections.push(newSection);
66
+ }
67
+ stack.push(newSection);
68
+ currentContent = [];
69
+ }
70
+ else {
71
+ currentContent.push(line);
72
+ }
73
+ }
74
+ if (currentContent.length > 0 && stack.length > 0) {
75
+ stack[stack.length - 1].content = currentContent.join('\n').trim();
76
+ }
77
+ return sections;
78
+ }
79
+ extractSectionData(section, result) {
80
+ const sectionTitleLower = section.title.toLowerCase();
81
+ let sectionWasProcessed = false;
82
+ for (const [key, aliases] of Object.entries(SpecKitParser.SPEC_SECTIONS)) {
83
+ // Use word boundary matching to avoid partial matches (e.g., "objective" shouldn't match "objectives")
84
+ if (aliases.some((alias) => {
85
+ const regex = new RegExp(`\\b${alias}\\b`, 'i');
86
+ return regex.test(sectionTitleLower);
87
+ })) {
88
+ sectionWasProcessed = true;
89
+ switch (key) {
90
+ case 'intent':
91
+ // For intent, get only the paragraph text, not list items
92
+ result.intent = this.extractParagraphText(section.content);
93
+ break;
94
+ case 'overview':
95
+ // For overview, get only the paragraph text, not list items
96
+ result.overview = this.extractParagraphText(section.content);
97
+ break;
98
+ case 'goals':
99
+ result.goals = this.parseListItems(section.content);
100
+ break;
101
+ case 'requirements':
102
+ result.requirements = this.parseListItems(section.content);
103
+ break;
104
+ case 'changes':
105
+ // parseChanges handles all nested subsections, so don't recurse into them
106
+ result.changes = this.parseChanges(section);
107
+ return; // Exit early - changes section is fully processed
108
+ }
109
+ }
110
+ }
111
+ // Only recurse into subsections if this section wasn't fully processed by a specialized method
112
+ // or if the section didn't match any known section types
113
+ if (!sectionWasProcessed) {
114
+ for (const subsection of section.subsections) {
115
+ this.extractSectionData(subsection, result);
116
+ }
117
+ }
118
+ }
119
+ extractParagraphText(content) {
120
+ const lines = content.split('\n');
121
+ const paragraphLines = [];
122
+ let hasContent = false;
123
+ for (const line of lines) {
124
+ const trimmedLine = line.trim();
125
+ // Stop at list items
126
+ if (trimmedLine.match(/^[-*+]\s+/) || trimmedLine.match(/^\d+\.\s+/)) {
127
+ break;
128
+ }
129
+ // Add non-empty lines
130
+ if (trimmedLine) {
131
+ paragraphLines.push(trimmedLine);
132
+ hasContent = true;
133
+ }
134
+ else if (hasContent && paragraphLines.length > 0) {
135
+ // Stop at empty line after we've collected some content
136
+ break;
137
+ }
138
+ }
139
+ return paragraphLines.join(' ').trim();
140
+ }
141
+ parseListItems(content) {
142
+ const lines = content.split('\n');
143
+ const items = [];
144
+ let currentItem = '';
145
+ for (const line of lines) {
146
+ const listMatch = line.match(/^[\s]*[-*+]\s+(.+)$/);
147
+ const numberedMatch = line.match(/^[\s]*\d+\.\s+(.+)$/);
148
+ if (listMatch || numberedMatch) {
149
+ if (currentItem) {
150
+ items.push(currentItem.trim());
151
+ }
152
+ currentItem = (listMatch?.[1] || numberedMatch?.[1] || '').trim();
153
+ }
154
+ else if (currentItem && line.trim()) {
155
+ currentItem += ' ' + line.trim();
156
+ }
157
+ }
158
+ if (currentItem) {
159
+ items.push(currentItem.trim());
160
+ }
161
+ return items;
162
+ }
163
+ parseChanges(section) {
164
+ const changes = [];
165
+ // Process direct subsections
166
+ for (const subsection of section.subsections) {
167
+ // Check if this subsection is a grouping section (like "Files to Create")
168
+ const subsectionTitleLower = subsection.title.toLowerCase();
169
+ const isGroupingSection = subsectionTitleLower.includes('files to') ||
170
+ subsectionTitleLower.includes('configuration') ||
171
+ subsectionTitleLower.includes('dependencies') ||
172
+ subsectionTitleLower.includes('scripts');
173
+ if (isGroupingSection && subsection.subsections.length > 0) {
174
+ // Process nested subsections within grouping sections
175
+ for (const nestedSection of subsection.subsections) {
176
+ const change = this.parseChangeSection(nestedSection);
177
+ if (change) {
178
+ changes.push(change);
179
+ }
180
+ }
181
+ }
182
+ else {
183
+ // Process as a direct change section
184
+ const change = this.parseChangeSection(subsection);
185
+ if (change) {
186
+ changes.push(change);
187
+ }
188
+ }
189
+ }
190
+ // Fallback to parsing list items if no subsections found
191
+ if (changes.length === 0) {
192
+ const listItems = this.parseListItems(section.content);
193
+ for (const item of listItems) {
194
+ const change = this.parseChangeFromListItem(item);
195
+ if (change) {
196
+ changes.push(change);
197
+ }
198
+ }
199
+ }
200
+ return changes;
201
+ }
202
+ parseChangeSection(section) {
203
+ const titleLower = section.title.toLowerCase();
204
+ let type = 'script_execute';
205
+ if (titleLower.includes('create') || titleLower.includes('new')) {
206
+ type = 'file_create';
207
+ }
208
+ else if (titleLower.includes('update') || titleLower.includes('modify')) {
209
+ type = 'file_update';
210
+ }
211
+ else if (titleLower.includes('delete') || titleLower.includes('remove')) {
212
+ type = 'file_delete';
213
+ }
214
+ else if (titleLower.includes('config')) {
215
+ type = 'config_update';
216
+ }
217
+ else if (titleLower.includes('dependency') || titleLower.includes('package')) {
218
+ if (titleLower.includes('add') || titleLower.includes('install')) {
219
+ type = 'dependency_add';
220
+ }
221
+ else if (titleLower.includes('remove') || titleLower.includes('uninstall')) {
222
+ type = 'dependency_remove';
223
+ }
224
+ else {
225
+ type = 'dependency_update';
226
+ }
227
+ }
228
+ // Look for path in title first, then in content
229
+ let pathMatch = section.title.match(/`([^`]+)`/);
230
+ if (!pathMatch && section.content) {
231
+ // Look for path in the first line of content
232
+ const firstLine = section.content.split('\n')[0];
233
+ pathMatch = firstLine.match(/`([^`]+)`/);
234
+ }
235
+ const path = safePath(pathMatch?.[1]);
236
+ const codeBlockMatch = section.content.match(/```[\w]*\n([\s\S]*?)```/);
237
+ const content = codeBlockMatch ? codeBlockMatch[1].trim() : undefined;
238
+ return {
239
+ type,
240
+ description: section.title,
241
+ path,
242
+ content,
243
+ };
244
+ }
245
+ parseChangeFromListItem(item) {
246
+ const itemLower = item.toLowerCase();
247
+ let type = 'script_execute';
248
+ // Check for script execution patterns first
249
+ if (itemLower.includes('run') ||
250
+ itemLower.includes('execute') ||
251
+ itemLower.includes('script')) {
252
+ type = 'script_execute';
253
+ }
254
+ else if (itemLower.includes('create') || itemLower.includes('new file')) {
255
+ type = 'file_create';
256
+ }
257
+ else if (itemLower.includes('delete') || itemLower.includes('remove file')) {
258
+ type = 'file_delete';
259
+ }
260
+ else if (itemLower.includes('update dependency')) {
261
+ type = 'dependency_update';
262
+ }
263
+ else if (itemLower.includes('install') || itemLower.includes('add dependency')) {
264
+ type = 'dependency_add';
265
+ }
266
+ else if (itemLower.includes('uninstall') || itemLower.includes('remove dependency')) {
267
+ type = 'dependency_remove';
268
+ }
269
+ else if (itemLower.includes('update') || itemLower.includes('modify')) {
270
+ type = 'file_update';
271
+ }
272
+ else if (itemLower.includes('config')) {
273
+ type = 'config_update';
274
+ }
275
+ const pathMatch = item.match(/`([^`]+)`/);
276
+ const path = safePath(pathMatch?.[1]);
277
+ return {
278
+ type,
279
+ description: item,
280
+ path,
281
+ };
282
+ }
283
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Parser for official GitHub Spec-Kit plan.md format
3
+ *
4
+ * Plan.md focuses on HOW (technical implementation):
5
+ * - Summary
6
+ * - Technical Context (stack, dependencies, constraints)
7
+ * - Constitution Check (compliance with project principles)
8
+ * - Project Structure (directories, files)
9
+ * - Implementation Details (API endpoints, database schema, etc.)
10
+ * - Complexity Tracking (design decisions)
11
+ */
12
+ interface PlanMetadata {
13
+ feature?: string;
14
+ branch?: string;
15
+ date?: string;
16
+ spec?: string;
17
+ [key: string]: unknown;
18
+ }
19
+ interface TechnicalContext {
20
+ language?: string;
21
+ dependencies?: string[];
22
+ storage?: string;
23
+ testing?: string;
24
+ target?: string;
25
+ type?: string;
26
+ performanceGoals?: string[];
27
+ constraints?: string[];
28
+ scale?: string;
29
+ }
30
+ interface ConstitutionCheck {
31
+ modularity?: boolean | string;
32
+ testability?: boolean | string;
33
+ security?: boolean | string;
34
+ performance?: boolean | string;
35
+ maintainability?: boolean | string;
36
+ documentation?: boolean | string;
37
+ [key: string]: boolean | string | undefined;
38
+ }
39
+ interface ProjectStructure {
40
+ documentation?: string;
41
+ sourceCode?: string;
42
+ selectedOption?: string;
43
+ }
44
+ interface ComplexityDecision {
45
+ title: string;
46
+ problem: string;
47
+ solution: string;
48
+ justification: string;
49
+ }
50
+ export interface ParsedPlan {
51
+ metadata: PlanMetadata;
52
+ summary: string;
53
+ technicalContext: TechnicalContext;
54
+ constitutionCheck: ConstitutionCheck;
55
+ projectStructure: ProjectStructure;
56
+ implementationDetails: Map<string, string>;
57
+ complexityDecisions: ComplexityDecision[];
58
+ }
59
+ export declare class PlanParser {
60
+ parsePlan(content: string): ParsedPlan;
61
+ private extractMetadata;
62
+ private extractSummary;
63
+ private parseTechnicalContext;
64
+ private parseConstitutionCheck;
65
+ private parseProjectStructure;
66
+ private parseImplementationDetails;
67
+ private parseComplexityDecisions;
68
+ private parseComplexityDecision;
69
+ }
70
+ export {};
71
+ //# sourceMappingURL=plan-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-parser.d.ts","sourceRoot":"","sources":["../../../src/speckit/parsers/plan-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,UAAU,YAAY;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,gBAAgB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,iBAAiB;IACzB,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACnC,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,UAAU,gBAAgB;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,mBAAmB,EAAE,kBAAkB,EAAE,CAAC;CAC3C;AAED,qBAAa,UAAU;IACrB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;IAmCtC,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,qBAAqB;IAiF7B,OAAO,CAAC,sBAAsB;IA4B9B,OAAO,CAAC,qBAAqB;IAoC7B,OAAO,CAAC,0BAA0B;IA2BlC,OAAO,CAAC,wBAAwB;IAyBhC,OAAO,CAAC,uBAAuB;CAiBhC"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Parser for official GitHub Spec-Kit plan.md format
3
+ *
4
+ * Plan.md focuses on HOW (technical implementation):
5
+ * - Summary
6
+ * - Technical Context (stack, dependencies, constraints)
7
+ * - Constitution Check (compliance with project principles)
8
+ * - Project Structure (directories, files)
9
+ * - Implementation Details (API endpoints, database schema, etc.)
10
+ * - Complexity Tracking (design decisions)
11
+ */
12
+ export class PlanParser {
13
+ parsePlan(content) {
14
+ const result = {
15
+ metadata: {},
16
+ summary: '',
17
+ technicalContext: {},
18
+ constitutionCheck: {},
19
+ projectStructure: {},
20
+ implementationDetails: new Map(),
21
+ complexityDecisions: [],
22
+ };
23
+ // Extract metadata from first section
24
+ result.metadata = this.extractMetadata(content);
25
+ // Parse summary section
26
+ result.summary = this.extractSummary(content);
27
+ // Parse technical context
28
+ result.technicalContext = this.parseTechnicalContext(content);
29
+ // Parse constitution check
30
+ result.constitutionCheck = this.parseConstitutionCheck(content);
31
+ // Parse project structure
32
+ result.projectStructure = this.parseProjectStructure(content);
33
+ // Parse implementation details (flexible sections)
34
+ result.implementationDetails = this.parseImplementationDetails(content);
35
+ // Parse complexity tracking
36
+ result.complexityDecisions = this.parseComplexityDecisions(content);
37
+ return result;
38
+ }
39
+ extractMetadata(content) {
40
+ const metadata = {};
41
+ // Extract title (# Implementation Plan: ...)
42
+ const titleMatch = content.match(/^#\s+Implementation Plan:\s+(.+)$/m);
43
+ if (titleMatch) {
44
+ metadata.feature = titleMatch[1].trim();
45
+ }
46
+ // Extract bold key-value pairs
47
+ const metadataRegex = /\*\*([^*]+)\*\*:\s*(?:`([^`\n]+)`|\[([^\]]+)\]\(([^)]+)\)|([^\n]+))/g;
48
+ let match;
49
+ while ((match = metadataRegex.exec(content)) !== null) {
50
+ const key = match[1].trim().toLowerCase().replace(/\s+/g, '_');
51
+ const value = match[2] || match[3] || match[5] || '';
52
+ metadata[key] = value.trim();
53
+ }
54
+ return metadata;
55
+ }
56
+ extractSummary(content) {
57
+ const summaryMatch = content.match(/##\s+Summary\s+([\s\S]*?)(?=\n##\s|$)/i);
58
+ return summaryMatch?.[1]?.trim() || '';
59
+ }
60
+ parseTechnicalContext(content) {
61
+ const context = {};
62
+ const contextMatch = content.match(/##\s+Technical Context([\s\S]*?)(?=\n##\s|$)/i);
63
+ if (!contextMatch) {
64
+ return context;
65
+ }
66
+ const contextSection = contextMatch[1];
67
+ // Parse bullet points with key-value pairs
68
+ const languageMatch = contextSection.match(/[-*]\s+\*\*Language\/Version\*\*:\s+(.+)/i);
69
+ if (languageMatch) {
70
+ context.language = languageMatch[1].trim();
71
+ }
72
+ const storageMatch = contextSection.match(/[-*]\s+\*\*Storage\*\*:\s+(.+)/i);
73
+ if (storageMatch) {
74
+ context.storage = storageMatch[1].trim();
75
+ }
76
+ const testingMatch = contextSection.match(/[-*]\s+\*\*Testing\*\*:\s+(.+)/i);
77
+ if (testingMatch) {
78
+ context.testing = testingMatch[1].trim();
79
+ }
80
+ const targetMatch = contextSection.match(/[-*]\s+\*\*Target\*\*:\s+(.+)/i);
81
+ if (targetMatch) {
82
+ context.target = targetMatch[1].trim();
83
+ }
84
+ const typeMatch = contextSection.match(/[-*]\s+\*\*Type\*\*:\s+(.+)/i);
85
+ if (typeMatch) {
86
+ context.type = typeMatch[1].trim();
87
+ }
88
+ const scaleMatch = contextSection.match(/[-*]\s+\*\*Scale\*\*:\s+(.+)/i);
89
+ if (scaleMatch) {
90
+ context.scale = scaleMatch[1].trim();
91
+ }
92
+ // Parse dependencies (multi-line list)
93
+ const depsMatch = contextSection.match(/[-*]\s+\*\*Dependencies\*\*:\s+([\s\S]*?)(?=\n[-*]\s+\*\*|$)/i);
94
+ if (depsMatch) {
95
+ const deps = depsMatch[1]
96
+ .split(/\n\s*[-*]\s+/)
97
+ .map((d) => d.trim())
98
+ .filter((d) => d.length > 0);
99
+ context.dependencies = deps;
100
+ }
101
+ // Parse performance goals
102
+ const perfMatch = contextSection.match(/[-*]\s+\*\*Performance Goals\*\*:\s+([\s\S]*?)(?=\n[-*]\s+\*\*|$)/i);
103
+ if (perfMatch) {
104
+ const goals = perfMatch[1]
105
+ .split(/\n\s*[-*]\s+/)
106
+ .map((g) => g.trim())
107
+ .filter((g) => g.length > 0);
108
+ context.performanceGoals = goals;
109
+ }
110
+ // Parse constraints
111
+ const constraintsMatch = contextSection.match(/[-*]\s+\*\*Constraints\*\*:\s+([\s\S]*?)(?=\n[-*]\s+\*\*|$)/i);
112
+ if (constraintsMatch) {
113
+ const constraints = constraintsMatch[1]
114
+ .split(/\n\s*[-*]\s+/)
115
+ .map((c) => c.trim())
116
+ .filter((c) => c.length > 0);
117
+ context.constraints = constraints;
118
+ }
119
+ return context;
120
+ }
121
+ parseConstitutionCheck(content) {
122
+ const check = {};
123
+ const checkMatch = content.match(/##\s+Constitution Check([\s\S]*?)(?=\n##\s|$)/i);
124
+ if (!checkMatch) {
125
+ return check;
126
+ }
127
+ const checkSection = checkMatch[1];
128
+ // Parse ✅ or ❌ followed by **Key**: Description
129
+ const checkRegex = /([✅❌✓✗×])\s+\*\*([^*]+)\*\*:\s+(.+)/g;
130
+ let match;
131
+ while ((match = checkRegex.exec(checkSection)) !== null) {
132
+ const status = match[1];
133
+ const key = match[2].trim().toLowerCase().replace(/\s+/g, '_');
134
+ const description = match[3].trim();
135
+ // Check if it passes or fails
136
+ const passes = status === '✅' || status === '✓';
137
+ check[key] = passes ? description : `FAIL: ${description}`;
138
+ }
139
+ return check;
140
+ }
141
+ parseProjectStructure(content) {
142
+ const structure = {};
143
+ const structureMatch = content.match(/##\s+Project Structure([\s\S]*?)(?=\n##\s|$)/i);
144
+ if (!structureMatch) {
145
+ return structure;
146
+ }
147
+ const structureSection = structureMatch[1];
148
+ // Extract documentation structure (code block)
149
+ const docsMatch = structureSection.match(/###\s+Documentation[\s\S]*?```[\s\S]*?\n([\s\S]*?)```/i);
150
+ if (docsMatch) {
151
+ structure.documentation = docsMatch[1].trim();
152
+ }
153
+ // Extract source code structure
154
+ const sourceMatch = structureSection.match(/###\s+Source Code Structure[\s\S]*?```[\s\S]*?\n([\s\S]*?)```/i);
155
+ if (sourceMatch) {
156
+ structure.sourceCode = sourceMatch[1].trim();
157
+ }
158
+ // Check for "Selected" or "(Selected)" marker
159
+ const selectedMatch = structureSection.match(/####\s+Option \d+:([^(]+)\(Selected\)/i);
160
+ if (selectedMatch) {
161
+ structure.selectedOption = selectedMatch[1].trim();
162
+ }
163
+ return structure;
164
+ }
165
+ parseImplementationDetails(content) {
166
+ const details = new Map();
167
+ // Find "Implementation Details" section
168
+ const detailsMatch = content.match(/##\s+Implementation Details([\s\S]*?)(?=\n##\s+Complexity|$)/i);
169
+ if (!detailsMatch) {
170
+ return details;
171
+ }
172
+ const detailsSection = detailsMatch[1];
173
+ // Split by ### headers
174
+ const subsectionRegex = /###\s+([^\n]+)\n([\s\S]*?)(?=\n###|$)/g;
175
+ let match;
176
+ while ((match = subsectionRegex.exec(detailsSection)) !== null) {
177
+ const title = match[1].trim();
178
+ const content = match[2].trim();
179
+ details.set(title, content);
180
+ }
181
+ return details;
182
+ }
183
+ parseComplexityDecisions(content) {
184
+ const decisions = [];
185
+ // Find "Complexity Tracking" section
186
+ const complexityMatch = content.match(/##\s+Complexity Tracking([\s\S]*?)$/i);
187
+ if (!complexityMatch) {
188
+ return decisions;
189
+ }
190
+ const complexitySection = complexityMatch[1];
191
+ // Split by ### headers (each decision)
192
+ const decisionBlocks = complexitySection.split(/###\s+/).slice(1);
193
+ for (const block of decisionBlocks) {
194
+ const decision = this.parseComplexityDecision(block);
195
+ if (decision) {
196
+ decisions.push(decision);
197
+ }
198
+ }
199
+ return decisions;
200
+ }
201
+ parseComplexityDecision(block) {
202
+ const titleMatch = block.match(/^([^\n]+)/);
203
+ const problemMatch = block.match(/\*\*Problem\*\*:\s+(.+?)(?=\n\*\*|$)/s);
204
+ const solutionMatch = block.match(/\*\*Solution\*\*:\s+([\s\S]+?)(?=\n\*\*|$)/);
205
+ const justificationMatch = block.match(/\*\*Justification\*\*:\s+([\s\S]+?)(?=\n###|$)/);
206
+ if (!titleMatch) {
207
+ return null;
208
+ }
209
+ return {
210
+ title: titleMatch[1].trim(),
211
+ problem: problemMatch?.[1]?.trim() || '',
212
+ solution: solutionMatch?.[1]?.trim() || '',
213
+ justification: justificationMatch?.[1]?.trim() || '',
214
+ };
215
+ }
216
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Parser for official GitHub Spec-Kit spec.md format
3
+ *
4
+ * Spec.md focuses on WHAT and WHY (not HOW):
5
+ * - Feature metadata
6
+ * - User Scenarios & Testing (prioritized user stories)
7
+ * - Requirements (functional requirements, key entities)
8
+ * - Success Criteria (measurable outcomes)
9
+ */
10
+ interface SpecMetadata {
11
+ feature?: string;
12
+ branch?: string;
13
+ date?: string;
14
+ status?: string;
15
+ [key: string]: unknown;
16
+ }
17
+ interface UserScenario {
18
+ priority: 'P1' | 'P2' | 'P3' | string;
19
+ title: string;
20
+ asA: string;
21
+ iWantTo: string;
22
+ soThat: string;
23
+ acceptanceScenarios: string[];
24
+ edgeCases: string[];
25
+ }
26
+ interface FunctionalRequirement {
27
+ code: string;
28
+ description: string;
29
+ needsClarification: boolean;
30
+ clarificationQuestion?: string;
31
+ }
32
+ interface EntityDefinition {
33
+ name: string;
34
+ represents: string;
35
+ keyAttributes: string[];
36
+ relationships: string[];
37
+ }
38
+ interface SuccessCriteria {
39
+ quantitative: string[];
40
+ qualitative: string[];
41
+ security?: string[];
42
+ performance?: string[];
43
+ }
44
+ export interface ParsedSpec {
45
+ metadata: SpecMetadata;
46
+ userScenarios: UserScenario[];
47
+ requirements: {
48
+ functional: FunctionalRequirement[];
49
+ entities: EntityDefinition[];
50
+ };
51
+ successCriteria: SuccessCriteria;
52
+ clarifications: string[];
53
+ }
54
+ export declare class SpecParser {
55
+ parseSpec(content: string): ParsedSpec;
56
+ private extractMetadata;
57
+ private parseUserScenarios;
58
+ private parseUserScenario;
59
+ private parseRequirements;
60
+ private parseFunctionalRequirements;
61
+ private parseEntities;
62
+ private parseEntity;
63
+ private parseSuccessCriteria;
64
+ private extractClarifications;
65
+ }
66
+ export {};
67
+ //# sourceMappingURL=spec-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-parser.d.ts","sourceRoot":"","sources":["../../../src/speckit/parsers/spec-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,UAAU,YAAY;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,YAAY;IACpB,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,UAAU,eAAe;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,YAAY,CAAC;IACvB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE;QACZ,UAAU,EAAE,qBAAqB,EAAE,CAAC;QACpC,QAAQ,EAAE,gBAAgB,EAAE,CAAC;KAC9B,CAAC;IACF,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,qBAAa,UAAU;IACrB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;IAiCtC,OAAO,CAAC,eAAe;IAsBvB,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,iBAAiB;IAkDzB,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,2BAA2B;IAkCnC,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,oBAAoB;IA4D5B,OAAO,CAAC,qBAAqB;CAW9B"}