@overdeck/core 0.49.0 → 0.49.1

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.
Files changed (60) hide show
  1. package/dist/{build-info-Ck2TzOSQ.js → build-info-Bts-0E0B.js} +3 -3
  2. package/dist/{build-info-Ck2TzOSQ.js.map → build-info-Bts-0E0B.js.map} +1 -1
  3. package/dist/build-info-CD0O1MvT.js +2 -0
  4. package/dist/cli/index.js +4 -4
  5. package/dist/dashboard/{build-info-DXfOjbg3.js → build-info-DTX8JwVP.js} +3 -3
  6. package/dist/dashboard/{build-info-DXfOjbg3.js.map → build-info-DTX8JwVP.js.map} +1 -1
  7. package/dist/dashboard/build-info-PQlh_Rhb.js +2 -0
  8. package/dist/dashboard/{deacon-CeqVPw0o.js → deacon-BEH-IY0v.js} +2 -2
  9. package/dist/dashboard/{deacon-CQxv6Qwv.js → deacon-TPYssFYt.js} +8 -8
  10. package/dist/dashboard/{deacon-CQxv6Qwv.js.map → deacon-TPYssFYt.js.map} +1 -1
  11. package/dist/dashboard/{deacon-merge-ugZ7jekt.js → deacon-merge-BOGFEV8_.js} +5 -5
  12. package/dist/dashboard/{deacon-merge-ugZ7jekt.js.map → deacon-merge-BOGFEV8_.js.map} +1 -1
  13. package/dist/dashboard/{deacon-pending-lifecycle-Cxu6Rezs.js → deacon-pending-lifecycle-DVHjOLje.js} +2 -2
  14. package/dist/dashboard/{deacon-pending-lifecycle-Cxu6Rezs.js.map → deacon-pending-lifecycle-DVHjOLje.js.map} +1 -1
  15. package/dist/dashboard/deacon.js +2 -2
  16. package/dist/dashboard/{deploy-patrol-DxnpZcnl.js → deploy-patrol-BXjlvoV3.js} +2 -2
  17. package/dist/dashboard/{deploy-patrol-DxnpZcnl.js.map → deploy-patrol-BXjlvoV3.js.map} +1 -1
  18. package/dist/dashboard/{identity-BnxKo9-z.js → identity-Bwh6VGeL.js} +2 -2
  19. package/dist/dashboard/{identity-BnxKo9-z.js.map → identity-Bwh6VGeL.js.map} +1 -1
  20. package/dist/dashboard/{lifecycle-ozA4GZNs.js → lifecycle-B0rL38D3.js} +2 -2
  21. package/dist/dashboard/{lifecycle-ozA4GZNs.js.map → lifecycle-B0rL38D3.js.map} +1 -1
  22. package/dist/dashboard/{memory-governor-W8994ASc.js → memory-governor-A4O6vI9d.js} +3 -3
  23. package/dist/dashboard/{memory-governor-W8994ASc.js.map → memory-governor-A4O6vI9d.js.map} +1 -1
  24. package/dist/dashboard/{memory-pressure-patrol-D_T_QLFC.js → memory-pressure-patrol-BWlWywmA.js} +2 -2
  25. package/dist/dashboard/{memory-pressure-patrol-D_T_QLFC.js.map → memory-pressure-patrol-BWlWywmA.js.map} +1 -1
  26. package/dist/dashboard/{merge-agent-B8YsWRDu.js → merge-agent-BRSz26Oh.js} +1 -1
  27. package/dist/dashboard/{merge-agent-DxecsTre.js → merge-agent-GWhmnxFk.js} +2 -2
  28. package/dist/dashboard/{merge-agent-DxecsTre.js.map → merge-agent-GWhmnxFk.js.map} +1 -1
  29. package/dist/dashboard/merge-ops-DtHx17Vd.js +2 -0
  30. package/dist/dashboard/{post-merge-lifecycle-worker-DC-i1kqz.js → post-merge-lifecycle-worker-KK1RTKRE.js} +2 -2
  31. package/dist/dashboard/{post-merge-lifecycle-worker-DC-i1kqz.js.map → post-merge-lifecycle-worker-KK1RTKRE.js.map} +1 -1
  32. package/dist/dashboard/{resolver-BdHzKhNL.js → resolver-C8rooxoz.js} +2 -2
  33. package/dist/dashboard/{resolver-BdHzKhNL.js.map → resolver-C8rooxoz.js.map} +1 -1
  34. package/dist/dashboard/server.js +28 -28
  35. package/dist/dashboard/{service-0hbSVeHz.js → service-BLb_CyPL.js} +6 -6
  36. package/dist/dashboard/{service-0hbSVeHz.js.map → service-BLb_CyPL.js.map} +1 -1
  37. package/dist/dashboard/{specialists-C2ZVszIW.js → specialists-DocAK-7A.js} +5 -5
  38. package/dist/dashboard/{specialists-C2ZVszIW.js.map → specialists-DocAK-7A.js.map} +1 -1
  39. package/dist/dashboard/{specialists-DhLEuxVX.js → specialists-xXcJHAyg.js} +1 -1
  40. package/dist/dashboard/{stall-sweeper-o4-q0vqY.js → stall-sweeper-DOyIfeLR.js} +2 -2
  41. package/dist/dashboard/{stall-sweeper-o4-q0vqY.js.map → stall-sweeper-DOyIfeLR.js.map} +1 -1
  42. package/dist/dashboard/{uat-train-fK0FbCx7.js → uat-train-COSy-vEX.js} +1 -1
  43. package/dist/dashboard/{uat-train-CSOval9z.js → uat-train-DVUNW03P.js} +0 -0
  44. package/dist/dashboard/{uat-train-CSOval9z.js.map → uat-train-DVUNW03P.js.map} +1 -1
  45. package/dist/dashboard/{workflows-BnNc1QlT.js → workflows-BYwR2aIN.js} +2 -2
  46. package/dist/dashboard/{workflows-BnNc1QlT.js.map → workflows-BYwR2aIN.js.map} +1 -1
  47. package/dist/dashboard/workflows-Cf4LAnhg.js +2 -0
  48. package/dist/dashboard/{workspaces-CqeaoIK6.js → workspaces-BlpbOPZ7.js} +11 -11
  49. package/dist/dashboard/{workspaces-CqeaoIK6.js.map → workspaces-BlpbOPZ7.js.map} +1 -1
  50. package/dist/{lifecycle-Bhu9mEsV.js → lifecycle-BhAciIXh.js} +1 -1
  51. package/dist/{lifecycle-CMRdm3mR.js → lifecycle-D8m3w850.js} +2 -2
  52. package/dist/{lifecycle-CMRdm3mR.js.map → lifecycle-D8m3w850.js.map} +1 -1
  53. package/dist/{merge-agent-DQ7DOXE2.js → merge-agent-CUnIrr8R.js} +2 -2
  54. package/dist/{merge-agent-DQ7DOXE2.js.map → merge-agent-CUnIrr8R.js.map} +1 -1
  55. package/dist/{merge-agent-D33x_y_T.js → merge-agent-DwfVId_E.js} +1 -1
  56. package/package.json +1 -1
  57. package/dist/build-info-B9bbKbXz.js +0 -2
  58. package/dist/dashboard/build-info-CrBYH-L2.js +0 -2
  59. package/dist/dashboard/merge-ops-C9wpFz1G.js +0 -2
  60. package/dist/dashboard/workflows-C6MkcBR_.js +0 -2
@@ -152,7 +152,7 @@ async function reconcileStuckMergingStates() {
152
152
  reviewGatesPassed: reviewGatesPassedSync,
153
153
  setReviewStatus: setReviewStatusSync,
154
154
  enqueuePostMerge: async (issueId, projectPath, branch) => {
155
- const { enqueuePostMergeLifecycle } = await import("./post-merge-lifecycle-worker-DC-i1kqz.js");
155
+ const { enqueuePostMergeLifecycle } = await import("./post-merge-lifecycle-worker-KK1RTKRE.js");
156
156
  return enqueuePostMergeLifecycle(issueId, projectPath, branch);
157
157
  },
158
158
  warn: (message) => console.warn(message)
@@ -402,7 +402,7 @@ async function reconcileStaleMergeStatus() {
402
402
  } catch {}
403
403
  const branch = `feature/${issueId.toLowerCase()}`;
404
404
  if (postMergeIncomplete) {
405
- const { enqueuePostMergeLifecycle } = await import("./post-merge-lifecycle-worker-DC-i1kqz.js");
405
+ const { enqueuePostMergeLifecycle } = await import("./post-merge-lifecycle-worker-KK1RTKRE.js");
406
406
  const action = enqueuePostMergeLifecycle(issueId, project.projectPath, branch);
407
407
  if (action) actions.push(action);
408
408
  continue;
@@ -441,7 +441,7 @@ async function reconcileStaleMergeStatus() {
441
441
  const msg = `Reconciled stale mergeStatus for ${issueId} — branch ${branch} is merged to main`;
442
442
  actions.push(msg);
443
443
  console.log(`[deacon] ${msg}`);
444
- const { enqueuePostMergeLifecycle } = await import("./post-merge-lifecycle-worker-DC-i1kqz.js");
444
+ const { enqueuePostMergeLifecycle } = await import("./post-merge-lifecycle-worker-KK1RTKRE.js");
445
445
  const action = enqueuePostMergeLifecycle(issueId, project.projectPath, branch);
446
446
  if (action) actions.push(action);
447
447
  }
@@ -889,7 +889,7 @@ async function autoCloseOut(now = /* @__PURE__ */ new Date()) {
889
889
  } } : {}
890
890
  };
