sela-core 1.0.6 → 1.0.8

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 (124) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/ErrorHandler.d.ts +1 -1
  3. package/dist/cli/ErrorHandler.js +14 -14
  4. package/dist/cli/commands/bulk.d.ts +1 -1
  5. package/dist/cli/commands/bulk.d.ts.map +1 -1
  6. package/dist/cli/commands/bulk.js +51 -33
  7. package/dist/cli/commands/init.d.ts +1 -1
  8. package/dist/cli/commands/init.d.ts.map +1 -1
  9. package/dist/cli/commands/init.js +26 -18
  10. package/dist/cli/commands/showReport.d.ts +1 -1
  11. package/dist/cli/commands/showReport.d.ts.map +1 -1
  12. package/dist/cli/commands/showReport.js +12 -10
  13. package/dist/cli/commands/status.d.ts +1 -1
  14. package/dist/cli/commands/status.d.ts.map +1 -1
  15. package/dist/cli/commands/status.js +8 -8
  16. package/dist/cli/commands/sync.d.ts +1 -1
  17. package/dist/cli/commands/sync.d.ts.map +1 -1
  18. package/dist/cli/commands/sync.js +38 -30
  19. package/dist/cli/index.js +9 -9
  20. package/dist/cli/ui/DnaTable.d.ts +1 -1
  21. package/dist/cli/ui/DnaTable.d.ts.map +1 -1
  22. package/dist/cli/ui/DnaTable.js +23 -16
  23. package/dist/cli/ui/RefactorWizard.d.ts +3 -3
  24. package/dist/cli/ui/RefactorWizard.d.ts.map +1 -1
  25. package/dist/cli/ui/RefactorWizard.js +108 -82
  26. package/dist/config/ConfigLoader.d.ts +3 -2
  27. package/dist/config/ConfigLoader.d.ts.map +1 -1
  28. package/dist/config/ConfigLoader.js +23 -13
  29. package/dist/config/DryRunGuard.d.ts +14 -0
  30. package/dist/config/DryRunGuard.d.ts.map +1 -0
  31. package/dist/config/DryRunGuard.js +79 -0
  32. package/dist/config/SelaConfig.d.ts +26 -16
  33. package/dist/config/SelaConfig.d.ts.map +1 -1
  34. package/dist/config/SelaConfig.js +54 -22
  35. package/dist/engine/HealingRegistry.d.ts +1 -1
  36. package/dist/engine/HealingRegistry.js +3 -3
  37. package/dist/engine/SelaEngine.d.ts +13 -10
  38. package/dist/engine/SelaEngine.d.ts.map +1 -1
  39. package/dist/engine/SelaEngine.js +292 -101
  40. package/dist/errors/SelaError.d.ts +132 -0
  41. package/dist/errors/SelaError.d.ts.map +1 -0
  42. package/dist/errors/SelaError.js +154 -0
  43. package/dist/fixtures/expectProxy.d.ts +1 -1
  44. package/dist/fixtures/expectProxy.d.ts.map +1 -1
  45. package/dist/fixtures/expectProxy.js +17 -10
  46. package/dist/fixtures/index.d.ts +7 -1
  47. package/dist/fixtures/index.d.ts.map +1 -1
  48. package/dist/fixtures/index.js +26 -11
  49. package/dist/fixtures/proxyTag.d.ts +12 -0
  50. package/dist/fixtures/proxyTag.d.ts.map +1 -0
  51. package/dist/fixtures/proxyTag.js +45 -0
  52. package/dist/index.d.ts +8 -6
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +9 -4
  55. package/dist/reporter/SelaReporter.d.ts +143 -0
  56. package/dist/reporter/SelaReporter.d.ts.map +1 -0
  57. package/dist/reporter/SelaReporter.js +428 -0
  58. package/dist/services/ASTSourceUpdater.d.ts +20 -1
  59. package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
  60. package/dist/services/ASTSourceUpdater.js +257 -89
  61. package/dist/services/ArgumentTypeAnalyzer.js +7 -7
  62. package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -1
  63. package/dist/services/BlastRadiusAnalyzer.js +5 -2
  64. package/dist/services/ChainValidator.js +22 -22
  65. package/dist/services/CrossFileHealer.js +7 -7
  66. package/dist/services/DefinitionTracer.d.ts +1 -1
  67. package/dist/services/DefinitionTracer.d.ts.map +1 -1
  68. package/dist/services/DefinitionTracer.js +28 -13
  69. package/dist/services/DnaEditorService.d.ts +2 -2
  70. package/dist/services/DnaEditorService.d.ts.map +1 -1
  71. package/dist/services/DnaEditorService.js +81 -62
  72. package/dist/services/DnaIndexService.d.ts +1 -1
  73. package/dist/services/DnaIndexService.d.ts.map +1 -1
  74. package/dist/services/DnaIndexService.js +14 -14
  75. package/dist/services/HealReportService.d.ts +43 -1
  76. package/dist/services/HealReportService.d.ts.map +1 -1
  77. package/dist/services/HealReportService.js +108 -25
  78. package/dist/services/HealingAdvisory.js +1 -1
  79. package/dist/services/HealingCacheService.d.ts +135 -0
  80. package/dist/services/HealingCacheService.d.ts.map +1 -0
  81. package/dist/services/HealingCacheService.js +460 -0
  82. package/dist/services/HealthReportService.d.ts +1 -1
  83. package/dist/services/HealthReportService.d.ts.map +1 -1
  84. package/dist/services/HealthReportService.js +25 -25
  85. package/dist/services/InitializerUpdater.d.ts.map +1 -1
  86. package/dist/services/InitializerUpdater.js +20 -1
  87. package/dist/services/IntentAuditor.d.ts +18 -1
  88. package/dist/services/IntentAuditor.d.ts.map +1 -1
  89. package/dist/services/IntentAuditor.js +50 -26
  90. package/dist/services/InteractiveReview.d.ts +15 -0
  91. package/dist/services/InteractiveReview.d.ts.map +1 -0
  92. package/dist/services/InteractiveReview.js +125 -0
  93. package/dist/services/LLMService.d.ts +18 -2
  94. package/dist/services/LLMService.d.ts.map +1 -1
  95. package/dist/services/LLMService.js +33 -23
  96. package/dist/services/PRAutomationService.d.ts +14 -1
  97. package/dist/services/PRAutomationService.d.ts.map +1 -1
  98. package/dist/services/PRAutomationService.js +287 -92
  99. package/dist/services/PendingPromptLedger.d.ts +44 -0
  100. package/dist/services/PendingPromptLedger.d.ts.map +1 -0
  101. package/dist/services/PendingPromptLedger.js +180 -0
  102. package/dist/services/ReportGenerator.d.ts +70 -0
  103. package/dist/services/ReportGenerator.d.ts.map +1 -0
  104. package/dist/services/ReportGenerator.js +191 -0
  105. package/dist/services/SafetyGuard.d.ts +30 -7
  106. package/dist/services/SafetyGuard.d.ts.map +1 -1
  107. package/dist/services/SafetyGuard.js +85 -116
  108. package/dist/services/SnapshotService.js +5 -5
  109. package/dist/services/SourceLinkService.d.ts +2 -2
  110. package/dist/services/SourceLinkService.d.ts.map +1 -1
  111. package/dist/services/SourceLinkService.js +31 -22
  112. package/dist/services/SourceUpdater.d.ts +1 -1
  113. package/dist/services/SourceUpdater.d.ts.map +1 -1
  114. package/dist/services/SourceUpdater.js +66 -34
  115. package/dist/services/TemplateDiffService.d.ts.map +1 -1
  116. package/dist/services/TemplateDiffService.js +18 -15
  117. package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
  118. package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
  119. package/dist/services/WorkspaceSnapshotService.js +200 -0
  120. package/dist/types/index.d.ts +1 -1
  121. package/dist/utils/IsolatedDiff.d.ts +45 -0
  122. package/dist/utils/IsolatedDiff.d.ts.map +1 -0
  123. package/dist/utils/IsolatedDiff.js +382 -0
  124. package/package.json +71 -67
