@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,255 @@
|
|
|
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
|
+
export class SpecParser {
|
|
11
|
+
parseSpec(content) {
|
|
12
|
+
const result = {
|
|
13
|
+
metadata: {},
|
|
14
|
+
userScenarios: [],
|
|
15
|
+
requirements: {
|
|
16
|
+
functional: [],
|
|
17
|
+
entities: [],
|
|
18
|
+
},
|
|
19
|
+
successCriteria: {
|
|
20
|
+
quantitative: [],
|
|
21
|
+
qualitative: [],
|
|
22
|
+
},
|
|
23
|
+
clarifications: [],
|
|
24
|
+
};
|
|
25
|
+
// Extract metadata from first section (before ## headers)
|
|
26
|
+
result.metadata = this.extractMetadata(content);
|
|
27
|
+
// Parse user scenarios section
|
|
28
|
+
result.userScenarios = this.parseUserScenarios(content);
|
|
29
|
+
// Parse requirements section
|
|
30
|
+
result.requirements = this.parseRequirements(content);
|
|
31
|
+
// Parse success criteria
|
|
32
|
+
result.successCriteria = this.parseSuccessCriteria(content);
|
|
33
|
+
// Extract all clarifications
|
|
34
|
+
result.clarifications = this.extractClarifications(content);
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
extractMetadata(content) {
|
|
38
|
+
const metadata = {};
|
|
39
|
+
// Extract title (# Feature: ...)
|
|
40
|
+
const titleMatch = content.match(/^#\s+Feature:\s+(.+)$/m);
|
|
41
|
+
if (titleMatch) {
|
|
42
|
+
metadata.feature = titleMatch[1].trim();
|
|
43
|
+
}
|
|
44
|
+
// Extract bold key-value pairs (** Key **: value)
|
|
45
|
+
// Stop at next ** or newline to handle multiple metadata on same line
|
|
46
|
+
const metadataRegex = /\*\*([^*]+)\*\*:\s*`?([^`*\n]+?)`?\s*(?=\*\*|\n|$)/g;
|
|
47
|
+
let match;
|
|
48
|
+
while ((match = metadataRegex.exec(content)) !== null) {
|
|
49
|
+
const key = match[1].trim().toLowerCase().replace(/\s+/g, '_');
|
|
50
|
+
const value = match[2].trim();
|
|
51
|
+
metadata[key] = value;
|
|
52
|
+
}
|
|
53
|
+
return metadata;
|
|
54
|
+
}
|
|
55
|
+
parseUserScenarios(content) {
|
|
56
|
+
const scenarios = [];
|
|
57
|
+
// Find "User Scenarios & Testing" section
|
|
58
|
+
const scenarioSectionMatch = content.match(/##\s+User Scenarios?\s*(?:&|and)?\s*Testing([\s\S]*?)(?=\n##\s|\n#\s|$)/i);
|
|
59
|
+
if (!scenarioSectionMatch) {
|
|
60
|
+
return scenarios;
|
|
61
|
+
}
|
|
62
|
+
const scenarioSection = scenarioSectionMatch[1];
|
|
63
|
+
// Split by ### headers (each scenario)
|
|
64
|
+
const scenarioBlocks = scenarioSection.split(/###\s+/).slice(1);
|
|
65
|
+
for (const block of scenarioBlocks) {
|
|
66
|
+
const scenario = this.parseUserScenario(block);
|
|
67
|
+
if (scenario) {
|
|
68
|
+
scenarios.push(scenario);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return scenarios;
|
|
72
|
+
}
|
|
73
|
+
parseUserScenario(block) {
|
|
74
|
+
// Extract priority and title from first line (P1: Title)
|
|
75
|
+
const titleMatch = block.match(/^(P\d+):\s+(.+)$/m);
|
|
76
|
+
if (!titleMatch) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const priority = titleMatch[1];
|
|
80
|
+
const title = titleMatch[2].trim();
|
|
81
|
+
// Extract user story components (handle multiline with line breaks)
|
|
82
|
+
const asAMatch = block.match(/\*\*As a\*\*\s+(.+?)(?=\s*\*\*|\n\n|$)/is);
|
|
83
|
+
const iWantToMatch = block.match(/\*\*I want(?:\s+to)?\*\*\s+(.+?)(?=\s*\*\*|\n\n|$)/is);
|
|
84
|
+
const soThatMatch = block.match(/\*\*So(?:\s+|\n)that\*\*\s+(.+?)(?=\s*\n\n|$)/is);
|
|
85
|
+
// Extract acceptance scenarios
|
|
86
|
+
const acceptanceScenarios = [];
|
|
87
|
+
const acceptanceMatch = block.match(/\*\*Acceptance Scenarios:\*\*([\s\S]*?)(?=\*\*Edge Cases:|\*\*\[NEEDS|###|$)/i);
|
|
88
|
+
if (acceptanceMatch) {
|
|
89
|
+
const scenarios = acceptanceMatch[1]
|
|
90
|
+
.split(/\n[-*]\s+/)
|
|
91
|
+
.map((s) => s.trim())
|
|
92
|
+
.filter((s) => s.length > 0);
|
|
93
|
+
acceptanceScenarios.push(...scenarios);
|
|
94
|
+
}
|
|
95
|
+
// Extract edge cases
|
|
96
|
+
const edgeCases = [];
|
|
97
|
+
const edgeCaseMatch = block.match(/\*\*Edge Cases:\*\*([\s\S]*?)(?=###|$)/i);
|
|
98
|
+
if (edgeCaseMatch) {
|
|
99
|
+
const cases = edgeCaseMatch[1]
|
|
100
|
+
.split(/\n[-*]\s+/)
|
|
101
|
+
.map((s) => s.trim())
|
|
102
|
+
.filter((s) => s.length > 0);
|
|
103
|
+
edgeCases.push(...cases);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
priority,
|
|
107
|
+
title,
|
|
108
|
+
asA: asAMatch?.[1]?.trim() || '',
|
|
109
|
+
iWantTo: iWantToMatch?.[1]?.trim() || '',
|
|
110
|
+
soThat: soThatMatch?.[1]?.trim() || '',
|
|
111
|
+
acceptanceScenarios,
|
|
112
|
+
edgeCases,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
parseRequirements(content) {
|
|
116
|
+
const requirements = {
|
|
117
|
+
functional: [],
|
|
118
|
+
entities: [],
|
|
119
|
+
};
|
|
120
|
+
// Find "Requirements" section
|
|
121
|
+
const reqSectionMatch = content.match(/##\s+Requirements([\s\S]*?)(?=\n##\s|\n#\s|$)/i);
|
|
122
|
+
if (!reqSectionMatch) {
|
|
123
|
+
return requirements;
|
|
124
|
+
}
|
|
125
|
+
const reqSection = reqSectionMatch[1];
|
|
126
|
+
// Parse functional requirements
|
|
127
|
+
requirements.functional = this.parseFunctionalRequirements(reqSection);
|
|
128
|
+
// Parse entity definitions
|
|
129
|
+
requirements.entities = this.parseEntities(reqSection);
|
|
130
|
+
return requirements;
|
|
131
|
+
}
|
|
132
|
+
parseFunctionalRequirements(section) {
|
|
133
|
+
const requirements = [];
|
|
134
|
+
// Find "Functional Requirements" subsection
|
|
135
|
+
const functionalMatch = section.match(/###\s+Functional Requirements([\s\S]*?)(?=\n###|$)/i);
|
|
136
|
+
if (!functionalMatch) {
|
|
137
|
+
return requirements;
|
|
138
|
+
}
|
|
139
|
+
const functionalSection = functionalMatch[1];
|
|
140
|
+
// Match FR-XXX: Description or [NEEDS CLARIFICATION: question]
|
|
141
|
+
const reqRegex = /\*\*(FR-\d+)\*\*:\s+(.+?)(?=\n\*\*FR-|\n###|$)/gs;
|
|
142
|
+
let match;
|
|
143
|
+
while ((match = reqRegex.exec(functionalSection)) !== null) {
|
|
144
|
+
const code = match[1];
|
|
145
|
+
const description = match[2].trim();
|
|
146
|
+
// Check if this is a clarification request
|
|
147
|
+
const clarificationMatch = description.match(/\[NEEDS CLARIFICATION:\s*(.+?)\]/);
|
|
148
|
+
requirements.push({
|
|
149
|
+
code,
|
|
150
|
+
description: clarificationMatch ? description : description,
|
|
151
|
+
needsClarification: !!clarificationMatch,
|
|
152
|
+
clarificationQuestion: clarificationMatch?.[1]?.trim(),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return requirements;
|
|
156
|
+
}
|
|
157
|
+
parseEntities(section) {
|
|
158
|
+
const entities = [];
|
|
159
|
+
// Find "Key Entities" subsection
|
|
160
|
+
const entitiesMatch = section.match(/###\s+Key Entities([\s\S]*?)(?=\n##|$)/i);
|
|
161
|
+
if (!entitiesMatch) {
|
|
162
|
+
return entities;
|
|
163
|
+
}
|
|
164
|
+
const entitiesSection = entitiesMatch[1];
|
|
165
|
+
// Split by ** EntityName **
|
|
166
|
+
const entityBlocks = entitiesSection.split(/\*\*([^*]+)\*\*/g).slice(1);
|
|
167
|
+
for (let i = 0; i < entityBlocks.length; i += 2) {
|
|
168
|
+
const entityName = entityBlocks[i].trim();
|
|
169
|
+
const entityContent = entityBlocks[i + 1] || '';
|
|
170
|
+
if (!entityName || !entityContent.trim()) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const entity = this.parseEntity(entityName, entityContent);
|
|
174
|
+
if (entity) {
|
|
175
|
+
entities.push(entity);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return entities;
|
|
179
|
+
}
|
|
180
|
+
parseEntity(name, content) {
|
|
181
|
+
const representsMatch = content.match(/[-*]\s+Represents:\s+(.+)/i);
|
|
182
|
+
const attributesMatch = content.match(/[-*]\s+Key Attributes:\s+(.+)/i);
|
|
183
|
+
const relationshipsMatch = content.match(/[-*]\s+Relationships:\s+(.+)/i);
|
|
184
|
+
return {
|
|
185
|
+
name,
|
|
186
|
+
represents: representsMatch?.[1]?.trim() || '',
|
|
187
|
+
keyAttributes: attributesMatch?.[1]
|
|
188
|
+
?.split(',')
|
|
189
|
+
.map((a) => a.trim())
|
|
190
|
+
.filter((a) => a.length > 0) || [],
|
|
191
|
+
relationships: relationshipsMatch?.[1]
|
|
192
|
+
?.split(',')
|
|
193
|
+
.map((r) => r.trim())
|
|
194
|
+
.filter((r) => r.length > 0) || [],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
parseSuccessCriteria(content) {
|
|
198
|
+
const criteria = {
|
|
199
|
+
quantitative: [],
|
|
200
|
+
qualitative: [],
|
|
201
|
+
};
|
|
202
|
+
// Find "Success Criteria" section
|
|
203
|
+
const criteriaSectionMatch = content.match(/##\s+Success Criteria([\s\S]*?)(?=\n##\s|\n#\s|$)/i);
|
|
204
|
+
if (!criteriaSectionMatch) {
|
|
205
|
+
return criteria;
|
|
206
|
+
}
|
|
207
|
+
const criteriaSection = criteriaSectionMatch[1];
|
|
208
|
+
// Parse quantitative metrics
|
|
209
|
+
const quantMatch = criteriaSection.match(/###\s+Quantitative Metrics([\s\S]*?)(?=\n###|$)/i);
|
|
210
|
+
if (quantMatch) {
|
|
211
|
+
const metrics = quantMatch[1]
|
|
212
|
+
.split(/\n[-*]\s+/)
|
|
213
|
+
.map((m) => m.trim())
|
|
214
|
+
.filter((m) => m.length > 0);
|
|
215
|
+
criteria.quantitative = metrics;
|
|
216
|
+
}
|
|
217
|
+
// Parse qualitative metrics
|
|
218
|
+
const qualMatch = criteriaSection.match(/###\s+Qualitative Metrics([\s\S]*?)(?=\n###|$)/i);
|
|
219
|
+
if (qualMatch) {
|
|
220
|
+
const metrics = qualMatch[1]
|
|
221
|
+
.split(/\n[-*]\s+/)
|
|
222
|
+
.map((m) => m.trim())
|
|
223
|
+
.filter((m) => m.length > 0);
|
|
224
|
+
criteria.qualitative = metrics;
|
|
225
|
+
}
|
|
226
|
+
// Parse security metrics (if present)
|
|
227
|
+
const securityMatch = criteriaSection.match(/###\s+Security Metrics([\s\S]*?)(?=\n###|$)/i);
|
|
228
|
+
if (securityMatch) {
|
|
229
|
+
const metrics = securityMatch[1]
|
|
230
|
+
.split(/\n[-*]\s+/)
|
|
231
|
+
.map((m) => m.trim())
|
|
232
|
+
.filter((m) => m.length > 0);
|
|
233
|
+
criteria.security = metrics;
|
|
234
|
+
}
|
|
235
|
+
// Parse performance metrics (if present)
|
|
236
|
+
const perfMatch = criteriaSection.match(/###\s+Performance Metrics([\s\S]*?)(?=\n###|$)/i);
|
|
237
|
+
if (perfMatch) {
|
|
238
|
+
const metrics = perfMatch[1]
|
|
239
|
+
.split(/\n[-*]\s+/)
|
|
240
|
+
.map((m) => m.trim())
|
|
241
|
+
.filter((m) => m.length > 0);
|
|
242
|
+
criteria.performance = metrics;
|
|
243
|
+
}
|
|
244
|
+
return criteria;
|
|
245
|
+
}
|
|
246
|
+
extractClarifications(content) {
|
|
247
|
+
const clarifications = [];
|
|
248
|
+
const clarificationRegex = /\[NEEDS CLARIFICATION:\s*([^\]]+)\]/g;
|
|
249
|
+
let match;
|
|
250
|
+
while ((match = clarificationRegex.exec(content)) !== null) {
|
|
251
|
+
clarifications.push(match[1].trim());
|
|
252
|
+
}
|
|
253
|
+
return clarifications;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for official GitHub Spec-Kit tasks.md format
|
|
3
|
+
*
|
|
4
|
+
* Tasks.md breaks down work into executable tasks:
|
|
5
|
+
* - Tasks organized by phases
|
|
6
|
+
* - Task IDs for tracking
|
|
7
|
+
* - Parallel execution markers
|
|
8
|
+
* - Dependencies and execution order
|
|
9
|
+
* - Implementation strategies
|
|
10
|
+
*/
|
|
11
|
+
interface TasksMetadata {
|
|
12
|
+
feature?: string;
|
|
13
|
+
branch?: string;
|
|
14
|
+
date?: string;
|
|
15
|
+
spec?: string;
|
|
16
|
+
plan?: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
interface Task {
|
|
20
|
+
id: string;
|
|
21
|
+
parallel: boolean;
|
|
22
|
+
userStory?: string;
|
|
23
|
+
description: string;
|
|
24
|
+
}
|
|
25
|
+
interface Phase {
|
|
26
|
+
name: string;
|
|
27
|
+
order: number;
|
|
28
|
+
tasks: Task[];
|
|
29
|
+
checkpoint?: string;
|
|
30
|
+
}
|
|
31
|
+
interface Dependency {
|
|
32
|
+
description: string;
|
|
33
|
+
requiredBefore?: string[];
|
|
34
|
+
canRunInParallel?: boolean;
|
|
35
|
+
}
|
|
36
|
+
interface ImplementationStrategy {
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
recommended?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface ParsedTasks {
|
|
42
|
+
metadata: TasksMetadata;
|
|
43
|
+
phases: Phase[];
|
|
44
|
+
dependencies: Dependency[];
|
|
45
|
+
strategies: ImplementationStrategy[];
|
|
46
|
+
}
|
|
47
|
+
export declare class TasksParser {
|
|
48
|
+
parseTasks(content: string): ParsedTasks;
|
|
49
|
+
private extractMetadata;
|
|
50
|
+
private parsePhases;
|
|
51
|
+
private parseTaskItems;
|
|
52
|
+
private extractCheckpoint;
|
|
53
|
+
private parseDependencies;
|
|
54
|
+
private parseStrategies;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=tasks-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks-parser.d.ts","sourceRoot":"","sources":["../../../src/speckit/parsers/tasks-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,UAAU,aAAa;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,IAAI;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,UAAU;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,UAAU,EAAE,sBAAsB,EAAE,CAAC;CACtC;AAED,qBAAa,WAAW;IACtB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW;IAuBxC,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,cAAc;IA8BtB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,iBAAiB;IAyCzB,OAAO,CAAC,eAAe;CA4CxB"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for official GitHub Spec-Kit tasks.md format
|
|
3
|
+
*
|
|
4
|
+
* Tasks.md breaks down work into executable tasks:
|
|
5
|
+
* - Tasks organized by phases
|
|
6
|
+
* - Task IDs for tracking
|
|
7
|
+
* - Parallel execution markers
|
|
8
|
+
* - Dependencies and execution order
|
|
9
|
+
* - Implementation strategies
|
|
10
|
+
*/
|
|
11
|
+
export class TasksParser {
|
|
12
|
+
parseTasks(content) {
|
|
13
|
+
const result = {
|
|
14
|
+
metadata: {},
|
|
15
|
+
phases: [],
|
|
16
|
+
dependencies: [],
|
|
17
|
+
strategies: [],
|
|
18
|
+
};
|
|
19
|
+
// Extract metadata
|
|
20
|
+
result.metadata = this.extractMetadata(content);
|
|
21
|
+
// Parse phases and tasks
|
|
22
|
+
result.phases = this.parsePhases(content);
|
|
23
|
+
// Parse dependencies section
|
|
24
|
+
result.dependencies = this.parseDependencies(content);
|
|
25
|
+
// Parse implementation strategies
|
|
26
|
+
result.strategies = this.parseStrategies(content);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
extractMetadata(content) {
|
|
30
|
+
const metadata = {};
|
|
31
|
+
// Extract title (# Tasks: ...)
|
|
32
|
+
const titleMatch = content.match(/^#\s+Tasks:\s+(.+)$/m);
|
|
33
|
+
if (titleMatch) {
|
|
34
|
+
metadata.feature = titleMatch[1].trim();
|
|
35
|
+
}
|
|
36
|
+
// Extract bold key-value pairs with links
|
|
37
|
+
const metadataRegex = /\*\*([^*]+)\*\*:\s*(?:`([^`\n]+)`|\[([^\]]+)\]\(([^)]+)\)|([^\n|]+))/g;
|
|
38
|
+
let match;
|
|
39
|
+
while ((match = metadataRegex.exec(content)) !== null) {
|
|
40
|
+
const key = match[1].trim().toLowerCase().replace(/\s+/g, '_');
|
|
41
|
+
const value = match[2] || match[3] || match[5] || '';
|
|
42
|
+
metadata[key] = value.trim();
|
|
43
|
+
}
|
|
44
|
+
return metadata;
|
|
45
|
+
}
|
|
46
|
+
parsePhases(content) {
|
|
47
|
+
const phases = [];
|
|
48
|
+
// Match ## Phase N: Name sections
|
|
49
|
+
const phaseRegex = /##\s+Phase\s+(\d+):\s+([^\n]+)([\s\S]*?)(?=\n##\s+(?:Phase|Dependencies)|$)/gi;
|
|
50
|
+
let match;
|
|
51
|
+
while ((match = phaseRegex.exec(content)) !== null) {
|
|
52
|
+
const order = parseInt(match[1], 10);
|
|
53
|
+
const name = match[2].trim();
|
|
54
|
+
const phaseContent = match[3];
|
|
55
|
+
const tasks = this.parseTaskItems(phaseContent);
|
|
56
|
+
const checkpoint = this.extractCheckpoint(phaseContent);
|
|
57
|
+
phases.push({
|
|
58
|
+
name,
|
|
59
|
+
order,
|
|
60
|
+
tasks,
|
|
61
|
+
checkpoint,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return phases;
|
|
65
|
+
}
|
|
66
|
+
parseTaskItems(content) {
|
|
67
|
+
const tasks = [];
|
|
68
|
+
// Match task lines: - [ID] [P?] [Story?] Description
|
|
69
|
+
// Format: - [001] Description
|
|
70
|
+
// or: - [001] [P] Description
|
|
71
|
+
// or: - [001] [P] [Story] Description
|
|
72
|
+
const taskRegex = /[-*]\s+\[(\d+)\](?:\s+\[P\])?(?:\s+\[([^\]]+)\])?\s+(.+)/g;
|
|
73
|
+
let match;
|
|
74
|
+
while ((match = taskRegex.exec(content)) !== null) {
|
|
75
|
+
const id = match[1];
|
|
76
|
+
const userStory = match[2]?.trim();
|
|
77
|
+
const description = match[3].trim();
|
|
78
|
+
// Check if [P] marker exists (for parallel execution)
|
|
79
|
+
const lineMatch = content.match(new RegExp(`\\[${id}\\]\\s+\\[P\\]`, 'i'));
|
|
80
|
+
const parallel = !!lineMatch;
|
|
81
|
+
tasks.push({
|
|
82
|
+
id,
|
|
83
|
+
parallel,
|
|
84
|
+
userStory,
|
|
85
|
+
description,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return tasks;
|
|
89
|
+
}
|
|
90
|
+
extractCheckpoint(content) {
|
|
91
|
+
const checkpointMatch = content.match(/\*\*Checkpoint\*\*:\s+(.+)/i);
|
|
92
|
+
return checkpointMatch?.[1]?.trim();
|
|
93
|
+
}
|
|
94
|
+
parseDependencies(content) {
|
|
95
|
+
const dependencies = [];
|
|
96
|
+
// Find "Dependencies & Execution Order" section
|
|
97
|
+
const depsMatch = content.match(/##\s+Dependencies\s*(?:&|and)?\s*Execution Order([\s\S]*?)(?=\n##\s|$)/i);
|
|
98
|
+
if (!depsMatch) {
|
|
99
|
+
return dependencies;
|
|
100
|
+
}
|
|
101
|
+
const depsSection = depsMatch[1];
|
|
102
|
+
// Parse subsections (### Required Sequential Order, ### Parallel Work Opportunities)
|
|
103
|
+
const subsections = depsSection.split(/###\s+/).slice(1);
|
|
104
|
+
for (const subsection of subsections) {
|
|
105
|
+
const lines = subsection.split('\n');
|
|
106
|
+
const title = lines[0].trim();
|
|
107
|
+
const items = [];
|
|
108
|
+
for (const line of lines.slice(1)) {
|
|
109
|
+
const itemMatch = line.match(/^[-*]\s+(.+)/);
|
|
110
|
+
if (itemMatch) {
|
|
111
|
+
items.push(itemMatch[1].trim());
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (items.length > 0) {
|
|
115
|
+
dependencies.push({
|
|
116
|
+
description: title,
|
|
117
|
+
requiredBefore: title.toLowerCase().includes('sequential') ? items : undefined,
|
|
118
|
+
canRunInParallel: title.toLowerCase().includes('parallel'),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return dependencies;
|
|
123
|
+
}
|
|
124
|
+
parseStrategies(content) {
|
|
125
|
+
const strategies = [];
|
|
126
|
+
// Find "Implementation Strategies" section
|
|
127
|
+
const strategiesMatch = content.match(/##\s+Implementation Strategies([\s\S]*?)$/i);
|
|
128
|
+
if (!strategiesMatch) {
|
|
129
|
+
return strategies;
|
|
130
|
+
}
|
|
131
|
+
const strategiesSection = strategiesMatch[1];
|
|
132
|
+
// Parse ### Strategy N: Name
|
|
133
|
+
const strategyRegex = /###\s+Strategy\s+\d+:\s+([^\n]+)([\s\S]*?)(?=\n###|$)/gi;
|
|
134
|
+
let match;
|
|
135
|
+
while ((match = strategyRegex.exec(strategiesSection)) !== null) {
|
|
136
|
+
const name = match[1].trim();
|
|
137
|
+
const description = match[2].trim();
|
|
138
|
+
// Check if recommended
|
|
139
|
+
const recommended = strategiesSection.toLowerCase().includes(`recommended`);
|
|
140
|
+
strategies.push({
|
|
141
|
+
name,
|
|
142
|
+
description,
|
|
143
|
+
recommended: recommended && match[0].toLowerCase().includes('recommended'),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// Extract recommended strategy
|
|
147
|
+
const recommendedMatch = strategiesSection.match(/\*\*Recommended\*\*:\s+([^.]+)/i);
|
|
148
|
+
if (recommendedMatch) {
|
|
149
|
+
const recommendedName = recommendedMatch[1].trim();
|
|
150
|
+
const strategy = strategies.find((s) => recommendedName.toLowerCase().includes(s.name.toLowerCase()));
|
|
151
|
+
if (strategy) {
|
|
152
|
+
strategy.recommended = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return strategies;
|
|
156
|
+
}
|
|
157
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eddacraft/anvil-adapters",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@eddacraft/anvil-aps": "0.1.0",
|
|
12
|
+
"@eddacraft/anvil-core": "0.1.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"vitest": "^4.0.18"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.lib.json",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eddacraft/anvil-adapters",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "packages/adapters/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"release": {
|
|
7
|
+
"version": {
|
|
8
|
+
"manifestRootsToUpdate": ["{projectRoot}/dist"],
|
|
9
|
+
"currentVersionResolver": "git-tag",
|
|
10
|
+
"fallbackCurrentVersionResolver": "disk"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"tags": [],
|
|
14
|
+
"targets": {
|
|
15
|
+
"build": {
|
|
16
|
+
"executor": "nx:run-script",
|
|
17
|
+
"outputs": ["{projectRoot}/dist"],
|
|
18
|
+
"options": {
|
|
19
|
+
"script": "build"
|
|
20
|
+
},
|
|
21
|
+
"dependsOn": ["^build"]
|
|
22
|
+
},
|
|
23
|
+
"nx-release-publish": {
|
|
24
|
+
"options": {
|
|
25
|
+
"packageRoot": "{projectRoot}/dist"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|