@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,489 @@
1
+ import type { APSPlan, Change, ValidationResult } from '@eddacraft/anvil-core';
2
+ import type {
3
+ ConversionError,
4
+ ConversionResult,
5
+ ConversionWarning,
6
+ ExternalSpec,
7
+ SpecContext,
8
+ } from '../common/types.js';
9
+ import { BaseAdapter } from '../common/types.js';
10
+
11
+ export class SpecKitExportAdapter extends BaseAdapter {
12
+ readonly name = 'speckit-export';
13
+ readonly version = '1.0.0';
14
+ readonly supportedFormats = ['speckit', 'spec.md'] as const;
15
+
16
+ async generateSpec(_intent: string, _context: SpecContext): Promise<APSPlan> {
17
+ throw new Error('Use speckit-import adapter for generating new specs');
18
+ }
19
+
20
+ async validateSpec(spec: APSPlan): Promise<ValidationResult> {
21
+ const issues: Array<{
22
+ path: string;
23
+ message: string;
24
+ code: string;
25
+ severity: 'error' | 'warning';
26
+ }> = [];
27
+
28
+ if (!spec.intent || spec.intent.length < 10) {
29
+ issues.push({
30
+ path: 'intent',
31
+ message: 'Intent is required and must be at least 10 characters',
32
+ code: 'INVALID_INTENT',
33
+ severity: 'error',
34
+ });
35
+ }
36
+
37
+ if (spec.proposed_changes.length === 0) {
38
+ issues.push({
39
+ path: 'proposed_changes',
40
+ message: 'No changes to export',
41
+ code: 'EMPTY_CHANGES',
42
+ severity: 'warning',
43
+ });
44
+ }
45
+
46
+ // Only errors make validation invalid, warnings are ok
47
+ const hasErrors = issues.some((issue) => issue.severity === 'error');
48
+
49
+ return {
50
+ valid: !hasErrors,
51
+ data: spec,
52
+ issues: issues.length > 0 ? issues : undefined,
53
+ summary: hasErrors
54
+ ? `Found ${issues.filter((i) => i.severity === 'error').length} error(s)`
55
+ : issues.length > 0
56
+ ? `Validation passed with ${issues.length} warning(s)`
57
+ : 'Validation passed',
58
+ };
59
+ }
60
+
61
+ async convertToAPS(_spec: ExternalSpec): Promise<ConversionResult<APSPlan>> {
62
+ return {
63
+ success: false,
64
+ errors: [
65
+ {
66
+ code: 'NOT_IMPLEMENTED',
67
+ message: 'Import from SpecKit format is handled by speckit-import adapter',
68
+ },
69
+ ],
70
+ };
71
+ }
72
+
73
+ async convertFromAPS(spec: APSPlan): Promise<ConversionResult<ExternalSpec>> {
74
+ const errors: ConversionError[] = [];
75
+ const warnings: ConversionWarning[] = [];
76
+
77
+ try {
78
+ const specContent = this.generateSpecMarkdown(spec);
79
+ const planContent = this.generatePlanMarkdown(spec);
80
+ const tasksContent = this.generateTasksMarkdown(spec);
81
+
82
+ const result: ExternalSpec = {
83
+ format: 'speckit',
84
+ version: '1.0.0',
85
+ content: {
86
+ specContent,
87
+ planContent,
88
+ tasksContent,
89
+ metadata: spec.metadata,
90
+ },
91
+ metadata: {
92
+ generated_at: new Date().toISOString(),
93
+ generator: this.name,
94
+ generator_version: this.version,
95
+ aps_id: spec.id,
96
+ aps_hash: spec.hash,
97
+ },
98
+ };
99
+
100
+ return {
101
+ success: true,
102
+ data: result,
103
+ warnings: warnings.length > 0 ? warnings : undefined,
104
+ };
105
+ } catch (error) {
106
+ errors.push({
107
+ code: 'EXPORT_ERROR',
108
+ message: error instanceof Error ? error.message : 'Failed to export to SpecKit format',
109
+ });
110
+ return { success: false, errors };
111
+ }
112
+ }
113
+
114
+ private generateSpecMarkdown(spec: APSPlan): string {
115
+ const sections: string[] = [];
116
+
117
+ sections.push(`# Specification`);
118
+ sections.push('');
119
+
120
+ sections.push(`## Intent`);
121
+ sections.push('');
122
+ sections.push(spec.intent);
123
+ sections.push('');
124
+
125
+ if (spec.metadata?.['overview']) {
126
+ sections.push(`## Overview`);
127
+ sections.push('');
128
+ sections.push(spec.metadata['overview'] as string);
129
+ sections.push('');
130
+ }
131
+
132
+ if (spec.metadata?.['goals'] && Array.isArray(spec.metadata['goals'])) {
133
+ sections.push(`## Goals`);
134
+ sections.push('');
135
+ for (const goal of spec.metadata['goals'] as string[]) {
136
+ sections.push(`- ${goal}`);
137
+ }
138
+ sections.push('');
139
+ }
140
+
141
+ if (spec.metadata?.['requirements'] && Array.isArray(spec.metadata['requirements'])) {
142
+ sections.push(`## Requirements`);
143
+ sections.push('');
144
+ for (const req of spec.metadata['requirements'] as string[]) {
145
+ sections.push(`- ${req}`);
146
+ }
147
+ sections.push('');
148
+ }
149
+
150
+ if (spec.proposed_changes.length > 0) {
151
+ sections.push(`## Changes`);
152
+ sections.push('');
153
+
154
+ const changesByType = this.groupChangesByType(spec.proposed_changes);
155
+
156
+ for (const [type, changes] of Object.entries(changesByType)) {
157
+ if (changes.length > 0) {
158
+ sections.push(`### ${this.formatChangeType(type)}`);
159
+ sections.push('');
160
+
161
+ for (const change of changes) {
162
+ sections.push(`#### ${change.description}`);
163
+ sections.push('');
164
+
165
+ if (change.path) {
166
+ sections.push(`Path: \`${change.path}\``);
167
+ sections.push('');
168
+ }
169
+
170
+ if (change.content) {
171
+ const extension = this.getFileExtension(change.path || '');
172
+ sections.push(`\`\`\`${extension}`);
173
+ sections.push(change.content);
174
+ sections.push(`\`\`\``);
175
+ sections.push('');
176
+ }
177
+
178
+ if (change.diff) {
179
+ sections.push(`\`\`\`diff`);
180
+ sections.push(change.diff);
181
+ sections.push(`\`\`\``);
182
+ sections.push('');
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ if (spec.metadata) {
190
+ const customMetadata = this.filterCustomMetadata(spec.metadata);
191
+ if (Object.keys(customMetadata).length > 0) {
192
+ sections.push(`## Metadata`);
193
+ sections.push('');
194
+ sections.push(`\`\`\`json`);
195
+ sections.push(JSON.stringify(customMetadata, null, 2));
196
+ sections.push(`\`\`\``);
197
+ sections.push('');
198
+ }
199
+ }
200
+
201
+ return sections.join('\n');
202
+ }
203
+
204
+ private generatePlanMarkdown(spec: APSPlan): string {
205
+ const sections: string[] = [];
206
+
207
+ sections.push(`# Implementation Plan`);
208
+ sections.push('');
209
+ sections.push(`Generated from APS: ${spec.id}`);
210
+ sections.push('');
211
+
212
+ sections.push(`## Summary`);
213
+ sections.push('');
214
+ sections.push(spec.intent);
215
+ sections.push('');
216
+
217
+ sections.push(`## Implementation Steps`);
218
+ sections.push('');
219
+
220
+ const steps = this.convertChangesToSteps(spec.proposed_changes);
221
+ for (let i = 0; i < steps.length; i++) {
222
+ const step = steps[i];
223
+ sections.push(`${i + 1}. **${step.title}**`);
224
+ if (step.details) {
225
+ sections.push(` - ${step.details}`);
226
+ }
227
+ if (step.dependencies.length > 0) {
228
+ sections.push(` - Dependencies: ${step.dependencies.join(', ')}`);
229
+ }
230
+ sections.push('');
231
+ }
232
+
233
+ if (spec.validations) {
234
+ sections.push(`## Validation Requirements`);
235
+ sections.push('');
236
+ sections.push(`- Required checks: ${spec.validations.required_checks.join(', ')}`);
237
+ if (spec.validations.skip_checks.length > 0) {
238
+ sections.push(`- Skipped checks: ${spec.validations.skip_checks.join(', ')}`);
239
+ }
240
+ sections.push('');
241
+ }
242
+
243
+ return sections.join('\n');
244
+ }
245
+
246
+ private generateTasksMarkdown(spec: APSPlan): string {
247
+ const sections: string[] = [];
248
+
249
+ sections.push(`# Tasks`);
250
+ sections.push('');
251
+ sections.push(`Generated from APS: ${spec.id}`);
252
+ sections.push(`Last updated: ${new Date().toISOString()}`);
253
+ sections.push('');
254
+
255
+ sections.push(`## Task List`);
256
+ sections.push('');
257
+
258
+ const tasks = this.convertChangesToTasks(spec.proposed_changes);
259
+ let completedCount = 0;
260
+
261
+ for (const task of tasks) {
262
+ const status = task.completed ? 'x' : ' ';
263
+ const statusEmoji = task.completed ? '✅' : '⏳';
264
+ sections.push(`- [${status}] ${statusEmoji} ${task.description}`);
265
+
266
+ if (task.subtasks.length > 0) {
267
+ for (const subtask of task.subtasks) {
268
+ const subStatus = subtask.completed ? 'x' : ' ';
269
+ sections.push(` - [${subStatus}] ${subtask.description}`);
270
+ }
271
+ }
272
+
273
+ if (task.completed) {
274
+ completedCount++;
275
+ }
276
+ }
277
+
278
+ sections.push('');
279
+ sections.push(`## Progress`);
280
+ sections.push('');
281
+ sections.push(`- Total tasks: ${tasks.length}`);
282
+ sections.push(`- Completed: ${completedCount}`);
283
+ sections.push(`- Remaining: ${tasks.length - completedCount}`);
284
+ sections.push(`- Progress: ${Math.round((completedCount / tasks.length) * 100)}%`);
285
+ sections.push('');
286
+
287
+ if (spec.executions && spec.executions.length > 0) {
288
+ sections.push(`## Execution History`);
289
+ sections.push('');
290
+
291
+ for (const execution of spec.executions) {
292
+ sections.push(`### ${new Date(execution.timestamp).toLocaleString()}`);
293
+ sections.push(`- Operation: ${execution.operation}`);
294
+ sections.push(`- Status: ${execution.status}`);
295
+ if (execution.executed_by) {
296
+ sections.push(`- Executed by: ${execution.executed_by}`);
297
+ }
298
+ if (execution.changes_applied && execution.changes_applied.length > 0) {
299
+ sections.push(`- Applied changes: ${execution.changes_applied.join(', ')}`);
300
+ }
301
+ if (execution.changes_failed && execution.changes_failed.length > 0) {
302
+ sections.push(`- Failed changes: ${execution.changes_failed.join(', ')}`);
303
+ }
304
+ if (execution.logs && execution.logs.length > 0) {
305
+ for (const log of execution.logs) {
306
+ sections.push(`- ${log}`);
307
+ }
308
+ }
309
+ sections.push('');
310
+ }
311
+ }
312
+
313
+ return sections.join('\n');
314
+ }
315
+
316
+ private groupChangesByType(changes: Change[]): Record<string, Change[]> {
317
+ const groups: Record<string, Change[]> = {};
318
+
319
+ for (const change of changes) {
320
+ if (!groups[change.type]) {
321
+ groups[change.type] = [];
322
+ }
323
+ groups[change.type].push(change);
324
+ }
325
+
326
+ return groups;
327
+ }
328
+
329
+ private formatChangeType(type: string): string {
330
+ const typeMap: Record<string, string> = {
331
+ file_create: 'Files to Create',
332
+ file_update: 'Files to Update',
333
+ file_delete: 'Files to Delete',
334
+ config_update: 'Configuration Changes',
335
+ dependency_add: 'Dependencies to Add',
336
+ dependency_remove: 'Dependencies to Remove',
337
+ dependency_update: 'Dependencies to Update',
338
+ script_execute: 'Scripts to Execute',
339
+ };
340
+
341
+ return typeMap[type] || type.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
342
+ }
343
+
344
+ private getFileExtension(path: string): string {
345
+ const ext = path.split('.').pop()?.toLowerCase();
346
+ const extMap: Record<string, string> = {
347
+ js: 'javascript',
348
+ ts: 'typescript',
349
+ jsx: 'javascript',
350
+ tsx: 'typescript',
351
+ py: 'python',
352
+ rb: 'ruby',
353
+ go: 'go',
354
+ rs: 'rust',
355
+ java: 'java',
356
+ cpp: 'cpp',
357
+ c: 'c',
358
+ h: 'c',
359
+ hpp: 'cpp',
360
+ cs: 'csharp',
361
+ php: 'php',
362
+ swift: 'swift',
363
+ kt: 'kotlin',
364
+ scala: 'scala',
365
+ r: 'r',
366
+ m: 'matlab',
367
+ sh: 'bash',
368
+ ps1: 'powershell',
369
+ sql: 'sql',
370
+ md: 'markdown',
371
+ yml: 'yaml',
372
+ yaml: 'yaml',
373
+ json: 'json',
374
+ xml: 'xml',
375
+ html: 'html',
376
+ css: 'css',
377
+ scss: 'scss',
378
+ less: 'less',
379
+ };
380
+
381
+ return extMap[ext || ''] || ext || 'text';
382
+ }
383
+
384
+ private filterCustomMetadata(metadata: Record<string, unknown>): Record<string, unknown> {
385
+ const standardKeys = ['overview', 'goals', 'requirements', 'source_format'];
386
+
387
+ const custom: Record<string, unknown> = {};
388
+ for (const [key, value] of Object.entries(metadata)) {
389
+ if (!standardKeys.includes(key)) {
390
+ custom[key] = value;
391
+ }
392
+ }
393
+
394
+ return custom;
395
+ }
396
+
397
+ private convertChangesToSteps(changes: Change[]): Array<{
398
+ title: string;
399
+ details: string;
400
+ dependencies: string[];
401
+ }> {
402
+ const steps: Array<{
403
+ title: string;
404
+ details: string;
405
+ dependencies: string[];
406
+ }> = [];
407
+
408
+ const pathDependencies = new Map<string, number>();
409
+
410
+ for (let i = 0; i < changes.length; i++) {
411
+ const change = changes[i];
412
+ const deps: string[] = [];
413
+
414
+ if (change.path) {
415
+ const previousIndex = pathDependencies.get(change.path);
416
+ if (previousIndex !== undefined) {
417
+ deps.push(`Step ${previousIndex + 1}`);
418
+ }
419
+ pathDependencies.set(change.path, i);
420
+ }
421
+
422
+ steps.push({
423
+ title: change.description,
424
+ details: change.path ? `Target: ${change.path}` : '',
425
+ dependencies: deps,
426
+ });
427
+ }
428
+
429
+ return steps;
430
+ }
431
+
432
+ private convertChangesToTasks(changes: Change[]): Array<{
433
+ description: string;
434
+ completed: boolean;
435
+ subtasks: Array<{
436
+ description: string;
437
+ completed: boolean;
438
+ }>;
439
+ }> {
440
+ const tasks: Array<{
441
+ description: string;
442
+ completed: boolean;
443
+ subtasks: Array<{
444
+ description: string;
445
+ completed: boolean;
446
+ }>;
447
+ }> = [];
448
+
449
+ for (const change of changes) {
450
+ const task = {
451
+ description: change.description,
452
+ completed: false,
453
+ subtasks: [] as Array<{
454
+ description: string;
455
+ completed: boolean;
456
+ }>,
457
+ };
458
+
459
+ if (change.type === 'file_create' || change.type === 'file_update') {
460
+ task.subtasks.push({
461
+ description: `Edit ${change.path || 'file'}`,
462
+ completed: false,
463
+ });
464
+
465
+ if (this.config.preserveMetadata) {
466
+ task.subtasks.push({
467
+ description: 'Add file metadata',
468
+ completed: false,
469
+ });
470
+ }
471
+ }
472
+
473
+ if (change.type.startsWith('dependency_')) {
474
+ task.subtasks.push({
475
+ description: 'Update package manifest',
476
+ completed: false,
477
+ });
478
+ task.subtasks.push({
479
+ description: 'Run package manager',
480
+ completed: false,
481
+ });
482
+ }
483
+
484
+ tasks.push(task);
485
+ }
486
+
487
+ return tasks;
488
+ }
489
+ }