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.
@@ -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;AAazC;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAgDhD"}
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"}
@@ -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 ledgerDir = path.resolve(opts.cwd, opts.ledgerDir);
64
- const payloads = (0, PendingPromptLedger_js_1.readGitPayloadsFromDir)(ledgerDir);
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 ${ledgerDir} — nothing to do.`);
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;AAoFhF,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;IAirBlB,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;YAoGtB,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"}
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;IACH,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB;IAoE9D,wEAAwE;IACxE,MAAM,CAAC,oBAAoB,CACzB,OAAO,EAAE,SAAS,EAAE,EACpB,IAAI,GAAE,YAAiB,GACtB,YAAY,EAAE;IAIjB,8EAA8E;IAC9E,MAAM,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS;IA8BvE;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAqBvB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG;QAC5C,QAAQ,EAAE,OAAO,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB;IAsDD;;;;OAIG;IACH,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;;IAgCrD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAcjC;IAEH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CA8B9B;IAEH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAuB/B;IAMH,MAAM,CACJ,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,eAAe,EAAE,EAC9B,mBAAmB,CAAC,EAAE,MAAM,EAC5B,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EACpD,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,EACxC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,GACtC,eAAe;IA8QlB,OAAO,CAAC,kBAAkB;IAmM1B,OAAO,CAAC,uBAAuB;IA0F/B,OAAO,CAAC,qBAAqB;IAuF7B,OAAO,CAAC,YAAY;IAkDpB,OAAO,CAAC,qBAAqB;IA0G7B,OAAO,CAAC,4BAA4B;IAgRpC,OAAO,CAAC,yBAAyB;IAsGjC,OAAO,CAAC,uBAAuB;IA4E/B,OAAO,CAAC,4BAA4B;IA4EpC,OAAO,CAAC,qBAAqB;IA6B7B,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,gBAAgB;IAwBxB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAahC,OAAO,CAAC,mBAAmB;IAuC3B,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,2BAA2B;IAsCnC,OAAO,CAAC,yBAAyB;IAqCjC,OAAO,CAAC,yBAAyB;IA+CjC,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,0BAA0B;IAmBlC,OAAO,CAAC,sBAAsB;IA2B9B,OAAO,CAAC,2BAA2B;IAoCnC,OAAO,CAAC,oBAAoB;IA2B5B,OAAO,CAAC,YAAY;IAUpB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkB3C,OAAO,CAAC,gBAAgB;IAmGxB,OAAO,CAAC,uBAAuB;IAuE/B,OAAO,CAAC,mBAAmB;IAiF3B,OAAO,CAAC,uBAAuB;IAiF/B,OAAO,CAAC,wBAAwB;IAyFhC,OAAO,CAAC,mBAAmB;IAyD3B,OAAO,CAAC,uBAAuB;IA0F/B,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,kBAAkB;CAgB3B"}
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.applyFileBatch(batch, input.applyOptions ?? {});
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;CACzB;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,OAAO,CAAC,MAAM,CAAC,sBAAsB;CAiLtC;AA2CD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkB5D"}
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
- try {
141
- original = fs.readFileSync(filePath, "utf8");
181
+ if (opts.contentOverride !== undefined) {
182
+ original = opts.contentOverride;
142
183
  }
143
- catch (e) {
144
- return {
145
- filePath,
146
- status: "skipped",
147
- reason: `cannot read file: ${e.message}`,
148
- written: false,
149
- finalContent: "",
150
- skipped,
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
- contentAfter: string;
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;AAMvE;;;;;;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,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;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,yEAAyE;AACzE,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAI3D"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sela-core",
3
- "version": "0.1.0-alpha.33",
3
+ "version": "0.1.0-alpha.35",
4
4
  "description": "AI self-healing Playwright wrapper - drop-in replacement for @playwright/test",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",