wogiflow 1.0.12 → 1.0.13

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 (45) hide show
  1. package/.workflow/specs/architecture.md.template +24 -0
  2. package/.workflow/specs/stack.md.template +33 -0
  3. package/.workflow/specs/testing.md.template +36 -0
  4. package/README.md +90 -1
  5. package/package.json +1 -1
  6. package/scripts/MEMORY-ARCHITECTURE.md +150 -0
  7. package/scripts/flow +20 -19
  8. package/scripts/flow-auto-context.js +97 -3
  9. package/scripts/flow-conflict-resolver.js +735 -0
  10. package/scripts/flow-context-gatherer.js +520 -0
  11. package/scripts/flow-context-monitor.js +148 -19
  12. package/scripts/flow-damage-control.js +5 -1
  13. package/scripts/flow-export-profile +168 -1
  14. package/scripts/flow-import-profile +257 -6
  15. package/scripts/flow-instruction-richness.js +182 -18
  16. package/scripts/flow-knowledge-router.js +2 -0
  17. package/scripts/flow-knowledge-sync.js +2 -0
  18. package/scripts/{flow-transcript-chunking.js → flow-long-input-chunking.js} +4 -2
  19. package/scripts/{flow-transcript-parsing.js → flow-long-input-parsing.js} +35 -0
  20. package/scripts/{flow-transcript-stories.js → flow-long-input-stories.js} +86 -38
  21. package/scripts/{flow-transcript-digest.js → flow-long-input.js} +231 -15
  22. package/scripts/flow-memory-db.js +386 -1
  23. package/scripts/flow-memory-sync.js +2 -0
  24. package/scripts/flow-model-adapter.js +53 -29
  25. package/scripts/flow-model-router.js +246 -1
  26. package/scripts/flow-morning.js +94 -0
  27. package/scripts/flow-onboard +223 -10
  28. package/scripts/flow-orchestrate-validation.js +539 -0
  29. package/scripts/flow-orchestrate.js +16 -507
  30. package/scripts/flow-pattern-extractor.js +1265 -0
  31. package/scripts/flow-prompt-composer.js +222 -2
  32. package/scripts/flow-quality-guard.js +594 -0
  33. package/scripts/flow-section-index.js +713 -0
  34. package/scripts/flow-section-resolver.js +484 -0
  35. package/scripts/flow-session-end.js +188 -2
  36. package/scripts/flow-skill-create.js +19 -3
  37. package/scripts/flow-skill-matcher.js +122 -7
  38. package/scripts/flow-statusline-setup.js +218 -0
  39. package/scripts/flow-step-review.js +19 -0
  40. package/scripts/flow-tech-debt.js +734 -0
  41. package/scripts/flow-utils.js +2 -0
  42. package/scripts/hooks/core/long-input-gate.js +293 -0
  43. package/scripts/flow-parallel-detector.js +0 -399
  44. package/scripts/flow-parallel-dispatch.js +0 -987
  45. /package/scripts/{flow-transcript-language.js → flow-long-input-language.js} +0 -0