@@ -12,50 +12,56 @@ const SourceLinkService_js_1 = require("../../services/SourceLinkService.js");
12
12
  const ProgressReporter_js_1 = require("../ui/ProgressReporter.js");
13
13
  function registerSyncSource(dna) {
14
14
  dna
15
- .command('sync-source <key>')
16
- .description('Write current DNA selector back to .spec.ts via AST')
17
- .option('--locator-index <n>', 'Which locator on the line (0-based)', '0')
18
- .option('--dry-run', 'Preview without writing')
19
- .option('--json', 'Emit JSON result')
20
- .option('--dir <path>', 'Override snapshot directory')
15
+ .command("sync-source <key>")
16
+ .description("Write current DNA selector back to .spec.ts via AST")
17
+ .option("--locator-index <n>", "Which locator on the line (0-based)", "0")
18
+ .option("--dry-run", "Preview without writing")
19
+ .option("--json", "Emit JSON result")
20
+ .option("--dir <path>", "Override snapshot directory")
21
21
  .action(async (key, opts) => {
22
22
  const config = ConfigLoader_js_1.ConfigLoader.getInstance();
23
23
  const snapshotDir = opts.dir ?? config.dnaStoragePath;
24
24
  const locatorIndex = Math.max(0, parseInt(opts.locatorIndex, 10) || 0);
25
25
  const progress = new ProgressReporter_js_1.ProgressReporter(opts.json);
26
26
  const dnaIndex = new DnaIndexService_js_1.DnaIndexService();
27
- await progress.run('Scanning DNA snapshots…', () => dnaIndex.buildIndex(snapshotDir));
27
+ await progress.run("Scanning DNA snapshots…", () => dnaIndex.buildIndex(snapshotDir));
28
28
  const record = dnaIndex.getByKey(key);
29
29
  if (!record) {
30
30
  if (opts.json) {
31
- process.stdout.write(JSON.stringify({ success: false, reason: `DNA not found: ${key}` }, null, 2) + '\n');
31
+ process.stdout.write(JSON.stringify({ success: false, reason: `DNA not found: ${key}` }, null, 2) + "\n");
32
32
  process.exit(3);
33
33
  }
34
34
  const err = new Error(`DNA not found: ${key}`);
35
- err.code = 'DNA_NOT_FOUND';
35
+ err.code = "DNA_NOT_FOUND";
36
36
  throw err;
37
37
  }
38
38
  if (!record.sourceFile || record.sourceLine <= 0) {
39
39
  if (opts.json) {
40
- process.stdout.write(JSON.stringify({ success: false, reason: `DNA "${key}" has no sourceFile/sourceLine` }, null, 2) + '\n');
40
+ process.stdout.write(JSON.stringify({
41
+ success: false,
42
+ reason: `DNA "${key}" has no sourceFile/sourceLine`,
43
+ }, null, 2) + "\n");
41
44
  }
42
45
  else {
43
- process.stdout.write(chalk_1.default.yellow(` ⚠ DNA "${key}" has no sourceFile/sourceLine cannot sync.\n`));
46
+ process.stdout.write(chalk_1.default.yellow(` ⚠ DNA "${key}" has no sourceFile/sourceLine - cannot sync.\n`));
44
47
  }
45
48
  return;
46
49
  }
47
50
  const sourceLink = new SourceLinkService_js_1.SourceLinkService();
48
- const locators = await progress.run('Analyzing AST for locator update…', async () => sourceLink.enumerateLocatorsOnLine(record.sourceFile, record.sourceLine));
51
+ const locators = await progress.run("Analyzing AST for locator update…", async () => sourceLink.enumerateLocatorsOnLine(record.sourceFile, record.sourceLine));
49
52
  const target = locators[locatorIndex] ?? locators[0];
50
53
  const relPath = path_1.default.relative(process.cwd(), record.sourceFile);
51
54
  if (!opts.json) {
52
- process.stdout.write('\n');
53
- process.stdout.write(` ${chalk_1.default.bold('DNA selector:')} ${chalk_1.default.green(record.selector)}\n`);
54
- process.stdout.write(` ${chalk_1.default.bold('Source file:')} ${relPath}:${record.sourceLine}\n`);
55
+ process.stdout.write("\n");
56
+ process.stdout.write(` ${chalk_1.default.bold("DNA selector:")} ${chalk_1.default.green(record.selector)}\n`);
57
+ process.stdout.write(` ${chalk_1.default.bold("Source file:")} ${relPath}:${record.sourceLine}\n`);
55
58
  }
56
59
  if (!target) {
57
60
  if (opts.json) {
58
- process.stdout.write(JSON.stringify({ success: false, reason: `No locator at index ${locatorIndex} on line ${record.sourceLine}` }, null, 2) + '\n');
61
+ process.stdout.write(JSON.stringify({
62
+ success: false,
63
+ reason: `No locator at index ${locatorIndex} on line ${record.sourceLine}`,
64
+ }, null, 2) + "\n");
59
65
  }
60
66
  else {
61
67
  process.stdout.write(chalk_1.default.yellow(` ⚠ No locator found at index ${locatorIndex} on line ${record.sourceLine}.\n\n`));
@@ -63,16 +69,16 @@ function registerSyncSource(dna) {
63
69
  return;
64
70
  }
65
71
  if (!opts.json) {
66
- process.stdout.write(` ${chalk_1.default.bold('Locator found:')} [${locatorIndex}] ${chalk_1.default.yellow(target.preview)}\n`);
67
- process.stdout.write(` ${chalk_1.default.bold('Current value:')} ${chalk_1.default.red(target.selector)}\n`);
68
- process.stdout.write(` ${chalk_1.default.bold('Will become:')} ${chalk_1.default.green(record.selector)}\n\n`);
72
+ process.stdout.write(` ${chalk_1.default.bold("Locator found:")} [${locatorIndex}] ${chalk_1.default.yellow(target.preview)}\n`);
73
+ process.stdout.write(` ${chalk_1.default.bold("Current value:")} ${chalk_1.default.red(target.selector)}\n`);
74
+ process.stdout.write(` ${chalk_1.default.bold("Will become:")} ${chalk_1.default.green(record.selector)}\n\n`);
69
75
  }
70
76
  if (target.selector === record.selector) {
71
77
  if (opts.json) {
72
- process.stdout.write(JSON.stringify({ success: true, reason: 'Already in sync nothing to do' }, null, 2) + '\n');
78
+ process.stdout.write(JSON.stringify({ success: true, reason: "Already in sync - nothing to do" }, null, 2) + "\n");
73
79
  }
74
80
  else {
75
- process.stdout.write(chalk_1.default.dim(' Source already matches DNA selector. Nothing to do.\n\n'));
81
+ process.stdout.write(chalk_1.default.dim(" Source already matches DNA selector. Nothing to do.\n\n"));
76
82
  }
77
83
  return;
78
84
  }
@@ -86,36 +92,38 @@ function registerSyncSource(dna) {
86
92
  locatorIndex,
87
93
  oldSelector: target.selector,
88
94
  newSelector: record.selector,
89
- }, null, 2) + '\n');
95
+ }, null, 2) + "\n");
90
96
  }
