@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,306 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import type {
4
+ AdapterError,
5
+ AdapterWarning,
6
+ ParseResult,
7
+ SerializeResult,
8
+ DetectionResult,
9
+ } from './types.js';
10
+
11
+ export function generateDeterministicPlanId(content: string): string {
12
+ const hash = createHash('sha256').update(content, 'utf8').digest('hex');
13
+ return `aps-${hash.substring(0, 8)}`;
14
+ }
15
+
16
+ /**
17
+ * Create a detection result
18
+ *
19
+ * @param detected - Whether format was detected
20
+ * @param confidence - Confidence score (0-100)
21
+ * @param reason - Optional reason
22
+ * @returns Detection result
23
+ */
24
+ export function createDetection(
25
+ detected: boolean,
26
+ confidence: number,
27
+ reason?: string
28
+ ): DetectionResult {
29
+ return {
30
+ detected,
31
+ confidence: Math.max(0, Math.min(100, confidence)),
32
+ reason,
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Create an adapter error
38
+ *
39
+ * @param code - Error code
40
+ * @param message - Error message
41
+ * @param options - Additional options
42
+ * @returns Adapter error
43
+ */
44
+ export function createError(
45
+ code: string,
46
+ message: string,
47
+ options?: {
48
+ path?: string;
49
+ line?: number;
50
+ column?: number;
51
+ details?: unknown;
52
+ }
53
+ ): AdapterError {
54
+ return {
55
+ code,
56
+ message,
57
+ ...options,
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Create an adapter warning
63
+ *
64
+ * @param code - Warning code
65
+ * @param message - Warning message
66
+ * @param options - Additional options
67
+ * @returns Adapter warning
68
+ */
69
+ export function createWarning(
70
+ code: string,
71
+ message: string,
72
+ options?: {
73
+ path?: string;
74
+ line?: number;
75
+ column?: number;
76
+ details?: unknown;
77
+ }
78
+ ): AdapterWarning {
79
+ return {
80
+ code,
81
+ message,
82
+ ...options,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Check if content matches a pattern
88
+ *
89
+ * Useful for format detection.
90
+ *
91
+ * @param content - Content to check
92
+ * @param patterns - Array of regex patterns or strings
93
+ * @returns Number of patterns matched (0 to patterns.length)
94
+ */
95
+ export function matchesPatterns(content: string, patterns: Array<string | RegExp>): number {
96
+ let matches = 0;
97
+ for (const pattern of patterns) {
98
+ if (typeof pattern === 'string') {
99
+ if (content.includes(pattern)) {
100
+ matches++;
101
+ }
102
+ } else {
103
+ if (pattern.test(content)) {
104
+ matches++;
105
+ }
106
+ }
107
+ }
108
+ return matches;
109
+ }
110
+
111
+ /**
112
+ * Calculate confidence score based on pattern matches
113
+ *
114
+ * @param matchCount - Number of patterns matched
115
+ * @param totalPatterns - Total number of patterns checked
116
+ * @param requiredMatches - Minimum matches for 100% confidence
117
+ * @returns Confidence score (0-100)
118
+ */
119
+ export function calculateConfidence(
120
+ matchCount: number,
121
+ totalPatterns: number,
122
+ requiredMatches: number = totalPatterns
123
+ ): number {
124
+ if (matchCount === 0) return 0;
125
+ if (matchCount >= requiredMatches) return 100;
126
+
127
+ // Linear interpolation
128
+ return Math.round((matchCount / requiredMatches) * 100);
129
+ }
130
+
131
+ /**
132
+ * Extract file extension from filename or path
133
+ *
134
+ * @param filename - Filename or path
135
+ * @returns Extension (including dot) or empty string
136
+ */
137
+ export function getExtension(filename: string): string {
138
+ const match = filename.match(/\.[^.]+$/);
139
+ return match ? match[0] : '';
140
+ }
141
+
142
+ /**
143
+ * Normalize format identifier
144
+ *
145
+ * Removes leading dot and converts to lowercase.
146
+ *
147
+ * @param format - Format identifier or extension
148
+ * @returns Normalized format
149
+ */
150
+ export function normalizeFormat(format: string): string {
151
+ return format.toLowerCase().replace(/^\./, '');
152
+ }
153
+
154
+ /**
155
+ * Format errors for display
156
+ *
157
+ * @param errors - Array of errors
158
+ * @returns Formatted error string
159
+ */
160
+ export function formatErrors(errors: AdapterError[]): string {
161
+ return errors
162
+ .map((error) => {
163
+ let msg = `[${error.code}] ${error.message}`;
164
+ if (error.path) {
165
+ msg += ` (at ${error.path}`;
166
+ if (error.line !== undefined) {
167
+ msg += `:${error.line}`;
168
+ if (error.column !== undefined) {
169
+ msg += `:${error.column}`;
170
+ }
171
+ }
172
+ msg += ')';
173
+ }
174
+ return msg;
175
+ })
176
+ .join('\n');
177
+ }
178
+
179
+ /**
180
+ * Format warnings for display
181
+ *
182
+ * @param warnings - Array of warnings
183
+ * @returns Formatted warning string
184
+ */
185
+ export function formatWarnings(warnings: AdapterWarning[]): string {
186
+ return warnings
187
+ .map((warning) => {
188
+ let msg = `[${warning.code}] ${warning.message}`;
189
+ if (warning.path) {
190
+ msg += ` (at ${warning.path}`;
191
+ if (warning.line !== undefined) {
192
+ msg += `:${warning.line}`;
193
+ if (warning.column !== undefined) {
194
+ msg += `:${warning.column}`;
195
+ }
196
+ }
197
+ msg += ')';
198
+ }
199
+ return msg;
200
+ })
201
+ .join('\n');
202
+ }
203
+
204
+ /**
205
+ * Merge parse results
206
+ *
207
+ * Useful when parsing composite formats.
208
+ *
209
+ * @param results - Array of parse results
210
+ * @returns Merged result
211
+ */
212
+ export function mergeParseResults(results: ParseResult[]): ParseResult {
213
+ if (results.length === 0) {
214
+ return {
215
+ success: false,
216
+ errors: [createError('NO_RESULTS', 'No parse results to merge')],
217
+ };
218
+ }
219
+
220
+ if (results.length === 1) {
221
+ return results[0];
222
+ }
223
+
224
+ const allErrors: AdapterError[] = [];
225
+ const allWarnings: AdapterWarning[] = [];
226
+ let finalData = results[0].data;
227
+
228
+ for (const result of results) {
229
+ if (!result.success) {
230
+ if (result.errors) {
231
+ allErrors.push(...result.errors);
232
+ }
233
+ }
234
+ if (result.warnings) {
235
+ allWarnings.push(...result.warnings);
236
+ }
237
+ if (result.data) {
238
+ finalData = result.data;
239
+ }
240
+ }
241
+
242
+ if (allErrors.length > 0) {
243
+ return {
244
+ success: false,
245
+ errors: allErrors,
246
+ warnings: allWarnings.length > 0 ? allWarnings : undefined,
247
+ };
248
+ }
249
+
250
+ return {
251
+ success: true,
252
+ data: finalData,
253
+ warnings: allWarnings.length > 0 ? allWarnings : undefined,
254
+ };
255
+ }
256
+
257
+ /**
258
+ * Check if result has errors
259
+ *
260
+ * @param result - Parse or serialize result
261
+ * @returns True if result has errors
262
+ */
263
+ export function hasErrors(result: ParseResult | SerializeResult): boolean {
264
+ return !result.success || (result.errors !== undefined && result.errors.length > 0);
265
+ }
266
+
267
+ /**
268
+ * Check if result has warnings
269
+ *
270
+ * @param result - Parse or serialize result
271
+ * @returns True if result has warnings
272
+ */
273
+ export function hasWarnings(result: ParseResult | SerializeResult): boolean {
274
+ return result.warnings !== undefined && result.warnings.length > 0;
275
+ }
276
+
277
+ /**
278
+ * Extract first line from content (useful for format detection)
279
+ *
280
+ * @param content - Content string
281
+ * @returns First non-empty line or empty string
282
+ */
283
+ export function getFirstLine(content: string): string {
284
+ const lines = content.split('\n');
285
+ for (const line of lines) {
286
+ const trimmed = line.trim();
287
+ if (trimmed) {
288
+ return trimmed;
289
+ }
290
+ }
291
+ return '';
292
+ }
293
+
294
+ /**
295
+ * Count occurrences of pattern in content
296
+ *
297
+ * @param content - Content to search
298
+ * @param pattern - Pattern to count
299
+ * @returns Number of occurrences
300
+ */
301
+ export function countOccurrences(content: string, pattern: string | RegExp): number {
302
+ if (typeof pattern === 'string') {
303
+ return (content.match(new RegExp(pattern, 'g')) || []).length;
304
+ }
305
+ return (content.match(new RegExp(pattern.source, 'g')) || []).length;
306
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * BMAD Format Adapter
3
+ *
4
+ * FormatAdapter implementation for BMAD (Breakthrough Method for Agile AI-Driven Development) format.
5
+ * Handles PRD, Architecture, Epic, Story, and Agent documents.
6
+ */
7
+
8
+ import { generateHash, type APSPlan, type ValidationResult } from '@eddacraft/anvil-core';
9
+ import {
10
+ BaseFormatAdapter,
11
+ type AdapterMetadata,
12
+ type DetectionResult,
13
+ type ParseResult,
14
+ type SerializeResult,
15
+ type ParseContext,
16
+ type AdapterOptions,
17
+ type PathDetectionHint,
18
+ } from '../base/types.js';
19
+ import { createDetection } from '../base/utils.js';
20
+ import {
21
+ analyzeContent,
22
+ calculateConfidenceScore,
23
+ buildDetectionReason,
24
+ extractFrontMatter,
25
+ identifyDocumentType,
26
+ } from './utils.js';
27
+ import { BMADDocumentType } from './types.js';
28
+ import { parseBMAD } from './parser.js';
29
+ import { serializeToBMAD } from './serializer.js';
30
+
31
+ /**
32
+ * BMAD FormatAdapter implementation
33
+ *
34
+ * Converts between BMAD format documents and APS plans.
35
+ */
36
+ export class BMADFormatAdapter extends BaseFormatAdapter {
37
+ readonly metadata: AdapterMetadata = {
38
+ name: 'bmad',
39
+ version: '1.0.0',
40
+ displayName: 'BMAD (Breakthrough Method for Agile AI-Driven Development)',
41
+ description: 'BMAD PRD and architecture document adapter',
42
+ formats: ['bmad', 'prd', 'architecture'],
43
+ extensions: ['.md'],
44
+ };
45
+
46
+ /**
47
+ * Detect if content is BMAD format
48
+ *
49
+ * Uses confidence scoring based on multiple indicators:
50
+ * - YAML front-matter (30 points)
51
+ * - Requirement identifiers FR/NFR/US (25 points)
52
+ * - User story format (20 points)
53
+ * - Change log table (15 points)
54
+ * - Document title (10 points)
55
+ *
56
+ * @param content - Document content to analyze
57
+ * @returns Detection result with confidence score
58
+ */
59
+ detect(content: string): DetectionResult {
60
+ const indicators = analyzeContent(content);
61
+ const confidence = calculateConfidenceScore(indicators);
62
+ const reason = buildDetectionReason(indicators);
63
+
64
+ // Detection threshold: 50% confidence
65
+ return createDetection(confidence >= 50, confidence, reason);
66
+ }
67
+
68
+ /**
69
+ * Detect with file path hints for improved accuracy
70
+ *
71
+ * Uses folder structure (e.g., `_bmad/`, `.bmad/`, `_config/`)
72
+ * to boost detection confidence.
73
+ *
74
+ * @param content - Document content to analyze
75
+ * @param hint - Path and directory information
76
+ * @returns Detection result with confidence score
77
+ */
78
+ detectWithPath(content: string, hint: PathDetectionHint): DetectionResult {
79
+ const indicators = analyzeContent(content, hint);
80
+ const confidence = calculateConfidenceScore(indicators);
81
+ const reason = buildDetectionReason(indicators);
82
+
83
+ return createDetection(confidence >= 50, confidence, reason);
84
+ }
85
+
86
+ /**
87
+ * Parse BMAD content to APS plan
88
+ *
89
+ * @param content - BMAD markdown content
90
+ * @param context - Parse context for provenance
91
+ * @param options - Adapter options
92
+ * @returns Parse result with APS plan
93
+ */
94
+ async parse(
95
+ content: string,
96
+ context?: ParseContext,
97
+ _options?: AdapterOptions
98
+ ): Promise<ParseResult> {
99
+ try {
100
+ // Parse content to APS plan
101
+ const plan = parseBMAD(content, context);
102
+
103
+ // Generate hash for the plan
104
+ const planWithHash = {
105
+ ...plan,
106
+ hash: generateHash(plan),
107
+ };
108
+
109
+ return this.createParseSuccess(planWithHash);
110
+ } catch (error) {
111
+ return this.createParseError([
112
+ {
113
+ code: 'PARSE_ERROR',
114
+ message: error instanceof Error ? error.message : 'Failed to parse BMAD content',
115
+ details: error,
116
+ },
117
+ ]);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Serialize APS plan to BMAD format
123
+ *
124
+ * @param plan - APS plan to serialize
125
+ * @param options - Adapter options
126
+ * @returns Serialize result with BMAD markdown
127
+ */
128
+ async serialize(plan: APSPlan, _options?: AdapterOptions): Promise<SerializeResult> {
129
+ try {
130
+ const content = serializeToBMAD(plan);
131
+ return this.createSerializeSuccess(content);
132
+ } catch (error) {
133
+ return this.createSerializeError([
134
+ {
135
+ code: 'SERIALIZE_ERROR',
136
+ message: error instanceof Error ? error.message : 'Failed to serialize to BMAD format',
137
+ details: error,
138
+ },
139
+ ]);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Validate BMAD content
145
+ *
146
+ * Checks for required BMAD elements without full conversion.
147
+ *
148
+ * @param content - BMAD content to validate
149
+ * @param options - Validation options
150
+ * @returns Validation result
151
+ */
152
+ async validate(content: string, _options?: AdapterOptions): Promise<ValidationResult> {
153
+ const issues: Array<{
154
+ path: string;
155
+ message: string;
156
+ code: string;
157
+ severity: 'error' | 'warning';
158
+ }> = [];
159
+
160
+ // Check for minimum content length
161
+ if (content.trim().length < 100) {
162
+ issues.push({
163
+ code: 'CONTENT_TOO_SHORT',
164
+ path: 'content',
165
+ message: 'Content is too short to be a valid BMAD document',
166
+ severity: 'error',
167
+ });
168
+ }
169
+
170
+ // Analyze content for BMAD indicators
171
+ const indicators = analyzeContent(content);
172
+ const confidence = calculateConfidenceScore(indicators);
173
+
174
+ // Low confidence suggests invalid BMAD format
175
+ if (confidence < 50) {
176
+ issues.push({
177
+ code: 'LOW_CONFIDENCE',
178
+ path: 'content',
179
+ message: `Content does not appear to be a valid BMAD document (confidence: ${confidence}%)`,
180
+ severity: 'error',
181
+ });
182
+ }
183
+
184
+ // Check for at least some requirements or stories
185
+ if (indicators.requirementCount === 0) {
186
+ issues.push({
187
+ code: 'NO_REQUIREMENTS',
188
+ path: 'content',
189
+ message: 'No requirements (FR/NFR/US) found in document',
190
+ severity: 'warning',
191
+ });
192
+ }
193
+
194
+ // v6: Warn on agent docs missing hasSidecar
195
+ const frontMatter = extractFrontMatter(content);
196
+ const docType = identifyDocumentType(content, frontMatter);
197
+ if (docType === BMADDocumentType.AGENT && frontMatter?.hasSidecar === undefined) {
198
+ issues.push({
199
+ code: 'MISSING_HAS_SIDECAR',
200
+ path: 'frontMatter.hasSidecar',
201
+ message: 'Agent document is missing the hasSidecar field (expected for BMAD v6)',
202
+ severity: 'warning',
203
+ });
204
+ }
205
+
206
+ return {
207
+ valid: issues.filter((i) => i.severity === 'error').length === 0,
208
+ issues: issues.length > 0 ? issues : undefined,
209
+ summary:
210
+ issues.filter((i) => i.severity === 'error').length === 0 && issues.length === 0
211
+ ? 'BMAD document is valid'
212
+ : issues.filter((i) => i.severity === 'error').length === 0
213
+ ? `Valid with ${issues.length} warning${issues.length > 1 ? 's' : ''}`
214
+ : `Found ${issues.filter((i) => i.severity === 'error').length} error${issues.filter((i) => i.severity === 'error').length > 1 ? 's' : ''}`,
215
+ };
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Create a new BMAD format adapter instance
221
+ *
222
+ * @param options - Adapter options
223
+ * @returns BMAD adapter instance
224
+ */
225
+ export function createBMADAdapter(options?: AdapterOptions): BMADFormatAdapter {
226
+ return new BMADFormatAdapter(options);
227
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * BMAD Adapter Module
3
+ *
4
+ * Export all BMAD adapter functionality.
5
+ */
6
+
7
+ export { BMADFormatAdapter, createBMADAdapter } from './format-adapter.js';
8
+ export { BMAD_FOLDERS } from './types.js';
9
+ export type {
10
+ BMADDocument,
11
+ BMADDocumentType,
12
+ BMADRequirement,
13
+ BMADUserStory,
14
+ BMADFrontMatter,
15
+ BMADChangeLogEntry,
16
+ RequirementType,
17
+ DetectionIndicators,
18
+ } from './types.js';
19
+ export { parseBMAD, parseBMADDocument, bmadToAPS } from './parser.js';
20
+ export { serializeToBMAD } from './serializer.js';
21
+ export * from './utils.js';