@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,351 @@
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
+ import { generatePlanId, generateHash, APS_SCHEMA_VERSION, } from '@eddacraft/anvil-core';
9
+ import { parseDocument } from '@eddacraft/anvil-aps';
10
+ import { BaseFormatAdapter, } from '../base/types.js';
11
+ import { createDetection } from '../base/utils.js';
12
+ /**
13
+ * APS Markdown FormatAdapter implementation
14
+ *
15
+ * Converts between APS markdown documents and APS plans.
16
+ */
17
+ export class APSMarkdownAdapter extends BaseFormatAdapter {
18
+ metadata = {
19
+ name: 'aps-markdown',
20
+ version: '1.0.0',
21
+ displayName: 'Anvil Plan Spec Markdown',
22
+ description: 'APS markdown format adapter for plan specs (.aps.md)',
23
+ formats: ['aps', 'aps-markdown', 'aps.md'],
24
+ extensions: ['.aps.md'],
25
+ };
26
+ /**
27
+ * Detect if content is APS markdown format
28
+ *
29
+ * Uses confidence scoring based on multiple indicators:
30
+ * - Tasks section with SCOPE-NNN headings (30 points)
31
+ * - **Intent:** field in tasks (20 points)
32
+ * - Modules section (25 points)
33
+ * - .aps.md path links (20 points)
34
+ * - **Scope:** field (10 points)
35
+ * - **Confidence:** field (10 points)
36
+ * - **Owner:** field (5 points)
37
+ * - **Priority:** field (5 points)
38
+ *
39
+ * @param content - Document content to analyze
40
+ * @returns Detection result with confidence score
41
+ */
42
+ detect(content) {
43
+ const indicators = this.analyzeContent(content);
44
+ const confidence = this.calculateConfidence(indicators);
45
+ const reason = this.buildDetectionReason(indicators);
46
+ // Detection threshold: 50% confidence
47
+ return createDetection(confidence >= 50, confidence, reason);
48
+ }
49
+ /**
50
+ * Parse APS markdown content to APS plan
51
+ *
52
+ * Converts an APS markdown document (leaf spec) to an APSPlan execution schema.
53
+ * Each task in the document becomes a proposed change in the plan.
54
+ *
55
+ * @param content - APS markdown content
56
+ * @param context - Parse context for provenance
57
+ * @param _options - Adapter options
58
+ * @returns Parse result with APS plan
59
+ */
60
+ async parse(content, context, _options) {
61
+ try {
62
+ const doc = await parseDocument(content, context?.repositoryPath);
63
+ const plan = this.convertToAPSPlan(doc, context);
64
+ return this.createParseSuccess(plan);
65
+ }
66
+ catch (error) {
67
+ return this.createParseError([
68
+ {
69
+ code: 'PARSE_ERROR',
70
+ message: error instanceof Error ? error.message : String(error),
71
+ },
72
+ ]);
73
+ }
74
+ }
75
+ /**
76
+ * Convert a parsed document to an APSPlan
77
+ */
78
+ convertToAPSPlan(doc, context) {
79
+ const planId = context?.planId ?? generatePlanId();
80
+ const timestamp = context?.timestamp ?? new Date().toISOString();
81
+ const intent = doc.metadata?.scope ? `${doc.title} (Scope: ${doc.metadata.scope})` : doc.title;
82
+ const proposed_changes = doc.tasks.map((task) => this.taskToChange(task));
83
+ const planWithoutHash = {
84
+ schema_version: APS_SCHEMA_VERSION,
85
+ id: planId,
86
+ intent,
87
+ proposed_changes,
88
+ provenance: {
89
+ timestamp,
90
+ author: context?.author ?? process.env['USER'] ?? 'unknown',
91
+ source: 'cli',
92
+ version: this.metadata.version,
93
+ repository: context?.repositoryPath ?? process.cwd(),
94
+ branch: context?.branch ?? 'main',
95
+ commit: context?.commit ?? '',
96
+ },
97
+ validations: {
98
+ required_checks: ['lint', 'test'],
99
+ skip_checks: [],
100
+ },
101
+ evidence: [],
102
+ executions: [],
103
+ };
104
+ const hash = generateHash(planWithoutHash);
105
+ return { ...planWithoutHash, hash };
106
+ }
107
+ /**
108
+ * Convert a task to a change object
109
+ */
110
+ taskToChange(task) {
111
+ const changeType = this.inferChangeType(task);
112
+ return {
113
+ type: changeType,
114
+ path: task.files?.[0] ?? `task/${task.id}`,
115
+ description: `${task.id}: ${task.title}\n\n${task.intent}`,
116
+ metadata: {
117
+ taskId: task.id,
118
+ confidence: task.confidence,
119
+ validation: task.validation,
120
+ expectedOutcome: task.expectedOutcome,
121
+ tags: task.tags,
122
+ files: task.files,
123
+ scopes: task.scopes,
124
+ dependencies: task.dependencies,
125
+ risks: task.risks,
126
+ packages: task.packages,
127
+ },
128
+ };
129
+ }
130
+ /**
131
+ * Infer the change type from task intent
132
+ */
133
+ inferChangeType(task) {
134
+ const intent = task.intent.toLowerCase();
135
+ const title = task.title.toLowerCase();
136
+ if (intent.includes('create') ||
137
+ intent.includes('add') ||
138
+ title.includes('implement') ||
139
+ title.includes('create')) {
140
+ return 'file_create';
141
+ }
142
+ if (intent.includes('update') ||
143
+ intent.includes('modify') ||
144
+ intent.includes('fix') ||
145
+ title.includes('update') ||
146
+ title.includes('fix')) {
147
+ return 'file_update';
148
+ }
149
+ if (intent.includes('delete') ||
150
+ intent.includes('remove') ||
151
+ title.includes('delete') ||
152
+ title.includes('remove')) {
153
+ return 'file_delete';
154
+ }
155
+ if (intent.includes('config') || intent.includes('setting')) {
156
+ return 'config_update';
157
+ }
158
+ return 'script_execute';
159
+ }
160
+ /**
161
+ * Serialize APS plan to APS markdown format
162
+ *
163
+ * NOTE: Not yet implemented - returns NOT_IMPLEMENTED error.
164
+ *
165
+ * @param _plan - APS plan to serialize
166
+ * @param _options - Adapter options
167
+ * @returns Serialize result with APS markdown
168
+ */
169
+ async serialize(_plan, _options) {
170
+ return this.createSerializeError([
171
+ {
172
+ code: 'NOT_IMPLEMENTED',
173
+ message: 'APSMarkdownAdapter.serialize() is not yet implemented',
174
+ },
175
+ ]);
176
+ }
177
+ /**
178
+ * Validate APS markdown content
179
+ *
180
+ * NOTE: Not yet implemented - returns NOT_IMPLEMENTED error.
181
+ *
182
+ * @param _content - APS markdown content to validate
183
+ * @param _options - Validation options
184
+ * @returns Validation result
185
+ */
186
+ async validate(_content, _options) {
187
+ return {
188
+ valid: false,
189
+ issues: [
190
+ {
191
+ code: 'NOT_IMPLEMENTED',
192
+ path: '',
193
+ message: 'APSMarkdownAdapter.validate() is not yet implemented',
194
+ severity: 'error',
195
+ },
196
+ ],
197
+ summary: 'Validation not yet implemented',
198
+ };
199
+ }
200
+ /**
201
+ * Override canImport to handle the compound extension .aps.md
202
+ */
203
+ canImport(format) {
204
+ const normalized = format.toLowerCase().replace(/^\./, '');
205
+ return (this.metadata.formats.includes(normalized) ||
206
+ this.metadata.extensions.some((ext) => ext.replace(/^\./, '') === normalized) ||
207
+ normalized === 'aps.md');
208
+ }
209
+ /**
210
+ * Analyze content for APS markdown indicators
211
+ */
212
+ analyzeContent(content) {
213
+ // Check for H1 title
214
+ const hasH1Title = /^#\s+.+$/m.test(content);
215
+ // Check for **Scope:** or **ID:** field (ID is the current spec, Scope is legacy)
216
+ const hasScopeField = /\*\*Scope:\*\*\s*\S+/i.test(content) || /\*\*ID:\*\*\s*\S+/i.test(content);
217
+ // Check for ## Tasks section
218
+ const hasTasksSection = /^##\s+Tasks\s*$/im.test(content);
219
+ // Check for ## Modules section
220
+ const hasModulesSection = /^##\s+Modules\s*$/im.test(content);
221
+ // Check for SCOPE-NNN pattern in ### headings (e.g., ### AUTH-001: Description)
222
+ const scopeTaskPattern = /^###\s+[A-Z]{2,10}-\d{3}:/gm;
223
+ const taskPatternMatches = content.match(scopeTaskPattern) || [];
224
+ const hasScopeTaskPattern = taskPatternMatches.length > 0;
225
+ const taskPatternCount = taskPatternMatches.length;
226
+ // Check for **Intent:** field
227
+ const hasIntentField = /\*\*Intent:\*\*/i.test(content);
228
+ // Check for **Path:** with .aps.md links
229
+ const apsLinkPattern = /\*\*Path:\*\*\s*\[.*?\]\([^)]*\.aps\.md\)/gi;
230
+ const apsLinkMatches = content.match(apsLinkPattern) || [];
231
+ const hasAPSModuleLinks = apsLinkMatches.length > 0;
232
+ const apsLinkCount = apsLinkMatches.length;
233
+ // Check for **Confidence:** field
234
+ const hasConfidenceField = /\*\*Confidence:\*\*/i.test(content);
235
+ // Check for **Owner:** field
236
+ const hasOwnerField = /\*\*Owner:\*\*/i.test(content);
237
+ // Check for **Priority:** field
238
+ const hasPriorityField = /\*\*Priority:\*\*/i.test(content);
239
+ // Check for **Packages:** field (monorepo support)
240
+ const hasPackagesField = /\*\*Packages:\*\*/i.test(content);
241
+ return {
242
+ hasH1Title,
243
+ hasScopeField,
244
+ hasTasksSection,
245
+ hasModulesSection,
246
+ hasScopeTaskPattern,
247
+ hasIntentField,
248
+ hasAPSModuleLinks,
249
+ hasConfidenceField,
250
+ hasOwnerField,
251
+ hasPriorityField,
252
+ hasPackagesField,
253
+ taskPatternCount,
254
+ apsLinkCount,
255
+ };
256
+ }
257
+ /**
258
+ * Calculate confidence score based on indicators
259
+ */
260
+ calculateConfidence(indicators) {
261
+ let score = 0;
262
+ // Leaf spec indicators (Tasks section path)
263
+ if (indicators.hasTasksSection) {
264
+ score += 15; // Has ## Tasks section
265
+ if (indicators.hasScopeTaskPattern) {
266
+ score += 30; // Has SCOPE-NNN task IDs
267
+ // Bonus for multiple tasks
268
+ if (indicators.taskPatternCount > 1) {
269
+ score += 5;
270
+ }
271
+ }
272
+ if (indicators.hasIntentField) {
273
+ score += 20; // Has **Intent:** in tasks
274
+ }
275
+ }
276
+ // Index file indicators (Modules section path)
277
+ if (indicators.hasModulesSection) {
278
+ score += 20; // Has ## Modules section
279
+ if (indicators.hasAPSModuleLinks) {
280
+ score += 25; // Has .aps.md path links
281
+ // Bonus for multiple modules
282
+ if (indicators.apsLinkCount > 1) {
283
+ score += 5;
284
+ }
285
+ }
286
+ }
287
+ // Common APS metadata fields
288
+ if (indicators.hasScopeField) {
289
+ score += 10; // Has **Scope:** field
290
+ }
291
+ if (indicators.hasConfidenceField) {
292
+ score += 10; // Has **Confidence:** field
293
+ }
294
+ if (indicators.hasOwnerField) {
295
+ score += 5; // Has **Owner:** field
296
+ }
297
+ if (indicators.hasPriorityField) {
298
+ score += 5; // Has **Priority:** field
299
+ }
300
+ if (indicators.hasPackagesField) {
301
+ score += 5; // Has **Packages:** field (monorepo support)
302
+ }
303
+ return Math.min(100, score);
304
+ }
305
+ /**
306
+ * Build detection reason message
307
+ */
308
+ buildDetectionReason(indicators) {
309
+ const reasons = [];
310
+ if (indicators.hasTasksSection) {
311
+ reasons.push('tasks-section');
312
+ }
313
+ if (indicators.hasScopeTaskPattern) {
314
+ reasons.push(`scope-tasks(${indicators.taskPatternCount})`);
315
+ }
316
+ if (indicators.hasIntentField) {
317
+ reasons.push('intent-field');
318
+ }
319
+ if (indicators.hasModulesSection) {
320
+ reasons.push('modules-section');
321
+ }
322
+ if (indicators.hasAPSModuleLinks) {
323
+ reasons.push(`aps-links(${indicators.apsLinkCount})`);
324
+ }
325
+ if (indicators.hasScopeField) {
326
+ reasons.push('scope-field');
327
+ }
328
+ if (indicators.hasConfidenceField) {
329
+ reasons.push('confidence-field');
330
+ }
331
+ if (indicators.hasOwnerField) {
332
+ reasons.push('owner-field');
333
+ }
334
+ if (indicators.hasPriorityField) {
335
+ reasons.push('priority-field');
336
+ }
337
+ if (indicators.hasPackagesField) {
338
+ reasons.push('packages-field');
339
+ }
340
+ return reasons.length > 0 ? reasons.join(', ') : 'no APS indicators';
341
+ }
342
+ }
343
+ /**
344
+ * Create a new APS markdown adapter instance
345
+ *
346
+ * @param options - Adapter options
347
+ * @returns APS markdown adapter instance
348
+ */
349
+ export function createAPSMarkdownAdapter(options) {
350
+ return new APSMarkdownAdapter(options);
351
+ }
@@ -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
+ export { APSMarkdownAdapter, createAPSMarkdownAdapter } from './adapter.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/aps-markdown/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * APS Markdown Adapter
3
+ *
4
+ * Exports the APSMarkdownAdapter for detecting and converting
5
+ * Anvil Plan Spec markdown documents.
6
+ */
7
+ export { APSMarkdownAdapter, createAPSMarkdownAdapter } from './adapter.js';
@@ -0,0 +1,63 @@
1
+ /**
2
+ * File Discovery Utility
3
+ *
4
+ * Discovers planning documents in repositories by searching for common patterns.
5
+ */
6
+ /**
7
+ * Discovered planning file
8
+ */
9
+ export interface DiscoveredFile {
10
+ /** Full path to file */
11
+ path: string;
12
+ /** File name */
13
+ name: string;
14
+ /** File size in bytes */
15
+ size: number;
16
+ /** Last modified timestamp */
17
+ modified: Date;
18
+ /** Confidence score that this is a planning document (0-100) */
19
+ confidence: number;
20
+ /** Reason for detection */
21
+ reason: string;
22
+ }
23
+ /**
24
+ * Search options
25
+ */
26
+ export interface SearchOptions {
27
+ /** Root directory to search from */
28
+ rootPath: string;
29
+ /** Maximum depth to search */
30
+ maxDepth?: number;
31
+ /** Directories to exclude */
32
+ excludeDirs?: string[];
33
+ /** File patterns to search for */
34
+ patterns?: string[];
35
+ }
36
+ /**
37
+ * Discover planning documents in a directory
38
+ *
39
+ * Searches for common planning document patterns like prd.md, plan.md, todo.md, etc.
40
+ *
41
+ * @param options - Search options
42
+ * @returns Array of discovered files, sorted by confidence
43
+ */
44
+ export declare function discoverPlanningFiles(options: SearchOptions): Promise<DiscoveredFile[]>;
45
+ /**
46
+ * Find the most likely planning document
47
+ *
48
+ * Returns the single most likely planning document based on confidence and recency.
49
+ *
50
+ * @param options - Search options
51
+ * @returns The most likely planning document, or undefined if none found
52
+ */
53
+ export declare function findBestPlanningFile(options: SearchOptions): Promise<DiscoveredFile | undefined>;
54
+ /**
55
+ * Group discovered files by name pattern
56
+ *
57
+ * Groups files like "prd.md", "plan.md" etc. for easier selection.
58
+ *
59
+ * @param files - Discovered files
60
+ * @returns Map of pattern to files
61
+ */
62
+ export declare function groupFilesByPattern(files: DiscoveredFile[]): Map<string, DiscoveredFile[]>;
63
+ //# sourceMappingURL=file-discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-discovery.d.ts","sourceRoot":"","sources":["../../src/base/file-discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,QAAQ,EAAE,IAAI,CAAC;IACf,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAiMD;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAiB7F;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAGrC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAsB1F"}
@@ -0,0 +1,246 @@
1
+ /**
2
+ * File Discovery Utility
3
+ *
4
+ * Discovers planning documents in repositories by searching for common patterns.
5
+ */
6
+ import { readdir, stat } from 'node:fs/promises';
7
+ import { join, basename } from 'node:path';
8
+ /**
9
+ * Maximum file size to consider (2MB)
10
+ */
11
+ const MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024;
12
+ /**
13
+ * Maximum directory depth for recursive search
14
+ */
15
+ const MAX_DEPTH = 20;
16
+ /**
17
+ * Default directories to exclude
18
+ */
19
+ const DEFAULT_EXCLUDE_DIRS = [
20
+ 'node_modules',
21
+ '.git',
22
+ 'dist',
23
+ 'build',
24
+ 'coverage',
25
+ '.next',
26
+ '.nuxt',
27
+ 'out',
28
+ 'target',
29
+ 'vendor',
30
+ ];
31
+ /**
32
+ * Default file patterns for planning documents
33
+ */
34
+ const DEFAULT_PATTERNS = [
35
+ 'prd',
36
+ 'plan',
37
+ 'todo',
38
+ 'tasks',
39
+ 'spec',
40
+ 'requirements',
41
+ 'rfc',
42
+ 'adr',
43
+ 'design',
44
+ 'proposal',
45
+ 'roadmap',
46
+ ];
47
+ /**
48
+ * Calculate confidence score for a file name
49
+ */
50
+ function calculateFileConfidence(filename) {
51
+ const lower = filename.toLowerCase();
52
+ let confidence = 0;
53
+ const reasons = [];
54
+ // Exact matches (high confidence)
55
+ const exactMatches = ['prd.md', 'plan.md', 'todo.md', 'tasks.md', 'spec.md', 'requirements.md'];
56
+ if (exactMatches.some((pattern) => lower === pattern)) {
57
+ confidence += 80;
58
+ reasons.push('exact-match');
59
+ }
60
+ // Pattern matches (medium-high confidence)
61
+ if (lower.includes('prd')) {
62
+ confidence += 60;
63
+ reasons.push('prd-pattern');
64
+ }
65
+ if (lower.includes('plan')) {
66
+ confidence += 55;
67
+ reasons.push('plan-pattern');
68
+ }
69
+ if (lower.includes('todo')) {
70
+ confidence += 50;
71
+ reasons.push('todo-pattern');
72
+ }
73
+ if (lower.includes('spec')) {
74
+ confidence += 50;
75
+ reasons.push('spec-pattern');
76
+ }
77
+ if (lower.includes('requirements')) {
78
+ confidence += 55;
79
+ reasons.push('requirements-pattern');
80
+ }
81
+ if (lower.includes('task')) {
82
+ confidence += 45;
83
+ reasons.push('task-pattern');
84
+ }
85
+ if (lower.includes('rfc')) {
86
+ confidence += 50;
87
+ reasons.push('rfc-pattern');
88
+ }
89
+ if (lower.includes('adr')) {
90
+ confidence += 50;
91
+ reasons.push('adr-pattern');
92
+ }
93
+ if (lower.includes('design')) {
94
+ confidence += 40;
95
+ reasons.push('design-pattern');
96
+ }
97
+ if (lower.includes('proposal')) {
98
+ confidence += 45;
99
+ reasons.push('proposal-pattern');
100
+ }
101
+ // Check for common planning directories
102
+ if (lower.includes('docs/') || lower.includes('/docs/')) {
103
+ confidence += 10;
104
+ reasons.push('in-docs-dir');
105
+ }
106
+ if (lower.includes('.anvil/') || lower.includes('/.anvil/')) {
107
+ confidence += 15;
108
+ reasons.push('in-anvil-dir');
109
+ }
110
+ // Markdown extension
111
+ if (lower.endsWith('.md') || lower.endsWith('.markdown')) {
112
+ confidence += 5;
113
+ reasons.push('markdown');
114
+ }
115
+ return {
116
+ confidence: Math.min(100, confidence),
117
+ reason: reasons.join(', ') || 'no-match',
118
+ };
119
+ }
120
+ /**
121
+ * Search directory recursively for planning files
122
+ */
123
+ async function searchDirectory(dirPath, options, currentDepth = 0, results = []) {
124
+ // Stop if max depth reached (clamped to safety limit)
125
+ const effectiveMaxDepth = Math.min(options.maxDepth, MAX_DEPTH);
126
+ if (currentDepth > effectiveMaxDepth) {
127
+ return results;
128
+ }
129
+ try {
130
+ const entries = await readdir(dirPath, { withFileTypes: true });
131
+ for (const entry of entries) {
132
+ const fullPath = join(dirPath, entry.name);
133
+ if (entry.isDirectory()) {
134
+ // Skip excluded directories
135
+ if (options.excludeDirs.includes(entry.name)) {
136
+ continue;
137
+ }
138
+ // Recurse into directory
139
+ await searchDirectory(fullPath, options, currentDepth + 1, results);
140
+ }
141
+ else if (entry.isFile()) {
142
+ // Check if file matches patterns
143
+ const lower = entry.name.toLowerCase();
144
+ const matchesPattern = options.patterns.some((pattern) => lower.includes(pattern));
145
+ if (matchesPattern && (lower.endsWith('.md') || lower.endsWith('.markdown'))) {
146
+ try {
147
+ const stats = await stat(fullPath);
148
+ // Skip files exceeding size limit
149
+ if (stats.size > MAX_FILE_SIZE_BYTES) {
150
+ continue;
151
+ }
152
+ const { confidence, reason } = calculateFileConfidence(fullPath);
153
+ if (confidence >= 40) {
154
+ // Threshold for inclusion
155
+ results.push({
156
+ path: fullPath,
157
+ name: entry.name,
158
+ size: stats.size,
159
+ modified: stats.mtime,
160
+ confidence,
161
+ reason,
162
+ });
163
+ }
164
+ }
165
+ catch (statError) {
166
+ console.error(`[FileDiscovery] Failed to stat file ${fullPath}:`, statError);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ catch (readError) {
173
+ console.error(`[FileDiscovery] Failed to read directory ${dirPath}:`, readError);
174
+ }
175
+ return results;
176
+ }
177
+ /**
178
+ * Discover planning documents in a directory
179
+ *
180
+ * Searches for common planning document patterns like prd.md, plan.md, todo.md, etc.
181
+ *
182
+ * @param options - Search options
183
+ * @returns Array of discovered files, sorted by confidence
184
+ */
185
+ export async function discoverPlanningFiles(options) {
186
+ const searchOptions = {
187
+ rootPath: options.rootPath,
188
+ maxDepth: options.maxDepth ?? 5,
189
+ excludeDirs: options.excludeDirs ?? DEFAULT_EXCLUDE_DIRS,
190
+ patterns: options.patterns ?? DEFAULT_PATTERNS,
191
+ };
192
+ const results = await searchDirectory(searchOptions.rootPath, searchOptions);
193
+ // Sort by confidence (descending), then by modified date (newest first)
194
+ return results.sort((a, b) => {
195
+ if (a.confidence !== b.confidence) {
196
+ return b.confidence - a.confidence;
197
+ }
198
+ return b.modified.getTime() - a.modified.getTime();
199
+ });
200
+ }
201
+ /**
202
+ * Find the most likely planning document
203
+ *
204
+ * Returns the single most likely planning document based on confidence and recency.
205
+ *
206
+ * @param options - Search options
207
+ * @returns The most likely planning document, or undefined if none found
208
+ */
209
+ export async function findBestPlanningFile(options) {
210
+ const files = await discoverPlanningFiles(options);
211
+ return files[0];
212
+ }
213
+ /**
214
+ * Group discovered files by name pattern
215
+ *
216
+ * Groups files like "prd.md", "plan.md" etc. for easier selection.
217
+ *
218
+ * @param files - Discovered files
219
+ * @returns Map of pattern to files
220
+ */
221
+ export function groupFilesByPattern(files) {
222
+ const groups = new Map();
223
+ for (const file of files) {
224
+ const lower = basename(file.name).toLowerCase();
225
+ let pattern = 'other';
226
+ if (lower.includes('prd'))
227
+ pattern = 'prd';
228
+ else if (lower.includes('plan'))
229
+ pattern = 'plan';
230
+ else if (lower.includes('todo'))
231
+ pattern = 'todo';
232
+ else if (lower.includes('spec'))
233
+ pattern = 'spec';
234
+ else if (lower.includes('requirements'))
235
+ pattern = 'requirements';
236
+ else if (lower.includes('rfc'))
237
+ pattern = 'rfc';
238
+ else if (lower.includes('adr'))
239
+ pattern = 'adr';
240
+ if (!groups.has(pattern)) {
241
+ groups.set(pattern, []);
242
+ }
243
+ groups.get(pattern).push(file);
244
+ }
245
+ return groups;
246
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Base Adapter Framework
3
+ *
4
+ * Core types, interfaces, and registry for format adapters.
5
+ */
6
+ export * from './types.js';
7
+ export * from './registry.js';
8
+ export * from './utils.js';
9
+ export * from './file-discovery.js';
10
+ //# sourceMappingURL=index.d.ts.map