sela-core 0.1.0-alpha.33 → 0.1.0-alpha.35
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/pr.d.ts.map +1 -1
- package/dist/cli/commands/pr.js +18 -3
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +19 -1
- package/dist/services/ASTSourceUpdater.d.ts +1 -1
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +2 -2
- package/dist/services/InteractiveReview.js +1 -1
- package/dist/services/MutationApplier.d.ts +15 -0
- package/dist/services/MutationApplier.d.ts.map +1 -1
- package/dist/services/MutationApplier.js +57 -11
- package/dist/services/PendingPromptLedger.d.ts +70 -1
- package/dist/services/PendingPromptLedger.d.ts.map +1 -1
- package/dist/services/PendingPromptLedger.js +172 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/pr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"pr.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/pr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAczC;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAsEhD"}
|
package/dist/cli/commands/pr.js
CHANGED
|
@@ -60,10 +60,10 @@ function registerPr(parent) {
|
|
|
60
60
|
.requiredOption("--ledger-dir <path>", "Directory containing all downloaded shard ledger JSON files")
|
|
61
61
|
.option("--cwd <path>", "Repository root used for git operations (defaults to process.cwd())", process.cwd())
|
|
62
62
|
.action(async (opts) => {
|
|
63
|
-
const
|
|
64
|
-
const payloads = (0, PendingPromptLedger_js_1.readGitPayloadsFromDir)(
|
|
63
|
+
const resolvedLedgerDir = path.resolve(opts.cwd, opts.ledgerDir);
|
|
64
|
+
const payloads = (0, PendingPromptLedger_js_1.readGitPayloadsFromDir)(resolvedLedgerDir);
|
|
65
65
|
if (payloads.length === 0) {
|
|
66
|
-
console.error(`[sela pr aggregate] No ledger payloads found in ${
|
|
66
|
+
console.error(`[sela pr aggregate] No ledger payloads found in ${resolvedLedgerDir} — nothing to do.`);
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
const healedEvents = (0, SelaReporter_js_1.deduplicateByStableId)(payloads.flatMap((p) => p.healedEvents));
|
|
@@ -71,6 +71,21 @@ function registerPr(parent) {
|
|
|
71
71
|
console.error("[sela pr aggregate] No healed events after deduplication — nothing to do.");
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
|
+
// Re-apply AST heals to disk before the git flow runs a diff.
|
|
75
|
+
// The aggregator machine checked out the original (broken) source files
|
|
76
|
+
// and never ran tests itself, so we must physically write the patched
|
|
77
|
+
// code here — otherwise git sees 0 mutations and skips the PR entirely.
|
|
78
|
+
//
|
|
79
|
+
// mergeLedgerEntriesByFile applies each shard's granular patches
|
|
80
|
+
// sequentially against the shared baseline so ALL shards' fixes are
|
|
81
|
+
// preserved (no last-write-wins clobber). Falls back to contentAfter
|
|
82
|
+
// blob for legacy ledgers that predate the patches field.
|
|
83
|
+
const patchedCount = await (0, PendingPromptLedger_js_1.mergeLedgerEntriesByFile)(resolvedLedgerDir, opts.cwd);
|
|
84
|
+
if (patchedCount === 0) {
|
|
85
|
+
console.error("[sela pr aggregate] No source patches could be applied to disk — skipping PR.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log(`[sela pr aggregate] Applied ${patchedCount} source patch(es) to disk.`);
|
|
74
89
|
const protectedEvents = payloads.flatMap((p) => p.protectedEvents);
|
|
75
90
|
await (0, PRAutomationService_js_1.runCentralizedGitFlow)({
|
|
76
91
|
cwd: opts.cwd,
|
|
@@ -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;
|
|
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;AAwFhF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,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;;IAuBnD,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;IA0DnB,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;IAgsBlB,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;IAItE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;YAuGtB,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"}
|
|
@@ -487,7 +487,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
487
487
|
if (plainInPlace && !DryRunGuard_1.DryRunGuard.active() && resolvedForCompose) {
|
|
488
488
|
try {
|
|
489
489
|
WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.preWrite(resolvedForCompose);
|
|
490
|
-
const composed = ASTSourceUpdater_1.ASTSourceUpdater.composeHeal({
|
|
490
|
+
const composed = await ASTSourceUpdater_1.ASTSourceUpdater.composeHeal({
|
|
491
491
|
filePath: resolvedForCompose,
|
|
492
492
|
line,
|
|
493
493
|
newSelector: newFullSelector,
|
|
@@ -510,6 +510,20 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
510
510
|
definitionSite: { file: resolvedForCompose, line: wl - 1 },
|
|
511
511
|
blastRadius: composed.decision.blastRadius,
|
|
512
512
|
};
|
|
513
|
+
// Record a granular patch for the CI aggregator so that heals
|
|
514
|
+
// from multiple shards targeting the same file are all preserved.
|
|
515
|
+
const edit = composed.batch?.edits[0];
|
|
516
|
+
if (edit?.anchor) {
|
|
517
|
+
const relPath = path
|
|
518
|
+
.relative(process.cwd(), resolvedForCompose)
|
|
519
|
+
.replace(/\\/g, "/");
|
|
520
|
+
(0, PendingPromptLedger_1.recordLedgerPatch)(relPath, {
|
|
521
|
+
stableId,
|
|
522
|
+
anchor: edit.anchor,
|
|
523
|
+
replacement: edit.replacement,
|
|
524
|
+
strategyUsed: strat,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
513
527
|
}
|
|
514
528
|
else {
|
|
515
529
|
const why = composed.kind === "abort"
|
|
@@ -999,6 +1013,10 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
999
1013
|
catch (err) {
|
|
1000
1014
|
logger_1.logger.warn(`Pending-prompts ledger write failed: ${err.message}`);
|
|
1001
1015
|
}
|
|
1016
|
+
finally {
|
|
1017
|
+
// Always clear the patch registry — patches are now in the ledger file.
|
|
1018
|
+
(0, PendingPromptLedger_1.clearLedgerPatches)();
|
|
1019
|
+
}
|
|
1002
1020
|
}
|
|
1003
1021
|
// Clear the report buffer last so any downstream consumers (e.g. test
|
|
1004
1022
|
// re-runs in the same process) start fresh on the next session.
|
|
@@ -64,7 +64,7 @@ export declare class ASTSourceUpdater {
|
|
|
64
64
|
* mutation strategy (in-place / fork / prompt), and — when `apply` is set —
|
|
65
65
|
* write it to disk through the type-safety gate.
|
|
66
66
|
*/
|
|
67
|
-
static composeHeal(input: ComposeHealInput): ComposeHealResult
|
|
67
|
+
static composeHeal(input: ComposeHealInput): Promise<ComposeHealResult>;
|
|
68
68
|
/** Drain-loop entry: apply pre-built batches through the write gate. */
|
|
69
69
|
static applyMutationBatches(batches: FileBatch[], opts?: ApplyOptions): ApplyOutcome[];
|
|
70
70
|
/** Convert a DecisionEngine result into an anchored MutationApplier batch. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ASTSourceUpdater.d.ts","sourceRoot":"","sources":["../../src/services/ASTSourceUpdater.ts"],"names":[],"mappings":"AAiBA,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,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAkB,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAEL,SAAS,EAET,YAAY,EACZ,YAAY,EACb,MAAM,mBAAmB,CAAC;AAY3B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,cAAc,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AA07DD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,4DAA4D;IAC5D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA4BD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAU;IACzB,QAAQ,CAAC,cAAc,iBAAwB;IAE/C,eAAe,IAAI,IAAI;IAYvB;;;;OAIG;
|
|
1
|
+
{"version":3,"file":"ASTSourceUpdater.d.ts","sourceRoot":"","sources":["../../src/services/ASTSourceUpdater.ts"],"names":[],"mappings":"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"}
|
|
@@ -1608,7 +1608,7 @@ class ASTSourceUpdater {
|
|
|
1608
1608
|
* mutation strategy (in-place / fork / prompt), and — when `apply` is set —
|
|
1609
1609
|
* write it to disk through the type-safety gate.
|
|
1610
1610
|
*/
|
|
1611
|
-
static composeHeal(input) {
|
|
1611
|
+
static async composeHeal(input) {
|
|
1612
1612
|
const original = fs.readFileSync(input.filePath, "utf8");
|
|
1613
1613
|
const project = new ts_morph_1.Project({
|
|
1614
1614
|
useInMemoryFileSystem: true,
|
|
@@ -1660,7 +1660,7 @@ class ASTSourceUpdater {
|
|
|
1660
1660
|
writtenLine: trace.target.targetNode.getStartLineNumber(),
|
|
1661
1661
|
};
|
|
1662
1662
|
if (input.apply && batch.edits.length > 0) {
|
|
1663
|
-
result.outcome = MutationApplier_1.MutationApplier.
|
|
1663
|
+
result.outcome = await MutationApplier_1.MutationApplier.applyFileBatchAsync(batch, input.applyOptions ?? {});
|
|
1664
1664
|
}
|
|
1665
1665
|
return result;
|
|
1666
1666
|
}
|
|
@@ -84,7 +84,7 @@ function printHealReview(deps) {
|
|
|
84
84
|
}
|
|
85
85
|
function renderEntry(entry, log) {
|
|
86
86
|
const fileName = path.basename(entry.absolutePath);
|
|
87
|
-
const diff = (0, IsolatedDiff_1.generateUnifiedDiff)(entry.contentBefore, entry.contentAfter, {
|
|
87
|
+
const diff = (0, IsolatedDiff_1.generateUnifiedDiff)(entry.contentBefore, entry.contentAfter ?? "", {
|
|
88
88
|
filePath: entry.relativePath,
|
|
89
89
|
});
|
|
90
90
|
log("");
|
|
@@ -41,6 +41,12 @@ export interface ApplyOptions {
|
|
|
41
41
|
write?: boolean;
|
|
42
42
|
/** Honour SELA_DRY_RUN by leaving disk untouched. Default true. */
|
|
43
43
|
respectDryRun?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* When provided, skip the disk read and parse this content instead.
|
|
46
|
+
* Used by the CI aggregator to apply patches sequentially in-memory
|
|
47
|
+
* without writing to disk between each patch.
|
|
48
|
+
*/
|
|
49
|
+
contentOverride?: string;
|
|
44
50
|
}
|
|
45
51
|
export declare class MutationApplier {
|
|
46
52
|
/** Apply many batches; each file is processed independently. */
|
|
@@ -59,6 +65,15 @@ export declare class MutationApplier {
|
|
|
59
65
|
* never permanently wedge CI.
|
|
60
66
|
*/
|
|
61
67
|
static applyFileBatch(batch: FileBatch, opts?: ApplyOptions): ApplyOutcome;
|
|
68
|
+
/**
|
|
69
|
+
* Async variant of applyFileBatch — waits for the per-file lock instead of
|
|
70
|
+
* skipping on contention. Use this from async heal paths (SelaEngine.heal)
|
|
71
|
+
* where a skip would silently discard the mutation from the ledger.
|
|
72
|
+
*
|
|
73
|
+
* When contentOverride is provided (e.g. CI aggregator chaining patches),
|
|
74
|
+
* no lock is acquired because there is no disk read-modify-write.
|
|
75
|
+
*/
|
|
76
|
+
static applyFileBatchAsync(batch: FileBatch, opts?: ApplyOptions): Promise<ApplyOutcome>;
|
|
62
77
|
private static applyFileBatchUnlocked;
|
|
63
78
|
}
|
|
64
79
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MutationApplier.d.ts","sourceRoot":"","sources":["../../src/services/MutationApplier.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,SAAS,EAET,UAAU,EACX,MAAM,kBAAkB,CAAC;AAY1B;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"MutationApplier.d.ts","sourceRoot":"","sources":["../../src/services/MutationApplier.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,SAAS,EAET,UAAU,EACX,MAAM,kBAAkB,CAAC;AAY1B;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAeD,qBAAa,eAAe;IAC1B,gEAAgE;IAChE,MAAM,CAAC,YAAY,CACjB,OAAO,EAAE,SAAS,EAAE,EACpB,IAAI,GAAE,YAAiB,GACtB,YAAY,EAAE;IAIjB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,cAAc,CACnB,KAAK,EAAE,SAAS,EAChB,IAAI,GAAE,YAAiB,GACtB,YAAY;IAqCf;;;;;;;OAOG;WACU,mBAAmB,CAC9B,KAAK,EAAE,SAAS,EAChB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,YAAY,CAAC;IAoCxB,OAAO,CAAC,MAAM,CAAC,sBAAsB;CAqLtC;AA2CD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkB5D"}
|
|
@@ -130,6 +130,47 @@ class MutationApplier {
|
|
|
130
130
|
}
|
|
131
131
|
return outcome;
|
|
132
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Async variant of applyFileBatch — waits for the per-file lock instead of
|
|
135
|
+
* skipping on contention. Use this from async heal paths (SelaEngine.heal)
|
|
136
|
+
* where a skip would silently discard the mutation from the ledger.
|
|
137
|
+
*
|
|
138
|
+
* When contentOverride is provided (e.g. CI aggregator chaining patches),
|
|
139
|
+
* no lock is acquired because there is no disk read-modify-write.
|
|
140
|
+
*/
|
|
141
|
+
static async applyFileBatchAsync(batch, opts = {}) {
|
|
142
|
+
const write = opts.write ?? true;
|
|
143
|
+
const dryRun = (opts.respectDryRun ?? true) && process.env.SELA_DRY_RUN === "1";
|
|
144
|
+
// No lock needed when there is no disk mutation.
|
|
145
|
+
if (!write || dryRun || opts.contentOverride !== undefined) {
|
|
146
|
+
return MutationApplier.applyFileBatchUnlocked(batch, opts);
|
|
147
|
+
}
|
|
148
|
+
const lock = new FileLock_1.FileLock((0, FileLock_1.lockDirFor)(batch.filePath), { staleMs: 30_000 });
|
|
149
|
+
const acquired = await lock.acquire();
|
|
150
|
+
if (!acquired) {
|
|
151
|
+
let finalContent = "";
|
|
152
|
+
try {
|
|
153
|
+
finalContent = fs.readFileSync(batch.filePath, "utf8");
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
/* unreadable — leave empty */
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
filePath: batch.filePath,
|
|
160
|
+
status: "skipped",
|
|
161
|
+
reason: "could not acquire per-file write lock after async retries (concurrent heal in progress)",
|
|
162
|
+
written: false,
|
|
163
|
+
finalContent,
|
|
164
|
+
skipped: [],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
return MutationApplier.applyFileBatchUnlocked(batch, opts);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
lock.release();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
133
174
|
static applyFileBatchUnlocked(batch, opts = {}) {
|
|
134
175
|
const write = opts.write ?? true;
|
|
135
176
|
const respectDryRun = opts.respectDryRun ?? true;
|
|
@@ -137,18 +178,23 @@ class MutationApplier {
|
|
|
137
178
|
const { filePath } = batch;
|
|
138
179
|
const skipped = [];
|
|
139
180
|
let original;
|
|
140
|
-
|
|
141
|
-
original =
|
|
181
|
+
if (opts.contentOverride !== undefined) {
|
|
182
|
+
original = opts.contentOverride;
|
|
142
183
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
filePath,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
184
|
+
else {
|
|
185
|
+
try {
|
|
186
|
+
original = fs.readFileSync(filePath, "utf8");
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
return {
|
|
190
|
+
filePath,
|
|
191
|
+
status: "skipped",
|
|
192
|
+
reason: `cannot read file: ${e.message}`,
|
|
193
|
+
written: false,
|
|
194
|
+
finalContent: "",
|
|
195
|
+
skipped,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
152
198
|
}
|
|
153
199
|
// Fresh parse — the single source of truth for span resolution.
|
|
154
200
|
const project = newGateProject();
|
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
import type { WorkspaceSnapshotService } from "./WorkspaceSnapshotService";
|
|
2
2
|
import type { HealedEvent, ProtectedEvent } from "./HealReportService";
|
|
3
|
+
import type { AnchorKey } from "./AnchorResolver";
|
|
4
|
+
/**
|
|
5
|
+
* A single AST-level patch produced by one composeHeal call.
|
|
6
|
+
* Stored in the ledger so the CI aggregator can apply ALL patches from ALL
|
|
7
|
+
* shards against a shared baseline rather than overwriting the file with a
|
|
8
|
+
* per-shard contentAfter blob (which would erase the other shards' fixes).
|
|
9
|
+
*
|
|
10
|
+
* The `anchor` is sha-based and structural-path-based so it re-resolves
|
|
11
|
+
* correctly in any AST derived from the same baseline, regardless of line
|
|
12
|
+
* shifts introduced by earlier patches in the same aggregation run.
|
|
13
|
+
*
|
|
14
|
+
* IMPORTANT: `anchor.filePath` is the WORKER's absolute path. The aggregator
|
|
15
|
+
* must override it with the local absolute path before calling resolveAnchor.
|
|
16
|
+
*/
|
|
17
|
+
export interface LedgerPatch {
|
|
18
|
+
/** Matches HealedEvent.stableId — used to deduplicate same-locator heals. */
|
|
19
|
+
stableId: string;
|
|
20
|
+
/** Full AnchorKey for re-resolution in a freshly-parsed SourceFile. */
|
|
21
|
+
anchor: AnchorKey;
|
|
22
|
+
/** The replacement code fragment to splice in at the resolved span. */
|
|
23
|
+
replacement: string;
|
|
24
|
+
/** Which MutationApplier strategy produced this patch (for diagnostics). */
|
|
25
|
+
strategyUsed: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Record a patch produced by a successful composeHeal call.
|
|
29
|
+
* @param relativePath posix-relative path to the mutated file (used as key)
|
|
30
|
+
* @param patch the patch descriptor
|
|
31
|
+
*/
|
|
32
|
+
export declare function recordLedgerPatch(relativePath: string, patch: LedgerPatch): void;
|
|
33
|
+
/** Drain and clear the patch registry (call after writePendingPromptsLedger). */
|
|
34
|
+
export declare function clearLedgerPatches(): void;
|
|
3
35
|
/**
|
|
4
36
|
* The subset of worker state that the master process needs to run the
|
|
5
37
|
* centralized git/PR flow in SelaReporter.onEnd(). Written by every worker
|
|
@@ -17,7 +49,13 @@ export interface PendingPromptEntry {
|
|
|
17
49
|
absolutePath: string;
|
|
18
50
|
relativePath: string;
|
|
19
51
|
contentBefore: string;
|
|
20
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Full file content after all heals by this worker. Populated for local-DX
|
|
54
|
+
* diff display. In sharded CI runs this is the per-shard blob — the
|
|
55
|
+
* aggregator MUST use `patches` for merging rather than overwriting with
|
|
56
|
+
* this field. Kept optional so new ledgers can omit it.
|
|
57
|
+
*/
|
|
58
|
+
contentAfter?: string;
|
|
21
59
|
createdBySela: boolean;
|
|
22
60
|
/** 1-based line number for the vscode:// deeplink, or null when unknown. */
|
|
23
61
|
lineNumber: number | null;
|
|
@@ -28,6 +66,13 @@ export interface PendingPromptEntry {
|
|
|
28
66
|
* (= file) count. Older ledgers without the field are treated as 1.
|
|
29
67
|
*/
|
|
30
68
|
healCount: number;
|
|
69
|
+
/**
|
|
70
|
+
* Granular AST-level patches produced by composeHeal calls that mutated
|
|
71
|
+
* this file. Empty array for legacy-path heals (regex / SourceUpdater).
|
|
72
|
+
* The CI aggregator uses these instead of contentAfter so that heals from
|
|
73
|
+
* multiple shards targeting the same file are all preserved.
|
|
74
|
+
*/
|
|
75
|
+
patches: LedgerPatch[];
|
|
31
76
|
}
|
|
32
77
|
/**
|
|
33
78
|
* Resolve the ledger directory for a given cwd. The directory is created
|
|
@@ -77,6 +122,30 @@ export declare function readGitPayloads(cwd: string): WorkerGitPayload[];
|
|
|
77
122
|
* the standard `.sela/pending-prompts` subpath.
|
|
78
123
|
*/
|
|
79
124
|
export declare function readGitPayloadsFromDir(dir: string): WorkerGitPayload[];
|
|
125
|
+
/**
|
|
126
|
+
* Read the `entries` (source-file patches) from every v2 ledger file in an
|
|
127
|
+
* arbitrary directory. Deduplicates by `relativePath`, keeping the last writer
|
|
128
|
+
* wins — safe because a given source file is only mutated by one shard.
|
|
129
|
+
*
|
|
130
|
+
* Used by `sela pr aggregate` to re-apply healed code to the aggregator
|
|
131
|
+
* machine's disk before the centralized Git flow runs a diff.
|
|
132
|
+
*/
|
|
133
|
+
export declare function readLedgerEntriesFromDir(dir: string): PendingPromptEntry[];
|
|
134
|
+
/**
|
|
135
|
+
* CI aggregator entry-point: read all shard ledger files from `dir`, group
|
|
136
|
+
* entries by relativePath, and apply each shard's granular patches
|
|
137
|
+
* sequentially against the shared baseline.
|
|
138
|
+
*
|
|
139
|
+
* New-path (patches present): all shards' patches are merged by re-resolving
|
|
140
|
+
* each anchor in the running AST — line-shift-immune because anchors use sha
|
|
141
|
+
* + structural path, not line numbers.
|
|
142
|
+
*
|
|
143
|
+
* Legacy-path (no patches): fall back to last-write-wins on contentAfter.
|
|
144
|
+
*
|
|
145
|
+
* The final merged content is written atomically to disk via atomicWriteFile.
|
|
146
|
+
* Returns the number of files that were successfully patched.
|
|
147
|
+
*/
|
|
148
|
+
export declare function mergeLedgerEntriesByFile(dir: string, cwd: string): Promise<number>;
|
|
80
149
|
/** Remove the ledger directory and every file beneath it. Idempotent. */
|
|
81
150
|
export declare function clearPendingPromptsLedger(cwd: string): void;
|
|
82
151
|
//# sourceMappingURL=PendingPromptLedger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PendingPromptLedger.d.ts","sourceRoot":"","sources":["../../src/services/PendingPromptLedger.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAEV,wBAAwB,EACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"PendingPromptLedger.d.ts","sourceRoot":"","sources":["../../src/services/PendingPromptLedger.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAEV,wBAAwB,EACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAQlD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;CACtB;AAUD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAOhF;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAMD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,eAAe,EAAE,cAAc,EAAE,CAAC;CACnC;AAcD,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAID;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,wBAAwB,CAAC;IACpC,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;CACpC,GAAG,MAAM,GAAG,IAAI,CA8BhB;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAgC1E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAmBtE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,GACV,kBAAkB,EAAE,CAsBtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CA2GjB;AAED,yEAAyE;AACzE,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAI3D"}
|
|
@@ -52,14 +52,44 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
52
52
|
};
|
|
53
53
|
})();
|
|
54
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.recordLedgerPatch = recordLedgerPatch;
|
|
56
|
+
exports.clearLedgerPatches = clearLedgerPatches;
|
|
55
57
|
exports.ledgerDir = ledgerDir;
|
|
56
58
|
exports.writePendingPromptsLedger = writePendingPromptsLedger;
|
|
57
59
|
exports.readPendingPromptsLedger = readPendingPromptsLedger;
|
|
58
60
|
exports.readGitPayloads = readGitPayloads;
|
|
59
61
|
exports.readGitPayloadsFromDir = readGitPayloadsFromDir;
|
|
62
|
+
exports.readLedgerEntriesFromDir = readLedgerEntriesFromDir;
|
|
63
|
+
exports.mergeLedgerEntriesByFile = mergeLedgerEntriesByFile;
|
|
60
64
|
exports.clearPendingPromptsLedger = clearPendingPromptsLedger;
|
|
61
65
|
const fs = __importStar(require("fs"));
|
|
62
66
|
const path = __importStar(require("path"));
|
|
67
|
+
const MutationApplier_1 = require("./MutationApplier");
|
|
68
|
+
const atomicWrite_1 = require("../utils/atomicWrite");
|
|
69
|
+
// ───────────────────────────────────────────────────────────────────
|
|
70
|
+
// Session-level patch registry
|
|
71
|
+
// ───────────────────────────────────────────────────────────────────
|
|
72
|
+
// Module-level registry so patches accumulate across heal() calls within
|
|
73
|
+
// a single worker process — same lifetime as sharedHealReport / sharedWorkspaceSnapshot.
|
|
74
|
+
const _patchRegistry = new Map();
|
|
75
|
+
/**
|
|
76
|
+
* Record a patch produced by a successful composeHeal call.
|
|
77
|
+
* @param relativePath posix-relative path to the mutated file (used as key)
|
|
78
|
+
* @param patch the patch descriptor
|
|
79
|
+
*/
|
|
80
|
+
function recordLedgerPatch(relativePath, patch) {
|
|
81
|
+
const list = _patchRegistry.get(relativePath);
|
|
82
|
+
if (list) {
|
|
83
|
+
list.push(patch);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
_patchRegistry.set(relativePath, [patch]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Drain and clear the patch registry (call after writePendingPromptsLedger). */
|
|
90
|
+
function clearLedgerPatches() {
|
|
91
|
+
_patchRegistry.clear();
|
|
92
|
+
}
|
|
63
93
|
const LEDGER_SUBDIR = path.join(".sela", "pending-prompts");
|
|
64
94
|
/**
|
|
65
95
|
* Resolve the ledger directory for a given cwd. The directory is created
|
|
@@ -182,6 +212,147 @@ function readGitPayloadsFromDir(dir) {
|
|
|
182
212
|
}
|
|
183
213
|
return payloads;
|
|
184
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Read the `entries` (source-file patches) from every v2 ledger file in an
|
|
217
|
+
* arbitrary directory. Deduplicates by `relativePath`, keeping the last writer
|
|
218
|
+
* wins — safe because a given source file is only mutated by one shard.
|
|
219
|
+
*
|
|
220
|
+
* Used by `sela pr aggregate` to re-apply healed code to the aggregator
|
|
221
|
+
* machine's disk before the centralized Git flow runs a diff.
|
|
222
|
+
*/
|
|
223
|
+
function readLedgerEntriesFromDir(dir) {
|
|
224
|
+
if (!fs.existsSync(dir))
|
|
225
|
+
return [];
|
|
226
|
+
const byRelPath = new Map();
|
|
227
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
228
|
+
for (const f of files) {
|
|
229
|
+
const full = path.join(dir, f);
|
|
230
|
+
try {
|
|
231
|
+
const raw = fs.readFileSync(full, "utf8");
|
|
232
|
+
const parsed = JSON.parse(raw);
|
|
233
|
+
if (parsed?.version === 2 && Array.isArray(parsed.entries)) {
|
|
234
|
+
for (const entry of parsed.entries) {
|
|
235
|
+
byRelPath.set(entry.relativePath, entry);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Corrupt file - skip.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return Array.from(byRelPath.values());
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* CI aggregator entry-point: read all shard ledger files from `dir`, group
|
|
247
|
+
* entries by relativePath, and apply each shard's granular patches
|
|
248
|
+
* sequentially against the shared baseline.
|
|
249
|
+
*
|
|
250
|
+
* New-path (patches present): all shards' patches are merged by re-resolving
|
|
251
|
+
* each anchor in the running AST — line-shift-immune because anchors use sha
|
|
252
|
+
* + structural path, not line numbers.
|
|
253
|
+
*
|
|
254
|
+
* Legacy-path (no patches): fall back to last-write-wins on contentAfter.
|
|
255
|
+
*
|
|
256
|
+
* The final merged content is written atomically to disk via atomicWriteFile.
|
|
257
|
+
* Returns the number of files that were successfully patched.
|
|
258
|
+
*/
|
|
259
|
+
async function mergeLedgerEntriesByFile(dir, cwd) {
|
|
260
|
+
if (!fs.existsSync(dir))
|
|
261
|
+
return 0;
|
|
262
|
+
const byRelPath = new Map();
|
|
263
|
+
const files = fs
|
|
264
|
+
.readdirSync(dir)
|
|
265
|
+
.filter((f) => f.endsWith(".json"))
|
|
266
|
+
.sort();
|
|
267
|
+
for (const f of files) {
|
|
268
|
+
const full = path.join(dir, f);
|
|
269
|
+
try {
|
|
270
|
+
const raw = fs.readFileSync(full, "utf8");
|
|
271
|
+
const parsed = JSON.parse(raw);
|
|
272
|
+
if (parsed?.version !== 2 || !Array.isArray(parsed.entries))
|
|
273
|
+
continue;
|
|
274
|
+
for (const entry of parsed.entries) {
|
|
275
|
+
if (!entry?.relativePath)
|
|
276
|
+
continue;
|
|
277
|
+
const key = entry.relativePath;
|
|
278
|
+
const acc = byRelPath.get(key);
|
|
279
|
+
if (!acc) {
|
|
280
|
+
byRelPath.set(key, {
|
|
281
|
+
absolutePath: path.resolve(cwd, key),
|
|
282
|
+
relativePath: key,
|
|
283
|
+
contentBefore: entry.contentBefore ?? "",
|
|
284
|
+
contentAfter: entry.contentAfter,
|
|
285
|
+
patches: [],
|
|
286
|
+
seenStableIds: new Set(),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
else if (entry.contentAfter) {
|
|
290
|
+
// Legacy: keep latest blob for the fallback path.
|
|
291
|
+
acc.contentAfter = entry.contentAfter;
|
|
292
|
+
}
|
|
293
|
+
// Merge patches with stableId dedup.
|
|
294
|
+
const target = byRelPath.get(key);
|
|
295
|
+
for (const patch of entry.patches ?? []) {
|
|
296
|
+
if (!patch?.stableId)
|
|
297
|
+
continue;
|
|
298
|
+
if (target.seenStableIds.has(patch.stableId))
|
|
299
|
+
continue;
|
|
300
|
+
target.seenStableIds.add(patch.stableId);
|
|
301
|
+
target.patches.push(patch);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// Corrupt file — skip.
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
let patchedCount = 0;
|
|
310
|
+
for (const acc of byRelPath.values()) {
|
|
311
|
+
const { absolutePath, relativePath, contentBefore, contentAfter, patches } = acc;
|
|
312
|
+
if (patches.length > 0) {
|
|
313
|
+
// NEW PATH: sequential anchor-based patch application.
|
|
314
|
+
let current = contentBefore;
|
|
315
|
+
for (const patch of patches) {
|
|
316
|
+
// Override anchor.filePath for this machine (workers ran on different runners).
|
|
317
|
+
const localAnchor = { ...patch.anchor, filePath: absolutePath };
|
|
318
|
+
const outcome = await MutationApplier_1.MutationApplier.applyFileBatchAsync({
|
|
319
|
+
filePath: absolutePath,
|
|
320
|
+
edits: [
|
|
321
|
+
{
|
|
322
|
+
kind: "replace",
|
|
323
|
+
anchor: localAnchor,
|
|
324
|
+
replacement: patch.replacement,
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
}, { write: false, contentOverride: current });
|
|
328
|
+
if (outcome.status === "applied") {
|
|
329
|
+
current = outcome.finalContent;
|
|
330
|
+
}
|
|
331
|
+
else if (outcome.status !== "no-op") {
|
|
332
|
+
console.warn(`[sela aggregate] patch skipped for ${relativePath} (stableId=${patch.stableId}): ${outcome.reason}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
(0, atomicWrite_1.atomicWriteFile)(absolutePath, current);
|
|
337
|
+
patchedCount++;
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
console.error(`[sela aggregate] Failed to write ${relativePath}: ${err.message}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else if (contentAfter !== undefined) {
|
|
344
|
+
// LEGACY PATH: last-write-wins blob.
|
|
345
|
+
try {
|
|
346
|
+
(0, atomicWrite_1.atomicWriteFile)(absolutePath, contentAfter);
|
|
347
|
+
patchedCount++;
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
console.error(`[sela aggregate] Failed to write ${relativePath}: ${err.message}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return patchedCount;
|
|
355
|
+
}
|
|
185
356
|
/** Remove the ledger directory and every file beneath it. Idempotent. */
|
|
186
357
|
function clearPendingPromptsLedger(cwd) {
|
|
187
358
|
const dir = ledgerDir(cwd);
|
|
@@ -201,6 +372,7 @@ function snapshotToEntry(snap, heals, cwd) {
|
|
|
201
372
|
createdBySela: snap.createdBySela,
|
|
202
373
|
lineNumber: findLineNumberForSnapshot(snap, heals, cwd),
|
|
203
374
|
healCount: countHealsForSnapshot(snap, heals, cwd),
|
|
375
|
+
patches: _patchRegistry.get(snap.relativePath) ?? [],
|
|
204
376
|
};
|
|
205
377
|
}
|
|
206
378
|
/**
|
package/package.json
CHANGED