91
97
  else {
92
- process.stdout.write(chalk_1.default.dim(' [dry-run] No files written.\n\n'));
98
+ process.stdout.write(chalk_1.default.dim(" [dry-run] No files written.\n\n"));
93
99
  }
94
100
  return;
95
101
  }
96
102
  const patch = {
97
103
  key,
98
- field: 'selector',
104
+ field: "selector",
99
105
  oldValue: target.selector,
100
106
  newValue: record.selector,
101
107
  applySourceUpdate: true,
102
108
  locatorIndex,
103
109
  };
104
- const result = await progress.run('Applying AST source update…', () => sourceLink.applySourceUpdate(record, patch));
110
+ const result = await progress.run("Applying AST source update…", () => sourceLink.applySourceUpdate(record, patch));
105
111
  if (opts.json) {
106
- process.stdout.write(JSON.stringify(result, null, 2) + '\n');
112
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
107
113
  if (!result.success)
108
114
  process.exit(5);
109
115
  }
110
116
  else {
111
117
  if (result.success) {
112
- const lineInfo = result.lineUpdated ? ` (line ${result.lineUpdated})` : '';
113
- process.stdout.write(` ${chalk_1.default.green('✓')} Source updated${lineInfo}\n\n`);
118
+ const lineInfo = result.lineUpdated
119
+ ? ` (line ${result.lineUpdated})`
120
+ : "";
121
+ process.stdout.write(` ${chalk_1.default.green("✓")} Source updated${lineInfo}\n\n`);
114
122
  }
115
123
  else {
116
- process.stdout.write(` ${chalk_1.default.red('')} Source update failed: ${result.reason}\n\n`);
124
+ process.stdout.write(` ${chalk_1.default.red("")} Source update failed: ${result.reason}\n\n`);
117
125
  const err = new Error(result.reason);
118
- err.code = 'AST_ERROR';
126
+ err.code = "AST_ERROR";
119
127
  throw err;
120
128
  }
121
129
  }
