k0ntext 3.3.0 → 3.5.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 (200) hide show
  1. package/README.md +240 -26
  2. package/dist/agents/cleanup-agent.d.ts.map +1 -1
  3. package/dist/agents/cleanup-agent.js +18 -6
  4. package/dist/agents/cleanup-agent.js.map +1 -1
  5. package/dist/agents/drift-agent.d.ts +7 -0
  6. package/dist/agents/drift-agent.d.ts.map +1 -1
  7. package/dist/agents/drift-agent.js +29 -8
  8. package/dist/agents/drift-agent.js.map +1 -1
  9. package/dist/cli/commands/cleanup.d.ts.map +1 -1
  10. package/dist/cli/commands/cleanup.js +8 -1
  11. package/dist/cli/commands/cleanup.js.map +1 -1
  12. package/dist/cli/commands/drift-detect.d.ts.map +1 -1
  13. package/dist/cli/commands/drift-detect.js +21 -1
  14. package/dist/cli/commands/drift-detect.js.map +1 -1
  15. package/dist/cli/commands/restore.d.ts +12 -0
  16. package/dist/cli/commands/restore.d.ts.map +1 -0
  17. package/dist/cli/commands/restore.js +261 -0
  18. package/dist/cli/commands/restore.js.map +1 -0
  19. package/dist/cli/commands/sync-templates.d.ts +15 -0
  20. package/dist/cli/commands/sync-templates.d.ts.map +1 -0
  21. package/dist/cli/commands/sync-templates.js +181 -0
  22. package/dist/cli/commands/sync-templates.js.map +1 -0
  23. package/dist/cli/commands/version-check.d.ts +12 -0
  24. package/dist/cli/commands/version-check.d.ts.map +1 -0
  25. package/dist/cli/commands/version-check.js +133 -0
  26. package/dist/cli/commands/version-check.js.map +1 -0
  27. package/dist/cli/generate.d.ts +5 -0
  28. package/dist/cli/generate.d.ts.map +1 -1
  29. package/dist/cli/generate.js +80 -16
  30. package/dist/cli/generate.js.map +1 -1
  31. package/dist/cli/index.js +145 -1
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/repl/index.d.ts +4 -0
  34. package/dist/cli/repl/index.d.ts.map +1 -1
  35. package/dist/cli/repl/index.js +113 -95
  36. package/dist/cli/repl/index.js.map +1 -1
  37. package/dist/cli/repl/tui/panels/config.d.ts +71 -0
  38. package/dist/cli/repl/tui/panels/config.d.ts.map +1 -0
  39. package/dist/cli/repl/tui/panels/config.js +392 -0
  40. package/dist/cli/repl/tui/panels/config.js.map +1 -0
  41. package/dist/cli/repl/tui/panels/drift.d.ts +95 -0
  42. package/dist/cli/repl/tui/panels/drift.d.ts.map +1 -0
  43. package/dist/cli/repl/tui/panels/drift.js +353 -0
  44. package/dist/cli/repl/tui/panels/drift.js.map +1 -0
  45. package/dist/cli/repl/tui/panels/indexing.d.ts +86 -0
  46. package/dist/cli/repl/tui/panels/indexing.d.ts.map +1 -0
  47. package/dist/cli/repl/tui/panels/indexing.js +254 -0
  48. package/dist/cli/repl/tui/panels/indexing.js.map +1 -0
  49. package/dist/cli/repl/tui/panels/search.d.ts +66 -0
  50. package/dist/cli/repl/tui/panels/search.d.ts.map +1 -0
  51. package/dist/cli/repl/tui/panels/search.js +215 -0
  52. package/dist/cli/repl/tui/panels/search.js.map +1 -0
  53. package/dist/cli/utils/backup-manager.d.ts +94 -0
  54. package/dist/cli/utils/backup-manager.d.ts.map +1 -0
  55. package/dist/cli/utils/backup-manager.js +230 -0
  56. package/dist/cli/utils/backup-manager.js.map +1 -0
  57. package/dist/cli/utils/file-detector.d.ts +87 -0
  58. package/dist/cli/utils/file-detector.d.ts.map +1 -0
  59. package/dist/cli/utils/file-detector.js +131 -0
  60. package/dist/cli/utils/file-detector.js.map +1 -0
  61. package/dist/cli/utils/index.d.ts +9 -0
  62. package/dist/cli/utils/index.d.ts.map +1 -0
  63. package/dist/cli/utils/index.js +9 -0
  64. package/dist/cli/utils/index.js.map +1 -0
  65. package/dist/cli/utils/modification-prompt.d.ts +41 -0
  66. package/dist/cli/utils/modification-prompt.d.ts.map +1 -0
  67. package/dist/cli/utils/modification-prompt.js +84 -0
  68. package/dist/cli/utils/modification-prompt.js.map +1 -0
  69. package/dist/cli/version/checker.d.ts +47 -0
  70. package/dist/cli/version/checker.d.ts.map +1 -0
  71. package/dist/cli/version/checker.js +143 -0
  72. package/dist/cli/version/checker.js.map +1 -0
  73. package/dist/cli/version/comparator.d.ts +46 -0
  74. package/dist/cli/version/comparator.d.ts.map +1 -0
  75. package/dist/cli/version/comparator.js +99 -0
  76. package/dist/cli/version/comparator.js.map +1 -0
  77. package/dist/cli/version/index.d.ts +11 -0
  78. package/dist/cli/version/index.d.ts.map +1 -0
  79. package/dist/cli/version/index.js +11 -0
  80. package/dist/cli/version/index.js.map +1 -0
  81. package/dist/cli/version/parser.d.ts +38 -0
  82. package/dist/cli/version/parser.d.ts.map +1 -0
  83. package/dist/cli/version/parser.js +90 -0
  84. package/dist/cli/version/parser.js.map +1 -0
  85. package/dist/cli/version/prompt.d.ts +40 -0
  86. package/dist/cli/version/prompt.d.ts.map +1 -0
  87. package/dist/cli/version/prompt.js +162 -0
  88. package/dist/cli/version/prompt.js.map +1 -0
  89. package/dist/cli/version/types.d.ts +89 -0
  90. package/dist/cli/version/types.d.ts.map +1 -0
  91. package/dist/cli/version/types.js +7 -0
  92. package/dist/cli/version/types.js.map +1 -0
  93. package/dist/db/client.d.ts +64 -2
  94. package/dist/db/client.d.ts.map +1 -1
  95. package/dist/db/client.js +148 -2
  96. package/dist/db/client.js.map +1 -1
  97. package/dist/db/schema.d.ts +41 -2
  98. package/dist/db/schema.d.ts.map +1 -1
  99. package/dist/db/schema.js +77 -2
  100. package/dist/db/schema.js.map +1 -1
  101. package/dist/mcp.js +2 -2
  102. package/dist/mcp.js.map +1 -1
  103. package/dist/template-engine/data-transformer.d.ts +17 -0
  104. package/dist/template-engine/data-transformer.d.ts.map +1 -0
  105. package/dist/template-engine/data-transformer.js +343 -0
  106. package/dist/template-engine/data-transformer.js.map +1 -0
  107. package/dist/template-engine/engine.d.ts +74 -0
  108. package/dist/template-engine/engine.d.ts.map +1 -0
  109. package/dist/template-engine/engine.js +183 -0
  110. package/dist/template-engine/engine.js.map +1 -0
  111. package/dist/template-engine/helpers.d.ts +81 -0
  112. package/dist/template-engine/helpers.d.ts.map +1 -0
  113. package/dist/template-engine/helpers.js +153 -0
  114. package/dist/template-engine/helpers.js.map +1 -0
  115. package/dist/template-engine/index.d.ts +10 -0
  116. package/dist/template-engine/index.d.ts.map +1 -0
  117. package/dist/template-engine/index.js +10 -0
  118. package/dist/template-engine/index.js.map +1 -0
  119. package/dist/template-engine/types.d.ts +147 -0
  120. package/dist/template-engine/types.d.ts.map +1 -0
  121. package/dist/template-engine/types.js +7 -0
  122. package/dist/template-engine/types.js.map +1 -0
  123. package/dist/template-sync/comparator.d.ts +138 -0
  124. package/dist/template-sync/comparator.d.ts.map +1 -0
  125. package/dist/template-sync/comparator.js +353 -0
  126. package/dist/template-sync/comparator.js.map +1 -0
  127. package/dist/template-sync/conflict-resolver.d.ts +112 -0
  128. package/dist/template-sync/conflict-resolver.d.ts.map +1 -0
  129. package/dist/template-sync/conflict-resolver.js +328 -0
  130. package/dist/template-sync/conflict-resolver.js.map +1 -0
  131. package/dist/template-sync/engine.d.ts +93 -0
  132. package/dist/template-sync/engine.d.ts.map +1 -0
  133. package/dist/template-sync/engine.js +350 -0
  134. package/dist/template-sync/engine.js.map +1 -0
  135. package/dist/template-sync/hasher.d.ts +67 -0
  136. package/dist/template-sync/hasher.d.ts.map +1 -0
  137. package/dist/template-sync/hasher.js +94 -0
  138. package/dist/template-sync/hasher.js.map +1 -0
  139. package/dist/template-sync/index.d.ts +20 -0
  140. package/dist/template-sync/index.d.ts.map +1 -0
  141. package/dist/template-sync/index.js +14 -0
  142. package/dist/template-sync/index.js.map +1 -0
  143. package/dist/template-sync/manifest.d.ts +131 -0
  144. package/dist/template-sync/manifest.d.ts.map +1 -0
  145. package/dist/template-sync/manifest.js +309 -0
  146. package/dist/template-sync/manifest.js.map +1 -0
  147. package/dist/template-sync/merger.d.ts +125 -0
  148. package/dist/template-sync/merger.d.ts.map +1 -0
  149. package/dist/template-sync/merger.js +371 -0
  150. package/dist/template-sync/merger.js.map +1 -0
  151. package/dist/template-sync/scanner.d.ts +106 -0
  152. package/dist/template-sync/scanner.d.ts.map +1 -0
  153. package/dist/template-sync/scanner.js +196 -0
  154. package/dist/template-sync/scanner.js.map +1 -0
  155. package/dist/template-sync/types.d.ts +199 -0
  156. package/dist/template-sync/types.d.ts.map +1 -0
  157. package/dist/template-sync/types.js +30 -0
  158. package/dist/template-sync/types.js.map +1 -0
  159. package/package.json +2 -1
  160. package/src/agents/cleanup-agent.ts +21 -6
  161. package/src/agents/drift-agent.ts +31 -8
  162. package/src/cli/commands/cleanup.ts +9 -1
  163. package/src/cli/commands/drift-detect.ts +24 -1
  164. package/src/cli/commands/restore.ts +318 -0
  165. package/src/cli/commands/sync-templates.ts +210 -0
  166. package/src/cli/commands/version-check.ts +158 -0
  167. package/src/cli/generate.ts +99 -17
  168. package/src/cli/index.ts +167 -1
  169. package/src/cli/repl/index.ts +118 -105
  170. package/src/cli/repl/tui/panels/config.ts +457 -0
  171. package/src/cli/repl/tui/panels/drift.ts +458 -0
  172. package/src/cli/repl/tui/panels/indexing.ts +324 -0
  173. package/src/cli/repl/tui/panels/search.ts +272 -0
  174. package/src/cli/utils/backup-manager.ts +275 -0
  175. package/src/cli/utils/file-detector.ts +181 -0
  176. package/src/cli/utils/index.ts +9 -0
  177. package/src/cli/utils/modification-prompt.ts +112 -0
  178. package/src/cli/version/checker.ts +172 -0
  179. package/src/cli/version/comparator.ts +106 -0
  180. package/src/cli/version/index.ts +11 -0
  181. package/src/cli/version/parser.ts +101 -0
  182. package/src/cli/version/prompt.ts +208 -0
  183. package/src/cli/version/types.ts +95 -0
  184. package/src/db/client.ts +220 -3
  185. package/src/db/schema.ts +109 -2
  186. package/src/mcp.ts +2 -2
  187. package/src/template-engine/data-transformer.ts +367 -0
  188. package/src/template-engine/engine.ts +213 -0
  189. package/src/template-engine/helpers.ts +163 -0
  190. package/src/template-engine/index.ts +10 -0
  191. package/src/template-engine/types.ts +158 -0
  192. package/src/template-sync/comparator.ts +452 -0
  193. package/src/template-sync/conflict-resolver.ts +401 -0
  194. package/src/template-sync/engine.ts +417 -0
  195. package/src/template-sync/hasher.ts +104 -0
  196. package/src/template-sync/index.ts +60 -0
  197. package/src/template-sync/manifest.ts +358 -0
  198. package/src/template-sync/merger.ts +454 -0
  199. package/src/template-sync/scanner.ts +254 -0
  200. package/src/template-sync/types.ts +247 -0
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Conflict Resolver
3
+ *
4
+ * Handles interactive conflict resolution for template sync.
5
+ * Uses @inquirer/prompts for user interaction.
6
+ */
7
+
8
+ import chalk from 'chalk';
9
+ import { confirm, select, checkbox } from '@inquirer/prompts';
10
+ import type { FileComparison, ResolutionChoice, ResolutionResult } from './types.js';
11
+ import { TemplateMerger } from './merger.js';
12
+
13
+ /**
14
+ * Resolution options for a single conflict
15
+ */
16
+ export interface ResolutionOptions {
17
+ /** Whether to show diffs by default */
18
+ showDiff?: boolean;
19
+ /** Whether to allow batch resolution */
20
+ allowBatch?: boolean;
21
+ /** Default choice */
22
+ defaultChoice?: ResolutionChoice;
23
+ }
24
+
25
+ /**
26
+ * Batch resolution strategy
27
+ */
28
+ export type BatchStrategy = 'keep-all' | 'overwrite-all' | 'individual';
29
+
30
+ /**
31
+ * Handles interactive conflict resolution
32
+ */
33
+ export class ConflictResolver {
34
+ constructor(
35
+ private projectRoot: string = process.cwd(),
36
+ private templateRoot: string
37
+ ) {}
38
+
39
+ /**
40
+ * Resolve conflicts interactively
41
+ *
42
+ * @param conflicts - Array of conflicting comparisons
43
+ * @param options - Resolution options
44
+ * @returns Array of resolution results
45
+ */
46
+ async resolveConflicts(
47
+ conflicts: FileComparison[],
48
+ options: ResolutionOptions = {}
49
+ ): Promise<ResolutionResult[]> {
50
+ if (conflicts.length === 0) {
51
+ return [];
52
+ }
53
+
54
+ const results: ResolutionResult[] = [];
55
+ const allowBatch = options.allowBatch !== false;
56
+
57
+ // Show summary
58
+ this.showConflictSummary(conflicts);
59
+
60
+ // Ask for batch vs individual resolution
61
+ if (allowBatch && conflicts.length > 1) {
62
+ const strategy = await this.promptBatchStrategy(conflicts.length);
63
+
64
+ if (strategy === 'keep-all') {
65
+ // Keep all local versions
66
+ for (const conflict of conflicts) {
67
+ results.push({ path: conflict.path, choice: 'keep-local' });
68
+ }
69
+ return results;
70
+ } else if (strategy === 'overwrite-all') {
71
+ // Overwrite all with templates
72
+ for (const conflict of conflicts) {
73
+ results.push({ path: conflict.path, choice: 'overwrite' });
74
+ }
75
+ return results;
76
+ }
77
+ // Otherwise, continue to individual resolution
78
+ }
79
+
80
+ // Individual resolution
81
+ for (const conflict of conflicts) {
82
+ const result = await this.resolveConflict(conflict, options);
83
+ results.push(result);
84
+
85
+ // Exit if user cancels
86
+ if (result.choice === 'skip') {
87
+ break;
88
+ }
89
+ }
90
+
91
+ return results;
92
+ }
93
+
94
+ /**
95
+ * Resolve a single conflict
96
+ *
97
+ * @param conflict - File comparison in conflict
98
+ * @param options - Resolution options
99
+ * @returns Resolution result
100
+ */
101
+ async resolveConflict(
102
+ conflict: FileComparison,
103
+ options: ResolutionOptions = {}
104
+ ): Promise<ResolutionResult> {
105
+ // Show file info
106
+ this.showConflictInfo(conflict);
107
+
108
+ // Ask for resolution
109
+ const choice = await this.promptResolution(conflict, options);
110
+
111
+ if (choice === 'show-diff') {
112
+ // Show diff and re-prompt
113
+ await this.showDiff(conflict);
114
+ return await this.resolveConflict(conflict, options);
115
+ }
116
+
117
+ return {
118
+ path: conflict.path,
119
+ choice
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Prompt for batch resolution strategy
125
+ *
126
+ * @param count - Number of conflicts
127
+ * @returns Batch strategy
128
+ */
129
+ private async promptBatchStrategy(count: number): Promise<BatchStrategy> {
130
+ return await select({
131
+ message: `Resolve ${count} conflicts:`,
132
+ choices: [
133
+ {
134
+ name: 'Keep all local versions',
135
+ value: 'keep-all',
136
+ description: 'Skip updating all conflicted files'
137
+ },
138
+ {
139
+ name: 'Overwrite all with templates',
140
+ value: 'overwrite-all',
141
+ description: 'Replace all local files with template versions (backups created)'
142
+ },
143
+ {
144
+ name: 'Resolve individually',
145
+ value: 'individual',
146
+ description: 'Review and resolve each conflict separately'
147
+ }
148
+ ]
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Prompt for resolution of a single conflict
154
+ *
155
+ * @param conflict - File comparison
156
+ * @param options - Resolution options
157
+ * @returns Resolution choice
158
+ */
159
+ private async promptResolution(
160
+ conflict: FileComparison,
161
+ options: ResolutionOptions
162
+ ): Promise<ResolutionChoice> {
163
+ const baseChoices = [
164
+ {
165
+ name: 'Show diff',
166
+ value: 'show-diff' as const,
167
+ description: 'View differences before deciding'
168
+ },
169
+ {
170
+ name: 'Keep local version',
171
+ value: 'keep-local' as const,
172
+ description: 'Skip updating this file (preserves your changes)'
173
+ },
174
+ {
175
+ name: 'Overwrite with template',
176
+ value: 'overwrite' as const,
177
+ description: 'Replace local file with template version (backup created)'
178
+ }
179
+ ];
180
+
181
+ const skipChoice = {
182
+ name: 'Skip for now',
183
+ value: 'skip' as const,
184
+ description: 'Leave unchanged and continue to next file'
185
+ };
186
+
187
+ const choices = options.defaultChoice !== 'skip'
188
+ ? [...baseChoices, skipChoice]
189
+ : baseChoices;
190
+
191
+ return await select<ResolutionChoice>({
192
+ message: 'How would you like to resolve this?',
193
+ choices,
194
+ default: options.defaultChoice ?? 'keep-local'
195
+ });
196
+ }
197
+
198
+ /**
199
+ * Show conflict information
200
+ *
201
+ * @param conflict - File comparison
202
+ */
203
+ private showConflictInfo(conflict: FileComparison): void {
204
+ console.log('');
205
+ console.log(chalk.cyan(`File: ${conflict.path}`));
206
+ console.log(chalk.dim(`State: ${this.formatState(conflict.state)}`));
207
+
208
+ if (conflict.userModified) {
209
+ console.log(chalk.yellow(' ⚠ You have modified this file'));
210
+ }
211
+
212
+ // Show file info if hashes differ
213
+ if (conflict.templateHash !== conflict.localHash) {
214
+ console.log(chalk.dim(` Template hash: ${conflict.templateHash}`));
215
+ console.log(chalk.dim(` Local hash: ${conflict.localHash}`));
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Show conflict summary
221
+ *
222
+ * @param conflicts - Array of conflicts
223
+ */
224
+ private showConflictSummary(conflicts: FileComparison[]): void {
225
+ console.log('');
226
+ console.log(chalk.yellow(`⚠ ${conflicts.length} conflict(s) detected:\n`));
227
+
228
+ for (const conflict of conflicts) {
229
+ const icon = conflict.userModified ? '⚠' : '→';
230
+ const userFlag = conflict.userModified ? ' [modified]' : '';
231
+ console.log(chalk.dim(` ${icon} ${conflict.path}${userFlag}`));
232
+ }
233
+
234
+ console.log('');
235
+ }
236
+
237
+ /**
238
+ * Show diff for a conflict
239
+ *
240
+ * @param conflict - File comparison
241
+ */
242
+ private async showDiff(conflict: FileComparison): Promise<void> {
243
+ const merger = new TemplateMerger(this.projectRoot, this.templateRoot, {
244
+ generateDiffs: true
245
+ });
246
+
247
+ const templatePath = path.join(this.templateRoot, conflict.path);
248
+ const localPath = path.join(this.projectRoot, '.claude', conflict.path);
249
+
250
+ const diff = await merger.generateDiff(localPath, templatePath);
251
+
252
+ console.log('');
253
+ console.log(chalk.gray('─'.repeat(60)));
254
+ console.log(chalk.bold('Diff:'));
255
+ console.log(chalk.gray('─'.repeat(60)));
256
+ console.log(chalk.dim(diff));
257
+ console.log(chalk.gray('─'.repeat(60)));
258
+ console.log('');
259
+
260
+ // Wait for user to acknowledge
261
+ await confirm({
262
+ message: 'Press Enter to continue...',
263
+ default: true
264
+ });
265
+ }
266
+
267
+ /**
268
+ * Format file state for display
269
+ *
270
+ * @param state - File state
271
+ * @returns Formatted state string
272
+ */
273
+ private formatState(state: FileComparison['state']): string {
274
+ const stateLabels: Record<FileComparison['state'], string> = {
275
+ identical: 'Identical to template',
276
+ 'safe-update': 'Template updated (safe to apply)',
277
+ conflict: 'Modified in both template and locally',
278
+ new: 'New file in template',
279
+ deleted: 'Removed from template',
280
+ 'user-only': 'User-only file (not in template)'
281
+ };
282
+
283
+ return stateLabels[state] || state;
284
+ }
285
+
286
+ /**
287
+ * Apply resolution results using merger
288
+ *
289
+ * @param resolutions - Array of resolution results
290
+ * @param conflicts - Original conflicts for reference
291
+ * @returns Results of applied resolutions
292
+ */
293
+ async applyResolutions(
294
+ resolutions: ResolutionResult[],
295
+ conflicts: FileComparison[]
296
+ ): Promise<{ applied: number; skipped: number; errors: number }> {
297
+ let applied = 0;
298
+ let skipped = 0;
299
+ let errors = 0;
300
+
301
+ const merger = new TemplateMerger(this.projectRoot, this.templateRoot);
302
+
303
+ for (const resolution of resolutions) {
304
+ const conflict = conflicts.find(c => c.path === resolution.path);
305
+ if (!conflict) continue;
306
+
307
+ if (resolution.choice === 'overwrite') {
308
+ const templatePath = path.join(this.templateRoot, conflict.path);
309
+ const localPath = path.join(this.projectRoot, '.claude', conflict.path);
310
+
311
+ const result = await merger.overwriteFile(templatePath, localPath);
312
+ if (result.success) {
313
+ applied++;
314
+ } else {
315
+ errors++;
316
+ console.error(chalk.red(`Error overwriting ${conflict.path}: ${result.error}`));
317
+ }
318
+ } else {
319
+ skipped++;
320
+ }
321
+ }
322
+
323
+ return { applied, skipped, errors };
324
+ }
325
+
326
+ /**
327
+ * Prompt for archival of removed files
328
+ *
329
+ * @param removedFiles - Array of removed file paths
330
+ * @returns true if user wants to archive
331
+ */
332
+ async promptArchiveRemoved(removedFiles: string[]): Promise<boolean> {
333
+ if (removedFiles.length === 0) {
334
+ return false;
335
+ }
336
+
337
+ console.log('');
338
+ console.log(chalk.yellow(`The following ${removedFiles.length} file(s) have been removed from the template:`));
339
+ for (const file of removedFiles.slice(0, 5)) {
340
+ console.log(chalk.dim(` - ${file}`));
341
+ }
342
+ if (removedFiles.length > 5) {
343
+ console.log(chalk.dim(` ... and ${removedFiles.length - 5} more`));
344
+ }
345
+ console.log('');
346
+
347
+ return await confirm({
348
+ message: 'Archive these files to .k0ntext/archive/ instead of deleting?',
349
+ default: true
350
+ });
351
+ }
352
+
353
+ /**
354
+ * Show dry run results
355
+ *
356
+ * @param comparisons - File comparisons
357
+ */
358
+ showDryRunResults(comparisons: FileComparison[]): void {
359
+ console.log('');
360
+ console.log(chalk.bold('Dry Run Results:\n'));
361
+
362
+ const byState = comparisons.reduce((acc, c) => {
363
+ acc[c.state] = acc[c.state] ?? [];
364
+ acc[c.state].push(c);
365
+ return acc;
366
+ }, {} as Record<string, FileComparison[]>);
367
+
368
+ const stateOrder: FileComparison['state'][] = [
369
+ 'new',
370
+ 'safe-update',
371
+ 'conflict',
372
+ 'identical',
373
+ 'user-only',
374
+ 'deleted'
375
+ ];
376
+
377
+ for (const state of stateOrder) {
378
+ const files = byState[state];
379
+ if (!files || files.length === 0) continue;
380
+
381
+ const stateLabel = this.formatState(state);
382
+ const icon = state === 'conflict' ? '⚠' : state === 'new' ? '+' : state === 'safe-update' ? '→' : '•';
383
+
384
+ console.log(chalk.cyan(`${icon} ${stateLabel}: ${files.length}`));
385
+
386
+ for (const file of files.slice(0, 5)) {
387
+ const userFlag = file.userModified ? ' [modified]' : '';
388
+ console.log(chalk.dim(` ${file.path}${userFlag}`));
389
+ }
390
+
391
+ if (files.length > 5) {
392
+ console.log(chalk.dim(` ... and ${files.length - 5} more`));
393
+ }
394
+
395
+ console.log('');
396
+ }
397
+ }
398
+ }
399
+
400
+ // Import path for dynamic use
401
+ import path from 'path';