@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,84 @@
1
+ import type { APSPlan, ValidationResult } from '@eddacraft/anvil-core';
2
+
3
+ export interface SpecContext {
4
+ repositoryPath?: string;
5
+ branch?: string;
6
+ commit?: string;
7
+ author?: string;
8
+ timestamp?: string;
9
+ additionalContext?: Record<string, unknown>;
10
+ }
11
+
12
+ export interface ExternalSpec {
13
+ format: string;
14
+ version: string;
15
+ content: unknown;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+
19
+ export interface ConversionResult<T = unknown> {
20
+ success: boolean;
21
+ data?: T;
22
+ errors?: ConversionError[];
23
+ warnings?: ConversionWarning[];
24
+ }
25
+
26
+ export interface ConversionError {
27
+ code: string;
28
+ message: string;
29
+ path?: string;
30
+ line?: number;
31
+ column?: number;
32
+ details?: unknown;
33
+ }
34
+
35
+ export interface ConversionWarning {
36
+ code: string;
37
+ message: string;
38
+ path?: string;
39
+ line?: number;
40
+ column?: number;
41
+ details?: unknown;
42
+ }
43
+
44
+ export interface SpecToolAdapter {
45
+ readonly name: string;
46
+ readonly version: string;
47
+ readonly supportedFormats: readonly string[];
48
+
49
+ generateSpec(intent: string, context: SpecContext): Promise<APSPlan>;
50
+ validateSpec(spec: APSPlan): Promise<ValidationResult>;
51
+ convertToAPS(spec: ExternalSpec): Promise<ConversionResult<APSPlan>>;
52
+ convertFromAPS(spec: APSPlan): Promise<ConversionResult<ExternalSpec>>;
53
+
54
+ canImport(format: string): boolean;
55
+ canExport(format: string): boolean;
56
+ }
57
+
58
+ export interface AdapterConfig {
59
+ preserveComments?: boolean;
60
+ preserveMetadata?: boolean;
61
+ strictMode?: boolean;
62
+ formatOptions?: Record<string, unknown>;
63
+ }
64
+
65
+ export abstract class BaseAdapter implements SpecToolAdapter {
66
+ abstract readonly name: string;
67
+ abstract readonly version: string;
68
+ abstract readonly supportedFormats: readonly string[];
69
+
70
+ constructor(protected config: AdapterConfig = {}) {}
71
+
72
+ abstract generateSpec(intent: string, context: SpecContext): Promise<APSPlan>;
73
+ abstract validateSpec(spec: APSPlan): Promise<ValidationResult>;
74
+ abstract convertToAPS(spec: ExternalSpec): Promise<ConversionResult<APSPlan>>;
75
+ abstract convertFromAPS(spec: APSPlan): Promise<ConversionResult<ExternalSpec>>;
76
+
77
+ canImport(format: string): boolean {
78
+ return this.supportedFormats.includes(format);
79
+ }
80
+
81
+ canExport(format: string): boolean {
82
+ return this.supportedFormats.includes(format);
83
+ }
84
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Generic Serializer Tests
3
+ * Tests for type-safe serialization of APS plans
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import { serializeToGeneric } from '../serializer.js';
8
+ import type { APSPlan } from '@eddacraft/anvil-core';
9
+
10
+ describe('serializeToGeneric', () => {
11
+ const basePlan: APSPlan = {
12
+ id: 'aps-test123',
13
+ schema_version: '0.1.0',
14
+ hash: 'test-hash',
15
+ intent: 'Test plan',
16
+ proposed_changes: [],
17
+ provenance: {
18
+ timestamp: '2024-01-01T00:00:00Z',
19
+ author: 'test@example.com',
20
+ source: 'cli',
21
+ version: '1.0.0',
22
+ },
23
+ validations: {
24
+ required_checks: [],
25
+ skip_checks: [],
26
+ },
27
+ evidence: [],
28
+ executions: [],
29
+ };
30
+
31
+ it('should handle plan with no metadata', () => {
32
+ const result = serializeToGeneric(basePlan);
33
+
34
+ expect(result).toContain('# Test plan');
35
+ expect(result).toContain('## Purpose');
36
+ });
37
+
38
+ it('should handle plan with string title metadata', () => {
39
+ const plan = {
40
+ ...basePlan,
41
+ metadata: {
42
+ title: 'Custom Title',
43
+ },
44
+ };
45
+
46
+ const result = serializeToGeneric(plan);
47
+
48
+ expect(result).toContain('# Custom Title');
49
+ });
50
+
51
+ it('should handle plan with non-string title metadata gracefully', () => {
52
+ const plan = {
53
+ ...basePlan,
54
+ metadata: {
55
+ title: 123 as unknown as string, // Invalid type
56
+ },
57
+ };
58
+
59
+ const result = serializeToGeneric(plan);
60
+
61
+ // Should fall back to intent
62
+ expect(result).toContain('# Test plan');
63
+ });
64
+
65
+ it('should handle plan with string overview metadata', () => {
66
+ const plan = {
67
+ ...basePlan,
68
+ metadata: {
69
+ overview: 'This is an overview',
70
+ },
71
+ };
72
+
73
+ const result = serializeToGeneric(plan);
74
+
75
+ expect(result).toContain('## Overview');
76
+ expect(result).toContain('This is an overview');
77
+ });
78
+
79
+ it('should handle plan with non-string overview metadata gracefully', () => {
80
+ const plan = {
81
+ ...basePlan,
82
+ metadata: {
83
+ overview: { text: 'overview' } as unknown as string, // Invalid type
84
+ },
85
+ };
86
+
87
+ const result = serializeToGeneric(plan);
88
+
89
+ // Should fall back to purpose section
90
+ expect(result).toContain('## Purpose');
91
+ expect(result).not.toContain('## Overview');
92
+ });
93
+
94
+ it('should handle plan with string array goals', () => {
95
+ const plan = {
96
+ ...basePlan,
97
+ metadata: {
98
+ goals: ['Goal 1', 'Goal 2', 'Goal 3'],
99
+ },
100
+ };
101
+
102
+ const result = serializeToGeneric(plan);
103
+
104
+ expect(result).toContain('## Goals');
105
+ expect(result).toContain('- Goal 1');
106
+ expect(result).toContain('- Goal 2');
107
+ expect(result).toContain('- Goal 3');
108
+ });
109
+
110
+ it('should handle plan with non-string array goals gracefully', () => {
111
+ const plan = {
112
+ ...basePlan,
113
+ metadata: {
114
+ goals: [1, 2, 3] as unknown as string[], // Invalid type
115
+ },
116
+ };
117
+
118
+ const result = serializeToGeneric(plan);
119
+
120
+ // Should not include goals section
121
+ expect(result).not.toContain('## Goals');
122
+ });
123
+
124
+ it('should handle plan with mixed-type goals array gracefully', () => {
125
+ const plan = {
126
+ ...basePlan,
127
+ metadata: {
128
+ goals: ['Goal 1', 123, 'Goal 2'] as unknown as string[], // Mixed types
129
+ },
130
+ };
131
+
132
+ const result = serializeToGeneric(plan);
133
+
134
+ // Should not include goals section since not all elements are strings
135
+ expect(result).not.toContain('## Goals');
136
+ });
137
+
138
+ it('should handle plan with all metadata types', () => {
139
+ const plan = {
140
+ ...basePlan,
141
+ metadata: {
142
+ title: 'Full Plan',
143
+ overview: 'Complete overview',
144
+ goals: ['Goal A', 'Goal B'],
145
+ },
146
+ proposed_changes: [
147
+ {
148
+ type: 'file_create' as const,
149
+ path: 'test.ts',
150
+ description: 'Create test file',
151
+ },
152
+ ],
153
+ };
154
+
155
+ const result = serializeToGeneric(plan);
156
+
157
+ expect(result).toContain('# Full Plan');
158
+ expect(result).toContain('## Overview');
159
+ expect(result).toContain('Complete overview');
160
+ expect(result).toContain('## Goals');
161
+ expect(result).toContain('- Goal A');
162
+ expect(result).toContain('- Goal B');
163
+ expect(result).toContain('## Changes');
164
+ expect(result).toContain('### Files to Create');
165
+ expect(result).toContain('test.ts');
166
+ });
167
+ });
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Generic Markdown Format Adapter
3
+ *
4
+ * FormatAdapter implementation for generic markdown planning documents.
5
+ * Serves as a fallback adapter for documents that don't match specific formats.
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
+ } from '../base/types.js';
18
+ import { createDetection } from '../base/utils.js';
19
+ import { analyzeContent, calculateConfidenceScore, buildDetectionReason } from './utils.js';
20
+ import { parseGeneric } from './parser.js';
21
+ import { serializeToGeneric } from './serializer.js';
22
+
23
+ const MIN_CONTENT_LENGTH = 50;
24
+ const FALLBACK_DETECTION_THRESHOLD = 30;
25
+ const MAX_FALLBACK_CONFIDENCE = 45;
26
+
27
+ /**
28
+ * Generic Markdown FormatAdapter implementation
29
+ *
30
+ * Converts between generic markdown documents and APS plans.
31
+ * Designed to work as a fallback for documents that don't match
32
+ * specific formats like BMAD or SpecKit.
33
+ */
34
+ export class GenericMarkdownAdapter extends BaseFormatAdapter {
35
+ readonly metadata: AdapterMetadata = {
36
+ name: 'generic-markdown',
37
+ version: '1.0.0',
38
+ displayName: 'Generic Markdown',
39
+ description: 'Generic markdown adapter for PRDs, plans, todos, and other planning documents',
40
+ formats: ['generic', 'markdown', 'prd', 'plan', 'todo', 'rfc', 'adr'],
41
+ extensions: ['.md', '.markdown'],
42
+ };
43
+
44
+ /**
45
+ * Detect if content is generic markdown planning document
46
+ *
47
+ * Uses lower confidence threshold (30-40%) to serve as fallback.
48
+ * This adapter should be registered last so specific adapters
49
+ * (BMAD, SpecKit) take precedence.
50
+ *
51
+ * @param content - Document content to analyze
52
+ * @returns Detection result with confidence score
53
+ */
54
+ detect(content: string): DetectionResult {
55
+ const indicators = analyzeContent(content);
56
+ const confidence = calculateConfidenceScore(indicators);
57
+ const reason = buildDetectionReason(indicators);
58
+
59
+ const cappedConfidence = Math.min(MAX_FALLBACK_CONFIDENCE, confidence);
60
+
61
+ return createDetection(
62
+ cappedConfidence >= FALLBACK_DETECTION_THRESHOLD,
63
+ cappedConfidence,
64
+ reason
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Parse generic markdown content to APS plan
70
+ *
71
+ * @param content - Markdown content
72
+ * @param context - Parse context for provenance
73
+ * @param options - Adapter options
74
+ * @returns Parse result with APS plan
75
+ */
76
+ async parse(
77
+ content: string,
78
+ context?: ParseContext,
79
+ _options?: AdapterOptions
80
+ ): Promise<ParseResult> {
81
+ try {
82
+ // Parse content to APS plan
83
+ const plan = parseGeneric(content, context);
84
+
85
+ // Generate hash for the plan
86
+ const planWithHash = {
87
+ ...plan,
88
+ hash: generateHash(plan),
89
+ };
90
+
91
+ return this.createParseSuccess(planWithHash);
92
+ } catch (error) {
93
+ return this.createParseError([
94
+ {
95
+ code: 'PARSE_ERROR',
96
+ message:
97
+ error instanceof Error ? error.message : 'Failed to parse generic markdown content',
98
+ details: error,
99
+ },
100
+ ]);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Serialize APS plan to generic markdown format
106
+ *
107
+ * @param plan - APS plan to serialize
108
+ * @param options - Adapter options
109
+ * @returns Serialize result with markdown content
110
+ */
111
+ async serialize(plan: APSPlan, _options?: AdapterOptions): Promise<SerializeResult> {
112
+ try {
113
+ const content = serializeToGeneric(plan);
114
+ return this.createSerializeSuccess(content);
115
+ } catch (error) {
116
+ return this.createSerializeError([
117
+ {
118
+ code: 'SERIALIZE_ERROR',
119
+ message:
120
+ error instanceof Error
121
+ ? error.message
122
+ : 'Failed to serialize to generic markdown format',
123
+ details: error,
124
+ },
125
+ ]);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Validate generic markdown content
131
+ *
132
+ * Checks for basic markdown structure without strict requirements.
133
+ *
134
+ * @param content - Content to validate
135
+ * @param options - Validation options
136
+ * @returns Validation result
137
+ */
138
+ async validate(content: string, _options?: AdapterOptions): Promise<ValidationResult> {
139
+ const issues: Array<{
140
+ path: string;
141
+ message: string;
142
+ code: string;
143
+ severity: 'error' | 'warning';
144
+ }> = [];
145
+
146
+ if (content.trim().length < MIN_CONTENT_LENGTH) {
147
+ issues.push({
148
+ code: 'CONTENT_TOO_SHORT',
149
+ path: 'content',
150
+ message: 'Content is too short to be a valid planning document',
151
+ severity: 'error',
152
+ });
153
+ }
154
+
155
+ const indicators = analyzeContent(content);
156
+ const confidence = calculateConfidenceScore(indicators);
157
+
158
+ if (confidence < FALLBACK_DETECTION_THRESHOLD) {
159
+ issues.push({
160
+ code: 'LOW_CONFIDENCE',
161
+ path: 'content',
162
+ message: `Content does not appear to be a planning document (confidence: ${confidence}%)`,
163
+ severity: 'error',
164
+ });
165
+ }
166
+
167
+ // Warn if missing common planning sections
168
+ if (
169
+ !indicators.hasRequirementsSection &&
170
+ !indicators.hasTasksSection &&
171
+ !indicators.hasFeaturesSection
172
+ ) {
173
+ issues.push({
174
+ code: 'NO_PLANNING_SECTIONS',
175
+ path: 'content',
176
+ message: 'Document lacks common planning sections (requirements, tasks, or features)',
177
+ severity: 'warning',
178
+ });
179
+ }
180
+
181
+ return {
182
+ valid: issues.filter((i) => i.severity === 'error').length === 0,
183
+ issues: issues.length > 0 ? issues : undefined,
184
+ summary:
185
+ issues.length === 0
186
+ ? 'Generic markdown document is valid'
187
+ : `Found ${issues.length} validation issue${issues.length > 1 ? 's' : ''}`,
188
+ };
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Create a new generic markdown adapter instance
194
+ *
195
+ * @param options - Adapter options
196
+ * @returns Generic markdown adapter instance
197
+ */
198
+ export function createGenericMarkdownAdapter(options?: AdapterOptions): GenericMarkdownAdapter {
199
+ return new GenericMarkdownAdapter(options);
200
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Generic Markdown Adapter
3
+ *
4
+ * Exports for generic markdown format adapter.
5
+ */
6
+
7
+ export { GenericMarkdownAdapter, createGenericMarkdownAdapter } from './format-adapter.js';
8
+ export type { GenericDocument, GenericIndicators } from './types.js';
9
+ export { parseGeneric } from './parser.js';
10
+ export { serializeToGeneric } from './serializer.js';
11
+ // Note: Utils are not exported to avoid conflicts with BMAD utils
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Generic Markdown Parser
3
+ *
4
+ * Parses generic markdown documents into APS plans.
5
+ */
6
+
7
+ import { type APSPlan, type Change, validateRelativePath } from '@eddacraft/anvil-core';
8
+ import type { ParseContext } from '../base/types.js';
9
+ import type { GenericDocument } from './types.js';
10
+ import { generateDeterministicPlanId } from '../base/utils.js';
11
+ import { parseGenericDocument } from './utils.js';
12
+
13
+ /**
14
+ * Convert generic document item to APS change
15
+ */
16
+ function itemToChange(item: string, type: 'requirement' | 'task' | 'feature'): Change {
17
+ // Determine change type based on item type
18
+ let changeType: Change['type'] = 'file_create';
19
+ let pathPrefix = 'src';
20
+
21
+ switch (type) {
22
+ case 'requirement':
23
+ changeType = 'file_create';
24
+ pathPrefix = 'src/features';
25
+ break;
26
+ case 'task':
27
+ changeType = 'file_update';
28
+ pathPrefix = 'src/tasks';
29
+ break;
30
+ case 'feature':
31
+ changeType = 'file_create';
32
+ pathPrefix = 'src/features';
33
+ break;
34
+ }
35
+
36
+ // Generate a reasonable path from the item description
37
+ const slug = item
38
+ .toLowerCase()
39
+ .replace(/[^a-z0-9\s]/g, '')
40
+ .replace(/\s+/g, '-')
41
+ .substring(0, 50);
42
+
43
+ const rawPath = `${pathPrefix}/${slug}.ts`;
44
+ let safePath: string;
45
+ try {
46
+ safePath = validateRelativePath(rawPath);
47
+ } catch {
48
+ safePath = rawPath.replace(/\.{2,}/g, '');
49
+ }
50
+
51
+ return {
52
+ type: changeType,
53
+ path: safePath,
54
+ description: item,
55
+ };
56
+ }
57
+
58
+ export function genericToAPS(
59
+ document: GenericDocument,
60
+ context?: ParseContext,
61
+ originalContent?: string
62
+ ): APSPlan {
63
+ const planId =
64
+ context?.planId ??
65
+ (originalContent
66
+ ? generateDeterministicPlanId(originalContent)
67
+ : `aps-${Date.now().toString(16).substring(0, 8)}`);
68
+
69
+ // Collect all changes from requirements, tasks, and features
70
+ const changes: Change[] = [];
71
+
72
+ // Add requirements as changes
73
+ if (document.requirements && document.requirements.length > 0) {
74
+ changes.push(...document.requirements.map((req) => itemToChange(req, 'requirement')));
75
+ }
76
+
77
+ // Add tasks as changes
78
+ if (document.tasks && document.tasks.length > 0) {
79
+ changes.push(...document.tasks.map((task) => itemToChange(task, 'task')));
80
+ }
81
+
82
+ // Add features as changes
83
+ if (document.features && document.features.length > 0) {
84
+ changes.push(...document.features.map((feat) => itemToChange(feat, 'feature')));
85
+ }
86
+
87
+ // Build intent from document
88
+ const intent =
89
+ document.intent || document.title || document.overview?.substring(0, 200) || 'Generic plan';
90
+
91
+ // Build provenance
92
+ const timestamp = context?.timestamp || new Date().toISOString();
93
+ const author = context?.author || 'unknown';
94
+
95
+ // Create APS plan
96
+ const plan: APSPlan = {
97
+ id: planId,
98
+ schema_version: '0.1.0',
99
+ intent,
100
+ proposed_changes: changes,
101
+ provenance: {
102
+ timestamp,
103
+ author,
104
+ source: 'cli',
105
+ version: '1.0.0',
106
+ repository: context?.repositoryPath,
107
+ branch: context?.branch,
108
+ commit: context?.commit,
109
+ },
110
+ validations: {
111
+ required_checks: ['lint', 'test'],
112
+ skip_checks: [],
113
+ },
114
+ metadata: {
115
+ source_format: 'generic-markdown',
116
+ title: document.title,
117
+ overview: document.overview,
118
+ goals: document.goals,
119
+ },
120
+ hash: '0000000000000000000000000000000000000000000000000000000000000000',
121
+ };
122
+
123
+ return plan;
124
+ }
125
+
126
+ export function parseGeneric(content: string, context?: ParseContext): APSPlan {
127
+ const document = parseGenericDocument(content);
128
+ return genericToAPS(document, context, content);
129
+ }