sela-core 0.1.0-alpha.36 → 0.1.0-alpha.38
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/dist/cli/commands/shadowRegression.d.ts.map +1 -1
- package/dist/cli/commands/shadowRegression.js +7 -4
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +22 -4
- package/dist/config/DryRunGuard.d.ts +15 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -1
- package/dist/config/DryRunGuard.js +30 -0
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +38 -2
- package/dist/reporter/SelaReporter.d.ts +9 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -1
- package/dist/reporter/SelaReporter.js +62 -0
- package/dist/services/ASTSourceUpdater.d.ts +50 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +152 -13
- package/dist/services/CrossFileHealer.d.ts.map +1 -1
- package/dist/services/CrossFileHealer.js +17 -0
- package/dist/services/HealReportService.d.ts +12 -1
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +1 -1
- package/dist/services/InitializerUpdater.d.ts +12 -1
- package/dist/services/InitializerUpdater.d.ts.map +1 -1
- package/dist/services/InitializerUpdater.js +45 -1
- package/dist/services/RegressionManifestService.d.ts +8 -0
- package/dist/services/RegressionManifestService.d.ts.map +1 -1
- package/dist/services/RegressionManifestService.js +8 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shadowRegression.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/shadowRegression.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"shadowRegression.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/shadowRegression.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUzC,OAAO,KAAK,EACV,gBAAgB,EAEjB,MAAM,mCAAmC,CAAC;AAe3C;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoI/D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,gBAAgB,EAAE,CAqC9D"}
|
|
@@ -62,15 +62,18 @@ function registerShadowRegression(program) {
|
|
|
62
62
|
.command("shadow-regression")
|
|
63
63
|
.description("Verify a bypassed shared-locator heal against its downstream blast radius " +
|
|
64
64
|
"(stateless, in-memory, zero disk mutation)")
|
|
65
|
-
.
|
|
65
|
+
.option("--manifest <path>", `Path to sela-regression-manifest.json (emitted by the primary run). ` +
|
|
66
|
+
`Defaults to <cwd>/.sela/${RegressionManifestService_js_1.REGRESSION_MANIFEST_FILE}`)
|
|
66
67
|
.option("--dir <path>", "Project dir holding sela-report.json (merge target)", process.cwd())
|
|
67
68
|
.option("--config <path>", "Playwright config to pass through to the run")
|
|
68
69
|
.option("--report <path>", "Override path to the base sela-report.json to merge into")
|
|
69
70
|
.action(async (opts) => {
|
|
70
71
|
const targetDir = path.resolve(opts.dir ?? process.cwd());
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
const manifestRaw = opts.manifest ??
|
|
73
|
+
path.join(targetDir, ".sela", RegressionManifestService_js_1.REGRESSION_MANIFEST_FILE);
|
|
74
|
+
const manifestAbs = path.isAbsolute(manifestRaw)
|
|
75
|
+
? manifestRaw
|
|
76
|
+
: path.resolve(process.cwd(), manifestRaw);
|
|
74
77
|
// 1. Load + validate the manifest (throws loudly on bad input).
|
|
75
78
|
let manifest;
|
|
76
79
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAiB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAiB,MAAM,cAAc,CAAC;AAgNzE,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAK5C,OAAO,CAAC,MAAM,CAAC,aAAa,CAA2B;IAEvD;;;;;;;;OAQG;IACH,MAAM,CAAC,WAAW,IAAI,cAAc;IAqCpC;;;;OAIG;IACH,MAAM,CAAC,YAAY,IAAI,UAAU;IAKjC,OAAO,CAAC,MAAM,CAAC,aAAa;IAsB5B,yEAAyE;IACzE,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,IAAI;IAKjC,uDAAuD;IACvD,MAAM,CAAC,KAAK,IAAI,IAAI;CAKrB"}
|
|
@@ -167,10 +167,28 @@ function loadRawConfig(configPath) {
|
|
|
167
167
|
lastError = err;
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
-
// Every attempt failed
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
|
|
170
|
+
// Every attempt failed. Two cases:
|
|
171
|
+
//
|
|
172
|
+
// 1. The only candidate was a .ts file and the error looks like a missing
|
|
173
|
+
// TypeScript loader (e.g. "Cannot find module" for a .ts import, or
|
|
174
|
+
// "Unknown file extension .ts"). The CLI runs in plain Node.js — no tsx/
|
|
175
|
+
// ts-node — so the config simply cannot be loaded here. Gracefully fall
|
|
176
|
+
// back to defaults rather than crashing a CLI command that doesn't need
|
|
177
|
+
// the config (e.g. `sela shadow-regression`).
|
|
178
|
+
//
|
|
179
|
+
// 2. Genuine config error (syntax, key validation, etc.) → hard throw so CI
|
|
180
|
+
// surfaces the problem instead of silently running on stale defaults.
|
|
181
|
+
const msg = lastError?.message ?? "";
|
|
182
|
+
const isNoTsLoader = configPath.endsWith(".ts") &&
|
|
183
|
+
(msg.includes("Cannot find module") ||
|
|
184
|
+
msg.includes("Unknown file extension") ||
|
|
185
|
+
msg.includes("ERR_UNKNOWN_FILE_EXTENSION"));
|
|
186
|
+
if (isNoTsLoader) {
|
|
187
|
+
logger_1.logger.warn(`[Sela] sela.config.ts found but no TypeScript loader is active — running on defaults. ` +
|
|
188
|
+
`If you need config values in the CLI, add a compiled sela.config.js alongside it.`);
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
throw new Error(`[Sela] Failed to load ${path.basename(configPath)}: ${msg || "unknown error"}. ` +
|
|
174
192
|
`Ensure a TypeScript loader (Playwright, ts-node, tsx) is active, ` +
|
|
175
193
|
`or provide a compiled sela.config.js alongside sela.config.ts.`);
|
|
176
194
|
}
|
|
@@ -10,5 +10,20 @@ export declare const DryRunGuard: {
|
|
|
10
10
|
* and a real run is auditable in test output and CI logs.
|
|
11
11
|
*/
|
|
12
12
|
logSkippedWrite(label: string, target: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Run `fn` with the dry-run gate forcibly ACTIVE, restoring the prior
|
|
15
|
+
* `SELA_DRY_RUN` value afterwards. Every write-head consulted by `fn`
|
|
16
|
+
* (SourceUpdater, ASTSourceUpdater, InitializerUpdater, CrossFileHealer,
|
|
17
|
+
* git auto-commit) sees `active() === true` and skips its disk write, so
|
|
18
|
+
* the scope is a hard zero-write guarantee — used for speculative
|
|
19
|
+
* "what would the heal look like" previews (e.g. the shared-locator
|
|
20
|
+
* bypass ghost diff) that must compute a mutation in memory without
|
|
21
|
+
* touching the source tree.
|
|
22
|
+
*
|
|
23
|
+
* Safe under Sela's execution model: heals are awaited serially within a
|
|
24
|
+
* worker and Playwright workers are separate processes, so the transient
|
|
25
|
+
* env mutation cannot leak across a concurrent heal.
|
|
26
|
+
*/
|
|
27
|
+
runSuppressed<T>(fn: () => Promise<T> | T): Promise<T>;
|
|
13
28
|
};
|
|
14
29
|
//# sourceMappingURL=DryRunGuard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DryRunGuard.d.ts","sourceRoot":"","sources":["../../src/config/DryRunGuard.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW;IACtB;;;;OAIG;iBACS,MAAM,CAAC,UAAU,GAAiB,OAAO;IAIrD;;;OAGG;2BACoB,MAAM,UAAU,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"DryRunGuard.d.ts","sourceRoot":"","sources":["../../src/config/DryRunGuard.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW;IACtB;;;;OAIG;iBACS,MAAM,CAAC,UAAU,GAAiB,OAAO;IAIrD;;;OAGG;2BACoB,MAAM,UAAU,MAAM,GAAG,IAAI;IAKpD;;;;;;;;;;;;;OAaG;kBACiB,CAAC,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAa7D,CAAC"}
|
|
@@ -77,4 +77,34 @@ exports.DryRunGuard = {
|
|
|
77
77
|
const display = target ? path.basename(target) : "<no-path>";
|
|
78
78
|
logger_1.logger.debug(`Dry Run - skip ${label} → ${display}`);
|
|
79
79
|
},
|
|
80
|
+
/**
|
|
81
|
+
* Run `fn` with the dry-run gate forcibly ACTIVE, restoring the prior
|
|
82
|
+
* `SELA_DRY_RUN` value afterwards. Every write-head consulted by `fn`
|
|
83
|
+
* (SourceUpdater, ASTSourceUpdater, InitializerUpdater, CrossFileHealer,
|
|
84
|
+
* git auto-commit) sees `active() === true` and skips its disk write, so
|
|
85
|
+
* the scope is a hard zero-write guarantee — used for speculative
|
|
86
|
+
* "what would the heal look like" previews (e.g. the shared-locator
|
|
87
|
+
* bypass ghost diff) that must compute a mutation in memory without
|
|
88
|
+
* touching the source tree.
|
|
89
|
+
*
|
|
90
|
+
* Safe under Sela's execution model: heals are awaited serially within a
|
|
91
|
+
* worker and Playwright workers are separate processes, so the transient
|
|
92
|
+
* env mutation cannot leak across a concurrent heal.
|
|
93
|
+
*/
|
|
94
|
+
async runSuppressed(fn) {
|
|
95
|
+
const prev = process.env.SELA_DRY_RUN;
|
|
96
|
+
// Must be the strict token `parseEnvBoolean` accepts ("true"/"false") —
|
|
97
|
+
// "1" throws. MutationApplier is additionally pinned with write:false by
|
|
98
|
+
// callers, so its `=== "1"` dry-run check is not relied upon here.
|
|
99
|
+
process.env.SELA_DRY_RUN = "true";
|
|
100
|
+
try {
|
|
101
|
+
return await fn();
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
if (prev === undefined)
|
|
105
|
+
delete process.env.SELA_DRY_RUN;
|
|
106
|
+
else
|
|
107
|
+
process.env.SELA_DRY_RUN = prev;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
80
110
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAcxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAe,QAAQ,EAAkB,MAAM,yBAAyB,CAAC;AAiGhF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,eAAe,CAAkC;IAKzD,OAAO,CAAC,qBAAqB,CAAkC;;IA0BzD,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqEnB,IAAI,CACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAcxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAe,QAAQ,EAAkB,MAAM,yBAAyB,CAAC;AAiGhF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,eAAe,CAAkC;IAKzD,OAAO,CAAC,qBAAqB,CAAkC;;IA0BzD,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqEnB,IAAI,CACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;IAm9BlB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAerB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,2BAA2B;IA4BnC,OAAO,CAAC,yBAAyB;IAgDjC,OAAO,CAAC,kBAAkB;IA8D1B,OAAO,CAAC,oBAAoB;IAyD5B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5E,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK/B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;YAsJtB,aAAa;IAsB3B,OAAO,CAAC,0BAA0B;IA+BlC,OAAO,CAAC,WAAW;IA6Bb,wBAAwB,CAC5B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;CAqBnB;AAED,OAAO,EAAE,CAAC"}
|
|
@@ -586,7 +586,41 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
586
586
|
line,
|
|
587
587
|
});
|
|
588
588
|
if (census.shared) {
|
|
589
|
-
|
|
589
|
+
// ROOT-DECLARATION GHOST DIFF (zero-write): reuse the same AST heal
|
|
590
|
+
// engine HEALED events use, in a suppressed dry-run preview, so the
|
|
591
|
+
// bypass card shows the elegant declaration-root mutation (uniform
|
|
592
|
+
// with HEALED) instead of a raw selector-string swap. Falls back to
|
|
593
|
+
// the legacy selector diff if the engine can't resolve a root.
|
|
594
|
+
let ghostDiff = "";
|
|
595
|
+
let previewDeclSite;
|
|
596
|
+
try {
|
|
597
|
+
const preview = await ASTSourceUpdater_1.ASTSourceUpdater.composeHealPreview({
|
|
598
|
+
filePath: resolvedForCompose ?? filePath,
|
|
599
|
+
line,
|
|
600
|
+
newSelector: diskNewSelector,
|
|
601
|
+
oldSelector: diskOldElementSelector,
|
|
602
|
+
});
|
|
603
|
+
ghostDiff = preview.ghostDiff;
|
|
604
|
+
previewDeclSite = preview.definitionSite;
|
|
605
|
+
}
|
|
606
|
+
catch (err) {
|
|
607
|
+
logger_1.logger.debug(`Bypass root-diff preview failed, using fallback: ${err?.message ?? err}`);
|
|
608
|
+
}
|
|
609
|
+
if (!ghostDiff) {
|
|
610
|
+
ghostDiff = (0, IsolatedDiff_1.generateUnifiedDiff)(`${diskOldElementSelector}\n`, `${diskNewSelector}\n`, { filePath: census.variablePath, context: 1, normalizeEol: true });
|
|
611
|
+
}
|
|
612
|
+
// Declaration locus for the HTML highlight. census.variablePath is
|
|
613
|
+
// "relative/path.ts:LINE"; prefer the preview's resolved site (gives
|
|
614
|
+
// an accurate cross-file path) when available.
|
|
615
|
+
const declColon = census.variablePath.lastIndexOf(":");
|
|
616
|
+
const declarationFile = previewDeclSite?.file ??
|
|
617
|
+
(declColon > 1
|
|
618
|
+
? census.variablePath.slice(0, declColon)
|
|
619
|
+
: census.variablePath);
|
|
620
|
+
const declarationLine = previewDeclSite?.line ??
|
|
621
|
+
(declColon > 1
|
|
622
|
+
? Number(census.variablePath.slice(declColon + 1)) || undefined
|
|
623
|
+
: undefined);
|
|
590
624
|
const message = `Sela Safety Guard: this locator is tied to a shared declaration at ` +
|
|
591
625
|
`${census.variablePath}, referenced by ${census.testFunctionCount} test(s). ` +
|
|
592
626
|
`Sela healed it in-memory to let this run pass, but did NOT mutate disk ` +
|
|
@@ -606,6 +640,8 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
606
640
|
oldSelector: elementSelectorOnly,
|
|
607
641
|
newSelector: newFullSelector,
|
|
608
642
|
variablePath: census.variablePath,
|
|
643
|
+
declarationFile,
|
|
644
|
+
declarationLine,
|
|
609
645
|
testFunctionCount: census.testFunctionCount,
|
|
610
646
|
affectedFiles: census.affectedFiles,
|
|
611
647
|
ghostDiff,
|
|
@@ -1220,7 +1256,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
1220
1256
|
if (!this.config.regressionActive) {
|
|
1221
1257
|
try {
|
|
1222
1258
|
const records = RegressionManifestService_1.RegressionManifestService.buildRecords(HealReportService_1.sharedHealReport.getBypassedEvents());
|
|
1223
|
-
const manifestPath = RegressionManifestService_1.RegressionManifestService.writeManifest(process.cwd(), records);
|
|
1259
|
+
const manifestPath = RegressionManifestService_1.RegressionManifestService.writeManifest(path.join(process.cwd(), ".sela"), records);
|
|
1224
1260
|
if (manifestPath) {
|
|
1225
1261
|
logger_1.logger.info(` Shadow-regression manifest: ${path.relative(process.cwd(), manifestPath)} ` +
|
|
1226
1262
|
`(${records.length} bypass${records.length !== 1 ? "es" : ""})`);
|
|
@@ -129,6 +129,15 @@ export declare class SelaReporter implements Reporter {
|
|
|
129
129
|
*/
|
|
130
130
|
private collectBypasses;
|
|
131
131
|
private readBypassedEvents;
|
|
132
|
+
/**
|
|
133
|
+
* Read raw (un-prettified) BYPASSED events directly from shard files.
|
|
134
|
+
* Must be called BEFORE mergeShards() — the merge runs
|
|
135
|
+
* normalizeEventsForReport() which converts internal:role=... selector
|
|
136
|
+
* strings to human-readable page.getByRole(...) form. The manifest writer
|
|
137
|
+
* needs the raw internal strings so RegressionInjector.lookup() and
|
|
138
|
+
* buildLiveLocator() can consume them without a round-trip parse.
|
|
139
|
+
*/
|
|
140
|
+
private readRawBypassEventsFromShards;
|
|
132
141
|
private collectRunSummary;
|
|
133
142
|
/**
|
|
134
143
|
* Render the summary table — but only when Sela actually did something.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelaReporter.d.ts","sourceRoot":"","sources":["../../src/reporter/SelaReporter.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,UAAU,EACV,KAAK,EACL,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAEL,iBAAiB,EAIjB,KAAK,aAAa,EACnB,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"SelaReporter.d.ts","sourceRoot":"","sources":["../../src/reporter/SelaReporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,6 +67,7 @@ const fs = __importStar(require("fs"));
|
|
|
67
67
|
const path = __importStar(require("path"));
|
|
68
68
|
const HealReportService_js_1 = require("../services/HealReportService.js");
|
|
69
69
|
const ReportMergeService_js_1 = require("../services/ReportMergeService.js");
|
|
70
|
+
const RegressionManifestService_js_1 = require("../services/RegressionManifestService.js");
|
|
70
71
|
const singleton_js_1 = require("../engine/singleton.js");
|
|
71
72
|
const SelaError_js_1 = require("../errors/SelaError.js");
|
|
72
73
|
const PendingPromptLedger_js_1 = require("../services/PendingPromptLedger.js");
|
|
@@ -223,6 +224,11 @@ class SelaReporter {
|
|
|
223
224
|
// main-process writer, so it is the safe place to merge them into the
|
|
224
225
|
// final sela-report.{html,json}. CI uses cross-machine `sela merge`.
|
|
225
226
|
if (!this.isCi()) {
|
|
227
|
+
// Capture raw bypass events from shards BEFORE merge — the merge runs
|
|
228
|
+
// normalizeEventsForReport() which prettifies internal:role=... selector
|
|
229
|
+
// strings to page.getByRole(...) form. The manifest needs the raw
|
|
230
|
+
// internal strings so buildLiveLocator() can consume them directly.
|
|
231
|
+
const rawBypassedFromShards = this.readRawBypassEventsFromShards(this.cwd);
|
|
226
232
|
try {
|
|
227
233
|
this.mergeShards(this.cwd);
|
|
228
234
|
}
|
|
@@ -230,6 +236,28 @@ class SelaReporter {
|
|
|
230
236
|
const msg = err instanceof Error ? err.message : String(err);
|
|
231
237
|
this.logger.error(`[Sela] shard merge failed: ${msg}`);
|
|
232
238
|
}
|
|
239
|
+
// Write regression manifest using raw (un-prettified) bypass events.
|
|
240
|
+
// Fall back to readBypassedEvents() only when no shards were present
|
|
241
|
+
// (e.g. single-process run where reporter has in-memory events or the
|
|
242
|
+
// sela-report.json was pre-populated by a previous run).
|
|
243
|
+
try {
|
|
244
|
+
const bypassedEvents = rawBypassedFromShards.length > 0
|
|
245
|
+
? rawBypassedFromShards
|
|
246
|
+
: this.readBypassedEvents();
|
|
247
|
+
if (bypassedEvents.length > 0) {
|
|
248
|
+
const records = RegressionManifestService_js_1.RegressionManifestService.buildRecords(bypassedEvents);
|
|
249
|
+
const manifestPath = RegressionManifestService_js_1.RegressionManifestService.writeManifest(path.join(this.cwd, ".sela"), records);
|
|
250
|
+
if (manifestPath) {
|
|
251
|
+
this.logger.info(` Shadow-regression manifest: ${path.relative(this.cwd, manifestPath)} ` +
|
|
252
|
+
`(${records.length} bypass${records.length !== 1 ? "es" : ""})`);
|
|
253
|
+
this.logger.info(" Verify with: npx sela shadow-regression");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
259
|
+
this.logger.error(`[Sela] regression manifest write failed: ${msg}`);
|
|
260
|
+
}
|
|
233
261
|
}
|
|
234
262
|
// ── CI: centralized git/PR flow ──────────────────────────────
|
|
235
263
|
//
|
|
@@ -341,6 +369,40 @@ class SelaReporter {
|
|
|
341
369
|
return [];
|
|
342
370
|
}
|
|
343
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Read raw (un-prettified) BYPASSED events directly from shard files.
|
|
374
|
+
* Must be called BEFORE mergeShards() — the merge runs
|
|
375
|
+
* normalizeEventsForReport() which converts internal:role=... selector
|
|
376
|
+
* strings to human-readable page.getByRole(...) form. The manifest writer
|
|
377
|
+
* needs the raw internal strings so RegressionInjector.lookup() and
|
|
378
|
+
* buildLiveLocator() can consume them without a round-trip parse.
|
|
379
|
+
*/
|
|
380
|
+
readRawBypassEventsFromShards(cwd) {
|
|
381
|
+
const shardFiles = ReportMergeService_js_1.ReportMergeService.discoverShardFiles((0, HealReportService_js_1.reportShardDir)(cwd), "sela-shard-*.json");
|
|
382
|
+
if (shardFiles.length === 0)
|
|
383
|
+
return [];
|
|
384
|
+
const seen = new Set();
|
|
385
|
+
const result = [];
|
|
386
|
+
for (const file of shardFiles) {
|
|
387
|
+
try {
|
|
388
|
+
const events = ReportMergeService_js_1.ReportMergeService.readShardEvents(file);
|
|
389
|
+
for (const e of events) {
|
|
390
|
+
if (e.kind !== "BYPASSED")
|
|
391
|
+
continue;
|
|
392
|
+
const dedup = e.deterministicKey ?? e.stableId ?? "";
|
|
393
|
+
if (dedup && seen.has(dedup))
|
|
394
|
+
continue;
|
|
395
|
+
if (dedup)
|
|
396
|
+
seen.add(dedup);
|
|
397
|
+
result.push(e);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
// Individual shard failures are non-fatal — merge() already logs them.
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
344
406
|
collectRunSummary() {
|
|
345
407
|
const healed = this.report.getHealedEvents().length;
|
|
346
408
|
const held = this.report.getProtectedEvents().length;
|
|
@@ -17,6 +17,12 @@ export interface ASTUpdateResult {
|
|
|
17
17
|
file: string;
|
|
18
18
|
line: number;
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Speculative unified diff of the definition-site mutation (before → after),
|
|
22
|
+
* computed in memory. Populated by the cross-file healer so the shared-locator
|
|
23
|
+
* bypass preview can render a root-declaration diff without a disk write.
|
|
24
|
+
*/
|
|
25
|
+
unifiedDiff?: string;
|
|
20
26
|
/** Number of test functions referencing the mutated constant (blast radius). */
|
|
21
27
|
blastRadius?: number;
|
|
22
28
|
}
|
|
@@ -44,6 +50,13 @@ export interface ComposeHealInput {
|
|
|
44
50
|
contextSpecific?: boolean;
|
|
45
51
|
/** Runtime: selector rot is global across all consumers. */
|
|
46
52
|
globalCorrect?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* When true, force MUTATE_IN_PLACE for arg-level diff preview regardless of
|
|
55
|
+
* consumer count. Used by composeHealPreview so shared-variable diffs are
|
|
56
|
+
* computed even when decideArgs would normally return PROMPT_INTERACTIVE.
|
|
57
|
+
* Has no effect on disk: the caller must ensure write:false / DryRunGuard.
|
|
58
|
+
*/
|
|
59
|
+
previewMode?: boolean;
|
|
47
60
|
/** When true, write the result via MutationApplier (with the gate). */
|
|
48
61
|
apply?: boolean;
|
|
49
62
|
applyOptions?: ApplyOptions;
|
|
@@ -69,6 +82,33 @@ export declare class ASTSourceUpdater {
|
|
|
69
82
|
* write it to disk through the type-safety gate.
|
|
70
83
|
*/
|
|
71
84
|
static composeHeal(input: ComposeHealInput): Promise<ComposeHealResult>;
|
|
85
|
+
/**
|
|
86
|
+
* Speculative, ZERO-WRITE preview of the root-declaration heal.
|
|
87
|
+
*
|
|
88
|
+
* Runs the exact same trace-back + mutation engine `composeHeal` uses, but
|
|
89
|
+
* under a forcibly-suppressed dry-run scope AND with `write:false`, so NO
|
|
90
|
+
* write-head (MutationApplier, InitializerUpdater, CrossFileHealer) can touch
|
|
91
|
+
* disk. Returns the root-declaration unified diff a real heal WOULD have
|
|
92
|
+
* produced — used by the shared-locator bypass gate so its report card shows
|
|
93
|
+
* the elegant declaration-root diff (uniform with HEALED events) instead of a
|
|
94
|
+
* raw selector-string swap.
|
|
95
|
+
*
|
|
96
|
+
* The diff is sourced from the canonical heal pipeline:
|
|
97
|
+
* • same-file paths (local const / same-file POM property) → MutationApplier
|
|
98
|
+
* `outcome.microDiff`,
|
|
99
|
+
* • cross-file imported declarations → `crossFileResult.unifiedDiff`.
|
|
100
|
+
*
|
|
101
|
+
* Returns an empty `ghostDiff` when the engine cannot resolve a root (trace
|
|
102
|
+
* abort / exotic shape); the caller falls back to the legacy selector diff.
|
|
103
|
+
*/
|
|
104
|
+
static composeHealPreview(input: ComposeHealInput): Promise<{
|
|
105
|
+
ghostDiff: string;
|
|
106
|
+
definitionSite?: {
|
|
107
|
+
file: string;
|
|
108
|
+
line: number;
|
|
109
|
+
};
|
|
110
|
+
writtenLine?: number;
|
|
111
|
+
}>;
|
|
72
112
|
/** Drain-loop entry: apply pre-built batches through the write gate. */
|
|
73
113
|
static applyMutationBatches(batches: FileBatch[], opts?: ApplyOptions): ApplyOutcome[];
|
|
74
114
|
/** Convert a DecisionEngine result into an anchored MutationApplier batch. */
|
|
@@ -140,6 +180,16 @@ export declare class ASTSourceUpdater {
|
|
|
140
180
|
private updateObjectProperty;
|
|
141
181
|
private lineToOffset;
|
|
142
182
|
resolveFilePath(raw: string): string | null;
|
|
183
|
+
/**
|
|
184
|
+
* ZERO-WRITE preview of the legacy semantic definition-site heal. Reuses the
|
|
185
|
+
* REAL tracer (DefinitionTracer) + write-head (InitializerUpdater) via
|
|
186
|
+
* semanticDispatch with the high-blast BLOCK bypassed, so it computes the
|
|
187
|
+
* root-declaration diff a shared symbolic locator (identifier / property /
|
|
188
|
+
* cross-file) WOULD receive — covering the bypass cases composeHeal returns
|
|
189
|
+
* PROMPT_INTERACTIVE for. MUST run inside DryRunGuard.runSuppressed so
|
|
190
|
+
* InitializerUpdater keeps the mutation in memory and never touches disk.
|
|
191
|
+
*/
|
|
192
|
+
previewSemanticHeal(caller: CallerContext, oldSelector: string, newSelector: string): ASTUpdateResult | null;
|
|
143
193
|
private semanticDispatch;
|
|
144
194
|
private applyDefinitionSiteHeal;
|
|
145
195
|
private handleIdentifierArg;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ASTSourceUpdater.d.ts","sourceRoot":"","sources":["../../src/services/ASTSourceUpdater.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAQ/C,OAAO,EAAE,cAAc,EAAgB,MAAM,mBAAmB,CAAC;AAOjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAmB,YAAY,EAAmC,MAAM,mBAAmB,CAAC;AACnG,OAAO,EAAkB,QAAQ,EAAc,MAAM,kBAAkB,CAAC;AAExE,OAAO,EAEL,SAAS,EAET,YAAY,EACZ,YAAY,EACb,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ASTSourceUpdater.d.ts","sourceRoot":"","sources":["../../src/services/ASTSourceUpdater.ts"],"names":[],"mappings":"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"}
|
|
@@ -1999,7 +1999,9 @@ class ASTSourceUpdater {
|
|
|
1999
1999
|
argTargets,
|
|
2000
2000
|
failingCallSite: failingNode,
|
|
2001
2001
|
contextSpecific: input.contextSpecific ?? false,
|
|
2002
|
-
|
|
2002
|
+
// previewMode forces MUTATE_IN_PLACE regardless of consumer count so
|
|
2003
|
+
// the diff is always computable (no disk write — caller holds the gate).
|
|
2004
|
+
globalCorrect: (input.globalCorrect ?? false) || (input.previewMode ?? false),
|
|
2003
2005
|
});
|
|
2004
2006
|
if (argDecision) {
|
|
2005
2007
|
const batch = ASTSourceUpdater.decisionToBatch(input.filePath, argDecision);
|
|
@@ -2042,6 +2044,74 @@ class ASTSourceUpdater {
|
|
|
2042
2044
|
}
|
|
2043
2045
|
return result;
|
|
2044
2046
|
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Speculative, ZERO-WRITE preview of the root-declaration heal.
|
|
2049
|
+
*
|
|
2050
|
+
* Runs the exact same trace-back + mutation engine `composeHeal` uses, but
|
|
2051
|
+
* under a forcibly-suppressed dry-run scope AND with `write:false`, so NO
|
|
2052
|
+
* write-head (MutationApplier, InitializerUpdater, CrossFileHealer) can touch
|
|
2053
|
+
* disk. Returns the root-declaration unified diff a real heal WOULD have
|
|
2054
|
+
* produced — used by the shared-locator bypass gate so its report card shows
|
|
2055
|
+
* the elegant declaration-root diff (uniform with HEALED events) instead of a
|
|
2056
|
+
* raw selector-string swap.
|
|
2057
|
+
*
|
|
2058
|
+
* The diff is sourced from the canonical heal pipeline:
|
|
2059
|
+
* • same-file paths (local const / same-file POM property) → MutationApplier
|
|
2060
|
+
* `outcome.microDiff`,
|
|
2061
|
+
* • cross-file imported declarations → `crossFileResult.unifiedDiff`.
|
|
2062
|
+
*
|
|
2063
|
+
* Returns an empty `ghostDiff` when the engine cannot resolve a root (trace
|
|
2064
|
+
* abort / exotic shape); the caller falls back to the legacy selector diff.
|
|
2065
|
+
*/
|
|
2066
|
+
static async composeHealPreview(input) {
|
|
2067
|
+
return DryRunGuard_1.DryRunGuard.runSuppressed(async () => {
|
|
2068
|
+
// 1) Canonical compose pipeline — handles option-object args (getByRole
|
|
2069
|
+
// name), inline string args, and cross-file imports it can resolve.
|
|
2070
|
+
const composed = await ASTSourceUpdater.composeHeal({
|
|
2071
|
+
...input,
|
|
2072
|
+
apply: true,
|
|
2073
|
+
// Force MUTATE_IN_PLACE for multi-consumer symbolic args so the root-
|
|
2074
|
+
// declaration diff is always computed (shared-locator bypass preview).
|
|
2075
|
+
previewMode: true,
|
|
2076
|
+
applyOptions: {
|
|
2077
|
+
...(input.applyOptions ?? {}),
|
|
2078
|
+
write: false,
|
|
2079
|
+
respectDryRun: true,
|
|
2080
|
+
},
|
|
2081
|
+
});
|
|
2082
|
+
if (composed.kind === "cross-file" && composed.crossFileResult?.unifiedDiff) {
|
|
2083
|
+
const cross = composed.crossFileResult;
|
|
2084
|
+
return {
|
|
2085
|
+
ghostDiff: cross.unifiedDiff ?? "",
|
|
2086
|
+
definitionSite: cross.definitionSite,
|
|
2087
|
+
writtenLine: cross.definitionSite?.line ?? composed.writtenLine,
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
if (composed.outcome?.microDiff) {
|
|
2091
|
+
return {
|
|
2092
|
+
ghostDiff: composed.outcome.microDiff,
|
|
2093
|
+
writtenLine: composed.writtenLine,
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
// 2) Fallback — gate-free legacy semantic preview. composeHeal returns
|
|
2097
|
+
// PROMPT_INTERACTIVE (no diff) for shared symbolic locators; this
|
|
2098
|
+
// reuses the real tracer + write-head to compute the root diff anyway.
|
|
2099
|
+
try {
|
|
2100
|
+
const semantic = new ASTSourceUpdater().previewSemanticHeal({ filePath: input.filePath, line: input.line }, input.oldSelector ?? "", input.newSelector ?? "");
|
|
2101
|
+
if (semantic?.unifiedDiff) {
|
|
2102
|
+
return {
|
|
2103
|
+
ghostDiff: semantic.unifiedDiff,
|
|
2104
|
+
definitionSite: semantic.definitionSite,
|
|
2105
|
+
writtenLine: semantic.definitionSite?.line,
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
catch (err) {
|
|
2110
|
+
logger_1.logger.debug(`previewSemanticHeal fallback failed: ${err?.message ?? err}`);
|
|
2111
|
+
}
|
|
2112
|
+
return { ghostDiff: "" };
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2045
2115
|
/** Drain-loop entry: apply pre-built batches through the write gate. */
|
|
2046
2116
|
static applyMutationBatches(batches, opts = {}) {
|
|
2047
2117
|
return MutationApplier_1.MutationApplier.applyBatches(batches, opts);
|
|
@@ -3482,7 +3552,27 @@ class ASTSourceUpdater {
|
|
|
3482
3552
|
// ═══════════════════════════════════════════════════════════════════
|
|
3483
3553
|
// T-10 / T-11: Semantic Dispatch Gate + handlers
|
|
3484
3554
|
// ═══════════════════════════════════════════════════════════════════
|
|
3485
|
-
|
|
3555
|
+
/**
|
|
3556
|
+
* ZERO-WRITE preview of the legacy semantic definition-site heal. Reuses the
|
|
3557
|
+
* REAL tracer (DefinitionTracer) + write-head (InitializerUpdater) via
|
|
3558
|
+
* semanticDispatch with the high-blast BLOCK bypassed, so it computes the
|
|
3559
|
+
* root-declaration diff a shared symbolic locator (identifier / property /
|
|
3560
|
+
* cross-file) WOULD receive — covering the bypass cases composeHeal returns
|
|
3561
|
+
* PROMPT_INTERACTIVE for. MUST run inside DryRunGuard.runSuppressed so
|
|
3562
|
+
* InitializerUpdater keeps the mutation in memory and never touches disk.
|
|
3563
|
+
*/
|
|
3564
|
+
previewSemanticHeal(caller, oldSelector, newSelector) {
|
|
3565
|
+
const filePath = this.resolveFilePath(caller.filePath);
|
|
3566
|
+
if (!filePath || caller.line <= 0)
|
|
3567
|
+
return null;
|
|
3568
|
+
let sourceFile = this.project.getSourceFile(filePath);
|
|
3569
|
+
if (sourceFile)
|
|
3570
|
+
sourceFile.refreshFromFileSystemSync();
|
|
3571
|
+
else
|
|
3572
|
+
sourceFile = this.project.addSourceFileAtPath(filePath);
|
|
3573
|
+
return this.semanticDispatch(sourceFile, { filePath, line: caller.line }, oldSelector, newSelector, true);
|
|
3574
|
+
}
|
|
3575
|
+
semanticDispatch(sourceFile, caller, oldSelector, newSelector, preview = false) {
|
|
3486
3576
|
try {
|
|
3487
3577
|
// Expand window: the failure site (caller.line) is often .click()/.fill()
|
|
3488
3578
|
// AFTER the locator assignment. Search up to 5 lines back so we catch
|
|
@@ -3493,14 +3583,57 @@ class ASTSourceUpdater {
|
|
|
3493
3583
|
if (!analysis)
|
|
3494
3584
|
continue;
|
|
3495
3585
|
const { shape, node } = analysis;
|
|
3496
|
-
if (shape === "
|
|
3586
|
+
if (shape === "COMPLEX")
|
|
3497
3587
|
continue;
|
|
3588
|
+
// Semantic methods (getByRole, getByText, etc.) pass a role/text as
|
|
3589
|
+
// arg0 (always a STRING_LITERAL) and text-matching options in arg1.
|
|
3590
|
+
// e.g. getByRole("button", { name: updateSelector }) — classify()
|
|
3591
|
+
// returns STRING_LITERAL for "button" and we'd normally skip, but
|
|
3592
|
+
// updateSelector in { name: updateSelector } is the real target.
|
|
3593
|
+
if (shape === "STRING_LITERAL") {
|
|
3594
|
+
const callExpr = call.getExpression();
|
|
3595
|
+
const method = ts_morph_1.Node.isPropertyAccessExpression(callExpr)
|
|
3596
|
+
? callExpr.getName()
|
|
3597
|
+
: "";
|
|
3598
|
+
if (SEMANTIC_METHODS.has(method)) {
|
|
3599
|
+
const optsArg = call.getArguments()[1];
|
|
3600
|
+
if (optsArg && ts_morph_1.Node.isObjectLiteralExpression(optsArg)) {
|
|
3601
|
+
for (const optProp of optsArg.getProperties()) {
|
|
3602
|
+
if (!ts_morph_1.Node.isPropertyAssignment(optProp))
|
|
3603
|
+
continue;
|
|
3604
|
+
const val = optProp.getInitializer();
|
|
3605
|
+
if (!val)
|
|
3606
|
+
continue;
|
|
3607
|
+
const valShape = ArgumentTypeAnalyzer_1.ArgumentTypeAnalyzer.classifyNode(val);
|
|
3608
|
+
let optResult = null;
|
|
3609
|
+
if (valShape === "IDENTIFIER") {
|
|
3610
|
+
optResult = this.handleIdentifierArg(val, sourceFile, caller, newSelector, oldSelector, preview);
|
|
3611
|
+
}
|
|
3612
|
+
else if (valShape === "PROPERTY_ACCESS") {
|
|
3613
|
+
optResult = this.handlePropertyAccessArg(val, sourceFile, caller, newSelector, preview);
|
|
3614
|
+
}
|
|
3615
|
+
if (optResult !== null)
|
|
3616
|
+
return optResult;
|
|
3617
|
+
// Symbolic options arg that couldn't be traced — hard-block so
|
|
3618
|
+
// the chain is not rewritten inline (preserves developer arch).
|
|
3619
|
+
if (valShape === "IDENTIFIER" || valShape === "PROPERTY_ACCESS") {
|
|
3620
|
+
return {
|
|
3621
|
+
success: false,
|
|
3622
|
+
reason: `semantic guard: options identifier "${val.getText()}" could not be traced`,
|
|
3623
|
+
strategy: "semantic-guard",
|
|
3624
|
+
};
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
continue;
|
|
3630
|
+
}
|
|
3498
3631
|
let result = null;
|
|
3499
3632
|
if (shape === "IDENTIFIER") {
|
|
3500
|
-
result = this.handleIdentifierArg(node, sourceFile, caller, newSelector, oldSelector);
|
|
3633
|
+
result = this.handleIdentifierArg(node, sourceFile, caller, newSelector, oldSelector, preview);
|
|
3501
3634
|
}
|
|
3502
3635
|
else if (shape === "PROPERTY_ACCESS") {
|
|
3503
|
-
result = this.handlePropertyAccessArg(node, sourceFile, caller, newSelector);
|
|
3636
|
+
result = this.handlePropertyAccessArg(node, sourceFile, caller, newSelector, preview);
|
|
3504
3637
|
}
|
|
3505
3638
|
else if (shape === "TEMPLATE_LITERAL") {
|
|
3506
3639
|
result = this.handleTemplateLiteralArg(node, sourceFile, caller, oldSelector, newSelector);
|
|
@@ -3550,7 +3683,7 @@ class ASTSourceUpdater {
|
|
|
3550
3683
|
// intact — while surfacing the cleaner semantic method form (getByRole) as
|
|
3551
3684
|
// a suggested upgrade in the advisory/report.
|
|
3552
3685
|
// ═══════════════════════════════════════════════════════════════════
|
|
3553
|
-
applyDefinitionSiteHeal(targetNode, targetFile, newSelector, strategy, blastRadius) {
|
|
3686
|
+
applyDefinitionSiteHeal(targetNode, targetFile, newSelector, strategy, blastRadius, preview = false) {
|
|
3554
3687
|
const wasInternal = (0, SelectorSanitizer_1.isInternalEngineSelector)(newSelector);
|
|
3555
3688
|
let writeValue = newSelector;
|
|
3556
3689
|
if (wasInternal) {
|
|
@@ -3568,7 +3701,9 @@ class ASTSourceUpdater {
|
|
|
3568
3701
|
writeValue = publicStr;
|
|
3569
3702
|
logger_1.logger.debug(`Method-Shift fallback: internal selector → public string "${writeValue}"`);
|
|
3570
3703
|
}
|
|
3571
|
-
const result = InitializerUpdater_1.InitializerUpdater.apply(targetNode, targetFile, writeValue
|
|
3704
|
+
const result = InitializerUpdater_1.InitializerUpdater.apply(targetNode, targetFile, writeValue, {
|
|
3705
|
+
computeDiff: preview,
|
|
3706
|
+
});
|
|
3572
3707
|
if (!result)
|
|
3573
3708
|
return null;
|
|
3574
3709
|
if (!this.validateFileSyntax(targetFile)) {
|
|
@@ -3601,10 +3736,11 @@ class ASTSourceUpdater {
|
|
|
3601
3736
|
file: targetFile.getFilePath(),
|
|
3602
3737
|
line: result.lineUpdated,
|
|
3603
3738
|
},
|
|
3739
|
+
unifiedDiff: result.unifiedDiff,
|
|
3604
3740
|
blastRadius,
|
|
3605
3741
|
};
|
|
3606
3742
|
}
|
|
3607
|
-
handleIdentifierArg(node, sourceFile, caller, newSelector, oldSelector = "") {
|
|
3743
|
+
handleIdentifierArg(node, sourceFile, caller, newSelector, oldSelector = "", preview = false) {
|
|
3608
3744
|
const traceResult = DefinitionTracer_1.DefinitionTracer.traceIdentifier(node, sourceFile, caller.line);
|
|
3609
3745
|
if (traceResult.found) {
|
|
3610
3746
|
const { targetNode, targetFile, declarationNode } = traceResult;
|
|
@@ -3612,7 +3748,10 @@ class ASTSourceUpdater {
|
|
|
3612
3748
|
const blast = BlastRadiusAnalyzer_1.BlastRadiusAnalyzer.analyze(blastNode, sourceFile, this.project);
|
|
3613
3749
|
logger_1.logger.debug(`BlastRadius IDENTIFIER: risk=${blast.risk} ` +
|
|
3614
3750
|
`(${blast.testFunctionCount} tests, ${blast.affectedFiles.length} files)`);
|
|
3615
|
-
|
|
3751
|
+
// Preview mode (shared-locator bypass ghost diff) deliberately bypasses
|
|
3752
|
+
// the high-blast BLOCK: it never writes disk, it only computes what the
|
|
3753
|
+
// root-declaration mutation WOULD look like. The real heal still blocks.
|
|
3754
|
+
if (blast.risk === "high" && !preview) {
|
|
3616
3755
|
this.advisoryBuffer.push({
|
|
3617
3756
|
code: HealingAdvisory_1.AdvisoryCode.BLAST_RADIUS_BLOCKED,
|
|
3618
3757
|
message: `Mutation blocked - ${blast.testFunctionCount} tests reference this constant.`,
|
|
@@ -3635,7 +3774,7 @@ class ASTSourceUpdater {
|
|
|
3635
3774
|
suggestedAction: "Run the full test suite to confirm all references still pass.",
|
|
3636
3775
|
});
|
|
3637
3776
|
}
|
|
3638
|
-
const healResult = this.applyDefinitionSiteHeal(targetNode, targetFile, newSelector, "semantic-identifier", blast.testFunctionCount);
|
|
3777
|
+
const healResult = this.applyDefinitionSiteHeal(targetNode, targetFile, newSelector, "semantic-identifier", blast.testFunctionCount, preview);
|
|
3639
3778
|
if (!healResult && oldSelector) {
|
|
3640
3779
|
// targetNode was not a string literal (e.g. CallExpression from an
|
|
3641
3780
|
// indirect reference like switch/case POM). Search targetFile for the
|
|
@@ -3693,7 +3832,7 @@ class ASTSourceUpdater {
|
|
|
3693
3832
|
blastRadius: 1,
|
|
3694
3833
|
};
|
|
3695
3834
|
}
|
|
3696
|
-
handlePropertyAccessArg(node, sourceFile, caller, newSelector) {
|
|
3835
|
+
handlePropertyAccessArg(node, sourceFile, caller, newSelector, preview = false) {
|
|
3697
3836
|
const traceResult = DefinitionTracer_1.DefinitionTracer.tracePropertyAccess(node, sourceFile, caller.line);
|
|
3698
3837
|
if (traceResult.found) {
|
|
3699
3838
|
const { targetNode, targetFile, declarationNode } = traceResult;
|
|
@@ -3701,7 +3840,7 @@ class ASTSourceUpdater {
|
|
|
3701
3840
|
const blast = BlastRadiusAnalyzer_1.BlastRadiusAnalyzer.analyze(blastNode, sourceFile, this.project);
|
|
3702
3841
|
logger_1.logger.debug(`BlastRadius PROPERTY_ACCESS: risk=${blast.risk} ` +
|
|
3703
3842
|
`(${blast.testFunctionCount} tests, ${blast.affectedFiles.length} files)`);
|
|
3704
|
-
if (blast.risk === "high") {
|
|
3843
|
+
if (blast.risk === "high" && !preview) {
|
|
3705
3844
|
this.advisoryBuffer.push({
|
|
3706
3845
|
code: HealingAdvisory_1.AdvisoryCode.BLAST_RADIUS_BLOCKED,
|
|
3707
3846
|
message: `Mutation blocked - ${blast.testFunctionCount} tests reference this property.`,
|
|
@@ -3724,7 +3863,7 @@ class ASTSourceUpdater {
|
|
|
3724
3863
|
suggestedAction: "Run the full test suite to confirm all references still pass.",
|
|
3725
3864
|
});
|
|
3726
3865
|
}
|
|
3727
|
-
return this.applyDefinitionSiteHeal(targetNode, targetFile, newSelector, "semantic-property-access", blast.testFunctionCount);
|
|
3866
|
+
return this.applyDefinitionSiteHeal(targetNode, targetFile, newSelector, "semantic-property-access", blast.testFunctionCount, preview);
|
|
3728
3867
|
}
|
|
3729
3868
|
if (!traceResult.found && traceResult.crossFile) {
|
|
3730
3869
|
const xResult = CrossFileHealer_1.CrossFileHealer.heal(traceResult.modulePath, traceResult.symbolName, newSelector, caller.filePath, this.project);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CrossFileHealer.d.ts","sourceRoot":"","sources":["../../src/services/CrossFileHealer.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,OAAO,EAMR,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"CrossFileHealer.d.ts","sourceRoot":"","sources":["../../src/services/CrossFileHealer.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,OAAO,EAMR,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAuC7D,qBAAa,eAAe;IAC1B,MAAM,CAAC,IAAI,CACT,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,SAAI,GACR,eAAe;IA6FlB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,eAAe,CACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,OAAO,GACf,eAAe;CAoEnB;AAMD,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CAQf"}
|
|
@@ -40,7 +40,20 @@ const fs = __importStar(require("fs"));
|
|
|
40
40
|
const ts_morph_1 = require("ts-morph");
|
|
41
41
|
const DefinitionTracer_js_1 = require("./DefinitionTracer.js");
|
|
42
42
|
const InitializerUpdater_js_1 = require("./InitializerUpdater.js");
|
|
43
|
+
const IsolatedDiff_1 = require("../utils/IsolatedDiff");
|
|
43
44
|
const logger_1 = require("../utils/logger");
|
|
45
|
+
/**
|
|
46
|
+
* Compute the in-memory unified diff for a ts-morph source file whose AST was
|
|
47
|
+
* just mutated. `before` is the pre-mutation text; the live file text is the
|
|
48
|
+
* `after`. Returns undefined when nothing changed. Never touches disk.
|
|
49
|
+
*/
|
|
50
|
+
function captureDefinitionDiff(before, externalSf, externalPath) {
|
|
51
|
+
return ((0, IsolatedDiff_1.generateUnifiedDiff)(before, externalSf.getFullText(), {
|
|
52
|
+
filePath: path.relative(process.cwd(), externalPath),
|
|
53
|
+
context: 3,
|
|
54
|
+
normalizeEol: true,
|
|
55
|
+
}) || undefined);
|
|
56
|
+
}
|
|
44
57
|
// ═══════════════════════════════════════════════════════════════════
|
|
45
58
|
// CrossFileHealer (T-9)
|
|
46
59
|
//
|
|
@@ -102,6 +115,7 @@ class CrossFileHealer {
|
|
|
102
115
|
strategy: "semantic-cross-file",
|
|
103
116
|
};
|
|
104
117
|
}
|
|
118
|
+
const before = traceResult.targetFile.getFullText();
|
|
105
119
|
const result = InitializerUpdater_js_1.InitializerUpdater.apply(traceResult.targetNode, traceResult.targetFile, newValue);
|
|
106
120
|
if (!result) {
|
|
107
121
|
return {
|
|
@@ -119,6 +133,7 @@ class CrossFileHealer {
|
|
|
119
133
|
lineUpdated: result.lineUpdated,
|
|
120
134
|
healedLocatorString: newValue,
|
|
121
135
|
definitionSite: { file: externalPath, line: result.lineUpdated + 1 },
|
|
136
|
+
unifiedDiff: captureDefinitionDiff(before, traceResult.targetFile, externalPath),
|
|
122
137
|
};
|
|
123
138
|
}
|
|
124
139
|
/**
|
|
@@ -174,6 +189,7 @@ class CrossFileHealer {
|
|
|
174
189
|
strategy: "semantic-cross-file",
|
|
175
190
|
};
|
|
176
191
|
}
|
|
192
|
+
const before = externalSf.getFullText();
|
|
177
193
|
const result = InitializerUpdater_js_1.InitializerUpdater.apply(targetNode, externalSf, newValue);
|
|
178
194
|
if (!result) {
|
|
179
195
|
return {
|
|
@@ -191,6 +207,7 @@ class CrossFileHealer {
|
|
|
191
207
|
lineUpdated: result.lineUpdated,
|
|
192
208
|
healedLocatorString: newValue,
|
|
193
209
|
definitionSite: { file: externalPath, line: result.lineUpdated + 1 },
|
|
210
|
+
unifiedDiff: captureDefinitionDiff(before, externalSf, externalPath),
|
|
194
211
|
};
|
|
195
212
|
}
|
|
196
213
|
}
|
|
@@ -113,11 +113,22 @@ export interface BypassedEvent extends HealEventBase {
|
|
|
113
113
|
newSelector: string;
|
|
114
114
|
/** "relative/path.ts:LINE" — the shared declaration that was left untouched. */
|
|
115
115
|
variablePath: string;
|
|
116
|
+
/**
|
|
117
|
+
* Origin locus of the shared declaration the ghost diff targets — the
|
|
118
|
+
* variable/property root, NOT the failing call-site (which stays on
|
|
119
|
+
* sourceFile/sourceLine for the Job-2 regression replay). Drives the HTML
|
|
120
|
+
* card's "open declaration" highlight.
|
|
121
|
+
*/
|
|
122
|
+
declarationFile?: string;
|
|
123
|
+
declarationLine?: number;
|
|
116
124
|
/** Distinct test/it blocks that reference the shared declaration. */
|
|
117
125
|
testFunctionCount: number;
|
|
118
126
|
/** Files that reference the shared declaration. */
|
|
119
127
|
affectedFiles: string[];
|
|
120
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* Speculative ROOT-DECLARATION unified diff of what Sela WOULD have written
|
|
130
|
+
* (never saved) — uniform in shape with a HEALED event's diff.
|
|
131
|
+
*/
|
|
121
132
|
ghostDiff: string;
|
|
122
133
|
/** Human-readable warning surfaced across all report channels. */
|
|
123
134
|
message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"HealReportService.d.ts","sourceRoot":"","sources":["../../src/services/HealReportService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1048,7 +1048,7 @@ const REPORT_JS = [
|
|
|
1048
1048
|
" +'<div class=\"diff-line dl-add\"><span class=\"gutter\">+</span><span class=\"ln\">'+(ev.newLineNumber||'')+'</span><span class=\"code\">'+esc(ev.newCodeLine||'')+'</span></div>';",
|
|
1049
1049
|
" }",
|
|
1050
1050
|
" if(isBypass){",
|
|
1051
|
-
" return'<div class=\"diff-block diff-ghost\"><div class=\"diff-bar\"><span>'+esc(ev.variablePath||ev.sourceFile)+'</span><span class=\"sep\">\xb7</span><span class=\"ghost-tag\">SPECULATIVE — NOT SAVED TO DISK</span></div><div>'+body+'</div></div>';",
|
|
1051
|
+
" return'<div class=\"diff-block diff-ghost\"><div class=\"diff-bar\"><span>'+esc((ev.declarationFile?ev.declarationFile+(ev.declarationLine?':'+ev.declarationLine:''):'')||ev.variablePath||ev.sourceFile)+'</span><span class=\"sep\">\xb7</span><span class=\"ghost-tag\">[SPECULATIVE ROOT FIX — PENDING REGRESSION VALIDATION] · NOT SAVED TO DISK</span></div><div>'+body+'</div></div>';",
|
|
1052
1052
|
" }",
|
|
1053
1053
|
" return'<div class=\"diff-block\"><div class=\"diff-bar\"><span>'+esc(ev.sourceFile)+'</span><span class=\"sep\">\xb7</span><span>line '+ev.newLineNumber+'</span>'+(ev.diffStrategy?'<span class=\"sep\">\xb7</span><span>'+esc(ev.diffStrategy)+'</span>':'')+'<span class=\"sep\">\xb7</span><span>'+(hasDiff?'git diff':'fallback')+'</span></div><div>'+body+'</div></div>';",
|
|
1054
1054
|
"}",
|
|
@@ -2,6 +2,17 @@ import { Node, SourceFile } from "ts-morph";
|
|
|
2
2
|
export interface InitializerResult {
|
|
3
3
|
lineUpdated: number;
|
|
4
4
|
healedLocatorString: string;
|
|
5
|
+
/**
|
|
6
|
+
* In-memory unified diff (before → after) of the mutated source file.
|
|
7
|
+
* Only populated when `apply` is called with `{ computeDiff: true }` —
|
|
8
|
+
* used by the shared-locator bypass preview to render a root-declaration
|
|
9
|
+
* diff with zero disk writes.
|
|
10
|
+
*/
|
|
11
|
+
unifiedDiff?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface InitializerApplyOptions {
|
|
14
|
+
/** Compute and return the in-memory before→after unified diff. */
|
|
15
|
+
computeDiff?: boolean;
|
|
5
16
|
}
|
|
6
17
|
export declare class InitializerUpdater {
|
|
7
18
|
/**
|
|
@@ -11,6 +22,6 @@ export declare class InitializerUpdater {
|
|
|
11
22
|
* Returns null if the node type is not directly mutable or if the
|
|
12
23
|
* mutation throws (caller should fall through to existing strategies).
|
|
13
24
|
*/
|
|
14
|
-
static apply(valueNode: Node, sourceFile: SourceFile, newValue: string): InitializerResult | null;
|
|
25
|
+
static apply(valueNode: Node, sourceFile: SourceFile, newValue: string, opts?: InitializerApplyOptions): InitializerResult | null;
|
|
15
26
|
}
|
|
16
27
|
//# sourceMappingURL=InitializerUpdater.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InitializerUpdater.d.ts","sourceRoot":"","sources":["../../src/services/InitializerUpdater.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAiB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"InitializerUpdater.d.ts","sourceRoot":"","sources":["../../src/services/InitializerUpdater.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAiB,MAAM,UAAU,CAAC;AAmB3D,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,kBAAkB;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CACV,SAAS,EAAE,IAAI,EACf,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,uBAA4B,GACjC,iBAAiB,GAAG,IAAI;CAiE5B"}
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.InitializerUpdater = void 0;
|
|
4
37
|
const ts_morph_1 = require("ts-morph");
|
|
@@ -7,7 +40,9 @@ const DryRunGuard_1 = require("../config/DryRunGuard");
|
|
|
7
40
|
const PathScopeGuard_1 = require("../config/PathScopeGuard");
|
|
8
41
|
const WorkspaceSnapshotService_1 = require("./WorkspaceSnapshotService");
|
|
9
42
|
const SelectorSanitizer_1 = require("./SelectorSanitizer");
|
|
43
|
+
const IsolatedDiff_1 = require("../utils/IsolatedDiff");
|
|
10
44
|
const logger_1 = require("../utils/logger");
|
|
45
|
+
const path = __importStar(require("path"));
|
|
11
46
|
class InitializerUpdater {
|
|
12
47
|
/**
|
|
13
48
|
* Mutate a StringLiteral or NoSubstitutionTemplateLiteral node to
|
|
@@ -16,7 +51,7 @@ class InitializerUpdater {
|
|
|
16
51
|
* Returns null if the node type is not directly mutable or if the
|
|
17
52
|
* mutation throws (caller should fall through to existing strategies).
|
|
18
53
|
*/
|
|
19
|
-
static apply(valueNode, sourceFile, newValue) {
|
|
54
|
+
static apply(valueNode, sourceFile, newValue, opts = {}) {
|
|
20
55
|
// ABSOLUTE GUARD: never serialise a Playwright internal engine selector
|
|
21
56
|
// into a string-literal definition. `.locator()` strings cannot express
|
|
22
57
|
// a semantic locator (getByRole/…), so writing `internal:role=…` here is
|
|
@@ -32,6 +67,7 @@ class InitializerUpdater {
|
|
|
32
67
|
return null;
|
|
33
68
|
}
|
|
34
69
|
try {
|
|
70
|
+
const before = opts.computeDiff ? sourceFile.getFullText() : "";
|
|
35
71
|
if (ts_morph_1.Node.isStringLiteral(valueNode)) {
|
|
36
72
|
valueNode.setLiteralValue(newValue);
|
|
37
73
|
}
|
|
@@ -41,6 +77,13 @@ class InitializerUpdater {
|
|
|
41
77
|
else {
|
|
42
78
|
return null;
|
|
43
79
|
}
|
|
80
|
+
const unifiedDiff = opts.computeDiff
|
|
81
|
+
? (0, IsolatedDiff_1.generateUnifiedDiff)(before, sourceFile.getFullText(), {
|
|
82
|
+
filePath: path.relative(process.cwd(), sourceFile.getFilePath()),
|
|
83
|
+
context: 3,
|
|
84
|
+
normalizeEol: true,
|
|
85
|
+
}) || undefined
|
|
86
|
+
: undefined;
|
|
44
87
|
// Centralised write gate - mirrors ASTSourceUpdater._persistChanges.
|
|
45
88
|
// InitializerUpdater is the canonical write-head for definition-site
|
|
46
89
|
// mutations (SemanticDispatch → tracer-resolved identifier / property
|
|
@@ -62,6 +105,7 @@ class InitializerUpdater {
|
|
|
62
105
|
return {
|
|
63
106
|
lineUpdated: valueNode.getStartLineNumber() - 1,
|
|
64
107
|
healedLocatorString: newValue,
|
|
108
|
+
unifiedDiff,
|
|
65
109
|
};
|
|
66
110
|
}
|
|
67
111
|
catch (e) {
|
|
@@ -14,6 +14,14 @@ export interface RegressionManifestRecord {
|
|
|
14
14
|
affectedFiles: string[];
|
|
15
15
|
/** Originating test title (provenance only). */
|
|
16
16
|
testTitle: string;
|
|
17
|
+
/**
|
|
18
|
+
* Speculative ROOT-DECLARATION unified diff Sela WOULD have written (never
|
|
19
|
+
* saved). Carried for report/audit parity; Job 2 matches on the call-site
|
|
20
|
+
* fields above and never consumes this. Optional — absent on legacy manifests.
|
|
21
|
+
*/
|
|
22
|
+
rootDiff?: string;
|
|
23
|
+
/** Origin locus of the shared declaration the rootDiff targets (provenance). */
|
|
24
|
+
declarationFile?: string;
|
|
17
25
|
}
|
|
18
26
|
export interface RegressionManifest {
|
|
19
27
|
generatedAt: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RegressionManifestService.d.ts","sourceRoot":"","sources":["../../src/services/RegressionManifestService.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,eAAO,MAAM,wBAAwB,kCAAkC,CAAC;AAExE,oEAAoE;AACpE,MAAM,WAAW,wBAAwB;IACvC,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"RegressionManifestService.d.ts","sourceRoot":"","sources":["../../src/services/RegressionManifestService.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,eAAO,MAAM,wBAAwB,kCAAkC,CAAC;AAExE,oEAAoE;AACpE,MAAM,WAAW,wBAAwB;IACvC,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,wBAAwB,EAAE,CAAC;CACrC;AAMD,qBAAa,yBAAyB;IACpC,+DAA+D;IAC/D,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,wBAAwB,EAAE;IAexE;;;;OAIG;IACH,MAAM,CAAC,aAAa,CAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,wBAAwB,EAAE,GAClC,MAAM,GAAG,IAAI;IAYhB;;;;OAIG;IACH,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB;IAkBzD,OAAO,CAAC,MAAM,CAAC,QAAQ;CAqDxB;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;gBAEzC,QAAQ,EAAE,kBAAkB;IAIxC,yEAAyE;IACzE,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB;IAMrD;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAY3E"}
|
|
@@ -61,6 +61,10 @@ class RegressionManifestService {
|
|
|
61
61
|
healedSelector: ev.newSelector,
|
|
62
62
|
affectedFiles: (ev.affectedFiles ?? []).map(normalizePath),
|
|
63
63
|
testTitle: ev.testTitle ?? "",
|
|
64
|
+
...(ev.ghostDiff ? { rootDiff: ev.ghostDiff } : {}),
|
|
65
|
+
...(ev.declarationFile
|
|
66
|
+
? { declarationFile: normalizePath(ev.declarationFile) }
|
|
67
|
+
: {}),
|
|
64
68
|
}));
|
|
65
69
|
}
|
|
66
70
|
/**
|
|
@@ -129,6 +133,10 @@ class RegressionManifestService {
|
|
|
129
133
|
? rec.affectedFiles.filter((x) => typeof x === "string")
|
|
130
134
|
: [],
|
|
131
135
|
testTitle: typeof rec.testTitle === "string" ? rec.testTitle : "",
|
|
136
|
+
...(typeof rec.rootDiff === "string" ? { rootDiff: rec.rootDiff } : {}),
|
|
137
|
+
...(typeof rec.declarationFile === "string"
|
|
138
|
+
? { declarationFile: rec.declarationFile }
|
|
139
|
+
: {}),
|
|
132
140
|
};
|
|
133
141
|
});
|
|
134
142
|
return {
|
package/package.json
CHANGED