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.
- package/README.md +1 -1
- package/dist/cli/ErrorHandler.d.ts +1 -1
- package/dist/cli/ErrorHandler.js +14 -14
- package/dist/cli/commands/bulk.d.ts +1 -1
- package/dist/cli/commands/bulk.d.ts.map +1 -1
- package/dist/cli/commands/bulk.js +51 -33
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +26 -18
- package/dist/cli/commands/showReport.d.ts +1 -1
- package/dist/cli/commands/showReport.d.ts.map +1 -1
- package/dist/cli/commands/showReport.js +12 -10
- package/dist/cli/commands/status.d.ts +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +8 -8
- package/dist/cli/commands/sync.d.ts +1 -1
- package/dist/cli/commands/sync.d.ts.map +1 -1
- package/dist/cli/commands/sync.js +38 -30
- package/dist/cli/index.js +9 -9
- package/dist/cli/ui/DnaTable.d.ts +1 -1
- package/dist/cli/ui/DnaTable.d.ts.map +1 -1
- package/dist/cli/ui/DnaTable.js +23 -16
- package/dist/cli/ui/RefactorWizard.d.ts +3 -3
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -1
- package/dist/cli/ui/RefactorWizard.js +108 -82
- package/dist/config/ConfigLoader.d.ts +3 -2
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +23 -13
- package/dist/config/DryRunGuard.d.ts +14 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -0
- package/dist/config/DryRunGuard.js +79 -0
- package/dist/config/SelaConfig.d.ts +26 -16
- package/dist/config/SelaConfig.d.ts.map +1 -1
- package/dist/config/SelaConfig.js +54 -22
- package/dist/engine/HealingRegistry.d.ts +1 -1
- package/dist/engine/HealingRegistry.js +3 -3
- package/dist/engine/SelaEngine.d.ts +13 -10
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +292 -101
- package/dist/errors/SelaError.d.ts +132 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +154 -0
- package/dist/fixtures/expectProxy.d.ts +1 -1
- package/dist/fixtures/expectProxy.d.ts.map +1 -1
- package/dist/fixtures/expectProxy.js +17 -10
- package/dist/fixtures/index.d.ts +7 -1
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +26 -11
- package/dist/fixtures/proxyTag.d.ts +12 -0
- package/dist/fixtures/proxyTag.d.ts.map +1 -0
- package/dist/fixtures/proxyTag.js +45 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/reporter/SelaReporter.d.ts +143 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +428 -0
- package/dist/services/ASTSourceUpdater.d.ts +20 -1
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +257 -89
- package/dist/services/ArgumentTypeAnalyzer.js +7 -7
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -1
- package/dist/services/BlastRadiusAnalyzer.js +5 -2
- package/dist/services/ChainValidator.js +22 -22
- package/dist/services/CrossFileHealer.js +7 -7
- package/dist/services/DefinitionTracer.d.ts +1 -1
- package/dist/services/DefinitionTracer.d.ts.map +1 -1
- package/dist/services/DefinitionTracer.js +28 -13
- package/dist/services/DnaEditorService.d.ts +2 -2
- package/dist/services/DnaEditorService.d.ts.map +1 -1
- package/dist/services/DnaEditorService.js +81 -62
- package/dist/services/DnaIndexService.d.ts +1 -1
- package/dist/services/DnaIndexService.d.ts.map +1 -1
- package/dist/services/DnaIndexService.js +14 -14
- package/dist/services/HealReportService.d.ts +43 -1
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +108 -25
- package/dist/services/HealingAdvisory.js +1 -1
- package/dist/services/HealingCacheService.d.ts +135 -0
- package/dist/services/HealingCacheService.d.ts.map +1 -0
- package/dist/services/HealingCacheService.js +460 -0
- package/dist/services/HealthReportService.d.ts +1 -1
- package/dist/services/HealthReportService.d.ts.map +1 -1
- package/dist/services/HealthReportService.js +25 -25
- package/dist/services/InitializerUpdater.d.ts.map +1 -1
- package/dist/services/InitializerUpdater.js +20 -1
- package/dist/services/IntentAuditor.d.ts +18 -1
- package/dist/services/IntentAuditor.d.ts.map +1 -1
- package/dist/services/IntentAuditor.js +50 -26
- package/dist/services/InteractiveReview.d.ts +15 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +125 -0
- package/dist/services/LLMService.d.ts +18 -2
- package/dist/services/LLMService.d.ts.map +1 -1
- package/dist/services/LLMService.js +33 -23
- package/dist/services/PRAutomationService.d.ts +14 -1
- package/dist/services/PRAutomationService.d.ts.map +1 -1
- package/dist/services/PRAutomationService.js +287 -92
- package/dist/services/PendingPromptLedger.d.ts +44 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -0
- package/dist/services/PendingPromptLedger.js +180 -0
- package/dist/services/ReportGenerator.d.ts +70 -0
- package/dist/services/ReportGenerator.d.ts.map +1 -0
- package/dist/services/ReportGenerator.js +191 -0
- package/dist/services/SafetyGuard.d.ts +30 -7
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +85 -116
- package/dist/services/SnapshotService.js +5 -5
- package/dist/services/SourceLinkService.d.ts +2 -2
- package/dist/services/SourceLinkService.d.ts.map +1 -1
- package/dist/services/SourceLinkService.js +31 -22
- package/dist/services/SourceUpdater.d.ts +1 -1
- package/dist/services/SourceUpdater.d.ts.map +1 -1
- package/dist/services/SourceUpdater.js +66 -34
- package/dist/services/TemplateDiffService.d.ts.map +1 -1
- package/dist/services/TemplateDiffService.js +18 -15
- package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +200 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +382 -0
- 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(
|
|
16
|
-
.description(
|
|
17
|
-
.option(
|
|
18
|
-
.option(
|
|
19
|
-
.option(
|
|
20
|
-
.option(
|
|
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(
|
|
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) +
|
|
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 =
|
|
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({
|
|
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
|
|
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(
|
|
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(
|
|
53
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
54
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
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({
|
|
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(
|
|
67
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
68
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
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:
|
|
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(
|
|
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) +
|
|
95
|
+
}, null, 2) + "\n");
|
|
90
96
|
}
|
|
91
97
|
else {
|
|
92
|
-
process.stdout.write(chalk_1.default.dim(
|
|
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:
|
|
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(
|
|
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) +
|
|
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
|
|
113
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
40
|
+
require("dotenv").config({ path: path.join(process.cwd(), ".env") });
|
|
41
41
|
}
|
|
42
42
|
catch {
|
|
43
|
-
// dotenv missing or .env absent
|
|
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(
|
|
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(
|
|
61
|
-
.description(
|
|
62
|
-
.version(
|
|
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
|
|
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(
|
|
75
|
-
.description(
|
|
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 +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;
|
|
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"}
|
package/dist/cli/ui/DnaTable.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
31
|
-
if (r.status ===
|
|
32
|
-
return chalk_1.default.yellow(
|
|
33
|
-
if (r.status ===
|
|
34
|
-
return chalk_1.default.red(
|
|
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(
|
|
37
|
-
return chalk_1.default.cyan(
|
|
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: [
|
|
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: [
|
|
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
|
|
2
|
-
import type { IDnaEditorService } from
|
|
3
|
-
import type { ISourceLinkService } from
|
|
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,
|
|
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(
|
|
15
|
-
if (typeof v ===
|
|
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 ===
|
|
21
|
-
return raw.trim() ===
|
|
22
|
-
if (field ===
|
|
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
|
|
35
|
-
|
|
36
|
-
case
|
|
37
|
-
|
|
38
|
-
case
|
|
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
|
|
45
|
-
if (typeof value !==
|
|
46
|
-
errs.push(
|
|
47
|
-
if (value ===
|
|
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 ===
|
|
50
|
-
errs.push(
|
|
54
|
+
if (typeof value === "string" && /[\r\n]/.test(value))
|
|
55
|
+
errs.push("selector must not contain raw newlines");
|
|
51
56
|
break;
|
|
52
|
-
case
|
|
53
|
-
if (value !== null && typeof value !==
|
|
54
|
-
errs.push(
|
|
55
|
-
if (typeof value ===
|
|
56
|
-
errs.push(
|
|
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
|
|
59
|
-
if (typeof value !==
|
|
60
|
-
errs.push(
|
|
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
|
|
63
|
-
if (typeof value !==
|
|
64
|
-
errs.push(
|
|
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
|
|
67
|
-
if (typeof value !==
|
|
68
|
-
errs.push(
|
|
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 +
|
|
82
|
+
process.stdout.write(content + "\n");
|
|
78
83
|
};
|
|
79
|
-
process.stdout.write(
|
|
80
|
-
line(
|
|
81
|
-
line(
|
|
82
|
-
line(
|
|
83
|
-
line(
|
|
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(
|
|
91
|
+
line("Source", `${rel}:${record.sourceLine}`);
|
|
87
92
|
}
|
|
88
|
-
line(
|
|
89
|
-
|
|
90
|
-
|
|
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(
|
|
94
|
-
process.stdout.write(` ${chalk_1.default.bold(
|
|
95
|
-
process.stdout.write(` ${
|
|
96
|
-
process.stdout.write(` ${chalk_1.default.red(
|
|
97
|
-
process.stdout.write(` ${chalk_1.default.green(
|
|
98
|
-
process.stdout.write(
|
|
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
|
|
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 ===
|
|
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(
|
|
148
|
+
process.stdout.write("\n");
|
|
138
149
|
for (const e of errs) {
|
|
139
|
-
process.stdout.write(` ${chalk_1.default.red(
|
|
150
|
+
process.stdout.write(` ${chalk_1.default.red("✗")} ${e}\n`);
|
|
140
151
|
}
|
|
141
|
-
process.stdout.write(
|
|
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(
|
|
157
|
+
const apply = await this.confirm("Apply changes?");
|
|
147
158
|
if (!apply) {
|
|
148
|
-
const again = await this.confirm(
|
|
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 ===
|
|
167
|
+
field === "selector" &&
|
|
157
168
|
record.sourceFile &&
|
|
158
169
|
record.sourceLine > 0) {
|
|
159
|
-
applySource = await this.confirm(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
203
|
+
const another = await this.confirm("Edit another field?");
|
|
193
204
|
if (!another)
|
|
194
205
|
break;
|
|
195
206
|
}
|
|
196
|
-
process.stdout.write(
|
|
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 =
|
|
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(
|
|
212
|
-
err.code =
|
|
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:
|
|
221
|
-
message:
|
|
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:
|
|
231
|
-
message:
|
|
241
|
+
name: "field",
|
|
242
|
+
message: "Which field to edit?",
|
|
232
243
|
choices: [
|
|
233
|
-
{
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
{
|
|
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 ===
|
|
270
|
+
? ""
|
|
271
|
+
: typeof current === "object"
|
|
246
272
|
? JSON.stringify(current, null, 2)
|
|
247
273
|
: String(current);
|
|
248
|
-
const hint = field ===
|
|
249
|
-
?
|
|
250
|
-
: field ===
|
|
251
|
-
?
|
|
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:
|
|
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:
|
|
288
|
+
const prompt = new enquirer_js_1.Confirm({ name: "ok", message });
|
|
263
289
|
return (await prompt.run());
|
|
264
290
|
}
|
|
265
291
|
buildDnaPatch(field, newValue) {
|