@stravica-ai/digital-operator-pa 0.1.12 → 0.1.13
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.
|
@@ -69,8 +69,31 @@ export async function fetchOrigin(runner, { cwd }) {
|
|
|
69
69
|
return runner.spawn('git', ['fetch', 'origin'], { cwd });
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// `--autostash` is load-bearing, not convenience. estate-sync commits
|
|
73
|
+
// FIRST (see run.mjs step 6), but only the registry-scoped IN-SCOPE dirty
|
|
74
|
+
// paths are ever staged — dirty paths under an excluded mount subtree
|
|
75
|
+
// (down-path deliveries of canonical-owned content, materialised into the
|
|
76
|
+
// workspace by library-sync on hosts that run no wrap-pr leg) legitimately
|
|
77
|
+
// remain unstaged at rebase time. A plain `git pull --rebase` refuses to
|
|
78
|
+
// start over those unstaged changes ("cannot pull with rebase: You have
|
|
79
|
+
// unstaged changes"), which wedged ops-01 twice on 2026-07-29
|
|
80
|
+
// (w-2026-07-29-019): exit 2 every tick while the sync's own
|
|
81
|
+
// push-race-lost commits piled up ahead and the workspace diverged
|
|
82
|
+
// (ahead-2/behind-5), taking every librarian sweep down with
|
|
83
|
+
// fail-clone-behind/dirty until a human ran stash -> rebase -> push ->
|
|
84
|
+
// pop by hand. `--autostash` is exactly that manual recovery, done by
|
|
85
|
+
// git itself: stash the unstaged (excluded-scope) dirt, rebase, pop.
|
|
86
|
+
// carry-home and housekeeping already pull with --autostash for the same
|
|
87
|
+
// reason.
|
|
88
|
+
//
|
|
89
|
+
// If popping the autostash conflicts (upstream changed the same excluded
|
|
90
|
+
// file differently), git completes the rebase, KEEPS the changes in the
|
|
91
|
+
// stash, and prints "applying autostash resulted in conflicts" — the
|
|
92
|
+
// working tree is left clean, the push still proceeds, and run.mjs
|
|
93
|
+
// detects that message and surfaces it in the audit record (see
|
|
94
|
+
// AUTOSTASH_CONFLICT_RE) rather than letting parked dirt go silent.
|
|
72
95
|
export async function pullRebase(runner, { cwd, branch }) {
|
|
73
|
-
return runner.spawn('git', ['pull', '--rebase', '--quiet', 'origin', branch], { cwd });
|
|
96
|
+
return runner.spawn('git', ['pull', '--rebase', '--autostash', '--quiet', 'origin', branch], { cwd });
|
|
74
97
|
}
|
|
75
98
|
|
|
76
99
|
export async function rebaseAbort(runner, { cwd }) {
|
|
@@ -25,8 +25,18 @@
|
|
|
25
25
|
// `git commit -m "estate-sync <ISO ts>" -m "<body>"`.
|
|
26
26
|
// (this happens BEFORE the pull — git pull --rebase refuses to run
|
|
27
27
|
// with uncommitted changes in the index or working tree)
|
|
28
|
-
// 7. git pull --rebase origin <branch> (fetch already done
|
|
28
|
+
// 7. git pull --rebase --autostash origin <branch> (fetch already done
|
|
29
|
+
// in step 2). --autostash matters: step 6 stages only IN-SCOPE dirty
|
|
30
|
+
// paths, so registry-excluded dirt (down-path deliveries of
|
|
31
|
+
// mount-owned content on hosts with no wrap-pr leg) legitimately
|
|
32
|
+
// remains unstaged here — without autostash the rebase refuses to
|
|
33
|
+
// start over it and the sync wedges with exit 2 on every tick
|
|
34
|
+
// (observed live on ops-01, 2026-07-29, w-2026-07-29-019).
|
|
29
35
|
// on rebase conflict: git rebase --abort + fail-rebase audit + exit 2
|
|
36
|
+
// (abort also restores the autostash). If the rebase succeeds but
|
|
37
|
+
// POPPING the autostash conflicts, git keeps the dirt in the stash
|
|
38
|
+
// and completes — we detect the message and record
|
|
39
|
+
// autostashConflict:true in the audit rather than going silent.
|
|
30
40
|
// 8. git push. on rejection: fetch + pull --rebase + push retry. one
|
|
31
41
|
// retry only.
|
|
32
42
|
// 9. append success audit to logs/runs.jsonl
|
|
@@ -89,6 +99,25 @@ function log(line, { stderr = process.stderr } = {}) {
|
|
|
89
99
|
stderr.write(`[estate-sync] ${line}\n`);
|
|
90
100
|
}
|
|
91
101
|
|
|
102
|
+
// Git's exact wording when a rebase completes but re-applying the
|
|
103
|
+
// autostash conflicts: "Applying autostash resulted in conflicts. Your
|
|
104
|
+
// changes are safe in the stash. ..." (sequencer.c, stable across
|
|
105
|
+
// versions). The rebase itself SUCCEEDS in this case (exit 0) — the
|
|
106
|
+
// excluded-scope dirt is parked in `git stash` and the working tree is
|
|
107
|
+
// left clean. That is a safe state (the next down-path re-materialises
|
|
108
|
+
// the same content), but it must never be silent: the audit record
|
|
109
|
+
// carries autostashConflict:true so a human can `git stash pop` / `drop`
|
|
110
|
+
// deliberately.
|
|
111
|
+
const AUTOSTASH_CONFLICT_RE = /applying autostash resulted in conflicts/i;
|
|
112
|
+
|
|
113
|
+
function detectAutostashConflict(res, stderr) {
|
|
114
|
+
const matched = AUTOSTASH_CONFLICT_RE.test(`${res.stdout || ''}\n${res.stderr || ''}`);
|
|
115
|
+
if (matched) {
|
|
116
|
+
log('rebase succeeded but applying the autostash conflicted — excluded-scope dirt is parked in `git stash` (run `git stash pop` or `git stash drop` deliberately)', { stderr });
|
|
117
|
+
}
|
|
118
|
+
return matched;
|
|
119
|
+
}
|
|
120
|
+
|
|
92
121
|
async function writeAudit(runsLog, record, stderr) {
|
|
93
122
|
try {
|
|
94
123
|
await appendFile(runsLog, JSON.stringify(record) + '\n');
|
|
@@ -317,8 +346,10 @@ export async function runSync({
|
|
|
317
346
|
// If fetch failed earlier, we'd be rebasing onto stale refs which
|
|
318
347
|
// either no-ops (if local refs already match) or fails spuriously.
|
|
319
348
|
let autoResolvedPaths = [];
|
|
349
|
+
let autostashConflict = false;
|
|
320
350
|
if (upfrontFetch.code === 0) {
|
|
321
351
|
const prRes = await pullRebase(runner, { cwd: repoRoot, branch });
|
|
352
|
+
autostashConflict = detectAutostashConflict(prRes, stderr) || autostashConflict;
|
|
322
353
|
if (prRes.code !== 0) {
|
|
323
354
|
log(`git pull --rebase failed: ${prRes.stderr.trim()}`, { stderr });
|
|
324
355
|
// Before aborting, see if the conflict is limited to files that
|
|
@@ -365,9 +396,12 @@ export async function runSync({
|
|
|
365
396
|
// pointing at remote refs (nothing to push). If we committed, we have
|
|
366
397
|
// at least one local-only commit. If we were just behind, pull
|
|
367
398
|
// brought us up to date — nothing to push.
|
|
368
|
-
const autoResolveFields =
|
|
369
|
-
|
|
370
|
-
|
|
399
|
+
const autoResolveFields = {
|
|
400
|
+
...(autoResolvedPaths.length > 0
|
|
401
|
+
? { autoResolved: autoResolvedPaths.length, autoResolvedPaths }
|
|
402
|
+
: {}),
|
|
403
|
+
...(autostashConflict ? { autostashConflict: true } : {}),
|
|
404
|
+
};
|
|
371
405
|
|
|
372
406
|
const { ahead: aheadAfter } = await aheadBehind(runner, { cwd: repoRoot });
|
|
373
407
|
const needsPush = Number.isFinite(aheadAfter) && aheadAfter > 0;
|
|
@@ -416,6 +450,7 @@ export async function runSync({
|
|
|
416
450
|
return 2;
|
|
417
451
|
}
|
|
418
452
|
const pr2 = await pullRebase(runner, { cwd: repoRoot, branch });
|
|
453
|
+
autostashConflict = detectAutostashConflict(pr2, stderr) || autostashConflict;
|
|
419
454
|
if (pr2.code !== 0) {
|
|
420
455
|
log(`git pull --rebase (for push-retry) failed: ${pr2.stderr.trim()}`, { stderr });
|
|
421
456
|
// Symmetric with step 7: if the push-retry rebase conflicts only
|
|
@@ -460,9 +495,12 @@ export async function runSync({
|
|
|
460
495
|
}
|
|
461
496
|
log('pushed after rebase', { stderr });
|
|
462
497
|
// Recompute so any auto-resolve during the push-retry is captured.
|
|
463
|
-
const autoResolveFieldsFinal =
|
|
464
|
-
|
|
465
|
-
|
|
498
|
+
const autoResolveFieldsFinal = {
|
|
499
|
+
...(autoResolvedPaths.length > 0
|
|
500
|
+
? { autoResolved: autoResolvedPaths.length, autoResolvedPaths }
|
|
501
|
+
: {}),
|
|
502
|
+
...(autostashConflict ? { autostashConflict: true } : {}),
|
|
503
|
+
};
|
|
466
504
|
await writeAudit(runsLog, {
|
|
467
505
|
ts: tsIso,
|
|
468
506
|
status: 'ok',
|
package/package.json
CHANGED