891
891
  try {
892
- const { closeOut } = await import("./workflows-C6MkcBR_.js");
892
+ const { closeOut } = await import("./workflows-Cf4LAnhg.js");
893
893
  const result = await Effect.runPromise(closeOut(ctx));
894
894
  if (!result.success) {
895
895
  const failed = result.steps.find((step) => !step.success && !step.skipped);
@@ -1011,4 +1011,4 @@ async function checkFirstCompletionAgents() {
1011
1011
  //#endregion
1012
1012
  export { ciRetryMap as a, reconcileMergedButReviewing as c, reconcileStuckReadyForMerge as d, reconcileAutoMergeRows as f, checkReadyForMergeStuck as i, reconcileStaleMergeBlockers as l, checkFailedMergeRetry as n, reconcileClosedPrReadyForMerge as o, reconcileStuckMergingStates as p, checkFirstCompletionAgents as r, reconcileFalseMerged as s, autoCloseOut as t, reconcileStaleMergeStatus as u };
1013
1013
 
1014
- //# sourceMappingURL=deacon-merge-ugZ7jekt.js.map
1014
+ //# sourceMappingURL=deacon-merge-BOGFEV8_.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"deacon-merge-ugZ7jekt.js","names":["execFileAsync","promisify"],"sources":["../../src/lib/cloister/deacon-stuck-merging.ts","../../src/lib/cloister/deacon-auto-merge-reconcile.ts","../../src/lib/cloister/deacon-merge.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { getAgentStateSync } from '../agents.js';\nimport { getMergeSetSync, type MergeSet } from '../merge-set.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport {\n loadReviewStatuses,\n reviewGatesPassedSync,\n setReviewStatusSync,\n type ReviewStatus,\n} from '../review-status.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\nimport {\n observeForgeMergeState,\n type ForgeMergeObservationResult,\n} from './merge-completeness.js';\n\nconst execFileAsync = promisify(execFile);\nexport const STUCK_MERGING_MS = 30 * 60 * 1000;\n\nexport interface GitHubBranchMergeObservation {\n merged: boolean;\n unverifiableReason?: string;\n}\n\nexport async function observeGitHubBranchMerge(\n issueId: string,\n branch: string,\n projectPath: string,\n): Promise<GitHubBranchMergeObservation> {\n const resolved = resolveGitHubIssueSync(issueId);\n if (!resolved.isGitHub) return { merged: false };\n try {\n const { stdout } = await execFileAsync(\n 'gh',\n ['pr', 'list', '--repo', `${resolved.owner}/${resolved.repo}`, '--head', branch, '--state', 'all', '--json', 'number,mergedAt,mergeCommit', '--limit', '5'],\n { cwd: projectPath },\n );\n const prs = JSON.parse(stdout || '[]') as Array<{ mergedAt: string | null; mergeCommit: unknown | null }>;\n return { merged: prs.some((pr) => pr.mergedAt || pr.mergeCommit) };\n } catch (error) {\n return {\n merged: false,\n unverifiableReason: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nfunction latestMergeActivityAt(status: ReviewStatus): number | null {\n const mergeEntry = [...(status.history ?? [])].reverse().find((entry) => entry.type === 'merge');\n const timestamp = Date.parse(mergeEntry?.timestamp ?? status.updatedAt);\n return Number.isFinite(timestamp) ? timestamp : null;\n}\n\nexport function isStuckMergingState(status: ReviewStatus, now: number): boolean {\n if (status.mergeStatus !== 'merging' && status.mergeStatus !== 'verifying') return false;\n const activityAt = latestMergeActivityAt(status);\n return activityAt !== null && now - activityAt >= STUCK_MERGING_MS;\n}\n\nasync function readSpecStatus(projectPath: string, issueId: string): Promise<string | undefined> {\n try {\n const { findSpecByIssue } = await import('../pan-dir/specs.js');\n return (await Effect.runPromise(findSpecByIssue(projectPath, issueId)))?.status;\n } catch {\n return undefined;\n }\n}\n\nfunction hasPlanningAgent(issueId: string): boolean {\n const planState = getAgentStateSync(`planning-${issueId.toLowerCase()}`);\n return planState?.status === 'running' || planState?.status === 'starting';\n}\n\nexport interface StuckMergingDeps {\n now(): number;\n loadStatuses(): Record<string, ReviewStatus>;\n resolveProject(issueId: string): { projectPath: string } | null;\n getMergeSet(issueId: string): MergeSet | null;\n resolveGitHubIssue(issueId: string): { isGitHub: boolean };\n observeForge(issueId: string): Promise<ForgeMergeObservationResult>;\n observeGitHub(issueId: string, branch: string, projectPath: string): Promise<GitHubBranchMergeObservation>;\n readSpecStatus(projectPath: string, issueId: string): Promise<string | undefined>;\n hasPlanningAgent(issueId: string): boolean;\n reviewGatesPassed(status: ReviewStatus): boolean;\n setReviewStatus: typeof setReviewStatusSync;\n enqueuePostMerge(issueId: string, projectPath: string, branch: string): Promise<string | null | undefined>;\n warn(message: string): void;\n}\n\nexport async function reconcileStuckMergingStatesWithDeps(deps: StuckMergingDeps): Promise<string[]> {\n const actions: string[] = [];\n const now = deps.now();\n\n for (const [issueId, status] of Object.entries(deps.loadStatuses())) {\n if (!isStuckMergingState(status, now)) continue;\n const project = deps.resolveProject(issueId);\n if (!project) continue;\n const specStatus = await deps.readSpecStatus(project.projectPath, issueId);\n if (specStatus === 'completed' || specStatus === 'cancelled') continue;\n\n const branch = `feature/${issueId.toLowerCase()}`;\n const mergeSet = deps.getMergeSet(issueId);\n const hasNonGitHubRepos = mergeSet?.repos.some((repo) => repo.forge !== 'github') === true;\n const githubIssue = deps.resolveGitHubIssue(issueId);\n let complete = false;\n let positiveMergedEvidence = false;\n let unverifiableReason: string | undefined;\n\n if (!githubIssue.isGitHub || hasNonGitHubRepos) {\n try {\n const observation = await deps.observeForge(issueId);\n complete = observation.complete;\n positiveMergedEvidence = observation.hasPositiveMergedEvidence;\n unverifiableReason = observation.repos.find((repo) => repo.state === 'unverifiable')?.reason;\n } catch (error) {\n unverifiableReason = error instanceof Error ? error.message : String(error);\n }\n } else {\n const observation = await deps.observeGitHub(issueId, branch, project.projectPath);\n complete = observation.merged;\n positiveMergedEvidence = observation.merged;\n unverifiableReason = observation.unverifiableReason;\n }\n\n if (unverifiableReason) {\n deps.warn(`[deacon] ${issueId}: stuck ${status.mergeStatus} state is unverifiable — ${unverifiableReason}`);\n continue;\n }\n\n if (complete && positiveMergedEvidence) {\n if (deps.hasPlanningAgent(issueId) || specStatus === 'draft' || specStatus === 'proposed') continue;\n deps.setReviewStatus(issueId, {\n mergeStatus: 'merged', mergeStep: 'post-merge-cleanup', readyForMerge: false,\n });\n actions.push(`Reconciled stuck ${status.mergeStatus} state for ${issueId} — forge confirms the merge`);\n const action = await deps.enqueuePostMerge(issueId, project.projectPath, branch);\n if (action) actions.push(action);\n continue;\n }\n\n const readyForMerge = deps.reviewGatesPassed({ ...status, mergeStatus: 'pending' });\n deps.setReviewStatus(issueId, {\n mergeStatus: 'pending',\n mergeNotes: `Reset stuck ${status.mergeStatus} state after 30 minutes because the forge has no completed merge evidence`,\n readyForMerge,\n });\n actions.push(`Reset stuck ${status.mergeStatus} state for ${issueId} to pending`);\n }\n\n return actions;\n}\n\nexport async function reconcileStuckMergingStates(): Promise<string[]> {\n return reconcileStuckMergingStatesWithDeps({\n now: () => Date.now(),\n loadStatuses: loadReviewStatuses,\n resolveProject: resolveProjectFromIssueSync,\n getMergeSet: getMergeSetSync,\n resolveGitHubIssue: resolveGitHubIssueSync,\n observeForge: observeForgeMergeState,\n observeGitHub: observeGitHubBranchMerge,\n readSpecStatus,\n hasPlanningAgent,\n reviewGatesPassed: reviewGatesPassedSync,\n setReviewStatus: setReviewStatusSync,\n enqueuePostMerge: async (issueId, projectPath, branch) => {\n const { enqueuePostMergeLifecycle } = await import('./post-merge-lifecycle-worker.js');\n return enqueuePostMergeLifecycle(issueId, projectPath, branch);\n },\n warn: (message) => console.warn(message),\n });\n}\n","import { getForgeAdapter, type ForgeType } from '../forge.js';\nimport { getMergeSetSync, type MergeSetRepoState } from '../merge-set.js';\nimport {\n cancelPending,\n listActiveAutoMerges,\n listProblemAutoMerges,\n markMerged,\n type PendingAutoMerge,\n} from '../overdeck/merge-sync.js';\nimport { readJournalStatusSync } from '../overdeck/review-status-record-sync.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\n\nconst AUTO_MERGE_RECONCILE_COOLDOWN_MS = 10 * 60 * 1000;\n// SQLite LIMIT -1 removes the limit. Patrol reconciliation must inspect the\n// whole queue or the oldest unresolved page can starve every newer row.\nconst UNBOUNDED_ROW_LIMIT = -1;\nexport const AUTO_MERGE_RECONCILE_FORGE_LIMIT = 4;\nconst autoMergeReconcileCooldowns = new Map<string, number>();\nconst autoMergeLastCheckedAt = new Map<string, number>();\n\nexport interface AutoMergeReconcileDeps {\n now(): number;\n listProblemAutoMerges: typeof listProblemAutoMerges;\n listActiveAutoMerges: typeof listActiveAutoMerges;\n cancelPending: typeof cancelPending;\n markMerged: typeof markMerged;\n readJournalStatus: typeof readJournalStatusSync;\n getMergeSet: typeof getMergeSetSync;\n resolveProject: typeof resolveProjectFromIssueSync;\n getForgeAdapter: typeof getForgeAdapter;\n log(message: string): void;\n warn(message: string): void;\n}\n\ninterface ForgeCandidate {\n issueId: string;\n problemRows: PendingAutoMerge[];\n representative: PendingAutoMerge;\n artifactRepo?: MergeSetRepoState;\n projectPath: string;\n}\n\nfunction groupByIssue(rows: PendingAutoMerge[]): Map<string, PendingAutoMerge[]> {\n const grouped = new Map<string, PendingAutoMerge[]>();\n for (const row of rows) {\n const issueRows = grouped.get(row.issueId) ?? [];\n issueRows.push(row);\n grouped.set(row.issueId, issueRows);\n }\n return grouped;\n}\n\nfunction normalizeArtifactUrl(url: string): string {\n return url.replace(/[?#].*$/, '').replace(/\\/+$/, '');\n}\n\nfunction repositoryFromArtifactUrl(url: string, forge: ForgeType): string | undefined {\n try {\n const parsed = new URL(url);\n if (forge === 'github' && parsed.hostname.toLowerCase() !== 'github.com') return undefined;\n const marker = forge === 'gitlab' ? '/-/merge_requests/' : '/pull/';\n const markerIndex = parsed.pathname.indexOf(marker);\n if (markerIndex <= 1) return undefined;\n return parsed.pathname.slice(1, markerIndex);\n } catch {\n return undefined;\n }\n}\n\nfunction pruneTracking(activeIssueIds: Set<string>): void {\n for (const issueId of autoMergeReconcileCooldowns.keys()) {\n if (!activeIssueIds.has(issueId)) autoMergeReconcileCooldowns.delete(issueId);\n }\n for (const issueId of autoMergeLastCheckedAt.keys()) {\n if (!activeIssueIds.has(issueId)) autoMergeLastCheckedAt.delete(issueId);\n }\n}\n\nasync function reconcileForgeCandidate(\n candidate: ForgeCandidate,\n deps: AutoMergeReconcileDeps,\n now: number,\n): Promise<string[]> {\n const { issueId, problemRows, representative, artifactRepo, projectPath } = candidate;\n autoMergeLastCheckedAt.set(issueId, now);\n\n try {\n const repository = repositoryFromArtifactUrl(representative.prUrl, representative.forge);\n const artifact = await deps.getForgeAdapter(representative.forge).findMergedArtifact({\n sourceBranch: artifactRepo?.sourceBranch ?? `feature/${issueId.toLowerCase()}`,\n ...(artifactRepo?.targetBranch ? { targetBranch: artifactRepo.targetBranch } : {}),\n artifactUrl: representative.prUrl,\n ...(artifactRepo?.artifactId ? { artifactId: artifactRepo.artifactId } : {}),\n ...(repository ? { repository } : {}),\n cwd: artifactRepo?.repoPath ?? projectPath,\n });\n\n if (!artifact) {\n autoMergeReconcileCooldowns.set(issueId, now + AUTO_MERGE_RECONCILE_COOLDOWN_MS);\n return [];\n }\n\n const actions: string[] = [];\n for (const row of problemRows) {\n if (!deps.markMerged(row.id)) continue;\n const action = `Reconciled auto-merge row ${row.id} for ${issueId} — forge confirms the merge`;\n actions.push(action);\n deps.log(`[deacon] ${action}`);\n }\n autoMergeReconcileCooldowns.delete(issueId);\n return actions;\n } catch (error) {\n autoMergeReconcileCooldowns.set(issueId, now + AUTO_MERGE_RECONCILE_COOLDOWN_MS);\n deps.warn(`[deacon] Auto-merge row reconciliation failed for ${issueId}: ${error instanceof Error ? error.message : String(error)}`);\n return [];\n }\n}\n\nexport async function reconcileAutoMergeRowsWithDeps(\n deps: AutoMergeReconcileDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n const now = deps.now();\n let rows: PendingAutoMerge[];\n\n try {\n rows = [\n ...deps.listProblemAutoMerges(UNBOUNDED_ROW_LIMIT),\n ...deps.listActiveAutoMerges(UNBOUNDED_ROW_LIMIT).filter((row) => row.status === 'pending'),\n ];\n } catch (error) {\n deps.warn(`[deacon] Auto-merge row reconciliation failed to list rows: ${error instanceof Error ? error.message : String(error)}`);\n return actions;\n }\n\n const grouped = groupByIssue(rows);\n pruneTracking(new Set(grouped.keys()));\n const forgeCandidates: ForgeCandidate[] = [];\n\n for (const [issueId, issueRows] of grouped) {\n const cooledUntil = autoMergeReconcileCooldowns.get(issueId);\n if (cooledUntil && now < cooledUntil) continue;\n\n try {\n const closedOut = deps.readJournalStatus(issueId)?.durable.closedOut === true;\n if (closedOut) {\n for (const row of issueRows) {\n if (!deps.cancelPending(row.id, 'auto-merge-reconciler')) continue;\n const action = `Cancelled closed-out auto-merge row ${row.id} for ${issueId}`;\n actions.push(action);\n deps.log(`[deacon] ${action}`);\n }\n autoMergeReconcileCooldowns.delete(issueId);\n autoMergeLastCheckedAt.delete(issueId);\n continue;\n }\n\n const problemRows = issueRows.filter(\n (row) => row.status === 'blocked' || row.status === 'failed',\n );\n if (problemRows.length === 0) continue;\n\n const representative = problemRows.reduce(\n (latest, row) => row.id > latest.id ? row : latest,\n );\n const project = deps.resolveProject(issueId);\n if (!project) throw new Error(`project resolution failed for ${issueId}`);\n const artifactUrl = normalizeArtifactUrl(representative.prUrl);\n const artifactRepo = deps.getMergeSet(issueId)?.repos.find(\n (repo) => repo.forge === representative.forge\n && repo.artifactUrl !== undefined\n && normalizeArtifactUrl(repo.artifactUrl) === artifactUrl,\n );\n forgeCandidates.push({\n issueId,\n problemRows,\n representative,\n artifactRepo,\n projectPath: project.projectPath,\n });\n } catch (error) {\n autoMergeReconcileCooldowns.set(issueId, now + AUTO_MERGE_RECONCILE_COOLDOWN_MS);\n deps.warn(`[deacon] Auto-merge row reconciliation failed for ${issueId}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n forgeCandidates.sort((left, right) => {\n const checkedDelta = (autoMergeLastCheckedAt.get(left.issueId) ?? 0)\n - (autoMergeLastCheckedAt.get(right.issueId) ?? 0);\n return checkedDelta || left.representative.id - right.representative.id;\n });\n const batch = forgeCandidates.slice(0, AUTO_MERGE_RECONCILE_FORGE_LIMIT);\n const reconciled = await Promise.all(\n batch.map((candidate) => reconcileForgeCandidate(candidate, deps, now)),\n );\n for (const candidateActions of reconciled) actions.push(...candidateActions);\n\n return actions;\n}\n\nexport async function reconcileAutoMergeRows(): Promise<string[]> {\n return reconcileAutoMergeRowsWithDeps({\n now: () => Date.now(),\n listProblemAutoMerges,\n listActiveAutoMerges,\n cancelPending,\n markMerged,\n readJournalStatus: readJournalStatusSync,\n getMergeSet: getMergeSetSync,\n resolveProject: resolveProjectFromIssueSync,\n getForgeAdapter,\n log: (message) => console.log(message),\n warn: (message) => console.warn(message),\n });\n}\n","import { existsSync, readdirSync } from 'fs';\nimport { readdir } from 'fs/promises';\nimport { join } from 'path';\nimport { exec, execFile } from 'node:child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from '../paths.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getAgentRuntimeStateSync, getAgentStateSync, listRunningAgentsSync, type AgentState } from '../agents.js';\nimport { withConcurrencyLimit } from '../concurrency.js';\nimport { loadReviewStatuses, setReviewStatusSync, reviewGatesPassedSync } from '../review-status.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\nimport { getMergeSetSync } from '../merge-set.js';\nimport { sessionExistsSync, sendKeys } from '../tmux.js';\nimport { isAgentIdleForNudge } from './agent-idle.js';\nimport { loadCloisterConfig } from './config.js';\nimport { getAutoCloseOutCanonicalState, sweepAutoCloseOutCache } from './deacon-canonical-state.js';\nimport { isStuckMergingState, observeGitHubBranchMerge } from './deacon-stuck-merging.js';\n\nexport { reconcileStuckMergingStates } from './deacon-stuck-merging.js';\nexport { reconcileAutoMergeRows } from './deacon-auto-merge-reconcile.js';\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n\ninterface MergeReminderState {\n mergeStuckAttempts?: Record<string, number>;\n}\n\nexport interface MergeReadyReminderDeps<State extends MergeReminderState = MergeReminderState> {\n loadState: () => State;\n saveState: (state: State) => void;\n hasMergeReadyNotifier: () => boolean;\n notifyMergeReady: (issueId: string) => void;\n}\n\n// ============================================================================\n// CI transient retry tracking (shared by checkPostReviewCommits + checkFailedMergeRetry)\n// ============================================================================\n\n// In-memory CI failure retry tracking — separate from mergeRetryCount because\n// CI failures are transient and should not permanently block merge attempts.\n// Declared here so checkPostReviewCommits can clear it when new commits arrive.\nexport const ciRetryMap = new Map<string, { count: number; lastAttempt: number }>();\n\n// ============================================================================\n// Ready-for-merge stuck detection (PAN-344)\n// ============================================================================\n\n// Minimum age (ms) of a readyForMerge status before deacon sends a merge-ready reminder.\n// This is NOT a stuck detection — it's a courtesy notification that a merge is waiting\n// for the human to click MERGE. One hour is reasonable; the human may be reviewing,\n// working on other things, or intentionally waiting.\nconst MERGE_READY_REMINDER_MS = 60 * 60 * 1000; // 1 hour\n// Minimum wait (ms) between successive merge-ready reminders for the same issue\nconst MERGE_READY_REMINDER_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour\n// Circuit breaker: stop reminding after this many times (per server lifetime)\nconst MERGE_READY_REMINDER_MAX = 3;\n\n// In-memory cooldowns for stuck-merge detection (reset on server restart is acceptable —\n// cooldowns are a performance optimisation, not critical state)\nconst mergeStuckCooldowns = new Map<string, number>();\n\n/**\n * Safety-net patrol: find issues that are readyForMerge but not yet merging/merged\n * and whose readyForMerge status is older than MERGE_STUCK_STALENESS_MS.\n *\n * Previously this auto-triggered the merge API. Now it is notify-only: it emits\n * a merge:ready Socket.io event so the dashboard can prompt the user to click\n * the MERGE button. The MERGE button is the sole merge trigger (PAN-354).\n *\n * Guards:\n * - Staleness: status must be at least 2 min old (avoids racing with primary trigger)\n * - Per-issue cooldown: 10 min between successive attempts\n * - Circuit breaker: max 3 attempts per issue per process lifetime\n */\nexport async function checkReadyForMergeStuck<State extends MergeReminderState>(deps: MergeReadyReminderDeps<State>): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const statuses = loadReviewStatuses();\n\n const now = Date.now();\n const state = deps.loadState();\n const attemptCounts = state.mergeStuckAttempts ?? {};\n let stateModified = false;\n\n for (const [key, status] of Object.entries(statuses)) {\n // Only act on issues that are ready but not yet merging/merged/failed\n if (!status.readyForMerge) continue;\n if (status.mergeStatus === 'merging' || status.mergeStatus === 'merged' || status.mergeStatus === 'failed') continue;\n\n // Wait at least 1 hour before sending a merge-ready reminder.\n // The human controls when to merge — this is just a courtesy notification.\n if (!status.updatedAt) continue;\n const statusAge = now - new Date(status.updatedAt).getTime();\n if (statusAge < MERGE_READY_REMINDER_MS) continue;\n\n // Per-issue cooldown (in-memory — reset on restart is acceptable for a rate-limiter)\n const lastAttempt = mergeStuckCooldowns.get(key);\n if (lastAttempt && (now - lastAttempt) < MERGE_READY_REMINDER_COOLDOWN_MS) continue;\n\n // Circuit breaker (persisted to deacon state so restart doesn't reset the count)\n const attempts = attemptCounts[key] ?? 0;\n if (attempts >= MERGE_READY_REMINDER_MAX) continue;\n\n const ageHours = Math.round((now - new Date(status.updatedAt).getTime()) / 3600000 * 10) / 10;\n console.log(`[deacon] Merge-ready reminder for ${key} (ready for ${ageHours}h, reminder ${attempts + 1}/${MERGE_READY_REMINDER_MAX})`);\n\n // Record attempt before notifying so a crash doesn't leave us in a retry loop\n mergeStuckCooldowns.set(key, now);\n attemptCounts[key] = attempts + 1;\n stateModified = true;\n\n // Notify the dashboard via Socket.io so the user knows to click MERGE.\n // Auto-triggering merge was removed in PAN-354; the MERGE button is the sole trigger.\n const msg = `Merge ready: ${key} has been waiting for merge for ${ageHours}h — click MERGE when ready`;\n if (deps.hasMergeReadyNotifier()) {\n deps.notifyMergeReady(status.issueId ?? key);\n actions.push(msg);\n console.log(`[deacon] merge:ready notification sent for ${key}`);\n } else {\n actions.push(msg);\n console.warn(`[deacon] No mergeReadyNotifier registered — dashboard will not be notified for ${key}`);\n }\n }\n\n // Persist updated attempt counts so circuit breaker survives server restarts\n if (stateModified) {\n state.mergeStuckAttempts = attemptCounts;\n deps.saveState(state);\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error in checkReadyForMergeStuck:', msg);\n }\n\n return actions;\n}\n\n/**\n * Detect issues whose feature branch is merged to main but mergeStatus is stale.\n * Happens when a merge bypasses the dashboard (manual git merge, direct push, or\n * deploy script crash). Sets mergeStatus='merged' so the dashboard shows the\n * correct state and close-out can proceed.\n */\nconst staleMergeReconciled = new Set<string>();\n\nexport async function reconcileStaleMergeStatus(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const statuses = loadReviewStatuses();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n const postMergeIncomplete = status.mergeStatus === 'merged' && status.mergeStep === 'post-merge-cleanup';\n if (status.mergeStatus === 'merged' && !postMergeIncomplete) continue;\n if (isStuckMergingState(status, Date.now())) continue;\n if (!postMergeIncomplete && staleMergeReconciled.has(issueId)) continue;\n\n const project = resolveProjectFromIssueSync(issueId);\n if (!project) continue;\n\n const mergeSet = getMergeSetSync(issueId);\n const shouldObserveForge = !resolveGitHubIssueSync(issueId).isGitHub\n || mergeSet?.repos.some((repo) => repo.forge !== 'github') === true;\n const mergeRelevant = status.readyForMerge === true\n || ['merging', 'verifying', 'queued', 'failed'].includes(status.mergeStatus ?? '')\n || ['merging', 'failed'].includes(mergeSet?.status ?? '');\n // Closed-out issues are TERMINAL: close-out flips the spec to\n // completed/cancelled and clears review status. Treating the cleared/\n // resurrected row as \"stale\" here re-fires the post-merge handoff,\n // which REOPENS the closed tracker issue (PAN-1190, 2026-06-11).\n let specStatus: string | undefined;\n try {\n const { findSpecByIssue } = await import('../pan-dir/specs.js');\n const spec = await Effect.runPromise(findSpecByIssue(project.projectPath, issueId));\n specStatus = spec?.status;\n if (spec && (spec.status === 'completed' || spec.status === 'cancelled')) {\n staleMergeReconciled.add(issueId);\n continue;\n }\n } catch {\n // Spec unreadable — fall through to the normal checks.\n }\n\n const branch = `feature/${issueId.toLowerCase()}`;\n if (postMergeIncomplete) {\n const { enqueuePostMergeLifecycle } = await import('./post-merge-lifecycle-worker.js');\n const action = enqueuePostMergeLifecycle(issueId, project.projectPath, branch);\n if (action) actions.push(action);\n continue;\n }\n\n let isMerged = false;\n\n // Check 1: diagnostic only. Branch topology alone is not proof of a\n // completed pipeline merge: a branch created at main with no\n // implementation commits also satisfies merge-base --is-ancestor. Keep\n // the stale-merge reconciler PR-backed so re-planning cannot\n // phantom-merge an already planned issue.\n try {\n const [branchTip, mainTip] = await Promise.all([\n execFileAsync('git', ['rev-parse', branch], { cwd: project.projectPath }),\n execFileAsync('git', ['rev-parse', 'main'], { cwd: project.projectPath }),\n ]);\n if (branchTip.stdout.trim() === mainTip.stdout.trim()) {\n console.log(`[deacon] ${issueId}: branch ${branch} points at main; not treating zero-commit branch as merged`);\n }\n } catch {\n // Branch is absent/unreadable — leave merge detection to the PR API.\n }\n\n // Check 2: query GitHub for PR mergedAt/mergeCommit. The\n // old regex-based detection (`\\(PAN-XXXX[ )]` against `git log --pretty=%s`)\n // matched ANY commit that mentioned the issue in a trailer, not just\n // genuine squash merges. That's how PAN-977/945/913/544/457 got\n // mergeStatus=merged and rolled into close-out without an actual merge:\n // unrelated commits landed on main with `(PAN-977)` references and the\n // deacon trusted them. GitHub's API is the only authoritative source.\n if (!isMerged) {\n const githubObservation = await observeGitHubBranchMerge(issueId, branch, project.projectPath);\n isMerged = githubObservation.merged;\n }\n if (shouldObserveForge && mergeRelevant) {\n try {\n const observation = await (await import('./merge-completeness.js')).observeForgeMergeState(issueId);\n const blocker = observation.repos.find((repo) => repo.state === 'unverifiable');\n if (blocker) { console.warn(`[deacon] ${issueId}: forge merge state is unverifiable — ${blocker.reason}`); continue; }\n isMerged = observation.complete && observation.hasPositiveMergedEvidence;\n } catch (error) {\n console.warn(`[deacon] ${issueId}: forge merge observation failed — ${error instanceof Error ? error.message : String(error)}`); continue;\n }\n }\n if (isMerged) {\n // PAN-1994: Defer terminalization only when a genuine re-plan is in\n // progress. An active planning-<issue> agent or a spec back at\n // draft/proposed means a fresh planning cycle is running; setting\n // mergeStatus='merged' would contaminate it. A lone zombie work agent\n // is NOT a reason to defer — the branch is already merged on GitHub,\n // so we terminalize and let postMergeLifecycle kill the stale session.\n const issueLower = issueId.toLowerCase();\n const planState = getAgentStateSync(`planning-${issueLower}`);\n const hasPlanningAgent = planState?.status === 'running' || planState?.status === 'starting';\n const isReplanning = specStatus === 'draft' || specStatus === 'proposed';\n\n if (hasPlanningAgent || isReplanning) {\n const reason = hasPlanningAgent\n ? 'active planning agent in progress'\n : `spec status is '${specStatus}' (re-planning in progress)`;\n console.log(`[deacon] ${issueId}: ${reason} — deferring stale-merge reconcile (PAN-1994)`);\n continue;\n }\n\n setReviewStatusSync(issueId, { mergeStatus: 'merged', mergeStep: 'post-merge-cleanup', readyForMerge: false });\n const msg = `Reconciled stale mergeStatus for ${issueId} — branch ${branch} is merged to main`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n\n // PAN-1027: queue the post-merge handoff without blocking unrelated patrol work.\n const { enqueuePostMergeLifecycle } = await import('./post-merge-lifecycle-worker.js');\n const action = enqueuePostMergeLifecycle(issueId, project.projectPath, branch);\n if (action) actions.push(action);\n }\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileStaleMergeStatus: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n/**\n * PAN-1027 reverse direction: detect issues whose internal mergeStatus='merged' but\n * whose GitHub PR is NOT merged (open, closed-without-merge, or reverted). When the\n * dashboard previously detected a merge that later got reverted (or the deacon's\n * forward-direction reconciler matched a squash-commit grep that wasn't actually a\n * merge), the issue gets stuck because every gate that checks `mergeStatus !== 'merged'`\n * skips it. This sweep resets the stale merged status so the issue can flow through\n * the pipeline again.\n */\nconst falseMergedReset = new Set<string>();\n\nexport async function reconcileFalseMerged(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../github-app.js');\n if (!isGitHubAppConfigured()) return actions;\n\n const statuses = loadReviewStatuses();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.mergeStatus !== 'merged') continue;\n if (!status.prUrl) continue;\n if (falseMergedReset.has(issueId)) continue;\n\n const prRef = status.prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!prRef) continue;\n\n try {\n const prState = await Effect.runPromise(getPullRequestState(prRef[1], prRef[2], Number.parseInt(prRef[3], 10)));\n if (!prState.merged) {\n // GitHub says not merged but our DB says merged — reset internal state.\n // Leave reviewStatus alone (it may legitimately be passed/failed/blocked from\n // the prior cycle); the issue can proceed through the pipeline once mergeStatus\n // is no longer blocking.\n setReviewStatusSync(issueId, { mergeStatus: 'pending' });\n falseMergedReset.add(issueId);\n const msg = `Reset stale mergeStatus=merged for ${issueId} — PR ${status.prUrl} is not merged on GitHub`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (err: unknown) {\n // Non-fatal: GitHub API hiccup. Try again next patrol.\n console.warn(`[deacon] Failed false-merged check for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileFalseMerged: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n/**\n * Detect issues whose merge_status='merged' but review_status is still in a\n * non-terminal state (reviewing, pending, etc.). If the merge actually\n * happened, the review must have passed at some point even if the dashboard\n * missed the transition (e.g. coordinator crashed mid-run, dashboard restart\n * dropped an in-flight update). Reconciling to review_status='passed' clears\n * the \"running reviewers with no data\" UI state PAN-1028 reproduced.\n */\nconst mergedReviewingReconciled = new Set<string>();\n/**\n * Per-issue throttle for the closed-PR readyForMerge reconciler so a transient\n * GitHub API failure doesn't burn the rate budget on the same issues every\n * patrol. Cleared when the issue's state changes.\n */\nconst closedPrReadyReconcileCooldowns = new Map<string, number>();\nconst CLOSED_PR_RECONCILE_COOLDOWN_MS = 10 * 60 * 1000;/**\n * Reconciler: issues with readyForMerge=true whose PR is no longer OPEN.\n *\n * The \"Awaiting Merge\" view filters on readyForMerge=true, so an issue whose\n * PR was closed without merging (cancel-flow, manual `gh pr close`, branch\n * deleted) stays in that list forever — the merge button points at a dead PR\n * and would only get cleared the next time the user clicks it (PAN-509-style\n * defensive check in `/api/issues/:id/merge`). That UX is bad: the page\n * actively lies to the human about what's ready to ship.\n *\n * This patrol catches it proactively: for every readyForMerge=true issue with\n * a GitHub PR URL, ask the forge for the current PR state. If MERGED, flip\n * mergeStatus to 'merged' (post-merge lifecycle catches up elsewhere). If\n * CLOSED-without-merge, reset readyForMerge=false and surface why on\n * mergeNotes so the human sees what happened instead of a missing button.\n */\nexport async function reconcileClosedPrReadyForMerge(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../github-app.js');\n if (!isGitHubAppConfigured()) return actions;\n\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (!status.readyForMerge) continue;\n if (!status.prUrl) continue;\n\n const cooledUntil = closedPrReadyReconcileCooldowns.get(issueId);\n if (cooledUntil && now < cooledUntil) continue;\n\n const match = status.prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!match) continue;\n const [, owner, repo, numberStr] = match;\n const prNumber = parseInt(numberStr, 10);\n if (!Number.isFinite(prNumber)) continue;\n\n try {\n const prState = await Effect.runPromise(getPullRequestState(owner, repo, prNumber));\n if (prState.state === 'OPEN' && !prState.merged) continue;\n\n if (prState.merged) {\n setReviewStatusSync(issueId, {\n readyForMerge: false,\n mergeStatus: 'merged',\n mergeNotes: undefined,\n });\n const msg = `Reset readyForMerge for ${issueId} — PR #${prNumber} is already merged`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n } else {\n setReviewStatusSync(issueId, {\n readyForMerge: false,\n mergeStatus: 'failed',\n mergeNotes: `PR #${prNumber} was closed without merging — reopen the PR or reset review state to re-queue this issue`,\n });\n const msg = `Reset readyForMerge for ${issueId} — PR #${prNumber} is ${prState.state} (not OPEN)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n // Don't re-check for 10 min even if state somehow gets re-set.\n closedPrReadyReconcileCooldowns.set(issueId, now + CLOSED_PR_RECONCILE_COOLDOWN_MS);\n } catch (prErr: unknown) {\n // Throttle on API failure so we don't hammer GitHub.\n closedPrReadyReconcileCooldowns.set(issueId, now + CLOSED_PR_RECONCILE_COOLDOWN_MS);\n console.warn(`[deacon] reconcileClosedPrReadyForMerge: ${issueId} PR state lookup failed: ${prErr instanceof Error ? prErr.message : String(prErr)}`);\n }\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileClosedPrReadyForMerge: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n\nconst staleMergeBlockerCooldowns = new Map<string, number>();\nconst STALE_MERGE_BLOCKER_COOLDOWN_MS = 2 * 60 * 1000; // 2 minutes\n\n/**\n * Reconciler: re-evaluate stale merge-blockers so resolved conflicts unstick.\n *\n * resolveConflictGate() already clears a stale merge_conflict/not_mergeable\n * blocker once the branch is mergeable against main again — but it only runs\n * on-demand from the review-dispatch routes. An issue that picks up a\n * merge-blocker (so readyForMerge stays false) and then falls out of the active\n * review flow is therefore never re-evaluated: the blocker persists forever and\n * the merge train never picks it up. That is the \"stuck after review\" failure\n * mode (PAN-2143) — a PR that was conflicting while main moved stays blocked\n * even after the conflict is resolved.\n *\n * This patrol runs resolveConflictGate for every in-pipeline issue still\n * carrying a merge-blocker. When the branch became mergeable again it clears the\n * stale blocker (which lets setReviewStatus recompute readyForMerge so the merge\n * train resumes); when the branch is genuinely conflicting, resolveConflictGate\n * dispatches the conflict resolver on its existing throttle. A per-issue 2-min\n * cooldown bounds the git-probe cost; resolveConflictGate also caches its\n * mergeability probe.\n */\nexport async function reconcileStaleMergeBlockers(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { resolveConflictGate, buildRealConflictGateDeps } = await import('./conflict-gate.js');\n const statuses = loadReviewStatuses();\n const now = Date.now();\n const deps = buildRealConflictGateDeps();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.mergeStatus === 'merged') continue;\n const hasMergeBlocker = (status.blockerReasons ?? []).some(\n (b) => b.type === 'merge_conflict' || b.type === 'not_mergeable',\n );\n if (!hasMergeBlocker) continue;\n\n const cooledUntil = staleMergeBlockerCooldowns.get(issueId);\n if (cooledUntil && now < cooledUntil) continue;\n staleMergeBlockerCooldowns.set(issueId, now + STALE_MERGE_BLOCKER_COOLDOWN_MS);\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const workspacePath = join(resolved.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspacePath)) continue;\n\n try {\n const result = await resolveConflictGate(issueId, workspacePath, 'main', deps);\n if (result.clearedStaleBlocker) {\n const msg = `Cleared stale merge blocker for ${issueId} — branch is mergeable again; readyForMerge will recompute`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (gateErr: unknown) {\n console.warn(`[deacon] reconcileStaleMergeBlockers: ${issueId} conflict-gate failed: ${gateErr instanceof Error ? gateErr.message : String(gateErr)}`);\n }\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileStaleMergeBlockers: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n\n/**\n * Reconciler (PAN-2198): periodic twin of the boot-only fixStuckReadyForMerge,\n * for the NO-BLOCKER strand of \"stuck after review\".\n *\n * readyForMerge is re-derived on every setReviewStatusSync write. When the last\n * write left it false and there is NO merge-blocker, nothing re-derives it until\n * the next write — so a PR whose review+test+verify all passed but whose\n * readyForMerge was left false (e.g. a verdict that landed via a write path that\n * didn't recompute, or a gate that was transiently non-final) converges ONLY on\n * server restart (PAN-1758: \"readyForMerge only flips via the startup repair\n * sweep\"). This patrol re-derives it on the 60s deacon tick instead.\n *\n * Blocker strands are deliberately excluded — those are owned by\n * reconcileStaleMergeBlockers, and excluding them also avoids fighting the\n * setReviewStatus deriver's hasBlockers override (which would flip readyForMerge\n * straight back to false). Loop-safe: it writes ONLY readyForMerge (no\n * review/test status transition, so no review/test re-dispatch events fire), and\n * is idempotent — once flipped true, the readyForMerge!==false guard excludes the\n * issue, so steady state is zero writes. Flipping readyForMerge=true only makes\n * the issue merge-ELIGIBLE; the merge train / MERGE button remains the trigger.\n */\nexport function reconcileStuckReadyForMerge(): string[] {\n const actions: string[] = [];\n try {\n for (const [issueId, status] of Object.entries(loadReviewStatuses())) {\n if (status.readyForMerge !== false) continue;\n if ((status.blockerReasons?.length ?? 0) > 0) continue; // blocker strand → reconcileStaleMergeBlockers\n if (!reviewGatesPassedSync(status)) continue;\n setReviewStatusSync(issueId, { readyForMerge: true });\n const msg = `Restored readyForMerge for ${issueId} — review+test+verify passed, no blocker (was stuck false)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileStuckReadyForMerge: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n\nexport async function reconcileMergedButReviewing(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const statuses = loadReviewStatuses();\n const nonTerminal = new Set(['reviewing', 'pending', undefined, null]);\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.mergeStatus !== 'merged') continue;\n const reviewNonTerminal = nonTerminal.has(status.reviewStatus as string | undefined);\n const testNonTerminal = nonTerminal.has(status.testStatus as string | undefined);\n if (!reviewNonTerminal && !testNonTerminal) continue;\n if (mergedReviewingReconciled.has(issueId)) continue;\n\n // Set BOTH review and test to 'passed' atomically. Setting only review='passed'\n // trips the canSkipTests dispatch path in setReviewStatus and spawns a test-agent\n // for an already-merged issue — pure waste. The merge is terminal, no test needed.\n setReviewStatusSync(issueId, {\n reviewStatus: 'passed',\n testStatus: 'passed',\n testNotes: status.testNotes ?? 'Skipped: issue is already merged',\n });\n mergedReviewingReconciled.add(issueId);\n const msg = `Reconciled review_status=${status.reviewStatus ?? 'null'}, test_status=${status.testStatus ?? 'null'} → passed for ${issueId} (merge_status=merged is terminal)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileMergedButReviewing: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n\n// Track per-issue cooldowns for failed-merge retry to avoid rapid re-queuing\nconst failedMergeRetryCooldowns = new Map<string, number>();\n// Track per-issue cooldowns for timeout nudges to avoid spamming the work agent\nconst timeoutNudgeCooldowns = new Map<string, number>();\n\n// Minimum time (ms) after merge failure before attempting a retry\nconst FAILED_MERGE_RETRY_COOLDOWN_MS = 30 * 60 * 1000; // 30 minutes\n// Minimum time (ms) between timeout nudges to the same work agent\nconst TIMEOUT_NUDGE_COOLDOWN_MS = 30 * 60 * 1000; // 30 minutes\n// Shorter cooldown for CI-transient failures (pending checks that resolve quickly)\nconst CI_TRANSIENT_RETRY_COOLDOWN_MS = 2 * 60 * 1000; // 2 minutes\n// Max number of automatic retries before requiring manual intervention\nexport const FAILED_MERGE_MAX_RETRIES = 3;\n\n/**\n * Auto-retry issues whose mergeStatus='failed' due to transient post-rebase\n * verification failures (e.g. flaky tests or tests fixed on main after failure).\n *\n * CI check failures (pending/failing) are handled differently from real merge\n * failures: they may resolve without any code change (e.g. CI queue clears,\n * GitHub status updates). These get a separate retry mechanism with a shorter\n * cooldown (2 min) and their own counter — they do NOT saturate mergeRetryCount.\n *\n * When review+test both passed but the post-rebase gate failed, the issue is\n * stuck: the deacon's merge-ready loop skips mergeStatus='failed' entries and\n * there is no other retry mechanism. After a 30-min cooldown, this patrol resets\n * the issue to readyForMerge=true so it reappears on the Awaiting Merge page.\n *\n * Guards:\n * - Review + test must both be 'passed' (don't retry if code quality failed)\n * - 30-min per-issue cooldown for non-CI failures, 2-min for CI transient\n * - Circuit breaker: max 3 retries (mergeRetryCount) for non-CI\n * - CI transient failures: max 5 retries with flat 2-minute cooldown\n */\nexport async function checkFailedMergeRetry(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const statuses = loadReviewStatuses();\n\n const now = Date.now();\n\n for (const [key, status] of Object.entries(statuses)) {\n // Only act on issues where merge failed but review+test both passed\n if (status.mergeStatus !== 'failed') continue;\n if (status.reviewStatus !== 'passed' || status.testStatus !== 'passed') continue;\n\n const isCiCheckFailure = typeof status.mergeNotes === 'string' &&\n status.mergeNotes.includes('failing required checks');\n const issueId = status.issueId || key;\n\n if (isCiCheckFailure) {\n // CI failures may be transient (pending checks, GitHub status lag).\n // Use a separate retry counter that does NOT saturate mergeRetryCount.\n const ciEntry = ciRetryMap.get(issueId) ?? { count: 0, lastAttempt: 0 };\n const timeSinceLastCi = now - ciEntry.lastAttempt;\n\n if (ciEntry.count >= 5) {\n // After 5 CI retries, back off to avoid hammering GitHub API.\n // Notify the work agent exactly once (when count first reaches 5) so it\n // can investigate rather than silently dead-ending the issue.\n if (ciEntry.count === 5) {\n console.log(`[deacon] CI check failure for ${issueId} — retries exhausted, notifying work agent`);\n const ciNotes = status.mergeNotes || 'CI checks are failing on the PR';\n const { writeFeedbackFile } = await import('./feedback-writer.js');\n const ciFileResult = await Effect.runPromise(writeFeedbackFile({\n issueId,\n specialist: 'merge-agent',\n outcome: 'ci-failure',\n summary: 'CI checks still failing after 5 transient retries — merge blocked',\n markdownBody: `## CI Check Failure — Merge Blocked\\n\\n${ciNotes}\\n\\n### Action Required\\n\\nFix the failing CI checks, commit, and push. Overdeck will detect the new commits and re-run the review pipeline automatically.\\n\\nAlternatively:\\n\\n\\`\\`\\`\\npan done ${issueId}\\n\\`\\`\\``,\n }).pipe(Effect.catch((err) => {\n console.error(`[deacon] Failed to write CI failure feedback for ${issueId}:`, err instanceof Error ? err.message : String(err));\n return Effect.succeed({ success: false, error: err instanceof Error ? err.message : String(err) });\n })));\n const agentSession = `agent-${issueId.toLowerCase()}`;\n if (sessionExistsSync(agentSession)) {\n const ciPath = (ciFileResult as { filePath?: string } | undefined)?.filePath;\n const ciMsg = ciPath\n ? `CI checks are failing on the PR after 5 retries.\\n\\nMUST READ: ${ciPath}\\n\\nFix the failures, commit, then run: pan done ${issueId}`\n : `CI checks are failing on the PR after 5 retries. Fix the failures, commit, then run: pan done ${issueId}`;\n await Effect.runPromise(sendKeys(agentSession, ciMsg));\n }\n ciEntry.count++; // increment past 5 so this block only fires once\n ciRetryMap.set(issueId, ciEntry);\n actions.push(`CI retry exhausted for ${issueId} — wrote feedback, notified agent`);\n } else {\n console.log(`[deacon] CI check failure for ${issueId} — max retries (5) exhausted, awaiting agent fix`);\n }\n continue;\n }\n if (timeSinceLastCi < CI_TRANSIENT_RETRY_COOLDOWN_MS) {\n continue; // still in cooldown\n }\n\n ciEntry.count++;\n ciEntry.lastAttempt = now;\n ciRetryMap.set(issueId, ciEntry);\n\n // Notify the work agent to re-submit via pan done, which re-enters the merge\n // queue from scratch. Merge is user-triggered (PAN-354) — deacon cannot\n // auto-retry; the agent must run pan done to create a fresh merge attempt.\n console.log(`[deacon] CI check failure for ${issueId} — notifying agent to re-submit (attempt ${ciEntry.count}/5)`);\n const ciNotes = status.mergeNotes || 'CI checks are failing on the PR';\n const { writeFeedbackFile } = await import('./feedback-writer.js');\n const ciFileResult2 = await Effect.runPromise(writeFeedbackFile({\n issueId,\n specialist: 'merge-agent',\n outcome: 'ci-failure',\n summary: 'CI checks failed at merge — re-submit to re-enter merge queue',\n markdownBody: `## CI Check Failure\\n\\n${ciNotes}\\n\\nCI checks failed at merge time. This may be transient (pending checks, GitHub status lag). Re-submit to re-enter the merge queue:\\n\\n\\`\\`\\`\\npan done ${issueId}\\n\\`\\`\\``,\n }).pipe(Effect.catch((err) => {\n console.error(`[deacon] Failed to write CI failure feedback for ${issueId}:`, err instanceof Error ? err.message : String(err));\n return Effect.succeed({ success: false, error: err instanceof Error ? err.message : String(err) });\n })));\n const agentSessionCi = `agent-${issueId.toLowerCase()}`;\n if (sessionExistsSync(agentSessionCi)) {\n const ciPath2 = (ciFileResult2 as { filePath?: string } | undefined)?.filePath;\n const ciMsg2 = ciPath2\n ? `CI checks failed on the PR for ${issueId}. This may be transient.\\n\\nMUST READ: ${ciPath2}\\n\\nFix any failures, commit, then run: pan done ${issueId}`\n : `CI checks failed on the PR for ${issueId}. This may be transient. Fix any failures, commit, then run: pan done ${issueId}`;\n await Effect.runPromise(sendKeys(agentSessionCi, ciMsg2));\n }\n actions.push(`CI failure notification for ${issueId} (attempt ${ciEntry.count}/5)`);\n continue;\n }\n\n // Timeout failures: the work agent didn't finish the rebase in time.\n // Write feedback and nudge the agent so it knows to continue/finish the rebase.\n // Then retry so the merge can proceed once the agent pushes.\n const isTimeoutFailure = typeof status.mergeNotes === 'string' &&\n (status.mergeNotes.includes('did not push') || status.mergeNotes.includes('stopped before completing'));\n if (isTimeoutFailure) {\n const issueIdForFb = status.issueId || key;\n const lastNudge = timeoutNudgeCooldowns.get(issueIdForFb);\n if (!lastNudge || (now - lastNudge) >= TIMEOUT_NUDGE_COOLDOWN_MS) {\n const timeoutNotes = status.mergeNotes!;\n const { writeFeedbackFile } = await import('./feedback-writer.js');\n await Effect.runPromise(writeFeedbackFile({\n issueId: issueIdForFb,\n specialist: 'merge-agent',\n outcome: 'timeout',\n summary: 'Merge timed out waiting for rebase — please rebase and push',\n markdownBody: `## Merge Timed Out — Rebase Required\\n\\n${timeoutNotes}\\n\\n### Action Required\\n\\nThe merge was requested but the rebased branch was not pushed in time. Please:\\n\\n1. Run \\`git fetch origin\\` and \\`git rebase origin/main\\` (or the target branch)\\n2. Resolve any conflicts\\n3. Run \\`git push --force-with-lease\\`\\n4. Run \\`pan done ${issueIdForFb}\\`\\n\\nAfter pushing, the merge will be retried automatically.`,\n }).pipe(Effect.catch((err) => {\n console.error(`[deacon] Failed to write timeout feedback for ${issueIdForFb}:`, err instanceof Error ? err.message : String(err));\n return Effect.void;\n })));\n const agentSession = `agent-${issueIdForFb.toLowerCase()}`;\n if (sessionExistsSync(agentSession)) {\n await Effect.runPromise(sendKeys(agentSession,\n `Merge timed out — the rebased branch was not pushed in time. Please rebase onto the target branch, resolve any conflicts, push with --force-with-lease, then run \"pan done ${issueIdForFb}\". After pushing, the merge will proceed automatically.`\n ));\n }\n timeoutNudgeCooldowns.set(issueIdForFb, now);\n actions.push(`Timeout failure for ${issueIdForFb} — wrote feedback, nudged work agent`);\n } else {\n actions.push(`Timeout failure for ${issueIdForFb} — nudge on cooldown (${Math.round((now - lastNudge) / 60000)}m ago)`);\n }\n }\n\n // Circuit breaker: max retries to avoid infinite loop on permanent failures\n const retryCount = status.mergeRetryCount || 0;\n if (retryCount >= FAILED_MERGE_MAX_RETRIES) {\n console.log(`[deacon] Failed-merge circuit breaker for ${key} (${retryCount}/${FAILED_MERGE_MAX_RETRIES} retries used)`);\n continue;\n }\n\n // Cooldown: wait at least 30 min after the merge failure before retrying\n if (status.updatedAt) {\n const statusAge = now - new Date(status.updatedAt).getTime();\n if (statusAge < FAILED_MERGE_RETRY_COOLDOWN_MS) continue;\n }\n\n // Per-issue in-memory cooldown to avoid re-triggering on the same patrol cycle\n const lastRetry = failedMergeRetryCooldowns.get(key);\n if (lastRetry && (now - lastRetry) < FAILED_MERGE_RETRY_COOLDOWN_MS) continue;\n\n failedMergeRetryCooldowns.set(key, now);\n\n const nextRetry = retryCount + 1;\n console.log(`[deacon] Auto-retrying failed merge for ${issueId} (attempt ${nextRetry}/${FAILED_MERGE_MAX_RETRIES})`);\n\n setReviewStatusSync(issueId, {\n mergeStatus: 'pending',\n readyForMerge: true,\n mergeRetryCount: nextRetry,\n });\n\n actions.push(`Reset failed merge for ${issueId} — retry ${nextRetry}/${FAILED_MERGE_MAX_RETRIES} (readyForMerge restored)`);\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error in checkFailedMergeRetry:', msg);\n }\n\n return actions;\n}\n\n\n// Track per-agent cooldowns for first-completion nudges\nconst firstCompletionCooldowns = new Map<string, number>();\nconst FIRST_COMPLETION_IDLE_MS = 10 * 60 * 1000; // 10 minutes idle before nudging\nconst FIRST_COMPLETION_COOLDOWN_MS = 15 * 60 * 1000; // 15 minutes between nudges\nfunction recordAutoCloseOutFailure(issueId: string, message: string): void {\n console.warn(`[deacon] Auto close-out failed for ${issueId}: ${message}`);\n setReviewStatusSync(issueId, {\n mergeNotes: `Auto close-out failed: ${message}`,\n updatedAt: new Date().toISOString(),\n });\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n issueId,\n message: `Auto close-out failed for ${issueId}: ${message}`,\n });\n}\n\nexport async function autoCloseOut(now = new Date()): Promise<string[]> {\n const closeOutConfig = (await Effect.runPromise(loadCloisterConfig())).close_out;\n if (closeOutConfig?.auto !== true) return [];\n\n // Evict stale cache entries before each patrol cycle\n sweepAutoCloseOutCache();\n\n const delayMinutes = Math.max(0, closeOutConfig.auto_delay_minutes ?? 60);\n const cutoff = now.getTime() - delayMinutes * 60 * 1000;\n const actions: string[] = [];\n const statuses = loadReviewStatuses();\n\n const candidates: Array<{ issueId: string }> = [];\n for (const [key, status] of Object.entries(statuses)) {\n const issueId = (status.issueId || key).toUpperCase();\n if (status.mergeStatus !== 'merged') continue;\n if (status.stuck || status.deaconIgnored) continue;\n\n const updatedAt = Date.parse(status.updatedAt || '');\n if (!Number.isFinite(updatedAt) || updatedAt > cutoff) continue;\n\n candidates.push({ issueId });\n }\n\n const tasks = candidates.map(({ issueId }) => Effect.tryPromise({\n try: async () => {\n let canonicalState: string | null;\n try {\n canonicalState = await getAutoCloseOutCanonicalState(issueId);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n recordAutoCloseOutFailure(issueId, message);\n return `Auto close-out failed for ${issueId}: ${message}`;\n }\n if (canonicalState !== 'verifying_on_main') return null;\n\n const resolvedProject = resolveProjectFromIssueSync(issueId);\n if (!resolvedProject) {\n const message = 'no project configured';\n recordAutoCloseOutFailure(issueId, message);\n return `Auto close-out failed for ${issueId}: ${message}`;\n }\n\n const ghResolved = resolveGitHubIssueSync(issueId);\n const ctx = {\n issueId,\n projectPath: resolvedProject.projectPath,\n auto: true,\n ...(ghResolved.isGitHub\n ? { github: { owner: ghResolved.owner, repo: ghResolved.repo, number: ghResolved.number } }\n : {}),\n };\n\n try {\n const { closeOut } = await import('../lifecycle/workflows.js');\n // PAN-1249: closeOut returns Effect<WorkflowResult>; bridge to Promise.\n const result = await Effect.runPromise(closeOut(ctx));\n if (!result.success) {\n const failed = result.steps.find(step => !step.success && !step.skipped);\n throw new Error(failed?.error ?? 'closeOut workflow failed');\n }\n const message = `Auto close-out completed for ${issueId}`;\n console.log(`[deacon] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'info', issueId, message });\n return message;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n recordAutoCloseOutFailure(issueId, message);\n return `Auto close-out failed for ${issueId}: ${message}`;\n }\n },\n catch: (cause) => cause,\n }));\n\n const results = await Effect.runPromise(withConcurrencyLimit(tasks, 5));\n for (const result of results) {\n if (result !== null) actions.push(result);\n }\n\n return actions;\n}\n\n/**\n * Detect work agents that finished implementation but never called `pan done`.\n *\n * This is the Layer 3 safety net. Layer 2 (work-agent-stop-hook) should catch most\n * cases within seconds of the agent going idle. This catches agents where the stop-hook\n * failed, was skipped, or where the AI analysis was inconclusive.\n *\n * Heuristics: agent is idle for >10 minutes, no completion marker exists, no review\n * status exists (meaning it never entered the specialist pipeline), and the agent\n * has committed code (git log shows commits on the feature branch).\n */\nexport async function checkFirstCompletionAgents(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const agents = listRunningAgentsSync();\n const now = Date.now();\n\n for (const agent of agents) {\n // Only check work agents (agent-min-XXX, agent-pan-XXX)\n // Guard against agents with undefined id (planning agents, test artifacts, etc.)\n const agentId = agent.id;\n if (!agentId || !agentId.startsWith('agent-') || !agent.tmuxActive) continue;\n if (agentId.startsWith('specialist-')) continue;\n\n // Skip if completion marker already exists (or was already processed by cloister)\n const completedFile = join(AGENTS_DIR, agent.id, 'completed');\n const processedMarker = join(AGENTS_DIR, agent.id, 'completed.processed');\n if (existsSync(completedFile) || existsSync(processedMarker)) continue;\n\n // Check idle duration and idle state via Stop hook\n // isAgentIdleForNudge uses FIRST_COMPLETION_IDLE_MS as the stale-active threshold:\n // if the agent's heartbeat is older than the idle minimum, it's safe to treat as idle.\n if (!isAgentIdleForNudge(agent.id, FIRST_COMPLETION_IDLE_MS)) continue;\n\n const runtimeState = getAgentRuntimeStateSync(agent.id);\n // PAN-2946: agents with no runtime record yet (fresh spawn, wiped state)\n // must not crash the patrol's first-completion pass.\n if (!runtimeState) continue;\n const lastActivity = new Date(runtimeState.lastActivity ?? 0);\n const idleMs = now - lastActivity.getTime();\n if (idleMs < FIRST_COMPLETION_IDLE_MS) continue;\n\n // Check cooldown\n const lastNudge = firstCompletionCooldowns.get(agent.id);\n if (lastNudge && (now - lastNudge) < FIRST_COMPLETION_COOLDOWN_MS) continue;\n\n // HARD GATE: Never nudge agents that have been through the review pipeline.\n // Check review-status.json — if ANY entry exists for this issue, the agent\n // has entered the specialist pipeline and must NOT receive a \"pan done\" nudge.\n // (Dead-end detection handles agents stuck in review/test cycles.)\n const issueId = agent.issueId || agent.id.replace('agent-', '').toUpperCase();\n const issueKey = issueId.toLowerCase();\n try {\n const statuses = loadReviewStatuses();\n // Keys are stored in original case (e.g., \"MIN-727\") — check all case variants\n const hasStatus = statuses[issueKey] || statuses[issueId] || statuses[issueId.toUpperCase()];\n if (hasStatus) {\n console.log(`[deacon] First-completion gate: skipping ${agent.id} — has review status entry (readyForMerge=${hasStatus.readyForMerge ?? false})`);\n continue;\n }\n } catch { /* load error, proceed with check */ }\n\n // HARD GATE: Also check for review feedback files in the workspace.\n // If a feedback directory exists and is non-empty, a review agent has already\n // processed this workspace — never send a \"pan done\" nudge.\n const agentStateForGate = getAgentStateSync(agent.id);\n if (agentStateForGate?.workspace) {\n const feedbackDir = join(agentStateForGate.workspace, '.pan', 'feedback');\n if (existsSync(feedbackDir)) {\n try {\n const feedbackFiles = readdirSync(feedbackDir);\n if (feedbackFiles.length > 0) {\n console.log(`[deacon] First-completion gate: skipping ${agent.id} — has ${feedbackFiles.length} review feedback file(s) in .pan/feedback/`);\n continue;\n }\n } catch { /* can't read feedback dir */ }\n }\n }\n\n // PAN-1185: SWARM slot agents must NOT receive issue-level `pan done`\n // nudges. They work on a single plan item; the issue-level done flow opens\n // a premature feature → main PR. Detect slots by workspace path suffix\n // `-slot-N/` — the workspace dir convention is stable across PAN-1176.\n if (agentStateForGate?.workspace && /-slot-\\d+\\/?$/.test(agentStateForGate.workspace)) {\n console.log(`[deacon] First-completion gate: skipping ${agent.id} — SWARM slot agent (workspace: ${agentStateForGate.workspace})`);\n continue;\n }\n\n // Check if the agent has commits (sign that work was done)\n const agentState = getAgentStateSync(agent.id);\n if (!agentState?.workspace || !existsSync(agentState.workspace)) continue;\n\n // For polyrepo workspaces, check inside sub-repos (fe/, api/, etc.)\n // For monorepo workspaces, check the workspace root directly\n let hasCommits = false;\n try {\n const { stdout: gitLog } = await execAsync(\n 'git log --oneline -3 2>/dev/null',\n { cwd: agentState.workspace }\n );\n hasCommits = gitLog.trim().length > 0;\n } catch {\n // Workspace root may not be a git repo (polyrepo) — check subdirectories\n try {\n const subdirs = readdirSync(agentState.workspace, { withFileTypes: true })\n .filter(d => d.isDirectory() && !d.name.startsWith('.'));\n for (const sub of subdirs) {\n try {\n const { stdout: subLog } = await execAsync(\n 'git log --oneline -3 2>/dev/null',\n { cwd: join(agentState.workspace, sub.name) }\n );\n if (subLog.trim().length > 0) {\n hasCommits = true;\n break;\n }\n } catch { /* not a git repo */ }\n }\n } catch { /* can't read workspace dir */ }\n }\n if (!hasCommits) continue; // No commits — agent may not have started yet\n\n // All heuristics passed: agent likely forgot pan done\n const idleMinutes = Math.round(idleMs / 60000);\n console.log(`[deacon] First-completion gap detected: ${agent.id} (${issueId}) idle for ${idleMinutes}m with commits but no completion marker`);\n\n firstCompletionCooldowns.set(agent.id, now);\n\n try {\n const nudgeMessage = `You appear to have stopped working without calling \\`pan done\\`. If your implementation is complete, run this now:\\n\\npan done ${issueId} -c \"Implementation complete\"\\n\\nIf you still have remaining tasks, continue working on them.`;\n await Effect.runPromise(sendKeys(agent.id, nudgeMessage));\n actions.push(`First-completion nudge: ${agent.id} (idle ${idleMinutes}m)`);\n console.log(`[deacon] Sent first-completion nudge to ${agent.id}`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Failed to send first-completion nudge to ${agent.id}:`, msg);\n }\n }\n } catch (error: unknown) {\n console.error('[deacon] Error in first-completion detection:', error);\n }\n\n return actions;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"deacon-merge-BOGFEV8_.js","names":["execFileAsync","promisify"],"sources":["../../src/lib/cloister/deacon-stuck-merging.ts","../../src/lib/cloister/deacon-auto-merge-reconcile.ts","../../src/lib/cloister/deacon-merge.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { getAgentStateSync } from '../agents.js';\nimport { getMergeSetSync, type MergeSet } from '../merge-set.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport {\n loadReviewStatuses,\n reviewGatesPassedSync,\n setReviewStatusSync,\n type ReviewStatus,\n} from '../review-status.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\nimport {\n observeForgeMergeState,\n type ForgeMergeObservationResult,\n} from './merge-completeness.js';\n\nconst execFileAsync = promisify(execFile);\nexport const STUCK_MERGING_MS = 30 * 60 * 1000;\n\nexport interface GitHubBranchMergeObservation {\n merged: boolean;\n unverifiableReason?: string;\n}\n\nexport async function observeGitHubBranchMerge(\n issueId: string,\n branch: string,\n projectPath: string,\n): Promise<GitHubBranchMergeObservation> {\n const resolved = resolveGitHubIssueSync(issueId);\n if (!resolved.isGitHub) return { merged: false };\n try {\n const { stdout } = await execFileAsync(\n 'gh',\n ['pr', 'list', '--repo', `${resolved.owner}/${resolved.repo}`, '--head', branch, '--state', 'all', '--json', 'number,mergedAt,mergeCommit', '--limit', '5'],\n { cwd: projectPath },\n );\n const prs = JSON.parse(stdout || '[]') as Array<{ mergedAt: string | null; mergeCommit: unknown | null }>;\n return { merged: prs.some((pr) => pr.mergedAt || pr.mergeCommit) };\n } catch (error) {\n return {\n merged: false,\n unverifiableReason: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nfunction latestMergeActivityAt(status: ReviewStatus): number | null {\n const mergeEntry = [...(status.history ?? [])].reverse().find((entry) => entry.type === 'merge');\n const timestamp = Date.parse(mergeEntry?.timestamp ?? status.updatedAt);\n return Number.isFinite(timestamp) ? timestamp : null;\n}\n\nexport function isStuckMergingState(status: ReviewStatus, now: number): boolean {\n if (status.mergeStatus !== 'merging' && status.mergeStatus !== 'verifying') return false;\n const activityAt = latestMergeActivityAt(status);\n return activityAt !== null && now - activityAt >= STUCK_MERGING_MS;\n}\n\nasync function readSpecStatus(projectPath: string, issueId: string): Promise<string | undefined> {\n try {\n const { findSpecByIssue } = await import('../pan-dir/specs.js');\n return (await Effect.runPromise(findSpecByIssue(projectPath, issueId)))?.status;\n } catch {\n return undefined;\n }\n}\n\nfunction hasPlanningAgent(issueId: string): boolean {\n const planState = getAgentStateSync(`planning-${issueId.toLowerCase()}`);\n return planState?.status === 'running' || planState?.status === 'starting';\n}\n\nexport interface StuckMergingDeps {\n now(): number;\n loadStatuses(): Record<string, ReviewStatus>;\n resolveProject(issueId: string): { projectPath: string } | null;\n getMergeSet(issueId: string): MergeSet | null;\n resolveGitHubIssue(issueId: string): { isGitHub: boolean };\n observeForge(issueId: string): Promise<ForgeMergeObservationResult>;\n observeGitHub(issueId: string, branch: string, projectPath: string): Promise<GitHubBranchMergeObservation>;\n readSpecStatus(projectPath: string, issueId: string): Promise<string | undefined>;\n hasPlanningAgent(issueId: string): boolean;\n reviewGatesPassed(status: ReviewStatus): boolean;\n setReviewStatus: typeof setReviewStatusSync;\n enqueuePostMerge(issueId: string, projectPath: string, branch: string): Promise<string | null | undefined>;\n warn(message: string): void;\n}\n\nexport async function reconcileStuckMergingStatesWithDeps(deps: StuckMergingDeps): Promise<string[]> {\n const actions: string[] = [];\n const now = deps.now();\n\n for (const [issueId, status] of Object.entries(deps.loadStatuses())) {\n if (!isStuckMergingState(status, now)) continue;\n const project = deps.resolveProject(issueId);\n if (!project) continue;\n const specStatus = await deps.readSpecStatus(project.projectPath, issueId);\n if (specStatus === 'completed' || specStatus === 'cancelled') continue;\n\n const branch = `feature/${issueId.toLowerCase()}`;\n const mergeSet = deps.getMergeSet(issueId);\n const hasNonGitHubRepos = mergeSet?.repos.some((repo) => repo.forge !== 'github') === true;\n const githubIssue = deps.resolveGitHubIssue(issueId);\n let complete = false;\n let positiveMergedEvidence = false;\n let unverifiableReason: string | undefined;\n\n if (!githubIssue.isGitHub || hasNonGitHubRepos) {\n try {\n const observation = await deps.observeForge(issueId);\n complete = observation.complete;\n positiveMergedEvidence = observation.hasPositiveMergedEvidence;\n unverifiableReason = observation.repos.find((repo) => repo.state === 'unverifiable')?.reason;\n } catch (error) {\n unverifiableReason = error instanceof Error ? error.message : String(error);\n }\n } else {\n const observation = await deps.observeGitHub(issueId, branch, project.projectPath);\n complete = observation.merged;\n positiveMergedEvidence = observation.merged;\n unverifiableReason = observation.unverifiableReason;\n }\n\n if (unverifiableReason) {\n deps.warn(`[deacon] ${issueId}: stuck ${status.mergeStatus} state is unverifiable — ${unverifiableReason}`);\n continue;\n }\n\n if (complete && positiveMergedEvidence) {\n if (deps.hasPlanningAgent(issueId) || specStatus === 'draft' || specStatus === 'proposed') continue;\n deps.setReviewStatus(issueId, {\n mergeStatus: 'merged', mergeStep: 'post-merge-cleanup', readyForMerge: false,\n });\n actions.push(`Reconciled stuck ${status.mergeStatus} state for ${issueId} — forge confirms the merge`);\n const action = await deps.enqueuePostMerge(issueId, project.projectPath, branch);\n if (action) actions.push(action);\n continue;\n }\n\n const readyForMerge = deps.reviewGatesPassed({ ...status, mergeStatus: 'pending' });\n deps.setReviewStatus(issueId, {\n mergeStatus: 'pending',\n mergeNotes: `Reset stuck ${status.mergeStatus} state after 30 minutes because the forge has no completed merge evidence`,\n readyForMerge,\n });\n actions.push(`Reset stuck ${status.mergeStatus} state for ${issueId} to pending`);\n }\n\n return actions;\n}\n\nexport async function reconcileStuckMergingStates(): Promise<string[]> {\n return reconcileStuckMergingStatesWithDeps({\n now: () => Date.now(),\n loadStatuses: loadReviewStatuses,\n resolveProject: resolveProjectFromIssueSync,\n getMergeSet: getMergeSetSync,\n resolveGitHubIssue: resolveGitHubIssueSync,\n observeForge: observeForgeMergeState,\n observeGitHub: observeGitHubBranchMerge,\n readSpecStatus,\n hasPlanningAgent,\n reviewGatesPassed: reviewGatesPassedSync,\n setReviewStatus: setReviewStatusSync,\n enqueuePostMerge: async (issueId, projectPath, branch) => {\n const { enqueuePostMergeLifecycle } = await import('./post-merge-lifecycle-worker.js');\n return enqueuePostMergeLifecycle(issueId, projectPath, branch);\n },\n warn: (message) => console.warn(message),\n });\n}\n","import { getForgeAdapter, type ForgeType } from '../forge.js';\nimport { getMergeSetSync, type MergeSetRepoState } from '../merge-set.js';\nimport {\n cancelPending,\n listActiveAutoMerges,\n listProblemAutoMerges,\n markMerged,\n type PendingAutoMerge,\n} from '../overdeck/merge-sync.js';\nimport { readJournalStatusSync } from '../overdeck/review-status-record-sync.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\n\nconst AUTO_MERGE_RECONCILE_COOLDOWN_MS = 10 * 60 * 1000;\n// SQLite LIMIT -1 removes the limit. Patrol reconciliation must inspect the\n// whole queue or the oldest unresolved page can starve every newer row.\nconst UNBOUNDED_ROW_LIMIT = -1;\nexport const AUTO_MERGE_RECONCILE_FORGE_LIMIT = 4;\nconst autoMergeReconcileCooldowns = new Map<string, number>();\nconst autoMergeLastCheckedAt = new Map<string, number>();\n\nexport interface AutoMergeReconcileDeps {\n now(): number;\n listProblemAutoMerges: typeof listProblemAutoMerges;\n listActiveAutoMerges: typeof listActiveAutoMerges;\n cancelPending: typeof cancelPending;\n markMerged: typeof markMerged;\n readJournalStatus: typeof readJournalStatusSync;\n getMergeSet: typeof getMergeSetSync;\n resolveProject: typeof resolveProjectFromIssueSync;\n getForgeAdapter: typeof getForgeAdapter;\n log(message: string): void;\n warn(message: string): void;\n}\n\ninterface ForgeCandidate {\n issueId: string;\n problemRows: PendingAutoMerge[];\n representative: PendingAutoMerge;\n artifactRepo?: MergeSetRepoState;\n projectPath: string;\n}\n\nfunction groupByIssue(rows: PendingAutoMerge[]): Map<string, PendingAutoMerge[]> {\n const grouped = new Map<string, PendingAutoMerge[]>();\n for (const row of rows) {\n const issueRows = grouped.get(row.issueId) ?? [];\n issueRows.push(row);\n grouped.set(row.issueId, issueRows);\n }\n return grouped;\n}\n\nfunction normalizeArtifactUrl(url: string): string {\n return url.replace(/[?#].*$/, '').replace(/\\/+$/, '');\n}\n\nfunction repositoryFromArtifactUrl(url: string, forge: ForgeType): string | undefined {\n try {\n const parsed = new URL(url);\n if (forge === 'github' && parsed.hostname.toLowerCase() !== 'github.com') return undefined;\n const marker = forge === 'gitlab' ? '/-/merge_requests/' : '/pull/';\n const markerIndex = parsed.pathname.indexOf(marker);\n if (markerIndex <= 1) return undefined;\n return parsed.pathname.slice(1, markerIndex);\n } catch {\n return undefined;\n }\n}\n\nfunction pruneTracking(activeIssueIds: Set<string>): void {\n for (const issueId of autoMergeReconcileCooldowns.keys()) {\n if (!activeIssueIds.has(issueId)) autoMergeReconcileCooldowns.delete(issueId);\n }\n for (const issueId of autoMergeLastCheckedAt.keys()) {\n if (!activeIssueIds.has(issueId)) autoMergeLastCheckedAt.delete(issueId);\n }\n}\n\nasync function reconcileForgeCandidate(\n candidate: ForgeCandidate,\n deps: AutoMergeReconcileDeps,\n now: number,\n): Promise<string[]> {\n const { issueId, problemRows, representative, artifactRepo, projectPath } = candidate;\n autoMergeLastCheckedAt.set(issueId, now);\n\n try {\n const repository = repositoryFromArtifactUrl(representative.prUrl, representative.forge);\n const artifact = await deps.getForgeAdapter(representative.forge).findMergedArtifact({\n sourceBranch: artifactRepo?.sourceBranch ?? `feature/${issueId.toLowerCase()}`,\n ...(artifactRepo?.targetBranch ? { targetBranch: artifactRepo.targetBranch } : {}),\n artifactUrl: representative.prUrl,\n ...(artifactRepo?.artifactId ? { artifactId: artifactRepo.artifactId } : {}),\n ...(repository ? { repository } : {}),\n cwd: artifactRepo?.repoPath ?? projectPath,\n });\n\n if (!artifact) {\n autoMergeReconcileCooldowns.set(issueId, now + AUTO_MERGE_RECONCILE_COOLDOWN_MS);\n return [];\n }\n\n const actions: string[] = [];\n for (const row of problemRows) {\n if (!deps.markMerged(row.id)) continue;\n const action = `Reconciled auto-merge row ${row.id} for ${issueId} — forge confirms the merge`;\n actions.push(action);\n deps.log(`[deacon] ${action}`);\n }\n autoMergeReconcileCooldowns.delete(issueId);\n return actions;\n } catch (error) {\n autoMergeReconcileCooldowns.set(issueId, now + AUTO_MERGE_RECONCILE_COOLDOWN_MS);\n deps.warn(`[deacon] Auto-merge row reconciliation failed for ${issueId}: ${error instanceof Error ? error.message : String(error)}`);\n return [];\n }\n}\n\nexport async function reconcileAutoMergeRowsWithDeps(\n deps: AutoMergeReconcileDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n const now = deps.now();\n let rows: PendingAutoMerge[];\n\n try {\n rows = [\n ...deps.listProblemAutoMerges(UNBOUNDED_ROW_LIMIT),\n ...deps.listActiveAutoMerges(UNBOUNDED_ROW_LIMIT).filter((row) => row.status === 'pending'),\n ];\n } catch (error) {\n deps.warn(`[deacon] Auto-merge row reconciliation failed to list rows: ${error instanceof Error ? error.message : String(error)}`);\n return actions;\n }\n\n const grouped = groupByIssue(rows);\n pruneTracking(new Set(grouped.keys()));\n const forgeCandidates: ForgeCandidate[] = [];\n\n for (const [issueId, issueRows] of grouped) {\n const cooledUntil = autoMergeReconcileCooldowns.get(issueId);\n if (cooledUntil && now < cooledUntil) continue;\n\n try {\n const closedOut = deps.readJournalStatus(issueId)?.durable.closedOut === true;\n if (closedOut) {\n for (const row of issueRows) {\n if (!deps.cancelPending(row.id, 'auto-merge-reconciler')) continue;\n const action = `Cancelled closed-out auto-merge row ${row.id} for ${issueId}`;\n actions.push(action);\n deps.log(`[deacon] ${action}`);\n }\n autoMergeReconcileCooldowns.delete(issueId);\n autoMergeLastCheckedAt.delete(issueId);\n continue;\n }\n\n const problemRows = issueRows.filter(\n (row) => row.status === 'blocked' || row.status === 'failed',\n );\n if (problemRows.length === 0) continue;\n\n const representative = problemRows.reduce(\n (latest, row) => row.id > latest.id ? row : latest,\n );\n const project = deps.resolveProject(issueId);\n if (!project) throw new Error(`project resolution failed for ${issueId}`);\n const artifactUrl = normalizeArtifactUrl(representative.prUrl);\n const artifactRepo = deps.getMergeSet(issueId)?.repos.find(\n (repo) => repo.forge === representative.forge\n && repo.artifactUrl !== undefined\n && normalizeArtifactUrl(repo.artifactUrl) === artifactUrl,\n );\n forgeCandidates.push({\n issueId,\n problemRows,\n representative,\n artifactRepo,\n projectPath: project.projectPath,\n });\n } catch (error) {\n autoMergeReconcileCooldowns.set(issueId, now + AUTO_MERGE_RECONCILE_COOLDOWN_MS);\n deps.warn(`[deacon] Auto-merge row reconciliation failed for ${issueId}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n forgeCandidates.sort((left, right) => {\n const checkedDelta = (autoMergeLastCheckedAt.get(left.issueId) ?? 0)\n - (autoMergeLastCheckedAt.get(right.issueId) ?? 0);\n return checkedDelta || left.representative.id - right.representative.id;\n });\n const batch = forgeCandidates.slice(0, AUTO_MERGE_RECONCILE_FORGE_LIMIT);\n const reconciled = await Promise.all(\n batch.map((candidate) => reconcileForgeCandidate(candidate, deps, now)),\n );\n for (const candidateActions of reconciled) actions.push(...candidateActions);\n\n return actions;\n}\n\nexport async function reconcileAutoMergeRows(): Promise<string[]> {\n return reconcileAutoMergeRowsWithDeps({\n now: () => Date.now(),\n listProblemAutoMerges,\n listActiveAutoMerges,\n cancelPending,\n markMerged,\n readJournalStatus: readJournalStatusSync,\n getMergeSet: getMergeSetSync,\n resolveProject: resolveProjectFromIssueSync,\n getForgeAdapter,\n log: (message) => console.log(message),\n warn: (message) => console.warn(message),\n });\n}\n","import { existsSync, readdirSync } from 'fs';\nimport { readdir } from 'fs/promises';\nimport { join } from 'path';\nimport { exec, execFile } from 'node:child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from '../paths.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getAgentRuntimeStateSync, getAgentStateSync, listRunningAgentsSync, type AgentState } from '../agents.js';\nimport { withConcurrencyLimit } from '../concurrency.js';\nimport { loadReviewStatuses, setReviewStatusSync, reviewGatesPassedSync } from '../review-status.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\nimport { getMergeSetSync } from '../merge-set.js';\nimport { sessionExistsSync, sendKeys } from '../tmux.js';\nimport { isAgentIdleForNudge } from './agent-idle.js';\nimport { loadCloisterConfig } from './config.js';\nimport { getAutoCloseOutCanonicalState, sweepAutoCloseOutCache } from './deacon-canonical-state.js';\nimport { isStuckMergingState, observeGitHubBranchMerge } from './deacon-stuck-merging.js';\n\nexport { reconcileStuckMergingStates } from './deacon-stuck-merging.js';\nexport { reconcileAutoMergeRows } from './deacon-auto-merge-reconcile.js';\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n\ninterface MergeReminderState {\n mergeStuckAttempts?: Record<string, number>;\n}\n\nexport interface MergeReadyReminderDeps<State extends MergeReminderState = MergeReminderState> {\n loadState: () => State;\n saveState: (state: State) => void;\n hasMergeReadyNotifier: () => boolean;\n notifyMergeReady: (issueId: string) => void;\n}\n\n// ============================================================================\n// CI transient retry tracking (shared by checkPostReviewCommits + checkFailedMergeRetry)\n// ============================================================================\n\n// In-memory CI failure retry tracking — separate from mergeRetryCount because\n// CI failures are transient and should not permanently block merge attempts.\n// Declared here so checkPostReviewCommits can clear it when new commits arrive.\nexport const ciRetryMap = new Map<string, { count: number; lastAttempt: number }>();\n\n// ============================================================================\n// Ready-for-merge stuck detection (PAN-344)\n// ============================================================================\n\n// Minimum age (ms) of a readyForMerge status before deacon sends a merge-ready reminder.\n// This is NOT a stuck detection — it's a courtesy notification that a merge is waiting\n// for the human to click MERGE. One hour is reasonable; the human may be reviewing,\n// working on other things, or intentionally waiting.\nconst MERGE_READY_REMINDER_MS = 60 * 60 * 1000; // 1 hour\n// Minimum wait (ms) between successive merge-ready reminders for the same issue\nconst MERGE_READY_REMINDER_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour\n// Circuit breaker: stop reminding after this many times (per server lifetime)\nconst MERGE_READY_REMINDER_MAX = 3;\n\n// In-memory cooldowns for stuck-merge detection (reset on server restart is acceptable —\n// cooldowns are a performance optimisation, not critical state)\nconst mergeStuckCooldowns = new Map<string, number>();\n\n/**\n * Safety-net patrol: find issues that are readyForMerge but not yet merging/merged\n * and whose readyForMerge status is older than MERGE_STUCK_STALENESS_MS.\n *\n * Previously this auto-triggered the merge API. Now it is notify-only: it emits\n * a merge:ready Socket.io event so the dashboard can prompt the user to click\n * the MERGE button. The MERGE button is the sole merge trigger (PAN-354).\n *\n * Guards:\n * - Staleness: status must be at least 2 min old (avoids racing with primary trigger)\n * - Per-issue cooldown: 10 min between successive attempts\n * - Circuit breaker: max 3 attempts per issue per process lifetime\n */\nexport async function checkReadyForMergeStuck<State extends MergeReminderState>(deps: MergeReadyReminderDeps<State>): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const statuses = loadReviewStatuses();\n\n const now = Date.now();\n const state = deps.loadState();\n const attemptCounts = state.mergeStuckAttempts ?? {};\n let stateModified = false;\n\n for (const [key, status] of Object.entries(statuses)) {\n // Only act on issues that are ready but not yet merging/merged/failed\n if (!status.readyForMerge) continue;\n if (status.mergeStatus === 'merging' || status.mergeStatus === 'merged' || status.mergeStatus === 'failed') continue;\n\n // Wait at least 1 hour before sending a merge-ready reminder.\n // The human controls when to merge — this is just a courtesy notification.\n if (!status.updatedAt) continue;\n const statusAge = now - new Date(status.updatedAt).getTime();\n if (statusAge < MERGE_READY_REMINDER_MS) continue;\n\n // Per-issue cooldown (in-memory — reset on restart is acceptable for a rate-limiter)\n const lastAttempt = mergeStuckCooldowns.get(key);\n if (lastAttempt && (now - lastAttempt) < MERGE_READY_REMINDER_COOLDOWN_MS) continue;\n\n // Circuit breaker (persisted to deacon state so restart doesn't reset the count)\n const attempts = attemptCounts[key] ?? 0;\n if (attempts >= MERGE_READY_REMINDER_MAX) continue;\n\n const ageHours = Math.round((now - new Date(status.updatedAt).getTime()) / 3600000 * 10) / 10;\n console.log(`[deacon] Merge-ready reminder for ${key} (ready for ${ageHours}h, reminder ${attempts + 1}/${MERGE_READY_REMINDER_MAX})`);\n\n // Record attempt before notifying so a crash doesn't leave us in a retry loop\n mergeStuckCooldowns.set(key, now);\n attemptCounts[key] = attempts + 1;\n stateModified = true;\n\n // Notify the dashboard via Socket.io so the user knows to click MERGE.\n // Auto-triggering merge was removed in PAN-354; the MERGE button is the sole trigger.\n const msg = `Merge ready: ${key} has been waiting for merge for ${ageHours}h — click MERGE when ready`;\n if (deps.hasMergeReadyNotifier()) {\n deps.notifyMergeReady(status.issueId ?? key);\n actions.push(msg);\n console.log(`[deacon] merge:ready notification sent for ${key}`);\n } else {\n actions.push(msg);\n console.warn(`[deacon] No mergeReadyNotifier registered — dashboard will not be notified for ${key}`);\n }\n }\n\n // Persist updated attempt counts so circuit breaker survives server restarts\n if (stateModified) {\n state.mergeStuckAttempts = attemptCounts;\n deps.saveState(state);\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error in checkReadyForMergeStuck:', msg);\n }\n\n return actions;\n}\n\n/**\n * Detect issues whose feature branch is merged to main but mergeStatus is stale.\n * Happens when a merge bypasses the dashboard (manual git merge, direct push, or\n * deploy script crash). Sets mergeStatus='merged' so the dashboard shows the\n * correct state and close-out can proceed.\n */\nconst staleMergeReconciled = new Set<string>();\n\nexport async function reconcileStaleMergeStatus(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const statuses = loadReviewStatuses();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n const postMergeIncomplete = status.mergeStatus === 'merged' && status.mergeStep === 'post-merge-cleanup';\n if (status.mergeStatus === 'merged' && !postMergeIncomplete) continue;\n if (isStuckMergingState(status, Date.now())) continue;\n if (!postMergeIncomplete && staleMergeReconciled.has(issueId)) continue;\n\n const project = resolveProjectFromIssueSync(issueId);\n if (!project) continue;\n\n const mergeSet = getMergeSetSync(issueId);\n const shouldObserveForge = !resolveGitHubIssueSync(issueId).isGitHub\n || mergeSet?.repos.some((repo) => repo.forge !== 'github') === true;\n const mergeRelevant = status.readyForMerge === true\n || ['merging', 'verifying', 'queued', 'failed'].includes(status.mergeStatus ?? '')\n || ['merging', 'failed'].includes(mergeSet?.status ?? '');\n // Closed-out issues are TERMINAL: close-out flips the spec to\n // completed/cancelled and clears review status. Treating the cleared/\n // resurrected row as \"stale\" here re-fires the post-merge handoff,\n // which REOPENS the closed tracker issue (PAN-1190, 2026-06-11).\n let specStatus: string | undefined;\n try {\n const { findSpecByIssue } = await import('../pan-dir/specs.js');\n const spec = await Effect.runPromise(findSpecByIssue(project.projectPath, issueId));\n specStatus = spec?.status;\n if (spec && (spec.status === 'completed' || spec.status === 'cancelled')) {\n staleMergeReconciled.add(issueId);\n continue;\n }\n } catch {\n // Spec unreadable — fall through to the normal checks.\n }\n\n const branch = `feature/${issueId.toLowerCase()}`;\n if (postMergeIncomplete) {\n const { enqueuePostMergeLifecycle } = await import('./post-merge-lifecycle-worker.js');\n const action = enqueuePostMergeLifecycle(issueId, project.projectPath, branch);\n if (action) actions.push(action);\n continue;\n }\n\n let isMerged = false;\n\n // Check 1: diagnostic only. Branch topology alone is not proof of a\n // completed pipeline merge: a branch created at main with no\n // implementation commits also satisfies merge-base --is-ancestor. Keep\n // the stale-merge reconciler PR-backed so re-planning cannot\n // phantom-merge an already planned issue.\n try {\n const [branchTip, mainTip] = await Promise.all([\n execFileAsync('git', ['rev-parse', branch], { cwd: project.projectPath }),\n execFileAsync('git', ['rev-parse', 'main'], { cwd: project.projectPath }),\n ]);\n if (branchTip.stdout.trim() === mainTip.stdout.trim()) {\n console.log(`[deacon] ${issueId}: branch ${branch} points at main; not treating zero-commit branch as merged`);\n }\n } catch {\n // Branch is absent/unreadable — leave merge detection to the PR API.\n }\n\n // Check 2: query GitHub for PR mergedAt/mergeCommit. The\n // old regex-based detection (`\\(PAN-XXXX[ )]` against `git log --pretty=%s`)\n // matched ANY commit that mentioned the issue in a trailer, not just\n // genuine squash merges. That's how PAN-977/945/913/544/457 got\n // mergeStatus=merged and rolled into close-out without an actual merge:\n // unrelated commits landed on main with `(PAN-977)` references and the\n // deacon trusted them. GitHub's API is the only authoritative source.\n if (!isMerged) {\n const githubObservation = await observeGitHubBranchMerge(issueId, branch, project.projectPath);\n isMerged = githubObservation.merged;\n }\n if (shouldObserveForge && mergeRelevant) {\n try {\n const observation = await (await import('./merge-completeness.js')).observeForgeMergeState(issueId);\n const blocker = observation.repos.find((repo) => repo.state === 'unverifiable');\n if (blocker) { console.warn(`[deacon] ${issueId}: forge merge state is unverifiable — ${blocker.reason}`); continue; }\n isMerged = observation.complete && observation.hasPositiveMergedEvidence;\n } catch (error) {\n console.warn(`[deacon] ${issueId}: forge merge observation failed — ${error instanceof Error ? error.message : String(error)}`); continue;\n }\n }\n if (isMerged) {\n // PAN-1994: Defer terminalization only when a genuine re-plan is in\n // progress. An active planning-<issue> agent or a spec back at\n // draft/proposed means a fresh planning cycle is running; setting\n // mergeStatus='merged' would contaminate it. A lone zombie work agent\n // is NOT a reason to defer — the branch is already merged on GitHub,\n // so we terminalize and let postMergeLifecycle kill the stale session.\n const issueLower = issueId.toLowerCase();\n const planState = getAgentStateSync(`planning-${issueLower}`);\n const hasPlanningAgent = planState?.status === 'running' || planState?.status === 'starting';\n const isReplanning = specStatus === 'draft' || specStatus === 'proposed';\n\n if (hasPlanningAgent || isReplanning) {\n const reason = hasPlanningAgent\n ? 'active planning agent in progress'\n : `spec status is '${specStatus}' (re-planning in progress)`;\n console.log(`[deacon] ${issueId}: ${reason} — deferring stale-merge reconcile (PAN-1994)`);\n continue;\n }\n\n setReviewStatusSync(issueId, { mergeStatus: 'merged', mergeStep: 'post-merge-cleanup', readyForMerge: false });\n const msg = `Reconciled stale mergeStatus for ${issueId} — branch ${branch} is merged to main`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n\n // PAN-1027: queue the post-merge handoff without blocking unrelated patrol work.\n const { enqueuePostMergeLifecycle } = await import('./post-merge-lifecycle-worker.js');\n const action = enqueuePostMergeLifecycle(issueId, project.projectPath, branch);\n if (action) actions.push(action);\n }\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileStaleMergeStatus: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n/**\n * PAN-1027 reverse direction: detect issues whose internal mergeStatus='merged' but\n * whose GitHub PR is NOT merged (open, closed-without-merge, or reverted). When the\n * dashboard previously detected a merge that later got reverted (or the deacon's\n * forward-direction reconciler matched a squash-commit grep that wasn't actually a\n * merge), the issue gets stuck because every gate that checks `mergeStatus !== 'merged'`\n * skips it. This sweep resets the stale merged status so the issue can flow through\n * the pipeline again.\n */\nconst falseMergedReset = new Set<string>();\n\nexport async function reconcileFalseMerged(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../github-app.js');\n if (!isGitHubAppConfigured()) return actions;\n\n const statuses = loadReviewStatuses();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.mergeStatus !== 'merged') continue;\n if (!status.prUrl) continue;\n if (falseMergedReset.has(issueId)) continue;\n\n const prRef = status.prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!prRef) continue;\n\n try {\n const prState = await Effect.runPromise(getPullRequestState(prRef[1], prRef[2], Number.parseInt(prRef[3], 10)));\n if (!prState.merged) {\n // GitHub says not merged but our DB says merged — reset internal state.\n // Leave reviewStatus alone (it may legitimately be passed/failed/blocked from\n // the prior cycle); the issue can proceed through the pipeline once mergeStatus\n // is no longer blocking.\n setReviewStatusSync(issueId, { mergeStatus: 'pending' });\n falseMergedReset.add(issueId);\n const msg = `Reset stale mergeStatus=merged for ${issueId} — PR ${status.prUrl} is not merged on GitHub`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (err: unknown) {\n // Non-fatal: GitHub API hiccup. Try again next patrol.\n console.warn(`[deacon] Failed false-merged check for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileFalseMerged: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n/**\n * Detect issues whose merge_status='merged' but review_status is still in a\n * non-terminal state (reviewing, pending, etc.). If the merge actually\n * happened, the review must have passed at some point even if the dashboard\n * missed the transition (e.g. coordinator crashed mid-run, dashboard restart\n * dropped an in-flight update). Reconciling to review_status='passed' clears\n * the \"running reviewers with no data\" UI state PAN-1028 reproduced.\n */\nconst mergedReviewingReconciled = new Set<string>();\n/**\n * Per-issue throttle for the closed-PR readyForMerge reconciler so a transient\n * GitHub API failure doesn't burn the rate budget on the same issues every\n * patrol. Cleared when the issue's state changes.\n */\nconst closedPrReadyReconcileCooldowns = new Map<string, number>();\nconst CLOSED_PR_RECONCILE_COOLDOWN_MS = 10 * 60 * 1000;/**\n * Reconciler: issues with readyForMerge=true whose PR is no longer OPEN.\n *\n * The \"Awaiting Merge\" view filters on readyForMerge=true, so an issue whose\n * PR was closed without merging (cancel-flow, manual `gh pr close`, branch\n * deleted) stays in that list forever — the merge button points at a dead PR\n * and would only get cleared the next time the user clicks it (PAN-509-style\n * defensive check in `/api/issues/:id/merge`). That UX is bad: the page\n * actively lies to the human about what's ready to ship.\n *\n * This patrol catches it proactively: for every readyForMerge=true issue with\n * a GitHub PR URL, ask the forge for the current PR state. If MERGED, flip\n * mergeStatus to 'merged' (post-merge lifecycle catches up elsewhere). If\n * CLOSED-without-merge, reset readyForMerge=false and surface why on\n * mergeNotes so the human sees what happened instead of a missing button.\n */\nexport async function reconcileClosedPrReadyForMerge(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../github-app.js');\n if (!isGitHubAppConfigured()) return actions;\n\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (!status.readyForMerge) continue;\n if (!status.prUrl) continue;\n\n const cooledUntil = closedPrReadyReconcileCooldowns.get(issueId);\n if (cooledUntil && now < cooledUntil) continue;\n\n const match = status.prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!match) continue;\n const [, owner, repo, numberStr] = match;\n const prNumber = parseInt(numberStr, 10);\n if (!Number.isFinite(prNumber)) continue;\n\n try {\n const prState = await Effect.runPromise(getPullRequestState(owner, repo, prNumber));\n if (prState.state === 'OPEN' && !prState.merged) continue;\n\n if (prState.merged) {\n setReviewStatusSync(issueId, {\n readyForMerge: false,\n mergeStatus: 'merged',\n mergeNotes: undefined,\n });\n const msg = `Reset readyForMerge for ${issueId} — PR #${prNumber} is already merged`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n } else {\n setReviewStatusSync(issueId, {\n readyForMerge: false,\n mergeStatus: 'failed',\n mergeNotes: `PR #${prNumber} was closed without merging — reopen the PR or reset review state to re-queue this issue`,\n });\n const msg = `Reset readyForMerge for ${issueId} — PR #${prNumber} is ${prState.state} (not OPEN)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n // Don't re-check for 10 min even if state somehow gets re-set.\n closedPrReadyReconcileCooldowns.set(issueId, now + CLOSED_PR_RECONCILE_COOLDOWN_MS);\n } catch (prErr: unknown) {\n // Throttle on API failure so we don't hammer GitHub.\n closedPrReadyReconcileCooldowns.set(issueId, now + CLOSED_PR_RECONCILE_COOLDOWN_MS);\n console.warn(`[deacon] reconcileClosedPrReadyForMerge: ${issueId} PR state lookup failed: ${prErr instanceof Error ? prErr.message : String(prErr)}`);\n }\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileClosedPrReadyForMerge: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n\nconst staleMergeBlockerCooldowns = new Map<string, number>();\nconst STALE_MERGE_BLOCKER_COOLDOWN_MS = 2 * 60 * 1000; // 2 minutes\n\n/**\n * Reconciler: re-evaluate stale merge-blockers so resolved conflicts unstick.\n *\n * resolveConflictGate() already clears a stale merge_conflict/not_mergeable\n * blocker once the branch is mergeable against main again — but it only runs\n * on-demand from the review-dispatch routes. An issue that picks up a\n * merge-blocker (so readyForMerge stays false) and then falls out of the active\n * review flow is therefore never re-evaluated: the blocker persists forever and\n * the merge train never picks it up. That is the \"stuck after review\" failure\n * mode (PAN-2143) — a PR that was conflicting while main moved stays blocked\n * even after the conflict is resolved.\n *\n * This patrol runs resolveConflictGate for every in-pipeline issue still\n * carrying a merge-blocker. When the branch became mergeable again it clears the\n * stale blocker (which lets setReviewStatus recompute readyForMerge so the merge\n * train resumes); when the branch is genuinely conflicting, resolveConflictGate\n * dispatches the conflict resolver on its existing throttle. A per-issue 2-min\n * cooldown bounds the git-probe cost; resolveConflictGate also caches its\n * mergeability probe.\n */\nexport async function reconcileStaleMergeBlockers(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { resolveConflictGate, buildRealConflictGateDeps } = await import('./conflict-gate.js');\n const statuses = loadReviewStatuses();\n const now = Date.now();\n const deps = buildRealConflictGateDeps();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.mergeStatus === 'merged') continue;\n const hasMergeBlocker = (status.blockerReasons ?? []).some(\n (b) => b.type === 'merge_conflict' || b.type === 'not_mergeable',\n );\n if (!hasMergeBlocker) continue;\n\n const cooledUntil = staleMergeBlockerCooldowns.get(issueId);\n if (cooledUntil && now < cooledUntil) continue;\n staleMergeBlockerCooldowns.set(issueId, now + STALE_MERGE_BLOCKER_COOLDOWN_MS);\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const workspacePath = join(resolved.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspacePath)) continue;\n\n try {\n const result = await resolveConflictGate(issueId, workspacePath, 'main', deps);\n if (result.clearedStaleBlocker) {\n const msg = `Cleared stale merge blocker for ${issueId} — branch is mergeable again; readyForMerge will recompute`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (gateErr: unknown) {\n console.warn(`[deacon] reconcileStaleMergeBlockers: ${issueId} conflict-gate failed: ${gateErr instanceof Error ? gateErr.message : String(gateErr)}`);\n }\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileStaleMergeBlockers: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n\n/**\n * Reconciler (PAN-2198): periodic twin of the boot-only fixStuckReadyForMerge,\n * for the NO-BLOCKER strand of \"stuck after review\".\n *\n * readyForMerge is re-derived on every setReviewStatusSync write. When the last\n * write left it false and there is NO merge-blocker, nothing re-derives it until\n * the next write — so a PR whose review+test+verify all passed but whose\n * readyForMerge was left false (e.g. a verdict that landed via a write path that\n * didn't recompute, or a gate that was transiently non-final) converges ONLY on\n * server restart (PAN-1758: \"readyForMerge only flips via the startup repair\n * sweep\"). This patrol re-derives it on the 60s deacon tick instead.\n *\n * Blocker strands are deliberately excluded — those are owned by\n * reconcileStaleMergeBlockers, and excluding them also avoids fighting the\n * setReviewStatus deriver's hasBlockers override (which would flip readyForMerge\n * straight back to false). Loop-safe: it writes ONLY readyForMerge (no\n * review/test status transition, so no review/test re-dispatch events fire), and\n * is idempotent — once flipped true, the readyForMerge!==false guard excludes the\n * issue, so steady state is zero writes. Flipping readyForMerge=true only makes\n * the issue merge-ELIGIBLE; the merge train / MERGE button remains the trigger.\n */\nexport function reconcileStuckReadyForMerge(): string[] {\n const actions: string[] = [];\n try {\n for (const [issueId, status] of Object.entries(loadReviewStatuses())) {\n if (status.readyForMerge !== false) continue;\n if ((status.blockerReasons?.length ?? 0) > 0) continue; // blocker strand → reconcileStaleMergeBlockers\n if (!reviewGatesPassedSync(status)) continue;\n setReviewStatusSync(issueId, { readyForMerge: true });\n const msg = `Restored readyForMerge for ${issueId} — review+test+verify passed, no blocker (was stuck false)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileStuckReadyForMerge: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n\nexport async function reconcileMergedButReviewing(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const statuses = loadReviewStatuses();\n const nonTerminal = new Set(['reviewing', 'pending', undefined, null]);\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.mergeStatus !== 'merged') continue;\n const reviewNonTerminal = nonTerminal.has(status.reviewStatus as string | undefined);\n const testNonTerminal = nonTerminal.has(status.testStatus as string | undefined);\n if (!reviewNonTerminal && !testNonTerminal) continue;\n if (mergedReviewingReconciled.has(issueId)) continue;\n\n // Set BOTH review and test to 'passed' atomically. Setting only review='passed'\n // trips the canSkipTests dispatch path in setReviewStatus and spawns a test-agent\n // for an already-merged issue — pure waste. The merge is terminal, no test needed.\n setReviewStatusSync(issueId, {\n reviewStatus: 'passed',\n testStatus: 'passed',\n testNotes: status.testNotes ?? 'Skipped: issue is already merged',\n });\n mergedReviewingReconciled.add(issueId);\n const msg = `Reconciled review_status=${status.reviewStatus ?? 'null'}, test_status=${status.testStatus ?? 'null'} → passed for ${issueId} (merge_status=merged is terminal)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (err: unknown) {\n console.warn(`[deacon] Error in reconcileMergedButReviewing: ${err instanceof Error ? err.message : String(err)}`);\n }\n return actions;\n}\n\n// Track per-issue cooldowns for failed-merge retry to avoid rapid re-queuing\nconst failedMergeRetryCooldowns = new Map<string, number>();\n// Track per-issue cooldowns for timeout nudges to avoid spamming the work agent\nconst timeoutNudgeCooldowns = new Map<string, number>();\n\n// Minimum time (ms) after merge failure before attempting a retry\nconst FAILED_MERGE_RETRY_COOLDOWN_MS = 30 * 60 * 1000; // 30 minutes\n// Minimum time (ms) between timeout nudges to the same work agent\nconst TIMEOUT_NUDGE_COOLDOWN_MS = 30 * 60 * 1000; // 30 minutes\n// Shorter cooldown for CI-transient failures (pending checks that resolve quickly)\nconst CI_TRANSIENT_RETRY_COOLDOWN_MS = 2 * 60 * 1000; // 2 minutes\n// Max number of automatic retries before requiring manual intervention\nexport const FAILED_MERGE_MAX_RETRIES = 3;\n\n/**\n * Auto-retry issues whose mergeStatus='failed' due to transient post-rebase\n * verification failures (e.g. flaky tests or tests fixed on main after failure).\n *\n * CI check failures (pending/failing) are handled differently from real merge\n * failures: they may resolve without any code change (e.g. CI queue clears,\n * GitHub status updates). These get a separate retry mechanism with a shorter\n * cooldown (2 min) and their own counter — they do NOT saturate mergeRetryCount.\n *\n * When review+test both passed but the post-rebase gate failed, the issue is\n * stuck: the deacon's merge-ready loop skips mergeStatus='failed' entries and\n * there is no other retry mechanism. After a 30-min cooldown, this patrol resets\n * the issue to readyForMerge=true so it reappears on the Awaiting Merge page.\n *\n * Guards:\n * - Review + test must both be 'passed' (don't retry if code quality failed)\n * - 30-min per-issue cooldown for non-CI failures, 2-min for CI transient\n * - Circuit breaker: max 3 retries (mergeRetryCount) for non-CI\n * - CI transient failures: max 5 retries with flat 2-minute cooldown\n */\nexport async function checkFailedMergeRetry(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const statuses = loadReviewStatuses();\n\n const now = Date.now();\n\n for (const [key, status] of Object.entries(statuses)) {\n // Only act on issues where merge failed but review+test both passed\n if (status.mergeStatus !== 'failed') continue;\n if (status.reviewStatus !== 'passed' || status.testStatus !== 'passed') continue;\n\n const isCiCheckFailure = typeof status.mergeNotes === 'string' &&\n status.mergeNotes.includes('failing required checks');\n const issueId = status.issueId || key;\n\n if (isCiCheckFailure) {\n // CI failures may be transient (pending checks, GitHub status lag).\n // Use a separate retry counter that does NOT saturate mergeRetryCount.\n const ciEntry = ciRetryMap.get(issueId) ?? { count: 0, lastAttempt: 0 };\n const timeSinceLastCi = now - ciEntry.lastAttempt;\n\n if (ciEntry.count >= 5) {\n // After 5 CI retries, back off to avoid hammering GitHub API.\n // Notify the work agent exactly once (when count first reaches 5) so it\n // can investigate rather than silently dead-ending the issue.\n if (ciEntry.count === 5) {\n console.log(`[deacon] CI check failure for ${issueId} — retries exhausted, notifying work agent`);\n const ciNotes = status.mergeNotes || 'CI checks are failing on the PR';\n const { writeFeedbackFile } = await import('./feedback-writer.js');\n const ciFileResult = await Effect.runPromise(writeFeedbackFile({\n issueId,\n specialist: 'merge-agent',\n outcome: 'ci-failure',\n summary: 'CI checks still failing after 5 transient retries — merge blocked',\n markdownBody: `## CI Check Failure — Merge Blocked\\n\\n${ciNotes}\\n\\n### Action Required\\n\\nFix the failing CI checks, commit, and push. Overdeck will detect the new commits and re-run the review pipeline automatically.\\n\\nAlternatively:\\n\\n\\`\\`\\`\\npan done ${issueId}\\n\\`\\`\\``,\n }).pipe(Effect.catch((err) => {\n console.error(`[deacon] Failed to write CI failure feedback for ${issueId}:`, err instanceof Error ? err.message : String(err));\n return Effect.succeed({ success: false, error: err instanceof Error ? err.message : String(err) });\n })));\n const agentSession = `agent-${issueId.toLowerCase()}`;\n if (sessionExistsSync(agentSession)) {\n const ciPath = (ciFileResult as { filePath?: string } | undefined)?.filePath;\n const ciMsg = ciPath\n ? `CI checks are failing on the PR after 5 retries.\\n\\nMUST READ: ${ciPath}\\n\\nFix the failures, commit, then run: pan done ${issueId}`\n : `CI checks are failing on the PR after 5 retries. Fix the failures, commit, then run: pan done ${issueId}`;\n await Effect.runPromise(sendKeys(agentSession, ciMsg));\n }\n ciEntry.count++; // increment past 5 so this block only fires once\n ciRetryMap.set(issueId, ciEntry);\n actions.push(`CI retry exhausted for ${issueId} — wrote feedback, notified agent`);\n } else {\n console.log(`[deacon] CI check failure for ${issueId} — max retries (5) exhausted, awaiting agent fix`);\n }\n continue;\n }\n if (timeSinceLastCi < CI_TRANSIENT_RETRY_COOLDOWN_MS) {\n continue; // still in cooldown\n }\n\n ciEntry.count++;\n ciEntry.lastAttempt = now;\n ciRetryMap.set(issueId, ciEntry);\n\n // Notify the work agent to re-submit via pan done, which re-enters the merge\n // queue from scratch. Merge is user-triggered (PAN-354) — deacon cannot\n // auto-retry; the agent must run pan done to create a fresh merge attempt.\n console.log(`[deacon] CI check failure for ${issueId} — notifying agent to re-submit (attempt ${ciEntry.count}/5)`);\n const ciNotes = status.mergeNotes || 'CI checks are failing on the PR';\n const { writeFeedbackFile } = await import('./feedback-writer.js');\n const ciFileResult2 = await Effect.runPromise(writeFeedbackFile({\n issueId,\n specialist: 'merge-agent',\n outcome: 'ci-failure',\n summary: 'CI checks failed at merge — re-submit to re-enter merge queue',\n markdownBody: `## CI Check Failure\\n\\n${ciNotes}\\n\\nCI checks failed at merge time. This may be transient (pending checks, GitHub status lag). Re-submit to re-enter the merge queue:\\n\\n\\`\\`\\`\\npan done ${issueId}\\n\\`\\`\\``,\n }).pipe(Effect.catch((err) => {\n console.error(`[deacon] Failed to write CI failure feedback for ${issueId}:`, err instanceof Error ? err.message : String(err));\n return Effect.succeed({ success: false, error: err instanceof Error ? err.message : String(err) });\n })));\n const agentSessionCi = `agent-${issueId.toLowerCase()}`;\n if (sessionExistsSync(agentSessionCi)) {\n const ciPath2 = (ciFileResult2 as { filePath?: string } | undefined)?.filePath;\n const ciMsg2 = ciPath2\n ? `CI checks failed on the PR for ${issueId}. This may be transient.\\n\\nMUST READ: ${ciPath2}\\n\\nFix any failures, commit, then run: pan done ${issueId}`\n : `CI checks failed on the PR for ${issueId}. This may be transient. Fix any failures, commit, then run: pan done ${issueId}`;\n await Effect.runPromise(sendKeys(agentSessionCi, ciMsg2));\n }\n actions.push(`CI failure notification for ${issueId} (attempt ${ciEntry.count}/5)`);\n continue;\n }\n\n // Timeout failures: the work agent didn't finish the rebase in time.\n // Write feedback and nudge the agent so it knows to continue/finish the rebase.\n // Then retry so the merge can proceed once the agent pushes.\n const isTimeoutFailure = typeof status.mergeNotes === 'string' &&\n (status.mergeNotes.includes('did not push') || status.mergeNotes.includes('stopped before completing'));\n if (isTimeoutFailure) {\n const issueIdForFb = status.issueId || key;\n const lastNudge = timeoutNudgeCooldowns.get(issueIdForFb);\n if (!lastNudge || (now - lastNudge) >= TIMEOUT_NUDGE_COOLDOWN_MS) {\n const timeoutNotes = status.mergeNotes!;\n const { writeFeedbackFile } = await import('./feedback-writer.js');\n await Effect.runPromise(writeFeedbackFile({\n issueId: issueIdForFb,\n specialist: 'merge-agent',\n outcome: 'timeout',\n summary: 'Merge timed out waiting for rebase — please rebase and push',\n markdownBody: `## Merge Timed Out — Rebase Required\\n\\n${timeoutNotes}\\n\\n### Action Required\\n\\nThe merge was requested but the rebased branch was not pushed in time. Please:\\n\\n1. Run \\`git fetch origin\\` and \\`git rebase origin/main\\` (or the target branch)\\n2. Resolve any conflicts\\n3. Run \\`git push --force-with-lease\\`\\n4. Run \\`pan done ${issueIdForFb}\\`\\n\\nAfter pushing, the merge will be retried automatically.`,\n }).pipe(Effect.catch((err) => {\n console.error(`[deacon] Failed to write timeout feedback for ${issueIdForFb}:`, err instanceof Error ? err.message : String(err));\n return Effect.void;\n })));\n const agentSession = `agent-${issueIdForFb.toLowerCase()}`;\n if (sessionExistsSync(agentSession)) {\n await Effect.runPromise(sendKeys(agentSession,\n `Merge timed out — the rebased branch was not pushed in time. Please rebase onto the target branch, resolve any conflicts, push with --force-with-lease, then run \"pan done ${issueIdForFb}\". After pushing, the merge will proceed automatically.`\n ));\n }\n timeoutNudgeCooldowns.set(issueIdForFb, now);\n actions.push(`Timeout failure for ${issueIdForFb} — wrote feedback, nudged work agent`);\n } else {\n actions.push(`Timeout failure for ${issueIdForFb} — nudge on cooldown (${Math.round((now - lastNudge) / 60000)}m ago)`);\n }\n }\n\n // Circuit breaker: max retries to avoid infinite loop on permanent failures\n const retryCount = status.mergeRetryCount || 0;\n if (retryCount >= FAILED_MERGE_MAX_RETRIES) {\n console.log(`[deacon] Failed-merge circuit breaker for ${key} (${retryCount}/${FAILED_MERGE_MAX_RETRIES} retries used)`);\n continue;\n }\n\n // Cooldown: wait at least 30 min after the merge failure before retrying\n if (status.updatedAt) {\n const statusAge = now - new Date(status.updatedAt).getTime();\n if (statusAge < FAILED_MERGE_RETRY_COOLDOWN_MS) continue;\n }\n\n // Per-issue in-memory cooldown to avoid re-triggering on the same patrol cycle\n const lastRetry = failedMergeRetryCooldowns.get(key);\n if (lastRetry && (now - lastRetry) < FAILED_MERGE_RETRY_COOLDOWN_MS) continue;\n\n failedMergeRetryCooldowns.set(key, now);\n\n const nextRetry = retryCount + 1;\n console.log(`[deacon] Auto-retrying failed merge for ${issueId} (attempt ${nextRetry}/${FAILED_MERGE_MAX_RETRIES})`);\n\n setReviewStatusSync(issueId, {\n mergeStatus: 'pending',\n readyForMerge: true,\n mergeRetryCount: nextRetry,\n });\n\n actions.push(`Reset failed merge for ${issueId} — retry ${nextRetry}/${FAILED_MERGE_MAX_RETRIES} (readyForMerge restored)`);\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error in checkFailedMergeRetry:', msg);\n }\n\n return actions;\n}\n\n\n// Track per-agent cooldowns for first-completion nudges\nconst firstCompletionCooldowns = new Map<string, number>();\nconst FIRST_COMPLETION_IDLE_MS = 10 * 60 * 1000; // 10 minutes idle before nudging\nconst FIRST_COMPLETION_COOLDOWN_MS = 15 * 60 * 1000; // 15 minutes between nudges\nfunction recordAutoCloseOutFailure(issueId: string, message: string): void {\n console.warn(`[deacon] Auto close-out failed for ${issueId}: ${message}`);\n setReviewStatusSync(issueId, {\n mergeNotes: `Auto close-out failed: ${message}`,\n updatedAt: new Date().toISOString(),\n });\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n issueId,\n message: `Auto close-out failed for ${issueId}: ${message}`,\n });\n}\n\nexport async function autoCloseOut(now = new Date()): Promise<string[]> {\n const closeOutConfig = (await Effect.runPromise(loadCloisterConfig())).close_out;\n if (closeOutConfig?.auto !== true) return [];\n\n // Evict stale cache entries before each patrol cycle\n sweepAutoCloseOutCache();\n\n const delayMinutes = Math.max(0, closeOutConfig.auto_delay_minutes ?? 60);\n const cutoff = now.getTime() - delayMinutes * 60 * 1000;\n const actions: string[] = [];\n const statuses = loadReviewStatuses();\n\n const candidates: Array<{ issueId: string }> = [];\n for (const [key, status] of Object.entries(statuses)) {\n const issueId = (status.issueId || key).toUpperCase();\n if (status.mergeStatus !== 'merged') continue;\n if (status.stuck || status.deaconIgnored) continue;\n\n const updatedAt = Date.parse(status.updatedAt || '');\n if (!Number.isFinite(updatedAt) || updatedAt > cutoff) continue;\n\n candidates.push({ issueId });\n }\n\n const tasks = candidates.map(({ issueId }) => Effect.tryPromise({\n try: async () => {\n let canonicalState: string | null;\n try {\n canonicalState = await getAutoCloseOutCanonicalState(issueId);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n recordAutoCloseOutFailure(issueId, message);\n return `Auto close-out failed for ${issueId}: ${message}`;\n }\n if (canonicalState !== 'verifying_on_main') return null;\n\n const resolvedProject = resolveProjectFromIssueSync(issueId);\n if (!resolvedProject) {\n const message = 'no project configured';\n recordAutoCloseOutFailure(issueId, message);\n return `Auto close-out failed for ${issueId}: ${message}`;\n }\n\n const ghResolved = resolveGitHubIssueSync(issueId);\n const ctx = {\n issueId,\n projectPath: resolvedProject.projectPath,\n auto: true,\n ...(ghResolved.isGitHub\n ? { github: { owner: ghResolved.owner, repo: ghResolved.repo, number: ghResolved.number } }\n : {}),\n };\n\n try {\n const { closeOut } = await import('../lifecycle/workflows.js');\n // PAN-1249: closeOut returns Effect<WorkflowResult>; bridge to Promise.\n const result = await Effect.runPromise(closeOut(ctx));\n if (!result.success) {\n const failed = result.steps.find(step => !step.success && !step.skipped);\n throw new Error(failed?.error ?? 'closeOut workflow failed');\n }\n const message = `Auto close-out completed for ${issueId}`;\n console.log(`[deacon] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'info', issueId, message });\n return message;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n recordAutoCloseOutFailure(issueId, message);\n return `Auto close-out failed for ${issueId}: ${message}`;\n }\n },\n catch: (cause) => cause,\n }));\n\n const results = await Effect.runPromise(withConcurrencyLimit(tasks, 5));\n for (const result of results) {\n if (result !== null) actions.push(result);\n }\n\n return actions;\n}\n\n/**\n * Detect work agents that finished implementation but never called `pan done`.\n *\n * This is the Layer 3 safety net. Layer 2 (work-agent-stop-hook) should catch most\n * cases within seconds of the agent going idle. This catches agents where the stop-hook\n * failed, was skipped, or where the AI analysis was inconclusive.\n *\n * Heuristics: agent is idle for >10 minutes, no completion marker exists, no review\n * status exists (meaning it never entered the specialist pipeline), and the agent\n * has committed code (git log shows commits on the feature branch).\n */\nexport async function checkFirstCompletionAgents(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const agents = listRunningAgentsSync();\n const now = Date.now();\n\n for (const agent of agents) {\n // Only check work agents (agent-min-XXX, agent-pan-XXX)\n // Guard against agents with undefined id (planning agents, test artifacts, etc.)\n const agentId = agent.id;\n if (!agentId || !agentId.startsWith('agent-') || !agent.tmuxActive) continue;\n if (agentId.startsWith('specialist-')) continue;\n\n // Skip if completion marker already exists (or was already processed by cloister)\n const completedFile = join(AGENTS_DIR, agent.id, 'completed');\n const processedMarker = join(AGENTS_DIR, agent.id, 'completed.processed');\n if (existsSync(completedFile) || existsSync(processedMarker)) continue;\n\n // Check idle duration and idle state via Stop hook\n // isAgentIdleForNudge uses FIRST_COMPLETION_IDLE_MS as the stale-active threshold:\n // if the agent's heartbeat is older than the idle minimum, it's safe to treat as idle.\n if (!isAgentIdleForNudge(agent.id, FIRST_COMPLETION_IDLE_MS)) continue;\n\n const runtimeState = getAgentRuntimeStateSync(agent.id);\n // PAN-2946: agents with no runtime record yet (fresh spawn, wiped state)\n // must not crash the patrol's first-completion pass.\n if (!runtimeState) continue;\n const lastActivity = new Date(runtimeState.lastActivity ?? 0);\n const idleMs = now - lastActivity.getTime();\n if (idleMs < FIRST_COMPLETION_IDLE_MS) continue;\n\n // Check cooldown\n const lastNudge = firstCompletionCooldowns.get(agent.id);\n if (lastNudge && (now - lastNudge) < FIRST_COMPLETION_COOLDOWN_MS) continue;\n\n // HARD GATE: Never nudge agents that have been through the review pipeline.\n // Check review-status.json — if ANY entry exists for this issue, the agent\n // has entered the specialist pipeline and must NOT receive a \"pan done\" nudge.\n // (Dead-end detection handles agents stuck in review/test cycles.)\n const issueId = agent.issueId || agent.id.replace('agent-', '').toUpperCase();\n const issueKey = issueId.toLowerCase();\n try {\n const statuses = loadReviewStatuses();\n // Keys are stored in original case (e.g., \"MIN-727\") — check all case variants\n const hasStatus = statuses[issueKey] || statuses[issueId] || statuses[issueId.toUpperCase()];\n if (hasStatus) {\n console.log(`[deacon] First-completion gate: skipping ${agent.id} — has review status entry (readyForMerge=${hasStatus.readyForMerge ?? false})`);\n continue;\n }\n } catch { /* load error, proceed with check */ }\n\n // HARD GATE: Also check for review feedback files in the workspace.\n // If a feedback directory exists and is non-empty, a review agent has already\n // processed this workspace — never send a \"pan done\" nudge.\n const agentStateForGate = getAgentStateSync(agent.id);\n if (agentStateForGate?.workspace) {\n const feedbackDir = join(agentStateForGate.workspace, '.pan', 'feedback');\n if (existsSync(feedbackDir)) {\n try {\n const feedbackFiles = readdirSync(feedbackDir);\n if (feedbackFiles.length > 0) {\n console.log(`[deacon] First-completion gate: skipping ${agent.id} — has ${feedbackFiles.length} review feedback file(s) in .pan/feedback/`);\n continue;\n }\n } catch { /* can't read feedback dir */ }\n }\n }\n\n // PAN-1185: SWARM slot agents must NOT receive issue-level `pan done`\n // nudges. They work on a single plan item; the issue-level done flow opens\n // a premature feature → main PR. Detect slots by workspace path suffix\n // `-slot-N/` — the workspace dir convention is stable across PAN-1176.\n if (agentStateForGate?.workspace && /-slot-\\d+\\/?$/.test(agentStateForGate.workspace)) {\n console.log(`[deacon] First-completion gate: skipping ${agent.id} — SWARM slot agent (workspace: ${agentStateForGate.workspace})`);\n continue;\n }\n\n // Check if the agent has commits (sign that work was done)\n const agentState = getAgentStateSync(agent.id);\n if (!agentState?.workspace || !existsSync(agentState.workspace)) continue;\n\n // For polyrepo workspaces, check inside sub-repos (fe/, api/, etc.)\n // For monorepo workspaces, check the workspace root directly\n let hasCommits = false;\n try {\n const { stdout: gitLog } = await execAsync(\n 'git log --oneline -3 2>/dev/null',\n { cwd: agentState.workspace }\n );\n hasCommits = gitLog.trim().length > 0;\n } catch {\n // Workspace root may not be a git repo (polyrepo) — check subdirectories\n try {\n const subdirs = readdirSync(agentState.workspace, { withFileTypes: true })\n .filter(d => d.isDirectory() && !d.name.startsWith('.'));\n for (const sub of subdirs) {\n try {\n const { stdout: subLog } = await execAsync(\n 'git log --oneline -3 2>/dev/null',\n { cwd: join(agentState.workspace, sub.name) }\n );\n if (subLog.trim().length > 0) {\n hasCommits = true;\n break;\n }\n } catch { /* not a git repo */ }\n }\n } catch { /* can't read workspace dir */ }\n }\n if (!hasCommits) continue; // No commits — agent may not have started yet\n\n // All heuristics passed: agent likely forgot pan done\n const idleMinutes = Math.round(idleMs / 60000);\n console.log(`[deacon] First-completion gap detected: ${agent.id} (${issueId}) idle for ${idleMinutes}m with commits but no completion marker`);\n\n firstCompletionCooldowns.set(agent.id, now);\n\n try {\n const nudgeMessage = `You appear to have stopped working without calling \\`pan done\\`. If your implementation is complete, run this now:\\n\\npan done ${issueId} -c \"Implementation complete\"\\n\\nIf you still have remaining tasks, continue working on them.`;\n await Effect.runPromise(sendKeys(agent.id, nudgeMessage));\n actions.push(`First-completion nudge: ${agent.id} (idle ${idleMinutes}m)`);\n console.log(`[deacon] Sent first-completion nudge to ${agent.id}`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Failed to send first-completion nudge to ${agent.id}:`, msg);\n }\n }\n } catch (error: unknown) {\n console.error('[deacon] Error in first-completion detection:', error);\n }\n\n return actions;\n}\n"],"mappings":"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"}
@@ -18,7 +18,7 @@ async function processPendingLifecycleForPatrol(pendingFile) {
18
18
  }
19
19
  console.log(`[deacon] Processing pending post-merge lifecycle for ${pending.issueId} (age: ${Math.round(age / 1e3)}s)`);
20
20
  try {
21
- const { postMergeLifecycle } = await import("./merge-agent-B8YsWRDu.js");
21
+ const { postMergeLifecycle } = await import("./merge-agent-BRSz26Oh.js");
22
22
  await postMergeLifecycle(pending.issueId, pending.projectPath, pending.sourceBranch, { skipDeploy: true });
23
23
  await settlePendingLifecycleClaim(claim, pending.issueId, true);
24
24
  return `Processed pending post-merge lifecycle for ${pending.issueId}`;
@@ -30,4 +30,4 @@ async function processPendingLifecycleForPatrol(pendingFile) {
30
30
  //#endregion
31
31
  export { processPendingLifecycleForPatrol };
32
32
 
33
- //# sourceMappingURL=deacon-pending-lifecycle-Cxu6Rezs.js.map
33
+ //# sourceMappingURL=deacon-pending-lifecycle-DVHjOLje.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"deacon-pending-lifecycle-Cxu6Rezs.js","names":[],"sources":["../../src/lib/cloister/deacon-pending-lifecycle.ts"],"sourcesContent":["import {\n claimPendingLifecycleFile,\n settlePendingLifecycleClaim,\n} from './pending-lifecycle-claim.js';\n\ninterface PendingLifecycleData {\n issueId: string;\n projectPath: string;\n sourceBranch?: string;\n timestamp?: number;\n}\n\nexport async function processPendingLifecycleForPatrol(\n pendingFile: string,\n): Promise<string | null> {\n const claim = await claimPendingLifecycleFile(pendingFile);\n if (!claim) return null;\n\n let pending: PendingLifecycleData;\n try {\n pending = JSON.parse(claim.raw) as PendingLifecycleData;\n } catch (error) {\n await claim.discard();\n throw error;\n }\n\n const age = Date.now() - (pending.timestamp ?? 0);\n if (age >= 60 * 60 * 1000) {\n await claim.discard();\n console.log(`[deacon] Discarded stale pending post-merge claim (age: ${Math.round(age / 60000)}m)`);\n return null;\n }\n\n console.log(`[deacon] Processing pending post-merge lifecycle for ${pending.issueId} (age: ${Math.round(age / 1000)}s)`);\n try {\n const { postMergeLifecycle } = await import('./merge-agent.js');\n await postMergeLifecycle(pending.issueId, pending.projectPath, pending.sourceBranch, { skipDeploy: true });\n await settlePendingLifecycleClaim(claim, pending.issueId, true);\n return `Processed pending post-merge lifecycle for ${pending.issueId}`;\n } catch (error) {\n await settlePendingLifecycleClaim(claim, pending.issueId, false);\n throw error;\n }\n}\n"],"mappings":";;AAYA,eAAsB,iCACpB,aACwB;CACxB,MAAM,QAAQ,MAAM,0BAA0B,YAAY;AAC1D,KAAI,CAAC,MAAO,QAAO;CAEnB,IAAI;AACJ,KAAI;AACF,YAAU,KAAK,MAAM,MAAM,IAAI;UACxB,OAAO;AACd,QAAM,MAAM,SAAS;AACrB,QAAM;;CAGR,MAAM,MAAM,KAAK,KAAK,IAAI,QAAQ,aAAa;AAC/C,KAAI,OAAO,OAAU,KAAM;AACzB,QAAM,MAAM,SAAS;AACrB,UAAQ,IAAI,2DAA2D,KAAK,MAAM,MAAM,IAAM,CAAC,IAAI;AACnG,SAAO;;AAGT,SAAQ,IAAI,wDAAwD,QAAQ,QAAQ,SAAS,KAAK,MAAM,MAAM,IAAK,CAAC,IAAI;AACxH,KAAI;EACF,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,QAAM,mBAAmB,QAAQ,SAAS,QAAQ,aAAa,QAAQ,cAAc,EAAE,YAAY,MAAM,CAAC;AAC1G,QAAM,4BAA4B,OAAO,QAAQ,SAAS,KAAK;AAC/D,SAAO,8CAA8C,QAAQ;UACtD,OAAO;AACd,QAAM,4BAA4B,OAAO,QAAQ,SAAS,MAAM;AAChE,QAAM"}
1
+ {"version":3,"file":"deacon-pending-lifecycle-DVHjOLje.js","names":[],"sources":["../../src/lib/cloister/deacon-pending-lifecycle.ts"],"sourcesContent":["import {\n claimPendingLifecycleFile,\n settlePendingLifecycleClaim,\n} from './pending-lifecycle-claim.js';\n\ninterface PendingLifecycleData {\n issueId: string;\n projectPath: string;\n sourceBranch?: string;\n timestamp?: number;\n}\n\nexport async function processPendingLifecycleForPatrol(\n pendingFile: string,\n): Promise<string | null> {\n const claim = await claimPendingLifecycleFile(pendingFile);\n if (!claim) return null;\n\n let pending: PendingLifecycleData;\n try {\n pending = JSON.parse(claim.raw) as PendingLifecycleData;\n } catch (error) {\n await claim.discard();\n throw error;\n }\n\n const age = Date.now() - (pending.timestamp ?? 0);\n if (age >= 60 * 60 * 1000) {\n await claim.discard();\n console.log(`[deacon] Discarded stale pending post-merge claim (age: ${Math.round(age / 60000)}m)`);\n return null;\n }\n\n console.log(`[deacon] Processing pending post-merge lifecycle for ${pending.issueId} (age: ${Math.round(age / 1000)}s)`);\n try {\n const { postMergeLifecycle } = await import('./merge-agent.js');\n await postMergeLifecycle(pending.issueId, pending.projectPath, pending.sourceBranch, { skipDeploy: true });\n await settlePendingLifecycleClaim(claim, pending.issueId, true);\n return `Processed pending post-merge lifecycle for ${pending.issueId}`;\n } catch (error) {\n await settlePendingLifecycleClaim(claim, pending.issueId, false);\n throw error;\n }\n}\n"],"mappings":";;AAYA,eAAsB,iCACpB,aACwB;CACxB,MAAM,QAAQ,MAAM,0BAA0B,YAAY;AAC1D,KAAI,CAAC,MAAO,QAAO;CAEnB,IAAI;AACJ,KAAI;AACF,YAAU,KAAK,MAAM,MAAM,IAAI;UACxB,OAAO;AACd,QAAM,MAAM,SAAS;AACrB,QAAM;;CAGR,MAAM,MAAM,KAAK,KAAK,IAAI,QAAQ,aAAa;AAC/C,KAAI,OAAO,OAAU,KAAM;AACzB,QAAM,MAAM,SAAS;AACrB,UAAQ,IAAI,2DAA2D,KAAK,MAAM,MAAM,IAAM,CAAC,IAAI;AACnG,SAAO;;AAGT,SAAQ,IAAI,wDAAwD,QAAQ,QAAQ,SAAS,KAAK,MAAM,MAAM,IAAK,CAAC,IAAI;AACxH,KAAI;EACF,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,QAAM,mBAAmB,QAAQ,SAAS,QAAQ,aAAa,QAAQ,cAAc,EAAE,YAAY,MAAM,CAAC;AAC1G,QAAM,4BAA4B,OAAO,QAAQ,SAAS,KAAK;AAC/D,SAAO,8CAA8C,QAAQ;UACtD,OAAO;AACd,QAAM,4BAA4B,OAAO,QAAQ,SAAS,MAAM;AAChE,QAAM"}
@@ -1,10 +1,10 @@
1
1
  import { Ct as flushAllPendingAutoCommits, Tt as init_auto_commit } from "./config-yaml-BwOEANMr.js";
2
2
  import { c as init_activity_logger, l as setActivityEventStoreProvider } from "./activity-logger-BAVLrkR7.js";
3
3
  import { G as init_agents, f as getAgentState } from "./agent-state-Bb3UUxQm.js";
4
- import { t as getCloisterService } from "./service-0hbSVeHz.js";
4
+ import { t as getCloisterService } from "./service-BLb_CyPL.js";
5
5
  import { i as ensureInternalTokenSync, s as init_internal_token } from "./internal-token-zY75jrRJ.js";
6
6
  import { d as getReviewStatusSync, p as init_review_status } from "./review-status-DBnR5t4z.js";
7
- import { F as setAgentStatusChangedNotifier, I as setAgentStoppedNotifier, L as setMergeReadyNotifier, M as resetPatrolHeartbeatForStartup, N as runPatrol } from "./deacon-CQxv6Qwv.js";
7
+ import { F as setAgentStatusChangedNotifier, I as setAgentStoppedNotifier, L as setMergeReadyNotifier, M as resetPatrolHeartbeatForStartup, N as runPatrol } from "./deacon-TPYssFYt.js";
8
8
  import { n as setCloisterEventStoreProvider } from "./event-store-provider-BKvk2rDj.js";
9
9
  import { t as emitReviewStatusChanged } from "./review-status-emit-Cg4tOb8U.js";
10
10
  import { n as init_deacon_event_client, t as createDeaconEventClient } from "./deacon-event-client-BKOL4AcL.js";
@@ -1,5 +1,5 @@
1
1
  import { Fi as init_paths, gi as OVERDECK_HOME } from "./config-yaml-BwOEANMr.js";
2
- import { t as getBuildInfo } from "./build-info-DXfOjbg3.js";
2
+ import { t as getBuildInfo } from "./build-info-DTX8JwVP.js";
3
3
  import { a as emitActivityEntrySync, c as init_activity_logger, o as emitActivityTtsSync } from "./activity-logger-BAVLrkR7.js";
4
4
  import { a as loadCloisterConfigSync, r as init_config } from "./config-DC75T4Mr.js";
5
5
  import { r as computeBuildStaleness } from "./staleness-D8UTEXV5.js";
@@ -342,4 +342,4 @@ async function runScheduledDeployPatrol() {
342
342
  //#endregion
343
343
  export { runScheduledDeployPatrol };
344
344
 
345
- //# sourceMappingURL=deploy-patrol-DxnpZcnl.js.map
345
+ //# sourceMappingURL=deploy-patrol-BXjlvoV3.js.map