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