package/dist/cli/index.js CHANGED
@@ -37,10 +37,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  const path = __importStar(require("path"));
38
38
  try {
39
39
  // eslint-disable-next-line @typescript-eslint/no-var-requires
40
- require('dotenv').config({ path: path.join(process.cwd(), '.env') });
40
+ require("dotenv").config({ path: path.join(process.cwd(), ".env") });
41
41
  }
42
42
  catch {
43
- // dotenv missing or .env absent continue silently
43
+ // dotenv missing or .env absent - continue silently
44
44
  }
45
45
  const commander_1 = require("commander");
46
46
  const ErrorHandler_js_1 = require("./ErrorHandler.js");
@@ -54,12 +54,12 @@ const sync_js_1 = require("./commands/sync.js");
54
54
  const showReport_js_1 = require("./commands/showReport.js");
55
55
  (0, ErrorHandler_js_1.registerGlobalHandlers)();
56
56
  // Diagnostic console.log from services (ConfigLoader etc.) → stderr so stdout stays pipe-clean
57
- console.log = (...args) => process.stderr.write(args.map(String).join(' ') + '\n');
57
+ console.log = (...args) => process.stderr.write(args.map(String).join(" ") + "\n");
58
58
  const program = new commander_1.Command();
59
59
  program
60
- .name('sela')
61
- .description('Sela CLI proactive DNA management for Fixwright')
62
- .version('1.0.3')
60
+ .name("sela")
61
+ .description("Sela CLI - proactive DNA management for Fixwright")
62
+ .version("1.0.3")
63
63
  .exitOverride(ErrorHandler_js_1.handleError)
64
64
  .configureOutput({ outputError: () => { } });
65
65
  // ── sela init ────────────────────────────────────────────────────────────────
@@ -68,11 +68,11 @@ program
68
68
  (0, status_js_1.registerStatus)(program);
69
69
  // ── sela list (T-07) ─────────────────────────────────────────────────────────
70
70
  (0, list_js_1.registerList)(program);
71
- // ── sela show-report open Sela Insights HTML ───────────────────────────────
71
+ // ── sela show-report - open Sela Insights HTML ───────────────────────────────
72
72
  (0, showReport_js_1.registerShowReport)(program);
73
73
  // ── sela dna <subcommand> ─────────────────────────────────────────────────────
74
- const dna = new commander_1.Command('dna')
75
- .description('DNA management commands')
74
+ const dna = new commander_1.Command("dna")
75
+ .description("DNA management commands")
76
76
  .exitOverride(ErrorHandler_js_1.handleError)
77
77
  .configureOutput({ outputError: () => { } });
78
78
  // ── sela dna find (T-08) ─────────────────────────────────────────────────────
@@ -1,3 +1,3 @@
1
- import type { DnaRecord } from '../../services/types.js';
1
+ import type { DnaRecord } from "../../services/types.js";
2
2
  export declare function renderTable(records: DnaRecord[]): string;
3
3
  //# sourceMappingURL=DnaTable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DnaTable.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/DnaTable.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAuCzD,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,CAoBxD"}