@@ -0,0 +1,735 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Interactive Conflict Resolution
5
+ *
6
+ * Presents pattern conflicts to user for resolution with recommendations.
7
+ * Uses arrow key navigation and real-time feedback.
8
+ *
9
+ * Usage:
10
+ * Programmatic:
11
+ * const { resolveConflicts } = require('./flow-conflict-resolver');
12
+ * const resolved = await resolveConflicts(conflicts);
13
+ *
14
+ * CLI (reads conflicts from file):
15
+ * flow conflict-resolve --input conflicts.json --output resolved.json
16
+ * node scripts/flow-conflict-resolver.js --input conflicts.json
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const readline = require('readline');
21
+
22
+ // ============================================================================
23
+ // Constants
24
+ // ============================================================================
25
+
26
+ // Display configuration
27
+ const DEFAULT_TERMINAL_WIDTH = 80;
28
+ const MAX_BOX_WIDTH = 62;
29
+ const MAX_LINE_WIDTH = 60;
30
+ const DEFAULT_MAX_PATH_LENGTH = 50;
31
+ const DAYS_PER_WEEK = 7;
32
+ const DAYS_PER_MONTH = 30;
33
+ const DAYS_PER_YEAR = 365;
34
+ const MS_PER_DAY = 1000 * 60 * 60 * 24;
35
+
36
+ // Colors for CLI output
37
+ // TODO: Consider using flow-output.js for shared color definitions
38
+ const c = {
39
+ reset: '\x1b[0m',
40
+ dim: '\x1b[2m',
41
+ bold: '\x1b[1m',
42
+ red: '\x1b[31m',
43
+ green: '\x1b[32m',
44
+ yellow: '\x1b[33m',
45
+ blue: '\x1b[34m',
46
+ cyan: '\x1b[36m',
47
+ magenta: '\x1b[35m',
48
+ bgBlue: '\x1b[44m',
49
+ bgGreen: '\x1b[42m',
50
+ underline: '\x1b[4m'
51
+ };
52
+
53
+ // Resolution values
54
+ const RESOLUTION = {
55
+ A: 'A',
56
+ B: 'B',
57
+ SKIP: 'SKIP'
58
+ };
59
+
60
+ // ============================================================================
61
+ // Terminal Utilities
62
+ // ============================================================================
63
+
64
+ /**
65
+ * Clear the terminal screen
66
+ */
67
+ function clearScreen() {
68
+ process.stdout.write('\x1b[2J\x1b[H');
69
+ }
70
+
71
+ /**
72
+ * Hide cursor
73
+ */
74
+ function hideCursor() {
75
+ process.stdout.write('\x1b[?25l');
76
+ }
77
+
78
+ /**
79
+ * Show cursor
80
+ */
81
+ function showCursor() {
82
+ process.stdout.write('\x1b[?25h');
83
+ }
84
+
85
+ /**
86
+ * Get terminal width
87
+ */
88
+ function getTerminalWidth() {
89
+ return process.stdout.columns || DEFAULT_TERMINAL_WIDTH;
90
+ }
91
+
92
+ /**
93
+ * Create a horizontal line
94
+ */
95
+ function horizontalLine(char = '─', width = null) {
96
+ const w = width || getTerminalWidth();
97
+ return char.repeat(Math.min(w, MAX_LINE_WIDTH));
98
+ }
99
+
100
+ /**
101
+ * Box drawing for headers
102
+ */
103
+ function boxHeader(text) {
104
+ const width = Math.min(getTerminalWidth(), MAX_BOX_WIDTH);
105
+ const textWidth = width - 4;
106
+ const paddedText = text.padEnd(textWidth).slice(0, textWidth);
107
+
108
+ return [
109
+ `${c.cyan}╔${'═'.repeat(width - 2)}╗${c.reset}`,
110
+ `${c.cyan}║${c.reset} ${c.bold}${paddedText}${c.reset}${c.cyan}║${c.reset}`,
111
+ `${c.cyan}╚${'═'.repeat(width - 2)}╝${c.reset}`
112
+ ].join('\n');
113
+ }
114
+
115
+ /**
116
+ * Format a file path for display
117
+ */
118
+ function formatFilePath(filePath, maxLength = DEFAULT_MAX_PATH_LENGTH) {
119
+ if (!filePath) return '';
120
+ if (filePath.length <= maxLength) return filePath;
121
+
122
+ const parts = filePath.split('/');
123
+ if (parts.length <= 2) return filePath.slice(-maxLength);
124
+
125
+ // Show first and last parts
126
+ return '.../' + parts.slice(-2).join('/');
127
+ }
128
+
129
+ /**
130
+ * Format time ago
131
+ */
132
+ function formatTimeAgo(date) {
133
+ if (!date) return 'Unknown';
134
+
135
+ const now = new Date();
136
+ const then = new Date(date);
137
+ const diffMs = now - then;
138
+ const diffDays = Math.floor(diffMs / MS_PER_DAY);
139
+
140
+ if (diffDays === 0) return 'Today';
141
+ if (diffDays === 1) return 'Yesterday';
142
+ if (diffDays < DAYS_PER_WEEK) return `${diffDays} days ago`;
143
+ if (diffDays < DAYS_PER_MONTH) return `${Math.floor(diffDays / DAYS_PER_WEEK)} weeks ago`;
144
+ if (diffDays < DAYS_PER_YEAR) return `${Math.floor(diffDays / DAYS_PER_MONTH)} months ago`;
145
+ return `${Math.floor(diffDays / DAYS_PER_YEAR)} years ago`;
146
+ }
147
+
148
+ // ============================================================================
149
+ // Conflict Display
150
+ // ============================================================================
151
+
152
+ /**
153
+ * Render a single conflict for display
154
+ */
155
+ function renderConflict(conflict, index, total, selectedOption = null) {
156
+ const lines = [];
157
+ const width = Math.min(getTerminalWidth(), MAX_BOX_WIDTH);
158
+
159
+ // Header
160
+ lines.push('');
161
+ lines.push(boxHeader(`Pattern Conflict Resolution`));
162
+ lines.push('');
163
+
164
+ // Progress indicator
165
+ lines.push(`${c.dim}Found ${total} conflicts requiring your decision:${c.reset}`);
166
+ lines.push('');
167
+
168
+ // Conflict header
169
+ lines.push(`${c.yellow}${'━'.repeat(width - 4)}${c.reset}`);
170
+ lines.push(`${c.bold}CONFLICT ${index + 1}/${total}: ${conflict.description}${c.reset}`);
171
+ lines.push(`${c.yellow}${'━'.repeat(width - 4)}${c.reset}`);
172
+ lines.push('');
173
+
174
+ // Option A
175
+ const isARecommended = conflict.recommendation === 'A';
176
+ const isASelected = selectedOption === 'A';
177
+ const aBg = isASelected ? c.bgBlue : '';
178
+ const aPrefix = isASelected ? '▶ ' : ' ';
179
+
180
+ lines.push(`${aBg}${aPrefix}${c.bold}Option A: ${conflict.patternA.pattern.name}${isARecommended ? ` ${c.green}(Recommended)${c.reset}${aBg}${c.bold}` : ''}${c.reset}`);
181
+
182
+ // Option A examples
183
+ if (conflict.patternA.pattern.examples && conflict.patternA.pattern.examples.length > 0) {
184
+ const examples = conflict.patternA.pattern.examples.slice(0, 3);
185
+ lines.push(`${c.dim} Examples: ${examples.join(', ')}${c.reset}`);
186
+ }
187
+
188
+ // Option A stats
189
+ const totalOccurrences = conflict.patternA.occurrences + conflict.patternB.occurrences;
190
+ const aPercentage = Math.round((conflict.patternA.occurrences / totalOccurrences) * 100);
191
+ lines.push(`${c.dim} Usage: ${conflict.patternA.occurrences} files (${aPercentage}%)${c.reset}`);
192
+ lines.push(`${c.dim} Last used: ${formatTimeAgo(conflict.patternA.newestOccurrence)}${c.reset}`);
193
+
194
+ // Option A file examples
195
+ if (conflict.patternA.files && conflict.patternA.files.length > 0) {
196
+ lines.push(`${c.dim} Files: ${conflict.patternA.files.slice(0, 2).map(f => formatFilePath(f, 30)).join(', ')}${c.reset}`);
197
+ }
198
+
199
+ lines.push('');
200
+
201
+ // Option B
202
+ const isBRecommended = conflict.recommendation === 'B';
203
+ const isBSelected = selectedOption === 'B';
204
+ const bBg = isBSelected ? c.bgBlue : '';
205
+ const bPrefix = isBSelected ? '▶ ' : ' ';
206
+
207
+ lines.push(`${bBg}${bPrefix}${c.bold}Option B: ${conflict.patternB.pattern.name}${isBRecommended ? ` ${c.green}(Recommended)${c.reset}${bBg}${c.bold}` : ''}${c.reset}`);
208
+
209
+ // Option B examples
210
+ if (conflict.patternB.pattern.examples && conflict.patternB.pattern.examples.length > 0) {
211
+ const examples = conflict.patternB.pattern.examples.slice(0, 3);
212
+ lines.push(`${c.dim} Examples: ${examples.join(', ')}${c.reset}`);
213
+ }
214
+
215
+ // Option B stats
216
+ const bPercentage = Math.round((conflict.patternB.occurrences / totalOccurrences) * 100);
217
+ lines.push(`${c.dim} Usage: ${conflict.patternB.occurrences} files (${bPercentage}%)${c.reset}`);
218
+ lines.push(`${c.dim} Last used: ${formatTimeAgo(conflict.patternB.newestOccurrence)}${c.reset}`);
219
+
220
+ // Option B file examples
221
+ if (conflict.patternB.files && conflict.patternB.files.length > 0) {
222
+ lines.push(`${c.dim} Files: ${conflict.patternB.files.slice(0, 2).map(f => formatFilePath(f, 30)).join(', ')}${c.reset}`);
223
+ }
224
+
225
+ lines.push('');
226
+
227
+ // Why recommended
228
+ if (conflict.recommendationReason) {
229
+ const recOption = conflict.recommendation;
230
+ lines.push(`${c.green} Why ${recOption} is recommended:${c.reset}`);
231
+ lines.push(`${c.dim} ${conflict.recommendationReason}${c.reset}`);
232
+ lines.push('');
233
+ }
234
+
235
+ // Skip option
236
+ const isSkipSelected = selectedOption === 'SKIP';
237
+ const skipBg = isSkipSelected ? c.bgBlue : '';
238
+ const skipPrefix = isSkipSelected ? '▶ ' : ' ';
239
+ lines.push(`${skipBg}${skipPrefix}${c.dim}[S] Skip - Don't enforce either pattern${c.reset}`);
240
+
241
+ lines.push('');
242
+
243
+ // Controls
244
+ lines.push(`${c.dim}${horizontalLine()}${c.reset}`);
245
+ lines.push(`${c.cyan} ↑/↓${c.reset} Navigate ${c.cyan}Enter${c.reset} Select ${c.cyan}A/B/S${c.reset} Quick select ${c.cyan}Q${c.reset} Quit`);
246
+
247
+ return lines.join('\n');
248
+ }
249
+
250
+ /**
251
+ * Render completion summary
252
+ */
253
+ function renderSummary(resolutions) {
254
+ const lines = [];
255
+
256
+ lines.push('');
257
+ lines.push(boxHeader(`Resolution Complete`));
258
+ lines.push('');
259
+
260
+ const countA = resolutions.filter(r => r.resolution === 'A').length;
261
+ const countB = resolutions.filter(r => r.resolution === 'B').length;
262
+ const countSkip = resolutions.filter(r => r.resolution === 'SKIP').length;
263
+
264
+ lines.push(`${c.green}✓${c.reset} Resolved ${resolutions.length} conflicts:`);
265
+ lines.push('');
266
+
267
+ if (countA > 0) {
268
+ lines.push(` ${c.cyan}${countA}${c.reset} chose Option A (primary pattern)`);
269
+ }
270
+ if (countB > 0) {
271
+ lines.push(` ${c.cyan}${countB}${c.reset} chose Option B (alternative pattern)`);
272
+ }
273
+ if (countSkip > 0) {
274
+ lines.push(` ${c.dim}${countSkip}${c.reset} skipped (no preference)`);
275
+ }
276
+
277
+ lines.push('');
278
+
279
+ // List resolutions
280
+ lines.push(`${c.dim}${horizontalLine()}${c.reset}`);
281
+ lines.push(`${c.bold}Decisions made:${c.reset}`);
282
+ lines.push('');
283
+
284
+ for (const res of resolutions) {
285
+ if (res.resolution === 'SKIP') {
286
+ lines.push(` ${c.dim}○ ${res.description}: Skipped${c.reset}`);
287
+ } else {
288
+ const chosen = res.resolution === 'A' ? res.patternA.pattern.name : res.patternB.pattern.name;
289
+ lines.push(` ${c.green}●${c.reset} ${res.description}: ${c.cyan}${chosen}${c.reset}`);
290
+ }
291
+ }
292
+
293
+ lines.push('');
294
+
295
+ return lines.join('\n');
296
+ }
297
+
298
+ // ============================================================================
299
+ // Interactive Resolution
300
+ // ============================================================================
301
+
302
+ /**
303
+ * Resolve conflicts interactively using arrow keys
304
+ */
305
+ async function resolveConflictsInteractive(conflicts) {
306
+ if (!conflicts || conflicts.length === 0) {
307
+ console.log(`${c.yellow}No conflicts to resolve.${c.reset}`);
308
+ return [];
309
+ }
310
+
311
+ // Check if we're in a TTY
312
+ if (!process.stdin.isTTY) {
313
+ console.error(`${c.red}Error: Interactive mode requires a TTY.${c.reset}`);
314
+ console.error(`${c.dim}Use --auto-accept for non-interactive resolution.${c.reset}`);
315
+ process.exit(1);
316
+ }
317
+
318
+ const resolutions = [];
319
+ let currentIndex = 0;
320
+
321
+ // Process each conflict
322
+ while (currentIndex < conflicts.length) {
323
+ const conflict = conflicts[currentIndex];
324
+ const resolution = await resolveOneConflict(conflict, currentIndex, conflicts.length);
325
+
326
+ if (resolution === 'QUIT') {
327
+ showCursor();
328
+ console.log(`\n${c.yellow}Resolution cancelled. No changes saved.${c.reset}`);
329
+ process.exit(0);
330
+ }
331
+
332
+ // Store resolution
333
+ resolutions.push({
334
+ ...conflict,
335
+ resolution: resolution,
336
+ resolvedAt: new Date().toISOString()
337
+ });
338
+
339
+ currentIndex++;
340
+ }
341
+
342
+ // Show summary
343
+ clearScreen();
344
+ console.log(renderSummary(resolutions));
345
+
346
+ return resolutions;
347
+ }
348
+
349
+ /**
350
+ * Resolve a single conflict with arrow key navigation
351
+ */
352
+ async function resolveOneConflict(conflict, index, total) {
353
+ return new Promise((resolve) => {
354
+ const options = ['A', 'B', 'SKIP'];
355
+ let selectedIndex = conflict.recommendation === 'B' ? 1 : 0; // Pre-select recommended
356
+
357
+ function render() {
358
+ clearScreen();
359
+ console.log(renderConflict(conflict, index, total, options[selectedIndex]));
360
+ }
361
+
362
+ // Initial render
363
+ hideCursor();
364
+ render();
365
+
366
+ // Setup raw mode for key input
367
+ readline.emitKeypressEvents(process.stdin);
368
+ if (process.stdin.setRawMode) {
369
+ process.stdin.setRawMode(true);
370
+ }
371
+
372
+ function handleKeypress(str, key) {
373
+ if (!key) return;
374
+
375
+ // Handle arrow keys
376
+ if (key.name === 'up') {
377
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1;
378
+ render();
379
+ } else if (key.name === 'down') {
380
+ selectedIndex = selectedIndex < options.length - 1 ? selectedIndex + 1 : 0;
381
+ render();
382
+ } else if (key.name === 'return') {
383
+ // Enter key - select current option
384
+ cleanup();
385
+ resolve(options[selectedIndex]);
386
+ } else if (str === 'a' || str === 'A') {
387
+ cleanup();
388
+ resolve('A');
389
+ } else if (str === 'b' || str === 'B') {
390
+ cleanup();
391
+ resolve('B');
392
+ } else if (str === 's' || str === 'S') {
393
+ cleanup();
394
+ resolve('SKIP');
395
+ } else if (str === 'q' || str === 'Q' || (key.ctrl && key.name === 'c')) {
396
+ cleanup();
397
+ resolve('QUIT');
398
+ }
399
+ }
400
+
401
+ function cleanup() {
402
+ process.stdin.removeListener('keypress', handleKeypress);
403
+ if (process.stdin.setRawMode) {
404
+ process.stdin.setRawMode(false);
405
+ }
406
+ showCursor();
407
+ }
408
+
409
+ process.stdin.on('keypress', handleKeypress);
410
+ });
411
+ }
412
+
413
+ /**
414
+ * Auto-accept all recommendations (non-interactive)
415
+ */
416
+ function resolveConflictsAuto(conflicts) {
417
+ if (!conflicts || conflicts.length === 0) {
418
+ return [];
419
+ }
420
+
421
+ return conflicts.map(conflict => ({
422
+ ...conflict,
423
+ resolution: conflict.recommendation || 'SKIP',
424
+ resolvedAt: new Date().toISOString(),
425
+ autoResolved: true
426
+ }));
427
+ }
428
+
429
+ // ============================================================================
430
+ // Main API
431
+ // ============================================================================
432
+
433
+ /**
434
+ * Main entry point for resolving conflicts
435
+ *
436
+ * @param {Array} conflicts - Array of conflict objects from pattern extractor
437
+ * @param {Object} options - Resolution options
438
+ * @param {boolean} options.interactive - Use interactive mode (default: true if TTY)
439
+ * @param {boolean} options.autoAccept - Auto-accept all recommendations
440
+ * @returns {Promise<Array>} Resolved conflicts with user decisions
441
+ */
442
+ async function resolveConflicts(conflicts, options = {}) {
443
+ const {
444
+ interactive = process.stdin.isTTY,
445
+ autoAccept = false
446
+ } = options;
447
+
448
+ if (autoAccept) {
449
+ return resolveConflictsAuto(conflicts);
450
+ }
451
+
452
+ if (interactive) {
453
+ return resolveConflictsInteractive(conflicts);
454
+ }
455
+
456
+ // Non-interactive, non-auto: just return with null resolutions
457
+ return conflicts.map(conflict => ({
458
+ ...conflict,
459
+ resolution: null
460
+ }));
461
+ }
462
+
463
+ /**
464
+ * Convert resolutions to decisions.md format
465
+ */
466
+ function resolutionsToDecisions(resolutions) {
467
+ const lines = ['## Pattern Decisions', ''];
468
+
469
+ for (const res of resolutions) {
470
+ if (res.resolution === 'SKIP') continue;
471
+
472
+ const chosenPattern = res.resolution === 'A'
473
+ ? res.patternA.pattern
474
+ : res.patternB.pattern;
475
+
476
+ lines.push(`### ${chosenPattern.subcategory.replace('.', ': ').replace(/\b\w/g, l => l.toUpperCase())}`);
477
+ lines.push('');
478
+ lines.push(`**Pattern**: ${chosenPattern.name}`);
479
+
480
+ if (chosenPattern.description) {
481
+ lines.push(`**Description**: ${chosenPattern.description}`);
482
+ }
483
+
484
+ if (chosenPattern.examples && chosenPattern.examples.length > 0) {
485
+ lines.push(`**Examples**: \`${chosenPattern.examples.slice(0, 3).join('`, `')}\``);
486
+ }
487
+
488
+ lines.push('');
489
+ }
490
+
491
+ return lines.join('\n');
492
+ }
493
+
494
+ /**
495
+ * Safe JSON parsing with prototype pollution prevention
496
+ */
497
+ function safeJsonParse(content, defaultValue = null) {
498
+ try {
499
+ // Check for prototype pollution attempts in raw content
500
+ if (/__proto__|constructor\s*["'`:]|prototype\s*["'`:]/i.test(content)) {
501
+ console.error(`${c.red}Suspicious content detected in JSON${c.reset}`);
502
+ return defaultValue;
503
+ }
504
+
505
+ const parsed = JSON.parse(content);
506
+
507
+ // Validate it's an array or object
508
+ if (typeof parsed !== 'object' || parsed === null) {
509
+ return defaultValue;
510
+ }
511
+
512
+ // Recursive check for prototype pollution in nested structures
513
+ function hasPrototypePollution(obj) {
514
+ if (typeof obj !== 'object' || obj === null) {
515
+ return false;
516
+ }
517
+ if (!Array.isArray(obj)) {
518
+ const keys = Object.getOwnPropertyNames(obj);
519
+ if (keys.includes('__proto__') || keys.includes('constructor') || keys.includes('prototype')) {
520
+ return true;
521
+ }
522
+ }
523
+ // Recursively check all values (array elements or object properties)
524
+ for (const value of Object.values(obj)) {
525
+ if (hasPrototypePollution(value)) {
526
+ return true;
527
+ }
528
+ }
529
+ return false;
530
+ }
531
+
532
+ if (hasPrototypePollution(parsed)) {
533
+ console.error(`${c.red}Prototype pollution attempt detected${c.reset}`);
534
+ return defaultValue;
535
+ }
536
+
537
+ return parsed;
538
+ } catch (err) {
539
+ console.error(`${c.red}JSON parse error: ${err.message}${c.reset}`);
540
+ return defaultValue;
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Load conflicts from JSON file
546
+ */
547
+ function loadConflictsFromFile(filePath) {
548
+ try {
549
+ const content = fs.readFileSync(filePath, 'utf-8');
550
+ const data = safeJsonParse(content);
551
+
552
+ if (!data) {
553
+ console.error(`${c.red}Error: Failed to parse conflicts file.${c.reset}`);
554
+ process.exit(1);
555
+ }
556
+
557
+ // Handle both direct array and wrapped format
558
+ if (Array.isArray(data)) {
559
+ return data;
560
+ }
561
+ if (data.conflicts && Array.isArray(data.conflicts)) {
562
+ return data.conflicts;
563
+ }
564
+
565
+ console.error(`${c.red}Error: Invalid conflicts file format.${c.reset}`);
566
+ process.exit(1);
567
+ } catch (err) {
568
+ console.error(`${c.red}Error loading conflicts: ${err.message}${c.reset}`);
569
+ process.exit(1);
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Save resolutions to JSON file
575
+ */
576
+ function saveResolutionsToFile(resolutions, filePath) {
577
+ try {
578
+ fs.writeFileSync(filePath, JSON.stringify(resolutions, null, 2), 'utf-8');
579
+ } catch (err) {
580
+ console.error(`${c.red}Error saving resolutions: ${err.message}${c.reset}`);
581
+ process.exit(1);
582
+ }
583
+ }
584
+
585
+ // ============================================================================
586
+ // CLI Interface
587
+ // ============================================================================
588
+
589
+ /**
590
+ * Parse command line arguments
591
+ */
592
+ function parseArgs(args) {
593
+ const options = {
594
+ input: null,
595
+ output: null,
596
+ autoAccept: false,
597
+ format: 'json',
598
+ help: false
599
+ };
600
+
601
+ for (let i = 0; i < args.length; i++) {
602
+ const arg = args[i];
603
+
604
+ if (arg === '--input' || arg === '-i') {
605
+ options.input = args[++i];
606
+ } else if (arg === '--output' || arg === '-o') {
607
+ options.output = args[++i];
608
+ } else if (arg === '--auto-accept' || arg === '--auto') {
609
+ options.autoAccept = true;
610
+ } else if (arg === '--format' || arg === '-f') {
611
+ options.format = args[++i];
612
+ } else if (arg === '--help' || arg === '-h') {
613
+ options.help = true;
614
+ } else if (!arg.startsWith('-') && !options.input) {
615
+ options.input = arg;
616
+ }
617
+ }
618
+
619
+ return options;
620
+ }
621
+
622
+ /**
623
+ * Print help message
624
+ */
625
+ function printHelp() {
626
+ console.log(`
627
+ ${c.bold}Wogi Flow - Conflict Resolver${c.reset}
628
+
629
+ Interactive resolution of pattern conflicts with recommendations.
630
+
631
+ ${c.cyan}Usage:${c.reset}
632
+ flow conflict-resolve [options] [input-file]
633
+ node scripts/flow-conflict-resolver.js [options]
634
+
635
+ ${c.cyan}Options:${c.reset}
636
+ -i, --input <file> Input file with conflicts JSON
637
+ -o, --output <file> Output file for resolutions (default: stdout)
638
+ --auto-accept Auto-accept all recommendations (non-interactive)
639
+ -f, --format <format> Output format: json, decisions (default: json)
640
+ -h, --help Show this help message
641
+
642
+ ${c.cyan}Examples:${c.reset}
643
+ ${c.dim}# Interactive resolution${c.reset}
644
+ flow conflict-resolve conflicts.json -o resolved.json
645
+
646
+ ${c.dim}# Auto-accept recommendations${c.reset}
647
+ flow conflict-resolve conflicts.json --auto-accept
648
+
649
+ ${c.dim}# Output as decisions.md format${c.reset}
650
+ flow conflict-resolve conflicts.json --format decisions
651
+
652
+ ${c.cyan}Navigation:${c.reset}
653
+ ↑/↓ Navigate between options
654
+ Enter Select current option
655
+ A/B/S Quick select Option A, B, or Skip
656
+ Q Quit without saving
657
+ `);
658
+ }
659
+
660
+ /**
661
+ * Main CLI entry point
662
+ */
663
+ async function main() {
664
+ const args = process.argv.slice(2);
665
+ const options = parseArgs(args);
666
+
667
+ if (options.help) {
668
+ printHelp();
669
+ process.exit(0);
670
+ }
671
+
672
+ if (!options.input) {
673
+ console.error(`${c.red}Error: No input file specified.${c.reset}`);
674
+ console.error(`${c.dim}Use --help for usage information.${c.reset}`);
675
+ process.exit(1);
676
+ }
677
+
678
+ // Load conflicts
679
+ const conflicts = loadConflictsFromFile(options.input);
680
+
681
+ if (conflicts.length === 0) {
682
+ console.log(`${c.green}No conflicts found in input file.${c.reset}`);
683
+ process.exit(0);
684
+ }
685
+
686
+ console.log(`${c.cyan}Loaded ${conflicts.length} conflicts from ${options.input}${c.reset}`);
687
+
688
+ // Resolve conflicts
689
+ const resolutions = await resolveConflicts(conflicts, {
690
+ autoAccept: options.autoAccept
691
+ });
692
+
693
+ // Format output
694
+ let output;
695
+ if (options.format === 'decisions') {
696
+ output = resolutionsToDecisions(resolutions);
697
+ } else {
698
+ output = JSON.stringify(resolutions, null, 2);
699
+ }
700
+
701
+ // Write output
702
+ if (options.output) {
703
+ fs.writeFileSync(options.output, output, 'utf-8');
704
+ console.log(`${c.green}Resolutions saved to ${options.output}${c.reset}`);
705
+ } else if (!options.autoAccept) {
706
+ // Only print to stdout in auto mode or when explicitly requested
707
+ console.log(output);
708
+ }
709
+
710
+ process.exit(0);
711
+ }
712
+
713
+ // ============================================================================
714
+ // Module Exports
715
+ // ============================================================================
716
+
717
+ module.exports = {
718
+ resolveConflicts,
719
+ resolveConflictsInteractive,
720
+ resolveConflictsAuto,
721
+ resolutionsToDecisions,
722
+ loadConflictsFromFile,
723
+ saveResolutionsToFile,
724
+ renderConflict,
725
+ renderSummary,
726
+ RESOLUTION
727
+ };
728
+
729
+ // Run CLI if executed directly
730
+ if (require.main === module) {
731
+ main().catch(err => {
732
+ console.error(`${c.red}Error: ${err.message}${c.reset}`);
733
+ process.exit(1);
734
+ });
735
+ }