@autopilot-harness/cli 0.2.5 → 0.2.6

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.
@@ -500,7 +500,7 @@ function evaluateVerifyReport(options) {
500
500
  return { outcome: "pass" };
501
501
  }
502
502
  function hasNoCodeCompletionEvidence(options) {
503
- const { reportPath, currentItemId, projectRoot } = options;
503
+ const { reportPath, currentItemId, projectRoot, notBefore } = options;
504
504
  if (!currentItemId || typeof currentItemId !== "string") return false;
505
505
  let root;
506
506
  if (projectRoot !== void 0 && projectRoot !== null) {
@@ -528,8 +528,44 @@ function hasNoCodeCompletionEvidence(options) {
528
528
  }
529
529
  const ok = report.ok;
530
530
  if (ok === false) return false;
531
+ const notBeforeRaw = typeof notBefore === "string" && notBefore.trim() && !notBefore.includes("\0") ? notBefore.trim() : null;
532
+ if (notBeforeRaw) {
533
+ const floor = Date.parse(notBeforeRaw);
534
+ if (Number.isNaN(floor)) return false;
535
+ const rawAt = report.at;
536
+ if (rawAt !== void 0 && rawAt !== null) {
537
+ const atMs = parseEvidenceAtMs(rawAt);
538
+ if (Number.isNaN(atMs) || atMs < floor) {
539
+ return false;
540
+ }
541
+ }
542
+ }
531
543
  return true;
532
544
  }
545
+ function parseEvidenceAtMs(rawAt) {
546
+ let atMs = NaN;
547
+ if (typeof rawAt === "number" && Number.isFinite(rawAt)) {
548
+ atMs = rawAt;
549
+ } else if (typeof rawAt === "string" && rawAt.trim() && !rawAt.includes("\0")) {
550
+ const trimmed = rawAt.trim();
551
+ if (/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(trimmed)) {
552
+ const n = Number(trimmed);
553
+ if (Number.isFinite(n)) atMs = n;
554
+ } else {
555
+ atMs = Date.parse(trimmed);
556
+ }
557
+ }
558
+ if (!Number.isNaN(atMs) && atMs >= 0) {
559
+ const whole = Math.trunc(atMs);
560
+ if (Math.abs(atMs - whole) < 1e-9) {
561
+ const digits = String(Math.abs(whole)).length;
562
+ if (digits >= 1 && digits <= 10) {
563
+ atMs = whole * 1e3;
564
+ }
565
+ }
566
+ }
567
+ return atMs;
568
+ }
533
569
  function defaultVerifyReportPath(projectRoot) {
534
570
  const root = normalizeProjectRoot(projectRoot) ?? "";
535
571
  return path2.join(root, ".autopilot", "verify-last.json");
@@ -1336,7 +1372,7 @@ var StateStore = class _StateStore {
1336
1372
  * Column-only: neutralize fix/confirm/pending re-entry without ensure/session.
1337
1373
  * Used when pause-threshold upsert failed but the session is still armed — a
1338
1374
  * later completed stop must not resume the review loop via code_edited/pending
1339
- * or loopCount>0→E3 (fix_round cleared so bare loopCount cannot re-arm).
1375
+ * or checklist executing fix_round>0→E3 (fix_round cleared here).
1340
1376
  */
1341
1377
  neutralizeReviewChain(conversationId) {
1342
1378
  if (this.isInvalidConversationId(conversationId)) {
@@ -1419,7 +1455,8 @@ var StateStore = class _StateStore {
1419
1455
  }
1420
1456
  /**
1421
1457
  * Column-only pause/disarm when the full upsertSession pause write failed.
1422
- * Without this, loopCount>0 completed stops can still hit E3 while armed.
1458
+ * Without this, checklist executing completed stops can still hit E3 via
1459
+ * fix_round>0 / chain_pending while the session looks armed.
1423
1460
  */
1424
1461
  pauseSessionForRepeatedErrors(conversationId, errorCount, lastError) {
1425
1462
  if (this.isInvalidConversationId(conversationId)) {
@@ -2462,11 +2499,36 @@ var ReviewEngine = class {
2462
2499
  if (chain.confirm_left === 0 || chain.item_confirm_complete === 1 && chain.confirm_left === null) {
2463
2500
  return this.e5Gate(session, chain);
2464
2501
  }
2465
- const inChain = chain.chain_pending === 1 || input.loopCount > 0 && chain.fix_round > 0 && isChecklistExecuting(session);
2466
- if (chain.confirm_left === null && chain.item_confirm_complete === 0 && inChain) {
2467
- return this.e3ArmConfirm(session, chain);
2502
+ if (chain.chain_pending === 0 && chain.fix_round > 0 && chain.code_edited === 0 && chain.item_confirm_complete === 0 && isChecklistExecuting(session)) {
2503
+ const cid2 = session.conversation_id;
2504
+ const rearmed = this.store.exclusiveWrite(() => {
2505
+ if (!this.sessionRunnable(cid2)) {
2506
+ return { commit: false, value: false };
2507
+ }
2508
+ 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) {
2510
+ return { commit: false, value: false };
2511
+ }
2512
+ this.store.updateReviewChain(cid2, { code_edited: 1 });
2513
+ return { commit: true, value: true };
2514
+ });
2515
+ if (rearmed) {
2516
+ const live = this.store.getReviewChain(cid2);
2517
+ if (live?.code_edited === 1) {
2518
+ const fix = this.e2Fix(session, live);
2519
+ if (fix) return fix;
2520
+ }
2521
+ }
2468
2522
  }
2469
- return this.e0NoCodeContinue(session, chain);
2523
+ const chainNow = this.store.getReviewChain(input.conversationId) ?? chain;
2524
+ if (chainNow.code_edited === 1) {
2525
+ return this.e2Fix(session, chainNow);
2526
+ }
2527
+ const inChain = chainNow.chain_pending === 1 || chainNow.fix_round > 0 && isChecklistExecuting(session);
2528
+ if (chainNow.confirm_left === null && chainNow.item_confirm_complete === 0 && inChain) {
2529
+ return this.e3ArmConfirm(session, chainNow);
2530
+ }
2531
+ return this.e0NoCodeContinue(session, chainNow);
2470
2532
  } catch (err) {
2471
2533
  const msg = err instanceof Error ? err.message : String(err);
2472
2534
  if (msg.includes("No session for conversation") || msg.includes("Invalid conversation id")) {
@@ -2812,10 +2874,9 @@ var ReviewEngine = class {
2812
2874
  this.store.updateReviewChain(conversationId, { code_edited: 1 });
2813
2875
  }
2814
2876
  } else {
2815
- const resumeFix = !this.isMidConfirmOrE5(chain) && (this.isFixFollowupMessage(pending) || chain?.code_edited === 1 || fixTipAtClaim);
2816
- if (resumeFix) {
2817
- this.store.updateReviewChain(conversationId, { code_edited: 1 });
2818
- }
2877
+ this.applyExecutingErrorRecoverChainAdjustments(conversationId, {
2878
+ fixTipAtClaim
2879
+ });
2819
2880
  }
2820
2881
  this.store.savePendingFollowup(conversationId, message, {
2821
2882
  armChain: false
@@ -3118,29 +3179,32 @@ var ReviewEngine = class {
3118
3179
  */
3119
3180
  emitRecoverAfterFailedClaim(conversationId, action, opts) {
3120
3181
  const ambientSoftReset = opts?.ambientSoftReset === true;
3121
- try {
3122
- return this.store.exclusiveWrite(() => {
3123
- const session = this.store.getSession(conversationId);
3124
- if (!session || !this.sessionErrorRecoverable(session)) {
3125
- return { commit: false, value: null };
3126
- }
3127
- const pending = this.store.getReviewChain(conversationId)?.pending_followup?.trim() ?? "";
3128
- if (isRecoverFollowupMessage(pending)) {
3129
- return { commit: false, value: null };
3130
- }
3131
- if (ambientSoftReset) {
3132
- this.applySoftResetAmbientChainForErrorRecover(conversationId);
3133
- }
3134
- this.store.savePendingFollowup(conversationId, action.message, {
3135
- armChain: false
3136
- });
3137
- const live = this.store.getReviewChain(conversationId)?.pending_followup?.trim() ?? "";
3138
- if (!isRecoverFollowupMessage(live)) {
3139
- return { commit: false, value: null };
3140
- }
3141
- this.store.clearChainPending(conversationId);
3142
- return { commit: true, value: action };
3182
+ const writeRecover = () => this.store.exclusiveWrite(() => {
3183
+ const session = this.store.getSession(conversationId);
3184
+ if (!session || !this.sessionErrorRecoverable(session)) {
3185
+ return { commit: false, value: null };
3186
+ }
3187
+ const pending = this.store.getReviewChain(conversationId)?.pending_followup?.trim() ?? "";
3188
+ if (isRecoverFollowupMessage(pending)) {
3189
+ return { commit: false, value: null };
3190
+ }
3191
+ if (ambientSoftReset) {
3192
+ this.applySoftResetAmbientChainForErrorRecover(conversationId);
3193
+ } else {
3194
+ this.applyExecutingErrorRecoverChainAdjustments(conversationId);
3195
+ }
3196
+ this.store.savePendingFollowup(conversationId, action.message, {
3197
+ armChain: false
3143
3198
  });
3199
+ const live = this.store.getReviewChain(conversationId)?.pending_followup?.trim() ?? "";
3200
+ if (!isRecoverFollowupMessage(live)) {
3201
+ return { commit: false, value: null };
3202
+ }
3203
+ this.store.clearChainPending(conversationId);
3204
+ return { commit: true, value: action };
3205
+ });
3206
+ try {
3207
+ return writeRecover();
3144
3208
  } catch {
3145
3209
  if (!this.sessionStillErrorRecoverable(conversationId)) {
3146
3210
  return null;
@@ -3152,6 +3216,31 @@ var ReviewEngine = class {
3152
3216
  }
3153
3217
  } catch {
3154
3218
  }
3219
+ try {
3220
+ return writeRecover();
3221
+ } catch {
3222
+ }
3223
+ try {
3224
+ return this.store.exclusiveWrite(() => {
3225
+ if (!this.sessionStillErrorRecoverable(conversationId)) {
3226
+ return { commit: false, value: null };
3227
+ }
3228
+ const pending = this.store.getReviewChain(conversationId)?.pending_followup?.trim() ?? "";
3229
+ if (isRecoverFollowupMessage(pending)) {
3230
+ return { commit: false, value: null };
3231
+ }
3232
+ this.store.savePendingFollowup(conversationId, action.message, {
3233
+ armChain: false
3234
+ });
3235
+ const live = this.store.getReviewChain(conversationId)?.pending_followup?.trim() ?? "";
3236
+ if (!isRecoverFollowupMessage(live)) {
3237
+ return { commit: false, value: null };
3238
+ }
3239
+ this.store.clearChainPending(conversationId);
3240
+ return { commit: true, value: action };
3241
+ });
3242
+ } catch {
3243
+ }
3155
3244
  this.emit(conversationId, action);
3156
3245
  try {
3157
3246
  this.store.clearChainPending(conversationId);
@@ -3197,6 +3286,37 @@ var ReviewEngine = class {
3197
3286
  return "failed";
3198
3287
  }
3199
3288
  }
3289
+ /**
3290
+ * Executing error-recover chain adjust (call under exclusiveWrite, before
3291
+ * savePendingFollowup + clearChainPending). Mirrors ambient readyForE3 /
3292
+ * resumeFix without soft-resetting mid-confirm/E5.
3293
+ *
3294
+ * Ready-for-E3 (delivered fix, chain_pending only, empty pending) arms
3295
+ * confirm_left=confirmRounds so post-recover continues E4 — not another E2
3296
+ * solely from fix_round>0. Mid-fix residue still forces code_edited.
3297
+ */
3298
+ applyExecutingErrorRecoverChainAdjustments(conversationId, opts) {
3299
+ this.store.ensureReviewChain(conversationId);
3300
+ const chain = this.store.getReviewChain(conversationId);
3301
+ if (!chain) return;
3302
+ if (isRecoverFollowupMessage(chain.pending_followup?.trim() ?? "")) {
3303
+ return;
3304
+ }
3305
+ const pending = chain.pending_followup?.trim() ?? "";
3306
+ const readyForE3 = !this.isMidConfirmOrE5(chain) && chain.chain_pending === 1 && chain.code_edited === 0 && pending.length === 0;
3307
+ const rounds = this.config.confirmRounds;
3308
+ if (readyForE3 && rounds > 0) {
3309
+ this.store.updateReviewChain(conversationId, {
3310
+ confirm_left: rounds,
3311
+ code_edited: 0
3312
+ });
3313
+ return;
3314
+ }
3315
+ const resumeFix = !this.isMidConfirmOrE5(chain) && (this.isFixFollowupMessage(pending) || chain.code_edited === 1 || opts?.fixTipAtClaim === true || chain.fix_round > 0);
3316
+ if (resumeFix) {
3317
+ this.store.updateReviewChain(conversationId, { code_edited: 1 });
3318
+ }
3319
+ }
3200
3320
  /**
3201
3321
  * Ambient/planning error recover: drop confirm/pending arming so recover
3202
3322
  * (armChain=false) cannot leave chain_pending=1 → phantom E3.
@@ -3287,7 +3407,9 @@ var ReviewEngine = class {
3287
3407
  */
3288
3408
  e0NoCodeContinue(session, chain) {
3289
3409
  if (!isChecklistExecuting(session)) return null;
3290
- if (chain.code_edited === 1 || chain.confirm_left !== null || chain.item_confirm_complete === 1 || chain.chain_pending === 1) {
3410
+ if (chain.code_edited === 1 || chain.confirm_left !== null || chain.item_confirm_complete === 1 || chain.chain_pending === 1 || // Mid-item review residue (e.g. recover cleared chain_pending): never
3411
+ // soft-advance/done — caller should have taken residue-E2 or E3.
3412
+ chain.fix_round > 0) {
3291
3413
  return null;
3292
3414
  }
3293
3415
  const parsed = this.parseSessionChecklist(session);
@@ -3322,7 +3444,8 @@ var ReviewEngine = class {
3322
3444
  if (!hasNoCodeCompletionEvidence({
3323
3445
  reportPath,
3324
3446
  currentItemId: liveId,
3325
- projectRoot: trustRoot ?? void 0
3447
+ projectRoot: trustRoot ?? void 0,
3448
+ notBefore: liveChain?.updated_at
3326
3449
  })) {
3327
3450
  return null;
3328
3451
  }
@@ -3378,7 +3501,7 @@ var ReviewEngine = class {
3378
3501
  return { commit: false, value: null };
3379
3502
  }
3380
3503
  const fresh = this.store.getReviewChain(cid2);
3381
- if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1) {
3504
+ if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1 || fresh.fix_round > 0) {
3382
3505
  return { commit: false, value: null };
3383
3506
  }
3384
3507
  const refreshed = this.parseSessionChecklist(lockedSession);
@@ -3398,7 +3521,8 @@ var ReviewEngine = class {
3398
3521
  if (!hasNoCodeCompletionEvidence({
3399
3522
  reportPath,
3400
3523
  currentItemId: targets.current.id,
3401
- projectRoot: trustRoot ?? void 0
3524
+ projectRoot: trustRoot ?? void 0,
3525
+ notBefore: fresh.updated_at
3402
3526
  })) {
3403
3527
  return { commit: false, value: null };
3404
3528
  }
@@ -3489,7 +3613,7 @@ var ReviewEngine = class {
3489
3613
  return { commit: false, value: null };
3490
3614
  }
3491
3615
  const fresh = this.store.getReviewChain(cid2);
3492
- if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1) {
3616
+ if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1 || fresh.fix_round > 0) {
3493
3617
  return { commit: false, value: null };
3494
3618
  }
3495
3619
  const refreshed = this.parseSessionChecklist(sess);
@@ -3508,7 +3632,8 @@ var ReviewEngine = class {
3508
3632
  if (hasNoCodeCompletionEvidence({
3509
3633
  reportPath,
3510
3634
  currentItemId: lockedItem.id,
3511
- projectRoot: trustRoot ?? void 0
3635
+ projectRoot: trustRoot ?? void 0,
3636
+ notBefore: fresh.updated_at
3512
3637
  })) {
3513
3638
  return { commit: false, value: null };
3514
3639
  }
@@ -3568,7 +3693,7 @@ var ReviewEngine = class {
3568
3693
  return { commit: false, value: false };
3569
3694
  }
3570
3695
  const fresh = this.store.getReviewChain(conversationId);
3571
- if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1) {
3696
+ if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || fresh.chain_pending === 1 || fresh.fix_round > 0) {
3572
3697
  return { commit: false, value: false };
3573
3698
  }
3574
3699
  this.store.updateReviewChain(conversationId, {
@@ -3651,7 +3776,8 @@ var ReviewEngine = class {
3651
3776
  return { commit: false, value: null };
3652
3777
  }
3653
3778
  const fresh = this.store.getReviewChain(cid2);
3654
- if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1) {
3779
+ const pendingLive = fresh?.pending_followup?.trim() ?? "";
3780
+ if (!fresh || fresh.code_edited === 1 || fresh.confirm_left !== null || fresh.item_confirm_complete === 1 || this.isFixFollowupMessage(pendingLive) || isRecoverOrStuckFollowupMessage(pendingLive)) {
3655
3781
  return { commit: false, value: null };
3656
3782
  }
3657
3783
  const sessionRound = this.nextSessionRound(fresh);
@@ -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.5";
78
+ export declare const PACKAGE_VERSION = "0.2.6";
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.5";
2
+ export const PACKAGE_VERSION = "0.2.6";
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.5",
3
+ "version": "0.2.6",
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.5",
35
- "@autopilot-harness/port-cursor": "0.2.5",
36
- "@autopilot-harness/i18n": "0.2.5",
37
- "@autopilot-harness/port-claude-code": "0.2.5"
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"
38
38
  },
39
39
  "devDependencies": {
40
40
  "esbuild": "^0.25.12",