@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,224 @@
1
+ /**
2
+ * BMAD Parser
3
+ *
4
+ * Parses BMAD format documents (PRD, Architecture, Epics, Stories) into APS plans.
5
+ */
6
+
7
+ import { type APSPlan, type Change, validateRelativePath } from '@eddacraft/anvil-core';
8
+ import type { ParseContext, AdapterError, AdapterWarning } from '../base/types.js';
9
+ import { BMADDocument, BMADRequirement, BMADUserStory, RequirementType } from './types.js';
10
+ import {
11
+ extractFrontMatter,
12
+ extractRequirements,
13
+ extractUserStories,
14
+ extractChangeLog,
15
+ identifyDocumentType,
16
+ extractTitle,
17
+ extractIntent,
18
+ } from './utils.js';
19
+ import { createError, createWarning, generateDeterministicPlanId } from '../base/utils.js';
20
+
21
+ /**
22
+ * Parse BMAD document into internal structure
23
+ *
24
+ * @param content - BMAD markdown content
25
+ * @returns Parsed document
26
+ */
27
+ /**
28
+ * Validate and sanitize a file path to prevent path traversal attacks.
29
+ * Falls back to stripping special characters if validation fails.
30
+ */
31
+ function safePath(raw: string): string {
32
+ try {
33
+ return validateRelativePath(raw);
34
+ } catch {
35
+ return raw.replace(/[^a-z0-9/._-]/gi, '').replace(/\.{2,}/g, '');
36
+ }
37
+ }
38
+
39
+ /** Maximum input size for BMAD parsing (2MB) */
40
+ const MAX_INPUT_SIZE = 2 * 1024 * 1024;
41
+
42
+ export function parseBMADDocument(content: string): BMADDocument {
43
+ if (content.length > MAX_INPUT_SIZE) {
44
+ throw new Error(`Input exceeds maximum size of ${MAX_INPUT_SIZE} bytes`);
45
+ }
46
+ const frontMatter = extractFrontMatter(content);
47
+ const docType = identifyDocumentType(content, frontMatter);
48
+ const requirements = extractRequirements(content);
49
+ const userStories = extractUserStories(content);
50
+ const changeLog = extractChangeLog(content);
51
+ const title = extractTitle(content);
52
+ const intent = extractIntent(content, docType);
53
+
54
+ return {
55
+ type: docType,
56
+ frontMatter: frontMatter || undefined,
57
+ title: title || undefined,
58
+ intent,
59
+ requirements,
60
+ userStories,
61
+ changeLog,
62
+ sections: new Map(),
63
+ raw: content,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Convert BMAD requirement to APS change
69
+ *
70
+ * @param requirement - BMAD requirement
71
+ * @returns APS change
72
+ */
73
+ function requirementToChange(requirement: BMADRequirement): Change {
74
+ // Map requirement type to change type
75
+ switch (requirement.type) {
76
+ case RequirementType.FUNCTIONAL:
77
+ // FR typically means creating or updating files
78
+ return {
79
+ type: 'file_create',
80
+ path: safePath(`features/${requirement.id.toLowerCase()}.ts`),
81
+ description: `${requirement.id}: ${requirement.description}`,
82
+ };
83
+
84
+ case RequirementType.NON_FUNCTIONAL:
85
+ // NFR typically means configuration or validation
86
+ return {
87
+ type: 'config_update',
88
+ path: 'config/requirements.json',
89
+ description: `${requirement.id}: ${requirement.description}`,
90
+ };
91
+
92
+ case RequirementType.USER_STORY:
93
+ // US typically means creating feature files
94
+ return {
95
+ type: 'file_create',
96
+ path: safePath(`features/stories/${requirement.id.toLowerCase()}.ts`),
97
+ description: `${requirement.id}: ${requirement.description}`,
98
+ };
99
+
100
+ default:
101
+ return {
102
+ type: 'file_create',
103
+ path: safePath(`requirements/${requirement.id.toLowerCase()}.md`),
104
+ description: requirement.description,
105
+ };
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Convert BMAD user story to APS change
111
+ *
112
+ * @param story - BMAD user story
113
+ * @returns APS change
114
+ */
115
+ function userStoryToChange(story: BMADUserStory): Change {
116
+ let description = `${story.id}: ${story.title}`;
117
+
118
+ if (story.userType && story.action && story.benefit) {
119
+ description += ` (As a ${story.userType}, I want ${story.action}, so that ${story.benefit})`;
120
+ }
121
+
122
+ if (story.acceptanceCriteria && story.acceptanceCriteria.length > 0) {
123
+ description += ` - Acceptance criteria: ${story.acceptanceCriteria.join('; ')}`;
124
+ }
125
+
126
+ return {
127
+ type: 'file_create',
128
+ path: safePath(`features/stories/${story.id.toLowerCase()}.ts`),
129
+ description,
130
+ };
131
+ }
132
+
133
+ export function bmadToAPS(
134
+ document: BMADDocument,
135
+ context?: ParseContext,
136
+ originalContent?: string
137
+ ): APSPlan {
138
+ const planId =
139
+ context?.planId ??
140
+ (originalContent
141
+ ? generateDeterministicPlanId(originalContent)
142
+ : `aps-${Date.now().toString(16).substring(0, 8)}`);
143
+
144
+ // Convert requirements and stories to changes
145
+ const changes: Change[] = [];
146
+ const errors: AdapterError[] = [];
147
+ const warnings: AdapterWarning[] = [];
148
+
149
+ // Add requirements as changes
150
+ for (const requirement of document.requirements) {
151
+ try {
152
+ const change = requirementToChange(requirement);
153
+ changes.push(change);
154
+ } catch (error) {
155
+ errors.push(
156
+ createError(
157
+ 'REQUIREMENT_CONVERSION_ERROR',
158
+ `Failed to convert requirement ${requirement.id}`,
159
+ {
160
+ details: error,
161
+ }
162
+ )
163
+ );
164
+ }
165
+ }
166
+
167
+ // Add user stories as changes
168
+ for (const story of document.userStories) {
169
+ try {
170
+ const change = userStoryToChange(story);
171
+ changes.push(change);
172
+ } catch (error) {
173
+ errors.push(
174
+ createError('STORY_CONVERSION_ERROR', `Failed to convert story ${story.id}`, {
175
+ details: error,
176
+ })
177
+ );
178
+ }
179
+ }
180
+
181
+ // If no changes found, add warning
182
+ if (changes.length === 0) {
183
+ warnings.push(
184
+ createWarning('NO_CHANGES', 'No requirements or user stories found in document', {
185
+ details: { type: document.type },
186
+ })
187
+ );
188
+ }
189
+
190
+ // Extract provenance from front-matter or context
191
+ const timestamp = document.frontMatter?.date || context?.timestamp || new Date().toISOString();
192
+ const author = document.frontMatter?.author || context?.author || 'unknown';
193
+ const version = document.frontMatter?.version || '1.0.0';
194
+
195
+ // Build APS plan
196
+ const plan: APSPlan = {
197
+ id: planId,
198
+ schema_version: '0.1.0',
199
+ intent: document.intent || 'BMAD document conversion',
200
+ proposed_changes: changes,
201
+ provenance: {
202
+ timestamp,
203
+ author,
204
+ source: 'cli',
205
+ version,
206
+ repository: context?.repositoryPath,
207
+ branch: context?.branch,
208
+ commit: context?.commit,
209
+ },
210
+ validations: {
211
+ required_checks: ['lint', 'test', 'coverage'],
212
+ skip_checks: [],
213
+ },
214
+ // Note: hash will be generated by the adapter after plan creation
215
+ hash: '0000000000000000000000000000000000000000000000000000000000000000',
216
+ };
217
+
218
+ return plan;
219
+ }
220
+
221
+ export function parseBMAD(content: string, context?: ParseContext): APSPlan {
222
+ const document = parseBMADDocument(content);
223
+ return bmadToAPS(document, context, content);
224
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * BMAD Serializer
3
+ *
4
+ * Serializes APS plans to BMAD format documents.
5
+ */
6
+
7
+ import type { APSPlan, Change } from '@eddacraft/anvil-core';
8
+
9
+ /**
10
+ * Serialize APS plan to BMAD format
11
+ *
12
+ * Generates a BMAD PRD document from an APS plan.
13
+ *
14
+ * @param plan - APS plan to serialize
15
+ * @returns BMAD markdown content
16
+ */
17
+ export function serializeToBMAD(plan: APSPlan): string {
18
+ const lines: string[] = [];
19
+
20
+ // Generate YAML front-matter
21
+ lines.push('---');
22
+ lines.push('name: "Product Requirements Document"');
23
+ lines.push(`version: "${plan.provenance.version || '1.0.0'}"`);
24
+ lines.push(`date: "${plan.provenance.timestamp}"`);
25
+ lines.push(`author: "${plan.provenance.author || 'unknown'}"`);
26
+ lines.push('description: "Generated from Anvil Plan Specification"');
27
+ lines.push('---');
28
+ lines.push('');
29
+
30
+ // Generate document header
31
+ const projectName = plan.metadata?.['projectName'] || 'Project';
32
+ lines.push(`# ${projectName} - Product Requirements Document`);
33
+ lines.push('');
34
+ lines.push(`**Author:** ${plan.provenance.author || 'unknown'}`);
35
+ lines.push(`**Date:** ${plan.provenance.timestamp}`);
36
+ lines.push(`**Version:** ${plan.provenance.version || '1.0.0'}`);
37
+ lines.push('');
38
+
39
+ // Generate change log
40
+ lines.push('## Change Log');
41
+ lines.push('');
42
+ lines.push('| Date | Version | Description | Author |');
43
+ lines.push('| :--- | :------ | :---------- | :----- |');
44
+
45
+ const date = new Date(plan.provenance.timestamp).toISOString().split('T')[0];
46
+ const version = plan.provenance.version || '1.0.0';
47
+ const author = plan.provenance.author || 'unknown';
48
+
49
+ lines.push(`| ${date} | ${version} | Initial version | ${author} |`);
50
+ lines.push('');
51
+
52
+ // Generate overview/intent section
53
+ lines.push('## Overview');
54
+ lines.push('');
55
+ lines.push(plan.intent);
56
+ lines.push('');
57
+
58
+ // Categorize changes
59
+ const functionalRequirements: Change[] = [];
60
+ const nonFunctionalRequirements: Change[] = [];
61
+ const userStories: Change[] = [];
62
+
63
+ for (const change of plan.proposed_changes) {
64
+ // Categorize based on change type and path
65
+ if (change.path.includes('stories/') || change.description.match(/^US-\d{2}/)) {
66
+ userStories.push(change);
67
+ } else if (
68
+ change.type === 'config_update' ||
69
+ change.type === 'dependency_add' ||
70
+ change.type === 'dependency_update' ||
71
+ change.description.match(/^NFR-\d{2}/)
72
+ ) {
73
+ nonFunctionalRequirements.push(change);
74
+ } else {
75
+ functionalRequirements.push(change);
76
+ }
77
+ }
78
+
79
+ // Generate Functional Requirements section
80
+ if (functionalRequirements.length > 0) {
81
+ lines.push('## Functional Requirements');
82
+ lines.push('');
83
+
84
+ functionalRequirements.forEach((change, index) => {
85
+ const reqNum = String(index + 1).padStart(2, '0');
86
+ const reqId = `FR-${reqNum}`;
87
+
88
+ // Extract description (remove existing FR-XX if present)
89
+ let description = change.description.replace(/^FR-\d{2}:\s*/, '');
90
+
91
+ // Add path context if not already in description
92
+ if (!description.includes(change.path)) {
93
+ description += ` (${change.path})`;
94
+ }
95
+
96
+ lines.push(`${reqId}: ${description}`);
97
+ });
98
+
99
+ lines.push('');
100
+ }
101
+
102
+ // Generate Non-Functional Requirements section
103
+ if (nonFunctionalRequirements.length > 0) {
104
+ lines.push('## Non-Functional Requirements');
105
+ lines.push('');
106
+
107
+ nonFunctionalRequirements.forEach((change, index) => {
108
+ const reqNum = String(index + 1).padStart(2, '0');
109
+ const reqId = `NFR-${reqNum}`;
110
+
111
+ // Extract description
112
+ const description = change.description.replace(/^NFR-\d{2}:\s*/, '');
113
+
114
+ lines.push(`${reqId}: ${description}`);
115
+ });
116
+
117
+ lines.push('');
118
+ }
119
+
120
+ // Add validation requirements as NFRs
121
+ if (plan.validations.required_checks.length > 0) {
122
+ if (nonFunctionalRequirements.length === 0) {
123
+ lines.push('## Non-Functional Requirements');
124
+ lines.push('');
125
+ }
126
+
127
+ const startIndex = nonFunctionalRequirements.length + 1;
128
+ plan.validations.required_checks.forEach((check, index) => {
129
+ const reqNum = String(startIndex + index).padStart(2, '0');
130
+ const reqId = `NFR-${reqNum}`;
131
+ lines.push(`${reqId}: Must pass ${check} validation`);
132
+ });
133
+
134
+ lines.push('');
135
+ }
136
+
137
+ // Generate User Stories section
138
+ if (userStories.length > 0) {
139
+ lines.push('## User Stories');
140
+ lines.push('');
141
+
142
+ userStories.forEach((story, index) => {
143
+ const storyNum = String(index + 1).padStart(2, '0');
144
+ const storyId = `US-${storyNum}`;
145
+
146
+ // Extract story title
147
+ let title = story.description.replace(/^US-\d{2}:\s*/, '');
148
+
149
+ // Check if description contains "As a... I want... so that..." pattern
150
+ const storyMatch = title.match(/\(As a (.+?), I want (.+?), so that (.+?)\)/);
151
+
152
+ if (storyMatch) {
153
+ const [, userType, action, benefit] = storyMatch;
154
+ title = title.replace(/\(As a .+?\)/, '').trim();
155
+
156
+ lines.push(`### ${storyId}: ${title}`);
157
+ lines.push('');
158
+ lines.push(`As a ${userType},`);
159
+ lines.push(`I want ${action},`);
160
+ lines.push(`so that ${benefit}.`);
161
+ lines.push('');
162
+
163
+ // Extract acceptance criteria if present
164
+ const criteriaMatch = title.match(/- Acceptance criteria: (.+)$/);
165
+ if (criteriaMatch) {
166
+ const criteria = criteriaMatch[1].split(';').map((c) => c.trim());
167
+ lines.push('**Acceptance Criteria:**');
168
+ lines.push('');
169
+ criteria.forEach((criterion, i) => {
170
+ lines.push(`${i + 1}. ${criterion}`);
171
+ });
172
+ lines.push('');
173
+ }
174
+ } else {
175
+ lines.push(`### ${storyId}: ${title}`);
176
+ lines.push('');
177
+ }
178
+ });
179
+ }
180
+
181
+ // Add repository information if available
182
+ if (plan.provenance.repository || plan.provenance.branch) {
183
+ lines.push('## Repository Information');
184
+ lines.push('');
185
+
186
+ if (plan.provenance.repository) {
187
+ lines.push(`**Repository:** ${plan.provenance.repository}`);
188
+ }
189
+ if (plan.provenance.branch) {
190
+ lines.push(`**Branch:** ${plan.provenance.branch}`);
191
+ }
192
+ if (plan.provenance.commit) {
193
+ lines.push(`**Commit:** ${plan.provenance.commit}`);
194
+ }
195
+
196
+ lines.push('');
197
+ }
198
+
199
+ // Add footer
200
+ lines.push('---');
201
+ lines.push('');
202
+ lines.push('*Generated by Anvil - https://github.com/EddaCraft/anvil-001*');
203
+ lines.push('');
204
+
205
+ return lines.join('\n');
206
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * BMAD Format Types
3
+ *
4
+ * Type definitions for BMAD (Breakthrough Method for Agile AI-Driven Development) format.
5
+ */
6
+
7
+ /**
8
+ * BMAD v6 folder structure constants
9
+ *
10
+ * v6 changed `.bmad` → `_bmad` and `_cfg` → `_config`.
11
+ * We support both legacy and new paths for backward compatibility.
12
+ */
13
+ export const BMAD_FOLDERS = {
14
+ /** New v6 project folder */
15
+ PROJECT: '_bmad',
16
+ /** Legacy project folder */
17
+ PROJECT_LEGACY: '.bmad',
18
+ /** New v6 config folder */
19
+ CONFIG: '_config',
20
+ /** Legacy config folder */
21
+ CONFIG_LEGACY: '_cfg',
22
+ /** Agent memory folder (v6) */
23
+ MEMORY: '_memory',
24
+ /** Module config file (v6) */
25
+ MODULE_CONFIG: 'module.yaml',
26
+ } as const;
27
+
28
+ /**
29
+ * YAML front-matter metadata
30
+ */
31
+ export interface BMADFrontMatter {
32
+ name?: string;
33
+ version?: string;
34
+ description?: string;
35
+ output_file?: string;
36
+ variables?: Record<string, string>;
37
+ template?: string;
38
+ date?: string;
39
+ author?: string;
40
+ /** v6: Whether the agent document has a sidecar config */
41
+ hasSidecar?: boolean;
42
+ }
43
+
44
+ /**
45
+ * BMAD document types
46
+ */
47
+ export enum BMADDocumentType {
48
+ PRD = 'prd',
49
+ ARCHITECTURE = 'architecture',
50
+ EPIC = 'epic',
51
+ STORY = 'story',
52
+ /** v6: Agent persona/configuration document */
53
+ AGENT = 'agent',
54
+ UNKNOWN = 'unknown',
55
+ }
56
+
57
+ /**
58
+ * Requirement types
59
+ */
60
+ export enum RequirementType {
61
+ FUNCTIONAL = 'FR',
62
+ NON_FUNCTIONAL = 'NFR',
63
+ USER_STORY = 'US',
64
+ }
65
+
66
+ /**
67
+ * Parsed requirement
68
+ */
69
+ export interface BMADRequirement {
70
+ type: RequirementType;
71
+ id: string;
72
+ number: number;
73
+ description: string;
74
+ line?: number;
75
+ }
76
+
77
+ /**
78
+ * User story structure
79
+ */
80
+ export interface BMADUserStory {
81
+ id: string;
82
+ title: string;
83
+ userType?: string;
84
+ action?: string;
85
+ benefit?: string;
86
+ acceptanceCriteria?: string[];
87
+ line?: number;
88
+ }
89
+
90
+ /**
91
+ * Change log entry
92
+ */
93
+ export interface BMADChangeLogEntry {
94
+ date: string;
95
+ version: string;
96
+ description: string;
97
+ author: string;
98
+ }
99
+
100
+ /**
101
+ * Parsed BMAD document
102
+ */
103
+ export interface BMADDocument {
104
+ type: BMADDocumentType;
105
+ frontMatter?: BMADFrontMatter;
106
+ title?: string;
107
+ intent?: string;
108
+ requirements: BMADRequirement[];
109
+ userStories: BMADUserStory[];
110
+ changeLog: BMADChangeLogEntry[];
111
+ sections: Map<string, string>;
112
+ raw: string;
113
+ }
114
+
115
+ /**
116
+ * Detection indicators for confidence scoring
117
+ */
118
+ export interface DetectionIndicators {
119
+ hasYamlFrontMatter: boolean;
120
+ hasFunctionalRequirements: boolean;
121
+ hasNonFunctionalRequirements: boolean;
122
+ hasUserStories: boolean;
123
+ hasUserStoryFormat: boolean;
124
+ hasChangeLogTable: boolean;
125
+ hasDocumentTitle: boolean;
126
+ requirementCount: number;
127
+ /** v6: Path is inside a BMAD project folder */
128
+ hasBmadFolderPath: boolean;
129
+ /** v6: Path is inside a BMAD config folder */
130
+ hasBmadConfigPath: boolean;
131
+ /** v6: Document has hasSidecar field */
132
+ hasHasSidecar: boolean;
133
+ /** v6: Document uses hyphenated variable syntax */
134
+ hasHyphenatedVariables: boolean;
135
+ }