@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,455 @@
1
+ /**
2
+ * APS Markdown Format Adapter
3
+ *
4
+ * FormatAdapter implementation for Anvil Plan Spec (APS) markdown format.
5
+ * Handles both leaf specs (.aps.md with Tasks section) and index files
6
+ * (.aps.md with Modules section).
7
+ */
8
+
9
+ import {
10
+ type APSPlan,
11
+ type ValidationResult,
12
+ type Change,
13
+ generatePlanId,
14
+ generateHash,
15
+ APS_SCHEMA_VERSION,
16
+ } from '@eddacraft/anvil-core';
17
+ import { parseDocument, type ParsedDocument, type Task } from '@eddacraft/anvil-aps';
18
+ import {
19
+ BaseFormatAdapter,
20
+ type AdapterMetadata,
21
+ type DetectionResult,
22
+ type ParseResult,
23
+ type SerializeResult,
24
+ type ParseContext,
25
+ type AdapterOptions,
26
+ } from '../base/types.js';
27
+ import { createDetection } from '../base/utils.js';
28
+
29
+ /**
30
+ * Detection indicators for APS markdown format
31
+ */
32
+ interface APSMarkdownIndicators {
33
+ /** Has H1 title */
34
+ hasH1Title: boolean;
35
+ /** Has **Scope:** or **ID:** field */
36
+ hasScopeField: boolean;
37
+ /** Has ## Tasks section */
38
+ hasTasksSection: boolean;
39
+ /** Has ## Modules section */
40
+ hasModulesSection: boolean;
41
+ /** Has SCOPE-NNN task ID pattern (e.g., AUTH-001) */
42
+ hasScopeTaskPattern: boolean;
43
+ /** Has **Intent:** field in tasks */
44
+ hasIntentField: boolean;
45
+ /** Has **Path:** with .aps.md links */
46
+ hasAPSModuleLinks: boolean;
47
+ /** Has **Confidence:** field */
48
+ hasConfidenceField: boolean;
49
+ /** Has **Owner:** field */
50
+ hasOwnerField: boolean;
51
+ /** Has **Priority:** field */
52
+ hasPriorityField: boolean;
53
+ /** Has **Packages:** field (monorepo support) */
54
+ hasPackagesField: boolean;
55
+ /** Count of SCOPE-NNN patterns found */
56
+ taskPatternCount: number;
57
+ /** Count of .aps.md links found */
58
+ apsLinkCount: number;
59
+ }
60
+
61
+ /**
62
+ * APS Markdown FormatAdapter implementation
63
+ *
64
+ * Converts between APS markdown documents and APS plans.
65
+ */
66
+ export class APSMarkdownAdapter extends BaseFormatAdapter {
67
+ readonly metadata: AdapterMetadata = {
68
+ name: 'aps-markdown',
69
+ version: '1.0.0',
70
+ displayName: 'Anvil Plan Spec Markdown',
71
+ description: 'APS markdown format adapter for plan specs (.aps.md)',
72
+ formats: ['aps', 'aps-markdown', 'aps.md'],
73
+ extensions: ['.aps.md'],
74
+ };
75
+
76
+ /**
77
+ * Detect if content is APS markdown format
78
+ *
79
+ * Uses confidence scoring based on multiple indicators:
80
+ * - Tasks section with SCOPE-NNN headings (30 points)
81
+ * - **Intent:** field in tasks (20 points)
82
+ * - Modules section (25 points)
83
+ * - .aps.md path links (20 points)
84
+ * - **Scope:** field (10 points)
85
+ * - **Confidence:** field (10 points)
86
+ * - **Owner:** field (5 points)
87
+ * - **Priority:** field (5 points)
88
+ *
89
+ * @param content - Document content to analyze
90
+ * @returns Detection result with confidence score
91
+ */
92
+ detect(content: string): DetectionResult {
93
+ const indicators = this.analyzeContent(content);
94
+ const confidence = this.calculateConfidence(indicators);
95
+ const reason = this.buildDetectionReason(indicators);
96
+
97
+ // Detection threshold: 50% confidence
98
+ return createDetection(confidence >= 50, confidence, reason);
99
+ }
100
+
101
+ /**
102
+ * Parse APS markdown content to APS plan
103
+ *
104
+ * Converts an APS markdown document (leaf spec) to an APSPlan execution schema.
105
+ * Each task in the document becomes a proposed change in the plan.
106
+ *
107
+ * @param content - APS markdown content
108
+ * @param context - Parse context for provenance
109
+ * @param _options - Adapter options
110
+ * @returns Parse result with APS plan
111
+ */
112
+ async parse(
113
+ content: string,
114
+ context?: ParseContext,
115
+ _options?: AdapterOptions
116
+ ): Promise<ParseResult> {
117
+ try {
118
+ const doc = await parseDocument(content, context?.repositoryPath);
119
+ const plan = this.convertToAPSPlan(doc, context);
120
+ return this.createParseSuccess(plan);
121
+ } catch (error) {
122
+ return this.createParseError([
123
+ {
124
+ code: 'PARSE_ERROR',
125
+ message: error instanceof Error ? error.message : String(error),
126
+ },
127
+ ]);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Convert a parsed document to an APSPlan
133
+ */
134
+ private convertToAPSPlan(doc: ParsedDocument, context?: ParseContext): APSPlan {
135
+ const planId = context?.planId ?? generatePlanId();
136
+ const timestamp = context?.timestamp ?? new Date().toISOString();
137
+
138
+ const intent = doc.metadata?.scope ? `${doc.title} (Scope: ${doc.metadata.scope})` : doc.title;
139
+
140
+ const proposed_changes = doc.tasks.map((task) => this.taskToChange(task));
141
+
142
+ const planWithoutHash = {
143
+ schema_version: APS_SCHEMA_VERSION,
144
+ id: planId,
145
+ intent,
146
+ proposed_changes,
147
+ provenance: {
148
+ timestamp,
149
+ author: context?.author ?? process.env['USER'] ?? 'unknown',
150
+ source: 'cli' as const,
151
+ version: this.metadata.version,
152
+ repository: context?.repositoryPath ?? process.cwd(),
153
+ branch: context?.branch ?? 'main',
154
+ commit: context?.commit ?? '',
155
+ },
156
+ validations: {
157
+ required_checks: ['lint', 'test'],
158
+ skip_checks: [],
159
+ },
160
+ evidence: [],
161
+ executions: [],
162
+ };
163
+
164
+ const hash = generateHash(planWithoutHash);
165
+ return { ...planWithoutHash, hash } as APSPlan;
166
+ }
167
+
168
+ /**
169
+ * Convert a task to a change object
170
+ */
171
+ private taskToChange(task: Task): Change {
172
+ const changeType = this.inferChangeType(task);
173
+
174
+ return {
175
+ type: changeType,
176
+ path: task.files?.[0] ?? `task/${task.id}`,
177
+ description: `${task.id}: ${task.title}\n\n${task.intent}`,
178
+ metadata: {
179
+ taskId: task.id,
180
+ confidence: task.confidence,
181
+ validation: task.validation,
182
+ expectedOutcome: task.expectedOutcome,
183
+ tags: task.tags,
184
+ files: task.files,
185
+ scopes: task.scopes,
186
+ dependencies: task.dependencies,
187
+ risks: task.risks,
188
+ packages: task.packages,
189
+ },
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Infer the change type from task intent
195
+ */
196
+ private inferChangeType(task: Task): Change['type'] {
197
+ const intent = task.intent.toLowerCase();
198
+ const title = task.title.toLowerCase();
199
+
200
+ if (
201
+ intent.includes('create') ||
202
+ intent.includes('add') ||
203
+ title.includes('implement') ||
204
+ title.includes('create')
205
+ ) {
206
+ return 'file_create';
207
+ }
208
+ if (
209
+ intent.includes('update') ||
210
+ intent.includes('modify') ||
211
+ intent.includes('fix') ||
212
+ title.includes('update') ||
213
+ title.includes('fix')
214
+ ) {
215
+ return 'file_update';
216
+ }
217
+ if (
218
+ intent.includes('delete') ||
219
+ intent.includes('remove') ||
220
+ title.includes('delete') ||
221
+ title.includes('remove')
222
+ ) {
223
+ return 'file_delete';
224
+ }
225
+ if (intent.includes('config') || intent.includes('setting')) {
226
+ return 'config_update';
227
+ }
228
+ return 'script_execute';
229
+ }
230
+
231
+ /**
232
+ * Serialize APS plan to APS markdown format
233
+ *
234
+ * NOTE: Not yet implemented - returns NOT_IMPLEMENTED error.
235
+ *
236
+ * @param _plan - APS plan to serialize
237
+ * @param _options - Adapter options
238
+ * @returns Serialize result with APS markdown
239
+ */
240
+ async serialize(_plan: APSPlan, _options?: AdapterOptions): Promise<SerializeResult> {
241
+ return this.createSerializeError([
242
+ {
243
+ code: 'NOT_IMPLEMENTED',
244
+ message: 'APSMarkdownAdapter.serialize() is not yet implemented',
245
+ },
246
+ ]);
247
+ }
248
+
249
+ /**
250
+ * Validate APS markdown content
251
+ *
252
+ * NOTE: Not yet implemented - returns NOT_IMPLEMENTED error.
253
+ *
254
+ * @param _content - APS markdown content to validate
255
+ * @param _options - Validation options
256
+ * @returns Validation result
257
+ */
258
+ async validate(_content: string, _options?: AdapterOptions): Promise<ValidationResult> {
259
+ return {
260
+ valid: false,
261
+ issues: [
262
+ {
263
+ code: 'NOT_IMPLEMENTED',
264
+ path: '',
265
+ message: 'APSMarkdownAdapter.validate() is not yet implemented',
266
+ severity: 'error',
267
+ },
268
+ ],
269
+ summary: 'Validation not yet implemented',
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Override canImport to handle the compound extension .aps.md
275
+ */
276
+ override canImport(format: string): boolean {
277
+ const normalized = format.toLowerCase().replace(/^\./, '');
278
+ return (
279
+ this.metadata.formats.includes(normalized) ||
280
+ this.metadata.extensions.some((ext) => ext.replace(/^\./, '') === normalized) ||
281
+ normalized === 'aps.md'
282
+ );
283
+ }
284
+
285
+ /**
286
+ * Analyze content for APS markdown indicators
287
+ */
288
+ private analyzeContent(content: string): APSMarkdownIndicators {
289
+ // Check for H1 title
290
+ const hasH1Title = /^#\s+.+$/m.test(content);
291
+
292
+ // Check for **Scope:** or **ID:** field (ID is the current spec, Scope is legacy)
293
+ const hasScopeField =
294
+ /\*\*Scope:\*\*\s*\S+/i.test(content) || /\*\*ID:\*\*\s*\S+/i.test(content);
295
+
296
+ // Check for ## Tasks section
297
+ const hasTasksSection = /^##\s+Tasks\s*$/im.test(content);
298
+
299
+ // Check for ## Modules section
300
+ const hasModulesSection = /^##\s+Modules\s*$/im.test(content);
301
+
302
+ // Check for SCOPE-NNN pattern in ### headings (e.g., ### AUTH-001: Description)
303
+ const scopeTaskPattern = /^###\s+[A-Z]{2,10}-\d{3}:/gm;
304
+ const taskPatternMatches = content.match(scopeTaskPattern) || [];
305
+ const hasScopeTaskPattern = taskPatternMatches.length > 0;
306
+ const taskPatternCount = taskPatternMatches.length;
307
+
308
+ // Check for **Intent:** field
309
+ const hasIntentField = /\*\*Intent:\*\*/i.test(content);
310
+
311
+ // Check for **Path:** with .aps.md links
312
+ const apsLinkPattern = /\*\*Path:\*\*\s*\[.*?\]\([^)]*\.aps\.md\)/gi;
313
+ const apsLinkMatches = content.match(apsLinkPattern) || [];
314
+ const hasAPSModuleLinks = apsLinkMatches.length > 0;
315
+ const apsLinkCount = apsLinkMatches.length;
316
+
317
+ // Check for **Confidence:** field
318
+ const hasConfidenceField = /\*\*Confidence:\*\*/i.test(content);
319
+
320
+ // Check for **Owner:** field
321
+ const hasOwnerField = /\*\*Owner:\*\*/i.test(content);
322
+
323
+ // Check for **Priority:** field
324
+ const hasPriorityField = /\*\*Priority:\*\*/i.test(content);
325
+
326
+ // Check for **Packages:** field (monorepo support)
327
+ const hasPackagesField = /\*\*Packages:\*\*/i.test(content);
328
+
329
+ return {
330
+ hasH1Title,
331
+ hasScopeField,
332
+ hasTasksSection,
333
+ hasModulesSection,
334
+ hasScopeTaskPattern,
335
+ hasIntentField,
336
+ hasAPSModuleLinks,
337
+ hasConfidenceField,
338
+ hasOwnerField,
339
+ hasPriorityField,
340
+ hasPackagesField,
341
+ taskPatternCount,
342
+ apsLinkCount,
343
+ };
344
+ }
345
+
346
+ /**
347
+ * Calculate confidence score based on indicators
348
+ */
349
+ private calculateConfidence(indicators: APSMarkdownIndicators): number {
350
+ let score = 0;
351
+
352
+ // Leaf spec indicators (Tasks section path)
353
+ if (indicators.hasTasksSection) {
354
+ score += 15; // Has ## Tasks section
355
+
356
+ if (indicators.hasScopeTaskPattern) {
357
+ score += 30; // Has SCOPE-NNN task IDs
358
+ // Bonus for multiple tasks
359
+ if (indicators.taskPatternCount > 1) {
360
+ score += 5;
361
+ }
362
+ }
363
+
364
+ if (indicators.hasIntentField) {
365
+ score += 20; // Has **Intent:** in tasks
366
+ }
367
+ }
368
+
369
+ // Index file indicators (Modules section path)
370
+ if (indicators.hasModulesSection) {
371
+ score += 20; // Has ## Modules section
372
+
373
+ if (indicators.hasAPSModuleLinks) {
374
+ score += 25; // Has .aps.md path links
375
+ // Bonus for multiple modules
376
+ if (indicators.apsLinkCount > 1) {
377
+ score += 5;
378
+ }
379
+ }
380
+ }
381
+
382
+ // Common APS metadata fields
383
+ if (indicators.hasScopeField) {
384
+ score += 10; // Has **Scope:** field
385
+ }
386
+
387
+ if (indicators.hasConfidenceField) {
388
+ score += 10; // Has **Confidence:** field
389
+ }
390
+
391
+ if (indicators.hasOwnerField) {
392
+ score += 5; // Has **Owner:** field
393
+ }
394
+
395
+ if (indicators.hasPriorityField) {
396
+ score += 5; // Has **Priority:** field
397
+ }
398
+
399
+ if (indicators.hasPackagesField) {
400
+ score += 5; // Has **Packages:** field (monorepo support)
401
+ }
402
+
403
+ return Math.min(100, score);
404
+ }
405
+
406
+ /**
407
+ * Build detection reason message
408
+ */
409
+ private buildDetectionReason(indicators: APSMarkdownIndicators): string {
410
+ const reasons: string[] = [];
411
+
412
+ if (indicators.hasTasksSection) {
413
+ reasons.push('tasks-section');
414
+ }
415
+ if (indicators.hasScopeTaskPattern) {
416
+ reasons.push(`scope-tasks(${indicators.taskPatternCount})`);
417
+ }
418
+ if (indicators.hasIntentField) {
419
+ reasons.push('intent-field');
420
+ }
421
+ if (indicators.hasModulesSection) {
422
+ reasons.push('modules-section');
423
+ }
424
+ if (indicators.hasAPSModuleLinks) {
425
+ reasons.push(`aps-links(${indicators.apsLinkCount})`);
426
+ }
427
+ if (indicators.hasScopeField) {
428
+ reasons.push('scope-field');
429
+ }
430
+ if (indicators.hasConfidenceField) {
431
+ reasons.push('confidence-field');
432
+ }
433
+ if (indicators.hasOwnerField) {
434
+ reasons.push('owner-field');
435
+ }
436
+ if (indicators.hasPriorityField) {
437
+ reasons.push('priority-field');
438
+ }
439
+ if (indicators.hasPackagesField) {
440
+ reasons.push('packages-field');
441
+ }
442
+
443
+ return reasons.length > 0 ? reasons.join(', ') : 'no APS indicators';
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Create a new APS markdown adapter instance
449
+ *
450
+ * @param options - Adapter options
451
+ * @returns APS markdown adapter instance
452
+ */
453
+ export function createAPSMarkdownAdapter(options?: AdapterOptions): APSMarkdownAdapter {
454
+ return new APSMarkdownAdapter(options);
455
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * APS Markdown Adapter
3
+ *
4
+ * Exports the APSMarkdownAdapter for detecting and converting
5
+ * Anvil Plan Spec markdown documents.
6
+ */
7
+
8
+ export { APSMarkdownAdapter, createAPSMarkdownAdapter } from './adapter.js';