1
+ {"version":3,"file":"DnaTable.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/DnaTable.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAkDzD,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,CAoBxD"}
@@ -8,17 +8,17 @@ const cli_table3_1 = __importDefault(require("cli-table3"));
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  // Truncate before chalk so ANSI codes don't corrupt visual width calculations.
11
- function trunc(s, maxLen, fallback = '—') {
12
- if (s == null || s.trim() === '')
11
+ function trunc(s, maxLen, fallback = "-") {
12
+ if (s == null || s.trim() === "")
13
13
  return chalk_1.default.dim(fallback);
14
- return s.length <= maxLen ? s : s.slice(0, maxLen - 1) + '';
14
+ return s.length <= maxLen ? s : s.slice(0, maxLen - 1) + "";
15
15
  }
16
16
  function relativeTime(iso) {
17
17
  if (!iso)
18
- return chalk_1.default.dim('never');
18
+ return chalk_1.default.dim("never");
19
19
  const d = Math.floor((Date.now() - new Date(iso).getTime()) / 86_400_000);
20
20
  if (d === 0)
21
- return chalk_1.default.green('today');
21
+ return chalk_1.default.green("today");
22
22
  if (d < 7)
23
23
  return `${d}d ago`;
24
24
  if (d < 30)
@@ -27,26 +27,33 @@ function relativeTime(iso) {
27
27
  }
28
28
  function statusBadge(r) {
29
29
  if (r.isOrphaned)
30
- return chalk_1.default.red('orphaned');
31
- if (r.status === 'healed')
32
- return chalk_1.default.yellow('⚠ unverified');
33
- if (r.status === 'broken')
34
- return chalk_1.default.red('broken');
30
+ return chalk_1.default.red("orphaned");
31
+ if (r.status === "healed")
32
+ return chalk_1.default.yellow("⚠ unverified");
33
+ if (r.status === "broken")
34
+ return chalk_1.default.red("broken");
35
35
  if (r.lastHealed !== null)
36
- return chalk_1.default.green('healed');
37
- return chalk_1.default.cyan('active');
36
+ return chalk_1.default.green("healed");
37
+ return chalk_1.default.cyan("active");
38
38
  }
39
39
  function sourceCell(r) {
40
40
  if (!r.sourceFile)
41
- return chalk_1.default.dim('—');
41
+ return chalk_1.default.dim("-");
42
42
  const rel = path_1.default.relative(process.cwd(), r.sourceFile);
43
43
  const display = rel.length < r.sourceFile.length ? rel : r.sourceFile;
44
- const suffix = r.sourceLine > 0 ? `:${r.sourceLine}` : '';
44
+ const suffix = r.sourceLine > 0 ? `:${r.sourceLine}` : "";
45
45
  return trunc(display + suffix, 32);
46
46
  }
47
47
  // colWidths = content + 2 (1-space padding each side)
48
48
  const COLS = {
49
- head: ['Key', 'Test Title', 'Source : Line', 'Selector', 'Last Healed', 'Status'],
49
+ head: [
50
+ "Key",
51
+ "Test Title",
52
+ "Source : Line",
53
+ "Selector",
54
+ "Last Healed",
55
+ "Status",
56
+ ],
50
57
  colWidths: [11, 24, 36, 44, 13, 11],
51
58
  };
52
59
  function renderTable(records) {
@@ -54,7 +61,7 @@ function renderTable(records) {
54
61
  head: COLS.head.map((h) => chalk_1.default.bold(h)),
55
62
  colWidths: COLS.colWidths,
56
63
  wordWrap: false,
57
- style: { head: [], border: ['dim'] },
64
+ style: { head: [], border: ["dim"] },
58
65
  });
59
66
  for (const r of records) {
60
67
  table.push([
@@ -1,6 +1,6 @@
1
- import type { IDnaIndexService } from '../../services/DnaIndexService.js';
2
- import type { IDnaEditorService } from '../../services/DnaEditorService.js';
3
- import type { ISourceLinkService } from '../../services/SourceLinkService.js';
1
+ import type { IDnaIndexService } from "../../services/DnaIndexService.js";
2
+ import type { IDnaEditorService } from "../../services/DnaEditorService.js";
3
+ import type { ISourceLinkService } from "../../services/SourceLinkService.js";
4
4
  export interface WizardOptions {
5
5
  noSource?: boolean;
6
6
  dryRun?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"RefactorWizard.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/RefactorWizard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAuB,MAAM,oCAAoC,CAAC;AACjG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAM9E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,cAAc;IACtB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,UAAU,EAAE,kBAAkB,CAAC;CAChC;AAwFD,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,GAAG,EAAE,cAAc,EACnB,IAAI,GAAE,aAAkB;IAGrC,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4G7C,OAAO,CAAC,UAAU;YAUJ,SAAS;YAwBT,WAAW;YAgBX,SAAS;YAyBT,OAAO;IAKrB,OAAO,CAAC,aAAa;CAGtB"}
1
+ {"version":3,"file":"RefactorWizard.d.ts","sourceRoot":"","sources":["../../../src/cli/ui/RefactorWizard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAM9E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,cAAc;IACtB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,UAAU,EAAE,kBAAkB,CAAC;CAChC;AA6HD,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,GAAG,EAAE,cAAc,EACnB,IAAI,GAAE,aAAkB;IAGrC,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwH7C,OAAO,CAAC,UAAU;YAUJ,SAAS;YAwBT,WAAW;YA+BX,SAAS;YA4BT,OAAO;IAKrB,OAAO,CAAC,aAAa;CAMtB"}
@@ -11,15 +11,15 @@ const LocatorPicker_js_1 = require("./LocatorPicker.js");
11
11
  // ── Helpers ───────────────────────────────────────────────────────────────────
12
12
  function formatValue(v) {
13
13
  if (v === null || v === undefined)
14
- return chalk_1.default.dim('null');
15
- if (typeof v === 'object')
14
+ return chalk_1.default.dim("null");
15
+ if (typeof v === "object")
16
16
  return JSON.stringify(v, null, 2);
17
17
  return String(v);
18
18
  }
19
19
  function parseInput(field, raw) {
20
- if (field === 'text')
21
- return raw.trim() === '' ? null : raw;
22
- if (field === 'attributes' || field === 'anchors') {
20
+ if (field === "text")
21
+ return raw.trim() === "" ? null : raw;
22
+ if (field === "attributes" || field === "anchors") {
23
23
  try {
24
24
  return JSON.parse(raw);
25
25
  }
@@ -31,41 +31,46 @@ function parseInput(field, raw) {
31
31
  }
32
32
  function getFieldValue(field, record, snap) {
33
33
  switch (field) {
34
- case 'selector': return record.selector;
35
- case 'text': return record.text;
36
- case 'tagName': return record.tagName;
37
- case 'attributes': return snap.attributes;
38
- case 'anchors': return snap.anchors;
34
+ case "selector":
35
+ return record.selector;
36
+ case "text":
37
+ return record.text;
38
+ case "tagName":
39
+ return record.tagName;
40
+ case "attributes":
41
+ return snap.attributes;
42
+ case "anchors":
43
+ return snap.anchors;
39
44
  }
40
45
  }
41
46
  function validateField(field, value) {
42
47
  const errs = [];
43
48
  switch (field) {
44
- case 'selector':
45
- if (typeof value !== 'string' || value.trim() === '')
46
- errs.push('selector must not be empty');
47
- if (value === 'undefined')
49
+ case "selector":
50
+ if (typeof value !== "string" || value.trim() === "")
51
+ errs.push("selector must not be empty");
52
+ if (value === "undefined")
48
53
  errs.push('selector must not be the string "undefined"');
49
- if (typeof value === 'string' && /[\r\n]/.test(value))
50
- errs.push('selector must not contain raw newlines');
54
+ if (typeof value === "string" && /[\r\n]/.test(value))
55
+ errs.push("selector must not contain raw newlines");
51
56
  break;
52
- case 'text':
53
- if (value !== null && typeof value !== 'string')
54
- errs.push('text must be a string or null');
55
- if (typeof value === 'string' && value.length >= 2000)
56
- errs.push('text must be fewer than 2000 characters');
57
+ case "text":
58
+ if (value !== null && typeof value !== "string")
59
+ errs.push("text must be a string or null");
60
+ if (typeof value === "string" && value.length >= 2000)
61
+ errs.push("text must be fewer than 2000 characters");
57
62
  break;
58
- case 'tagName':
59
- if (typeof value !== 'string' || !/^[a-z][a-z0-9-]*$/.test(value))
60
- errs.push('tagName must match /^[a-z][a-z0-9-]*$/');
63
+ case "tagName":
64
+ if (typeof value !== "string" || !/^[a-z][a-z0-9-]*$/.test(value))
65
+ errs.push("tagName must match /^[a-z][a-z0-9-]*$/");
61
66
  break;
62
- case 'attributes':
63
- if (typeof value !== 'object' || value === null || Array.isArray(value))
64
- errs.push('attributes must be a plain object (JSON)');
67
+ case "attributes":
68
+ if (typeof value !== "object" || value === null || Array.isArray(value))
69
+ errs.push("attributes must be a plain object (JSON)");
65
70
  break;
66
- case 'anchors':
67
- if (typeof value !== 'object' || value === null)
68
- errs.push('anchors must be an object (JSON)');
71
+ case "anchors":
72
+ if (typeof value !== "object" || value === null)
73
+ errs.push("anchors must be an object (JSON)");
69
74
  break;
70
75
  }
71
76
  return errs;
@@ -74,28 +79,34 @@ function showRecordCard(record) {
74
79
  const w = 62;
75
80
  const line = (label, value) => {
76
81
  const content = ` ${chalk_1.default.dim(label.padEnd(14))} ${value}`;
77
- process.stdout.write(content + '\n');
82
+ process.stdout.write(content + "\n");
78
83
  };
79
- process.stdout.write('\n' + chalk_1.default.dim(''.repeat(w)) + '\n');
80
- line('Key', chalk_1.default.dim(record.key));
81
- line('Selector', chalk_1.default.yellow(record.selector));
82
- line('Tag', record.tagName);
83
- line('Text', record.text ? chalk_1.default.cyan(record.text.slice(0, 40)) : chalk_1.default.dim('—'));
84
+ process.stdout.write("\n" + chalk_1.default.dim("".repeat(w)) + "\n");
85
+ line("Key", chalk_1.default.dim(record.key));
86
+ line("Selector", chalk_1.default.yellow(record.selector));
87
+ line("Tag", record.tagName);
88
+ line("Text", record.text ? chalk_1.default.cyan(record.text.slice(0, 40)) : chalk_1.default.dim("-"));
84
89
  if (record.sourceFile) {
85
90
  const rel = path_1.default.relative(process.cwd(), record.sourceFile);
86
- line('Source', `${rel}:${record.sourceLine}`);
91
+ line("Source", `${rel}:${record.sourceLine}`);
87
92
  }
88
- line('Last Healed', record.lastHealed ? new Date(record.lastHealed).toLocaleDateString() : chalk_1.default.dim('never'));
89
- line('Status', record.isOrphaned ? chalk_1.default.red('orphaned') : record.lastHealed ? chalk_1.default.green('healed') : chalk_1.default.cyan('active'));
90
- process.stdout.write(chalk_1.default.dim('─'.repeat(w)) + '\n\n');
93
+ line("Last Healed", record.lastHealed
94
+ ? new Date(record.lastHealed).toLocaleDateString()
95
+ : chalk_1.default.dim("never"));
96
+ line("Status", record.isOrphaned
97
+ ? chalk_1.default.red("orphaned")
98
+ : record.lastHealed
99
+ ? chalk_1.default.green("healed")
100
+ : chalk_1.default.cyan("active"));
101
+ process.stdout.write(chalk_1.default.dim("─".repeat(w)) + "\n\n");
91
102
  }
92
103
  function showDiff(field, oldVal, newVal) {
93
- process.stdout.write('\n');
94
- process.stdout.write(` ${chalk_1.default.bold('Preview ' + field)}\n`);
95
- process.stdout.write(` ${''.repeat(56)}\n`);
96
- process.stdout.write(` ${chalk_1.default.red('')} ${chalk_1.default.red(formatValue(oldVal))}\n`);
97
- process.stdout.write(` ${chalk_1.default.green('+')} ${chalk_1.default.green(formatValue(newVal))}\n`);
98
- process.stdout.write('\n');
104
+ process.stdout.write("\n");
105
+ process.stdout.write(` ${chalk_1.default.bold("Preview - " + field)}\n`);
106
+ process.stdout.write(` ${"".repeat(56)}\n`);
107
+ process.stdout.write(` ${chalk_1.default.red("")} ${chalk_1.default.red(formatValue(oldVal))}\n`);
108
+ process.stdout.write(` ${chalk_1.default.green("+")} ${chalk_1.default.green(formatValue(newVal))}\n`);
109
+ process.stdout.write("\n");
99
110
  }
100
111
  // ── RefactorWizard ────────────────────────────────────────────────────────────
101
112
  class RefactorWizard {
@@ -112,7 +123,7 @@ class RefactorWizard {
112
123
  : await this.selectDna();
113
124
  // Load full snapshot once (needed for attributes/anchors fields)
114
125
  let snap = await this.svc.dnaEditor.read(record.key);
115
- // Edit loop user can edit multiple fields per session
126
+ // Edit loop - user can edit multiple fields per session
116
127
  for (;;) {
117
128
  // ── SHOW_CURRENT ──────────────────────────────────────────
118
129
  showRecordCard(record);
@@ -120,7 +131,7 @@ class RefactorWizard {
120
131
  const field = await this.selectField();
121
132
  // ── LOCATOR_DISAMBIGUATE (selector field only) ────────────
122
133
  let locatorIndex = 0;
123
- if (field === 'selector' && record.sourceFile && record.sourceLine > 0) {
134
+ if (field === "selector" && record.sourceFile && record.sourceLine > 0) {
124
135
  const locators = this.svc.sourceLink.enumerateLocatorsOnLine(record.sourceFile, record.sourceLine);
125
136
  if (locators.length > 1) {
126
137
  locatorIndex = await (0, LocatorPicker_js_1.pickLocator)(locators, record.selector);
@@ -134,18 +145,18 @@ class RefactorWizard {
134
145
  const errs = validateField(field, newValue);
135
146
  if (errs.length === 0)
136
147
  break;
137
- process.stdout.write('\n');
148
+ process.stdout.write("\n");
138
149
  for (const e of errs) {
139
- process.stdout.write(` ${chalk_1.default.red('')} ${e}\n`);
150
+ process.stdout.write(` ${chalk_1.default.red("")} ${e}\n`);
140
151
  }
141
- process.stdout.write('\n');
152
+ process.stdout.write("\n");
142
153
  }
143
154
  // ── PREVIEW_DIFF ──────────────────────────────────────────
144
155
  showDiff(field, oldValue, newValue);
145
156
  // ── CONFIRM ───────────────────────────────────────────────
146
- const apply = await this.confirm('Apply changes?');
157
+ const apply = await this.confirm("Apply changes?");
147
158
  if (!apply) {
148
- const again = await this.confirm('Edit another field?');
159
+ const again = await this.confirm("Edit another field?");
149
160
  if (!again)
150
161
  break;
151
162
  continue;
@@ -153,10 +164,10 @@ class RefactorWizard {
153
164
  // ── APPLY_SOURCE? ─────────────────────────────────────────
154
165
  let applySource = false;
155
166
  if (!this.opts.noSource &&
156
- field === 'selector' &&
167
+ field === "selector" &&
157
168
  record.sourceFile &&
158
169
  record.sourceLine > 0) {
159
- applySource = await this.confirm('Also update .spec.ts source file?');
170
+ applySource = await this.confirm("Also update .spec.ts source file?");
160
171
  }
161
172
  // ── COMMIT ────────────────────────────────────────────────
162
173
  const patch = {
@@ -168,18 +179,18 @@ class RefactorWizard {
168
179
  locatorIndex,
169
180
  };
170
181
  if (this.opts.dryRun) {
171
- process.stdout.write(`\n ${chalk_1.default.yellow('[dry-run]')} Would write: ${JSON.stringify({ field, newValue })}\n\n`);
182
+ process.stdout.write(`\n ${chalk_1.default.yellow("[dry-run]")} Would write: ${JSON.stringify({ field, newValue })}\n\n`);
172
183
  }
173
184
  else {
174
185
  const bakPath = await this.svc.dnaEditor.write(record.key, this.buildDnaPatch(field, newValue));
175
- process.stdout.write(`\n ${chalk_1.default.green('')} DNA written. Backup: ${chalk_1.default.dim(path_1.default.basename(bakPath))}\n`);
186
+ process.stdout.write(`\n ${chalk_1.default.green("")} DNA written. Backup: ${chalk_1.default.dim(path_1.default.basename(bakPath))}\n`);
176
187
  if (applySource) {
177
188
  const result = await this.svc.sourceLink.applySourceUpdate(record, patch);
178
189
  if (result.success) {
179
- process.stdout.write(` ${chalk_1.default.green('')} Source updated on line ${result.lineUpdated ?? record.sourceLine}\n`);
190
+ process.stdout.write(` ${chalk_1.default.green("")} Source updated on line ${result.lineUpdated ?? record.sourceLine}\n`);
180
191
  }
181
192
  else {
182
- process.stdout.write(` ${chalk_1.default.yellow('')} Source update: ${result.reason}\n`);
193
+ process.stdout.write(` ${chalk_1.default.yellow("")} Source update: ${result.reason}\n`);
183
194
  }
184
195
  }
185
196
  // Refresh record from index after write
@@ -189,18 +200,18 @@ class RefactorWizard {
189
200
  record = refreshed;
190
201
  }
191
202
  // ── DONE / CONTINUE? ─────────────────────────────────────
192
- const another = await this.confirm('Edit another field?');
203
+ const another = await this.confirm("Edit another field?");
193
204
  if (!another)
194
205
  break;
195
206
  }
196
- process.stdout.write('\n' + chalk_1.default.dim('Wizard complete.') + '\n\n');
207
+ process.stdout.write("\n" + chalk_1.default.dim("Wizard complete.") + "\n\n");
197
208
  }
198
209
  // ── Private helpers ───────────────────────────────────────────────────────
199
210
  resolveKey(key) {
200
211
  const record = this.svc.dnaIndex.getByKey(key);
201
212
  if (!record) {
202
213
  const err = new Error(`DNA not found: ${key}`);
203
- err.code = 'DNA_NOT_FOUND';
214
+ err.code = "DNA_NOT_FOUND";
204
215
  throw err;
205
216
  }
206
217
  return record;
@@ -208,8 +219,8 @@ class RefactorWizard {
208
219
  async selectDna() {
209
220
  const records = this.svc.dnaIndex.getIndex().records;
210
221
  if (records.length === 0) {
211
- const err = new Error('No DNA records found in snapshot directory');
212
- err.code = 'SNAPSHOT_DIR_MISSING';
222
+ const err = new Error("No DNA records found in snapshot directory");
223
+ err.code = "SNAPSHOT_DIR_MISSING";
213
224
  throw err;
214
225
  }
215
226
  const choices = records.map((r) => ({
@@ -217,8 +228,8 @@ class RefactorWizard {
217
228
  message: `${chalk_1.default.dim(r.key.slice(0, 8))} ${r.selector.padEnd(40).slice(0, 40)} ${chalk_1.default.dim(r.testTitle.slice(0, 30))}`,
218
229
  }));
219
230
  const prompt = new enquirer_js_1.AutoComplete({
220
- name: 'dna',
221
- message: 'Search DNA record (type to filter)',
231
+ name: "dna",
232
+ message: "Search DNA record (type to filter)",
222
233
  limit: 10,
223
234
  choices,
224
235
  });
@@ -227,31 +238,46 @@ class RefactorWizard {
227
238
  }
228
239
  async selectField() {
229
240
  const prompt = new enquirer_js_1.Select({
230
- name: 'field',
231
- message: 'Which field to edit?',
241
+ name: "field",
242
+ message: "Which field to edit?",
232
243
  choices: [
233
- { name: 'selector', message: `${chalk_1.default.yellow('selector')} CSS/Playwright selector` },
234
- { name: 'text', message: `${chalk_1.default.cyan('text')} Visible element text` },
235
- { name: 'tagName', message: `${chalk_1.default.dim('tagName')} HTML tag name` },
236
- { name: 'attributes', message: `${chalk_1.default.dim('attributes')} Element attributes (JSON)` },
237
- { name: 'anchors', message: `${chalk_1.default.dim('anchors')} Anchor context (JSON)` },
244
+ {
245
+ name: "selector",
246
+ message: `${chalk_1.default.yellow("selector")} CSS/Playwright selector`,
247
+ },
248
+ {
249
+ name: "text",
250
+ message: `${chalk_1.default.cyan("text")} Visible element text`,
251
+ },
252
+ {
253
+ name: "tagName",
254
+ message: `${chalk_1.default.dim("tagName")} HTML tag name`,
255
+ },
256
+ {
257
+ name: "attributes",
258
+ message: `${chalk_1.default.dim("attributes")} Element attributes (JSON)`,
259
+ },
260
+ {
261
+ name: "anchors",
262
+ message: `${chalk_1.default.dim("anchors")} Anchor context (JSON)`,
263
+ },
238
264
  ],
239
265
  });
240
266
  return (await prompt.run());
241
267
  }
242
268
  async editValue(field, current) {
243
269
  const initialStr = current === null || current === undefined
244
- ? ''
245
- : typeof current === 'object'
270
+ ? ""
271
+ : typeof current === "object"
246
272
  ? JSON.stringify(current, null, 2)
247
273
  : String(current);
248
- const hint = field === 'text'
249
- ? ' (leave blank for null)'
250
- : field === 'attributes' || field === 'anchors'
251
- ? ' (JSON)'
252
- : '';
274
+ const hint = field === "text"
275
+ ? " (leave blank for null)"
276
+ : field === "attributes" || field === "anchors"
277
+ ? " (JSON)"
278
+ : "";
253
279
  const prompt = new enquirer_js_1.Input({
254
- name: 'value',
280
+ name: "value",
255
281
  message: `New ${field}${hint}`,
256
282
  initial: initialStr,
257
283
  });
@@ -259,7 +285,7 @@ class RefactorWizard {
259
285
  return parseInput(field, raw);
260
286
  }
261
287
  async confirm(message) {
262
- const prompt = new enquirer_js_1.Confirm({ name: 'ok', message });
288
+ const prompt = new enquirer_js_1.Confirm({ name: "ok", message });
263
289
  return (await prompt.run());
264
290
  }
265
291
  buildDnaPatch(field, newValue) {