@autopilot-harness/cli 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1159,9 +1159,10 @@ var StateStore = class _StateStore {
1159
1159
  ).run(nowIso(), conversationId);
1160
1160
  }
1161
1161
  /**
1162
- * Column-only redeliver hold (recover claim sleep). Also clears chain_pending so
1163
- * armChain=false recover cannot leave a phantom E3 arm. Avoids updateReviewChain
1164
- * read-merge-write clobbering confirm_left / pending under concurrency.
1162
+ * Column-only redeliver hold (recover claim sleep). Does not touch
1163
+ * chain_pending callers that need disarm (executing / ambient non-resumeFix)
1164
+ * clearChainPending before this. Mid-fix ambient resumeFix keeps
1165
+ * chain_pending for abort wasArmed handoff.
1165
1166
  */
1166
1167
  setPendingRedeliverHold(conversationId, at) {
1167
1168
  if (this.isInvalidConversationId(conversationId)) {
@@ -1173,7 +1174,7 @@ var StateStore = class _StateStore {
1173
1174
  }
1174
1175
  this.db.prepare(
1175
1176
  `UPDATE review_chains SET
1176
- pending_redeliver_at = ?, chain_pending = 0, updated_at = ?
1177
+ pending_redeliver_at = ?, updated_at = ?
1177
1178
  WHERE conversation_id = ?`
1178
1179
  ).run(stamp, nowIso(), conversationId);
1179
1180
  }
@@ -2473,11 +2474,16 @@ var ReviewEngine = class {
2473
2474
  const transcriptPath = input.transcriptPath?.trim() || void 0;
2474
2475
  const events = transcriptPath ? readTranscriptTail(transcriptPath) : [];
2475
2476
  if (chain.code_edited === 1) {
2476
- return this.e2Fix(session, chain);
2477
+ const tip = chain.pending_followup?.trim() ?? "";
2478
+ if (!isRecoverOrStuckFollowupMessage(tip)) {
2479
+ const fix = this.e2Fix(session, chain);
2480
+ if (fix) return fix;
2481
+ }
2477
2482
  }
2483
+ const chainLive = this.store.getReviewChain(session.conversation_id) ?? chain;
2478
2484
  const redelivered = this.tryRedeliverPending(
2479
2485
  session.conversation_id,
2480
- chain,
2486
+ chainLive,
2481
2487
  events,
2482
2488
  transcriptPath
2483
2489
  );
@@ -2487,26 +2493,54 @@ var ReviewEngine = class {
2487
2493
  }
2488
2494
  if (this.pendingBlocksAdvance(
2489
2495
  session.conversation_id,
2490
- chain,
2496
+ chainLive,
2491
2497
  events,
2492
2498
  transcriptPath
2493
2499
  )) {
2494
2500
  return null;
2495
2501
  }
2496
- if (chain.confirm_left !== null && chain.confirm_left > 0) {
2497
- return this.e4Confirm(session, chain);
2502
+ const chainAfterPending = this.store.getReviewChain(session.conversation_id) ?? chainLive;
2503
+ {
2504
+ const liveTip = chainAfterPending.pending_followup?.trim() ?? "";
2505
+ if (isRecoverOrStuckFollowupMessage(liveTip)) {
2506
+ return null;
2507
+ }
2508
+ }
2509
+ if (chainAfterPending.code_edited === 1) {
2510
+ const tip = chainAfterPending.pending_followup?.trim() ?? "";
2511
+ if (!isRecoverOrStuckFollowupMessage(tip)) {
2512
+ const fix = this.e2Fix(session, chainAfterPending);
2513
+ if (fix) return fix;
2514
+ const afterE2 = this.store.getReviewChain(session.conversation_id) ?? chainAfterPending;
2515
+ const afterTip = afterE2.pending_followup?.trim() ?? "";
2516
+ if (isRecoverOrStuckFollowupMessage(afterTip)) {
2517
+ const again = this.tryRedeliverPending(
2518
+ session.conversation_id,
2519
+ afterE2,
2520
+ events,
2521
+ transcriptPath
2522
+ );
2523
+ if (again) return again;
2524
+ return null;
2525
+ }
2526
+ }
2498
2527
  }
2499
- if (chain.confirm_left === 0 || chain.item_confirm_complete === 1 && chain.confirm_left === null) {
2500
- return this.e5Gate(session, chain);
2528
+ if (chainAfterPending.confirm_left !== null && chainAfterPending.confirm_left > 0) {
2529
+ return this.e4Confirm(session, chainAfterPending);
2501
2530
  }
2502
- if (chain.chain_pending === 0 && chain.fix_round > 0 && chain.code_edited === 0 && chain.item_confirm_complete === 0 && isChecklistExecuting(session)) {
2531
+ if (chainAfterPending.confirm_left === 0 || chainAfterPending.item_confirm_complete === 1 && chainAfterPending.confirm_left === null) {
2532
+ return this.e5Gate(session, chainAfterPending);
2533
+ }
2534
+ if (chainAfterPending.chain_pending === 0 && chainAfterPending.fix_round > 0 && chainAfterPending.code_edited === 0 && chainAfterPending.item_confirm_complete === 0 && isChecklistExecuting(session)) {
2503
2535
  const cid2 = session.conversation_id;
2504
2536
  const rearmed = this.store.exclusiveWrite(() => {
2505
2537
  if (!this.sessionRunnable(cid2)) {
2506
2538
  return { commit: false, value: false };
2507
2539
  }
2508
2540
  const fresh = this.store.getReviewChain(cid2);
2509
- if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1 || fresh.fix_round <= 0) {
2541
+ if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1 || fresh.fix_round <= 0 || isRecoverOrStuckFollowupMessage(
2542
+ fresh.pending_followup?.trim() ?? ""
2543
+ )) {
2510
2544
  return { commit: false, value: false };
2511
2545
  }
2512
2546
  this.store.updateReviewChain(cid2, { code_edited: 1 });
@@ -2517,12 +2551,48 @@ var ReviewEngine = class {
2517
2551
  if (live?.code_edited === 1) {
2518
2552
  const fix = this.e2Fix(session, live);
2519
2553
  if (fix) return fix;
2554
+ const afterE2 = this.store.getReviewChain(cid2) ?? live;
2555
+ const afterTip = afterE2.pending_followup?.trim() ?? "";
2556
+ if (isRecoverOrStuckFollowupMessage(afterTip)) {
2557
+ const again = this.tryRedeliverPending(
2558
+ cid2,
2559
+ afterE2,
2560
+ events,
2561
+ transcriptPath
2562
+ );
2563
+ if (again) return again;
2564
+ return null;
2565
+ }
2520
2566
  }
2521
2567
  }
2522
2568
  }
2523
2569
  const chainNow = this.store.getReviewChain(input.conversationId) ?? chain;
2524
2570
  if (chainNow.code_edited === 1) {
2525
- return this.e2Fix(session, chainNow);
2571
+ const tip = chainNow.pending_followup?.trim() ?? "";
2572
+ if (isRecoverOrStuckFollowupMessage(tip)) {
2573
+ const again = this.tryRedeliverPending(
2574
+ session.conversation_id,
2575
+ chainNow,
2576
+ events,
2577
+ transcriptPath
2578
+ );
2579
+ if (again) return again;
2580
+ return null;
2581
+ }
2582
+ const fix = this.e2Fix(session, chainNow);
2583
+ if (fix) return fix;
2584
+ const afterE2 = this.store.getReviewChain(session.conversation_id) ?? chainNow;
2585
+ const afterTip = afterE2.pending_followup?.trim() ?? "";
2586
+ if (isRecoverOrStuckFollowupMessage(afterTip)) {
2587
+ const again = this.tryRedeliverPending(
2588
+ session.conversation_id,
2589
+ afterE2,
2590
+ events,
2591
+ transcriptPath
2592
+ );
2593
+ if (again) return again;
2594
+ return null;
2595
+ }
2526
2596
  }
2527
2597
  const inChain = chainNow.chain_pending === 1 || chainNow.fix_round > 0 && isChecklistExecuting(session);
2528
2598
  if (chainNow.confirm_left === null && chainNow.item_confirm_complete === 0 && inChain) {
@@ -2541,18 +2611,16 @@ var ReviewEngine = class {
2541
2611
  const pending = chain.pending_followup?.trim();
2542
2612
  if (!pending) return false;
2543
2613
  if (events.length > 0 && automationFollowupPresent(events, pending)) {
2544
- try {
2545
- const cleared = this.store.clearPendingFollowupIf(
2546
- conversationId,
2547
- (m) => m.trim() === pending
2548
- );
2549
- if (!cleared) {
2614
+ const cleared = this.clearMatchingPendingAndArmAmbient(
2615
+ conversationId,
2616
+ pending
2617
+ );
2618
+ if (!cleared) {
2619
+ try {
2550
2620
  const live = this.store.getReviewChain(conversationId)?.pending_followup?.trim() ?? "";
2551
- if (live && !(events.length > 0 && automationFollowupPresent(events, live))) {
2552
- return true;
2553
- }
2621
+ if (live) return true;
2622
+ } catch {
2554
2623
  }
2555
- } catch {
2556
2624
  }
2557
2625
  return false;
2558
2626
  }
@@ -2560,17 +2628,23 @@ var ReviewEngine = class {
2560
2628
  return true;
2561
2629
  }
2562
2630
  tryRedeliverPending(conversationId, chain, events, transcriptPath) {
2563
- const pending = chain.pending_followup?.trim();
2631
+ let pending = chain.pending_followup?.trim() ?? "";
2632
+ if (!pending) {
2633
+ try {
2634
+ pending = this.store.getReviewChain(conversationId)?.pending_followup?.trim() ?? "";
2635
+ } catch {
2636
+ pending = "";
2637
+ }
2638
+ }
2564
2639
  if (!pending) return null;
2565
2640
  if (!transcriptPath) return null;
2566
2641
  if (events.length > 0 && automationFollowupPresent(events, pending)) {
2567
- try {
2568
- const cleared = this.store.clearPendingFollowupIf(
2569
- conversationId,
2570
- (m) => m.trim() === pending
2571
- );
2572
- if (cleared) return null;
2573
- } catch {
2642
+ const cleared = this.clearMatchingPendingAndArmAmbient(
2643
+ conversationId,
2644
+ pending
2645
+ );
2646
+ if (cleared) {
2647
+ return null;
2574
2648
  }
2575
2649
  }
2576
2650
  if (events.length > 0 && followupInFlight(events)) {
@@ -2590,10 +2664,16 @@ var ReviewEngine = class {
2590
2664
  return { commit: false, value: null };
2591
2665
  }
2592
2666
  if (events.length > 0 && automationFollowupPresent(events, livePending)) {
2593
- this.store.clearPendingFollowupIf(
2667
+ const cleared = this.store.clearPendingFollowupIf(
2594
2668
  conversationId,
2595
2669
  (m) => m.trim() === livePending
2596
2670
  );
2671
+ if (cleared) {
2672
+ this.armAmbientChainAfterDeliveredFixTip(
2673
+ conversationId,
2674
+ livePending
2675
+ );
2676
+ }
2597
2677
  return { commit: true, value: null };
2598
2678
  }
2599
2679
  try {
@@ -2608,10 +2688,13 @@ var ReviewEngine = class {
2608
2688
  return { commit: true, value: null };
2609
2689
  }
2610
2690
  if (events.length > 0 && automationFollowupPresent(events, after)) {
2611
- this.store.clearPendingFollowupIf(
2691
+ const cleared = this.store.clearPendingFollowupIf(
2612
2692
  conversationId,
2613
2693
  (m) => m.trim() === after
2614
2694
  );
2695
+ if (cleared) {
2696
+ this.armAmbientChainAfterDeliveredFixTip(conversationId, after);
2697
+ }
2615
2698
  return { commit: true, value: null };
2616
2699
  }
2617
2700
  return {
@@ -2666,15 +2749,45 @@ var ReviewEngine = class {
2666
2749
  * Clear sticky code_edited so Stop→revert→resend cannot open a phantom fix
2667
2750
  * chain on the next completed stop (disk may already be clean).
2668
2751
  * Leave fix/confirm pending alone (delivery retry still valid if inject raced).
2669
- * Both clears share one exclusiveWrite so a mid-abort failure cannot leave
2752
+ *
2753
+ * Project-scope ambient/planning: after recover soft-reset cleared
2754
+ * chain_pending, sticky code_edited may be the only handoff marker. Clearing
2755
+ * it without a freeze leaves fix_round residue that cannot E3 (bare fix_round
2756
+ * must not phantom-confirm). Freeze into confirm_left, or re-arm chain_pending
2757
+ * when an undelivered fix tip remains. Checklist executing keeps residue-E2.
2758
+ *
2759
+ * Clears + freeze share one exclusiveWrite so a mid-abort failure cannot leave
2670
2760
  * recover gone while code_edited sticky (or the reverse).
2671
2761
  */
2672
2762
  handleAbortedStop(session) {
2673
2763
  try {
2674
2764
  const cid2 = session.conversation_id;
2765
+ const ambientFreeze = this.config.reviewScope === "project" && !isChecklistExecuting(session);
2675
2766
  this.store.exclusiveWrite(() => {
2676
2767
  this.store.clearPendingFollowupIf(cid2, isRecoverOrStuckFollowupMessage);
2768
+ const chain = this.store.getReviewChain(cid2);
2769
+ const pending = chain?.pending_followup?.trim() ?? "";
2770
+ const fixPending = this.isFixFollowupMessage(pending);
2771
+ const mid = this.isMidConfirmOrE5(chain);
2772
+ const fixRound = chain?.fix_round ?? 0;
2773
+ const wasArmed = chain?.chain_pending === 1;
2677
2774
  this.store.clearCodeEdited(cid2);
2775
+ if (ambientFreeze && !mid && fixRound > 0) {
2776
+ if (fixPending) {
2777
+ this.store.updateReviewChain(cid2, { chain_pending: 1 });
2778
+ } else if (!pending && wasArmed) {
2779
+ const rounds = this.config.confirmRounds;
2780
+ if (rounds > 0) {
2781
+ this.store.updateReviewChain(cid2, {
2782
+ confirm_left: rounds,
2783
+ chain_pending: 0,
2784
+ code_edited: 0
2785
+ });
2786
+ } else {
2787
+ this.store.updateReviewChain(cid2, { chain_pending: 1 });
2788
+ }
2789
+ }
2790
+ }
2678
2791
  return { commit: true, value: void 0 };
2679
2792
  });
2680
2793
  } catch {
@@ -2860,7 +2973,10 @@ var ReviewEngine = class {
2860
2973
  if (!stamp2) {
2861
2974
  return { commit: false, value: { role: "failed" } };
2862
2975
  }
2863
- this.store.clearChainPending(conversationId);
2976
+ const liveRedeliver = this.store.getReviewChain(conversationId);
2977
+ if (liveRedeliver?.code_edited !== 1) {
2978
+ this.store.clearChainPending(conversationId);
2979
+ }
2864
2980
  this.armRecoverClaimRedeliverHold(conversationId, debounceMs);
2865
2981
  return {
2866
2982
  commit: true,
@@ -2871,7 +2987,10 @@ var ReviewEngine = class {
2871
2987
  this.applySoftResetAmbientChainForErrorRecover(conversationId);
2872
2988
  const live = this.store.getReviewChain(conversationId);
2873
2989
  if (fixTipAtClaim && live && !this.isMidConfirmOrE5(live)) {
2874
- this.store.updateReviewChain(conversationId, { code_edited: 1 });
2990
+ this.store.updateReviewChain(conversationId, {
2991
+ code_edited: 1,
2992
+ chain_pending: 1
2993
+ });
2875
2994
  }
2876
2995
  } else {
2877
2996
  this.applyExecutingErrorRecoverChainAdjustments(conversationId, {
@@ -2885,7 +3004,14 @@ var ReviewEngine = class {
2885
3004
  if (!stamp) {
2886
3005
  return { commit: false, value: { role: "failed" } };
2887
3006
  }
2888
- this.store.clearChainPending(conversationId);
3007
+ if (!ambient) {
3008
+ this.store.clearChainPending(conversationId);
3009
+ } else {
3010
+ const liveAfter = this.store.getReviewChain(conversationId);
3011
+ if (liveAfter?.code_edited !== 1) {
3012
+ this.store.clearChainPending(conversationId);
3013
+ }
3014
+ }
2889
3015
  this.armRecoverClaimRedeliverHold(conversationId, debounceMs);
2890
3016
  return {
2891
3017
  commit: true,
@@ -3060,7 +3186,10 @@ var ReviewEngine = class {
3060
3186
  if (!this.store.casBumpPendingFollowupAt(conversationId, expectedStamp)) {
3061
3187
  return { commit: false, value: null };
3062
3188
  }
3063
- this.store.clearChainPending(conversationId);
3189
+ const afterBump = this.store.getReviewChain(conversationId);
3190
+ if (afterBump?.code_edited !== 1) {
3191
+ this.store.clearChainPending(conversationId);
3192
+ }
3064
3193
  return {
3065
3194
  commit: true,
3066
3195
  value: {
@@ -3200,7 +3329,14 @@ var ReviewEngine = class {
3200
3329
  if (!isRecoverFollowupMessage(live)) {
3201
3330
  return { commit: false, value: null };
3202
3331
  }
3203
- this.store.clearChainPending(conversationId);
3332
+ if (!ambientSoftReset) {
3333
+ this.store.clearChainPending(conversationId);
3334
+ } else {
3335
+ const liveChain = this.store.getReviewChain(conversationId);
3336
+ if (liveChain?.code_edited !== 1) {
3337
+ this.store.clearChainPending(conversationId);
3338
+ }
3339
+ }
3204
3340
  return { commit: true, value: action };
3205
3341
  });
3206
3342
  try {
@@ -3236,14 +3372,20 @@ var ReviewEngine = class {
3236
3372
  if (!isRecoverFollowupMessage(live)) {
3237
3373
  return { commit: false, value: null };
3238
3374
  }
3239
- this.store.clearChainPending(conversationId);
3375
+ const liveChain = this.store.getReviewChain(conversationId);
3376
+ if (liveChain?.code_edited !== 1) {
3377
+ this.store.clearChainPending(conversationId);
3378
+ }
3240
3379
  return { commit: true, value: action };
3241
3380
  });
3242
3381
  } catch {
3243
3382
  }
3244
3383
  this.emit(conversationId, action);
3245
3384
  try {
3246
- this.store.clearChainPending(conversationId);
3385
+ const liveChain = this.store.getReviewChain(conversationId);
3386
+ if (liveChain?.code_edited !== 1) {
3387
+ this.store.clearChainPending(conversationId);
3388
+ }
3247
3389
  } catch {
3248
3390
  }
3249
3391
  try {
@@ -3331,7 +3473,8 @@ var ReviewEngine = class {
3331
3473
  * do not regress to another fix. Ready-for-E3 requires empty pending (not
3332
3474
  * merely !fixPending). Force code_edited only for an active fix
3333
3475
  * (code_edited / undelivered fix pending / chain_pending with pending still
3334
- * set). Bare leftover fix_round is ignored (ambient phantom residue).
3476
+ * set). Mid-fix resumeFix keeps chain_pending=1 so abort can freeze via
3477
+ * wasArmed; bare leftover fix_round stays disarmed (ambient phantom residue).
3335
3478
  */
3336
3479
  applySoftResetAmbientChainForErrorRecover(conversationId) {
3337
3480
  this.store.ensureReviewChain(conversationId);
@@ -3350,7 +3493,10 @@ var ReviewEngine = class {
3350
3493
  this.store.softResetAmbientChainUnlessRecover(conversationId, {
3351
3494
  confirm_left: atE5 || midConfirm ? chain.confirm_left : readyForE3 && rounds > 0 ? rounds : null,
3352
3495
  item_confirm_complete: atE5 ? chain.item_confirm_complete : 0,
3353
- chain_pending: 0,
3496
+ // Mid-fix resume keeps chain_pending so abort clearing code_edited still
3497
+ // has wasArmed handoff (freeze into confirm). Ready-for-E3 / idle residue
3498
+ // stay 0 — bare fix_round must not phantom-E3 after recover tip clears.
3499
+ chain_pending: resumeFix ? 1 : 0,
3354
3500
  // Preserve an in-flight edit marker (E2 wins over E4); else force only for mid-fix.
3355
3501
  code_edited: resumeFix || chain.code_edited === 1 ? 1 : 0
3356
3502
  });
@@ -3360,6 +3506,51 @@ var ReviewEngine = class {
3360
3506
  const line = firstSubstantiveLine(message) || message.trim();
3361
3507
  return line.startsWith("Review fix") || line.startsWith("\u81EA\u5BA1\u4FEE\u590D");
3362
3508
  }
3509
+ /**
3510
+ * Unlocked callers: clear snapshot needle + ambient E3 arm in one
3511
+ * exclusiveWrite. A crash mid-pair must not leave tip cleared with
3512
+ * chain_pending still 0 (silent ambient stall). Safe under an open writer
3513
+ * only via {@link armAmbientChainAfterDeliveredFixTip} after an inline clear
3514
+ * (clearPendingFollowupIf runs in-txn when writeDepth > 0).
3515
+ */
3516
+ clearMatchingPendingAndArmAmbient(conversationId, pending) {
3517
+ try {
3518
+ return this.store.exclusiveWrite(() => {
3519
+ const cleared = this.store.clearPendingFollowupIf(
3520
+ conversationId,
3521
+ (m) => m.trim() === pending
3522
+ );
3523
+ if (cleared) {
3524
+ this.armAmbientChainAfterDeliveredFixTip(conversationId, pending);
3525
+ }
3526
+ return { commit: true, value: cleared };
3527
+ });
3528
+ } catch {
3529
+ return false;
3530
+ }
3531
+ }
3532
+ /**
3533
+ * After a delivered ambient fix tip is cleared, recover/abort may have left
3534
+ * chain_pending=0. Re-arm so this completed stop can E3 → confirm instead of
3535
+ * silent stall. Never arms bare stale fix_round without a delivered fix tip
3536
+ * (keeps F-ERR-AMBIENT-STALE-FIX-ROUND / F-ABORT-NO-PHANTOM-FIX).
3537
+ * Call under the same writer txn as the clear when possible — throws must
3538
+ * propagate so the clear can roll back. Still arms when code_edited=1 so a
3539
+ * concurrent abort clearing sticky edit keeps wasArmed handoff.
3540
+ */
3541
+ armAmbientChainAfterDeliveredFixTip(conversationId, deliveredPending) {
3542
+ if (!this.isFixFollowupMessage(deliveredPending)) return;
3543
+ if (this.config.reviewScope !== "project") return;
3544
+ const session = this.store.getSession(conversationId);
3545
+ if (!session || isChecklistExecuting(session)) return;
3546
+ const chain = this.store.getReviewChain(conversationId);
3547
+ if (!chain || chain.fix_round <= 0) return;
3548
+ if (this.isMidConfirmOrE5(chain)) return;
3549
+ if (chain.chain_pending === 1) return;
3550
+ const live = chain.pending_followup?.trim() ?? "";
3551
+ if (live) return;
3552
+ this.store.updateReviewChain(conversationId, { chain_pending: 1 });
3553
+ }
3363
3554
  /** Mid-confirm or E5-ready — must not phantom-arm code_edited over E4/E5. */
3364
3555
  isMidConfirmOrE5(chain) {
3365
3556
  if (!chain) return false;
@@ -3501,7 +3692,7 @@ var ReviewEngine = class {
3501
3692
  return { commit: false, value: null };
3502
3693
  }
3503
3694
  const fresh = this.store.getReviewChain(cid2);
3504
- if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1 || fresh.fix_round > 0) {
3695
+ if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1 || fresh.fix_round > 0 || isRecoverOrStuckFollowupMessage(fresh.pending_followup?.trim() ?? "")) {
3505
3696
  return { commit: false, value: null };
3506
3697
  }
3507
3698
  const refreshed = this.parseSessionChecklist(lockedSession);
@@ -3613,7 +3804,7 @@ var ReviewEngine = class {
3613
3804
  return { commit: false, value: null };
3614
3805
  }
3615
3806
  const fresh = this.store.getReviewChain(cid2);
3616
- if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1 || fresh.fix_round > 0) {
3807
+ if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1 || fresh.fix_round > 0 || isRecoverOrStuckFollowupMessage(fresh.pending_followup?.trim() ?? "")) {
3617
3808
  return { commit: false, value: null };
3618
3809
  }
3619
3810
  const refreshed = this.parseSessionChecklist(sess);
@@ -3740,6 +3931,10 @@ var ReviewEngine = class {
3740
3931
  if (!fresh || fresh.code_edited !== 1) {
3741
3932
  return { commit: false, value: null };
3742
3933
  }
3934
+ const pendingLive = fresh.pending_followup?.trim() ?? "";
3935
+ if (isRecoverOrStuckFollowupMessage(pendingLive)) {
3936
+ return { commit: false, value: null };
3937
+ }
3743
3938
  const fixRound = this.nextSessionRound(fresh);
3744
3939
  const message = this.render("review.fix", { round: fixRound, total: rounds });
3745
3940
  const out = {
@@ -3827,6 +4022,10 @@ var ReviewEngine = class {
3827
4022
  if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== expectedLeft) {
3828
4023
  return { commit: false, value: null };
3829
4024
  }
4025
+ const pendingLive = fresh.pending_followup?.trim() ?? "";
4026
+ if (isRecoverOrStuckFollowupMessage(pendingLive)) {
4027
+ return { commit: false, value: null };
4028
+ }
3830
4029
  const sessionRound = this.nextSessionRound(fresh);
3831
4030
  const message = this.render(kind, {
3832
4031
  n,
@@ -3906,6 +4105,10 @@ var ReviewEngine = class {
3906
4105
  if (!atE5) {
3907
4106
  return { commit: false, value: null };
3908
4107
  }
4108
+ const pendingLive = fresh.pending_followup?.trim() ?? "";
4109
+ if (isRecoverOrStuckFollowupMessage(pendingLive)) {
4110
+ return { commit: false, value: null };
4111
+ }
3909
4112
  const sess = this.store.getSession(cid2);
3910
4113
  if (!sess || !isChecklistExecuting(sess)) {
3911
4114
  return { commit: false, value: null };
@@ -3996,6 +4199,10 @@ var ReviewEngine = class {
3996
4199
  if (!atE5) {
3997
4200
  return { commit: false, value: null };
3998
4201
  }
4202
+ const pendingLive = fresh.pending_followup?.trim() ?? "";
4203
+ if (isRecoverOrStuckFollowupMessage(pendingLive)) {
4204
+ return { commit: false, value: null };
4205
+ }
3999
4206
  const lockedSession = this.store.getSession(cid2);
4000
4207
  if (!lockedSession || !sessionReviewRunnable(lockedSession, this.config.reviewScope)) {
4001
4208
  return { commit: false, value: null };
@@ -75,7 +75,7 @@ export interface HooksFile {
75
75
  [event: string]: HookCommand[] | undefined;
76
76
  };
77
77
  }
78
- export declare const PACKAGE_VERSION = "0.2.6";
78
+ export declare const PACKAGE_VERSION = "0.2.8";
79
79
  export declare const HOOK_MARKER = "autopilot-harness";
80
80
  export declare const AUTOPILOT_EVENTS: readonly ["beforeSubmitPrompt", "afterFileEdit", "stop"];
81
81
  //# sourceMappingURL=types.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /** Shared types for Autopilot init. */
2
- export const PACKAGE_VERSION = "0.2.6";
2
+ export const PACKAGE_VERSION = "0.2.8";
3
3
  export const HOOK_MARKER = "autopilot-harness";
4
4
  export const AUTOPILOT_EVENTS = [
5
5
  "beforeSubmitPrompt",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autopilot-harness/cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Autopilot harness CLI — structured planning, checklist execution, and multi-lens self-review for Cursor and Claude Code (bin: autopilot-harness)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,10 +31,10 @@
31
31
  "@clack/prompts": "^0.10.0",
32
32
  "commander": "^13.1.0",
33
33
  "yaml": "^2.7.0",
34
- "@autopilot-harness/core": "0.2.6",
35
- "@autopilot-harness/i18n": "0.2.6",
36
- "@autopilot-harness/port-claude-code": "0.2.6",
37
- "@autopilot-harness/port-cursor": "0.2.6"
34
+ "@autopilot-harness/core": "0.2.8",
35
+ "@autopilot-harness/i18n": "0.2.8",
36
+ "@autopilot-harness/port-cursor": "0.2.8",
37
+ "@autopilot-harness/port-claude-code": "0.2.8"
38
38
  },
39
39
  "devDependencies": {
40
40
  "esbuild": "^0.25.12",