@straiffi/archon 1.7.7 → 1.7.9

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 (140) hide show
  1. package/dist/client/assets/ActivityView-CDLQOXZm.js +1 -0
  2. package/dist/client/assets/GitHubActionsDialog-DYxLX2HS.js +1 -0
  3. package/dist/client/assets/VerificationDialog-DEKvZG75.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-DVbmGv0b.js → architectureDiagram-3BPJPVTR-ByNXcCIO.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-BsxPuJKK.js → blockDiagram-GPEHLZMM-ByykLAaG.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-9Jjpu2R1.js → c4Diagram-AAUBKEIU-CmWA4Pli.js} +1 -1
  7. package/dist/client/assets/channel-BvcRv90i.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-Dzljq_5a.js → chunk-2J33WTMH-CTgbXRh4.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-CiBHaLdV.js → chunk-3OPIFGDE-D3FajVfq.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-DZFrI-_H.js → chunk-5ZQYHXKU-CUMg_Vfo.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-u519MeJ7.js → chunk-727SXJPM-Oc9MgxZD.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-DC91Nw9K.js → chunk-AQP2D5EJ-5JZboHeP.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CR7cwH-D.js → chunk-CSCIHK7Q-BpkSMHjB.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-DJwQEfa_.js → chunk-KSCS5N6A-BPBMAswO.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-w6TWSOTT.js → chunk-L5ZTLDWV-BA2sHmu7.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-Dfw_jEjR.js → chunk-LZXEDZCA-Blc9q-fQ.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-DHdJgCA7.js → chunk-ND2GUHAM-GlVS74JP.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-GvOuUiUy.js → chunk-NZK2D7GU-fctBFnAm.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O--r1VQgDc.js → chunk-O5CBEL6O-eGs4DDLJ.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-Xgc6shf5.js → chunk-WU5MYG2G-vxa50E7g.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-IuovJyxl.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BBJ3avX6.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-IcfTsQ6f.js → dagre-BM42HDAG-DXGXNVUp.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-D8Z4_NSQ.js → diagram-2AECGRRQ-BRYr1GvB.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-CIjC8QzH.js → diagram-5GNKFQAL-BcwgMag4.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-BBbYFygn.js → diagram-KO2AKTUF-Delk7lbr.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-lZstJdXv.js → diagram-LMA3HP47-BVtc_fjo.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-Cnqij60i.js → diagram-OG6HWLK6-weIPvUz5.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-1DAJwSbR.js → erDiagram-TEJ5UH35-DKde70dA.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-DIQkR95e.js → flowDiagram-I6XJVG4X-B7Jrk-6c.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-mDGbRM53.js → ganttDiagram-6RSMTGT7-CDzWDgnA.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BQirGZuE.js → gitGraphDiagram-PVQCEYII-DYZCryuN.js} +1 -1
  33. package/dist/client/assets/index-BlKOx9Vf.css +2 -0
  34. package/dist/client/assets/index-CmaBDvIB.js +273 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-CljoXByt.js → infoDiagram-5YYISTIA-DmQOgVKK.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-ouY7AiSo.js → ishikawaDiagram-YF4QCWOH-DCpu-q-Z.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-BiI9xI0r.js → journeyDiagram-JHISSGLW-DhBoglhI.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-CvuW7aYT.js → kanban-definition-UN3LZRKU-BSjvGFTY.js} +1 -1
  39. package/dist/client/assets/{line-CoE97hT8.js → line-ClKBZwDr.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-BHswQsmF.js → mermaid-parser.core-BRA4mJ40.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-a9PzZlL1.js → mermaid.core-BZ7kSfEt.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-CIkEQqLV.js → mindmap-definition-RKZ34NQL-DoUdLHgj.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-BIvcv2M5.js → pieDiagram-4H26LBE5-NVnzqltb.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BWCVll7J.js → quadrantDiagram-W4KKPZXB-DOITgkan.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-w5l0CZn2.js → requirementDiagram-4Y6WPE33-D0VPxrFE.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BuKReL9w.js → sankeyDiagram-5OEKKPKP-Bx0uU7xV.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Br5KpukA.js → sequenceDiagram-3UESZ5HK-B4lFL6cO.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-BfFCsPCI.js → stateDiagram-AJRCARHV-CsLVsMZ4.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CvrvaIdF.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-BrDP2iO0.js → timeline-definition-PNZ67QCA-BkcYpPFI.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-C7lGYnr9.js → vennDiagram-CIIHVFJN-Xc5f2sRQ.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BRKJRuEF.js → wardleyDiagram-YWT4CUSO-n3Dpk02Q.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BjaB0hQ8.js → xychartDiagram-2RQKCTM6-BTMzF8zg.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +460 -2
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +542 -30
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/activeAutomationChild.js +31 -0
  60. package/dist/server/lib/activeAutomationChild.js.map +1 -0
  61. package/dist/server/lib/automationCheckGate.js +341 -0
  62. package/dist/server/lib/automationCheckGate.js.map +1 -0
  63. package/dist/server/lib/automationProgression.js +20 -21
  64. package/dist/server/lib/automationProgression.js.map +1 -1
  65. package/dist/server/lib/automationReconciliation.js +35 -2
  66. package/dist/server/lib/automationReconciliation.js.map +1 -1
  67. package/dist/server/lib/automationResumeDecision.js +61 -1
  68. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  69. package/dist/server/lib/automationStageDetails.js +266 -41
  70. package/dist/server/lib/automationStageDetails.js.map +1 -1
  71. package/dist/server/lib/buildCompletionContinuation.js +109 -0
  72. package/dist/server/lib/buildCompletionContinuation.js.map +1 -0
  73. package/dist/server/lib/buildSequences.js +81 -0
  74. package/dist/server/lib/buildSequences.js.map +1 -1
  75. package/dist/server/lib/buildTicketCommit.js +219 -0
  76. package/dist/server/lib/buildTicketCommit.js.map +1 -0
  77. package/dist/server/lib/bundleAutomationReadiness.js +7 -0
  78. package/dist/server/lib/bundleAutomationReadiness.js.map +1 -0
  79. package/dist/server/lib/bundleReviewOperations.js +17 -5
  80. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  81. package/dist/server/lib/chatOperations.js +2 -2
  82. package/dist/server/lib/chatOperations.js.map +1 -1
  83. package/dist/server/lib/checkDefinitions.js +278 -0
  84. package/dist/server/lib/checkDefinitions.js.map +1 -0
  85. package/dist/server/lib/checkGateEvaluations.js +295 -0
  86. package/dist/server/lib/checkGateEvaluations.js.map +1 -0
  87. package/dist/server/lib/checkRunner.js +674 -0
  88. package/dist/server/lib/checkRunner.js.map +1 -0
  89. package/dist/server/lib/checkRuns.js +736 -0
  90. package/dist/server/lib/checkRuns.js.map +1 -0
  91. package/dist/server/lib/checkWorkspaceIdentity.js +207 -0
  92. package/dist/server/lib/checkWorkspaceIdentity.js.map +1 -0
  93. package/dist/server/lib/executionReconciliation.js +79 -2
  94. package/dist/server/lib/executionReconciliation.js.map +1 -1
  95. package/dist/server/lib/executionRetry.js +14 -6
  96. package/dist/server/lib/executionRetry.js.map +1 -1
  97. package/dist/server/lib/executionRetryContext.js +3 -3
  98. package/dist/server/lib/executionRetryContext.js.map +1 -1
  99. package/dist/server/lib/executionRunRetention.js +41 -3
  100. package/dist/server/lib/executionRunRetention.js.map +1 -1
  101. package/dist/server/lib/executionRuns.js +135 -2
  102. package/dist/server/lib/executionRuns.js.map +1 -1
  103. package/dist/server/lib/executionTimeline.js +52 -1
  104. package/dist/server/lib/executionTimeline.js.map +1 -1
  105. package/dist/server/lib/projectAutoConfig.js +47 -65
  106. package/dist/server/lib/projectAutoConfig.js.map +1 -1
  107. package/dist/server/lib/projects.js +99 -7
  108. package/dist/server/lib/projects.js.map +1 -1
  109. package/dist/server/lib/requiredCheckPolicy.js +93 -0
  110. package/dist/server/lib/requiredCheckPolicy.js.map +1 -0
  111. package/dist/server/lib/shutdown.js +4 -4
  112. package/dist/server/lib/shutdown.js.map +1 -1
  113. package/dist/server/lib/testCommandRunner.js +3 -2
  114. package/dist/server/lib/testCommandRunner.js.map +1 -1
  115. package/dist/server/lib/testSessions.js +2 -6
  116. package/dist/server/lib/testSessions.js.map +1 -1
  117. package/dist/server/lib/ticketAutomationCommit.js +361 -108
  118. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  119. package/dist/server/lib/tickets.js +21 -1
  120. package/dist/server/lib/tickets.js.map +1 -1
  121. package/dist/server/lib/types.js +2 -0
  122. package/dist/server/lib/types.js.map +1 -1
  123. package/dist/server/workers/build.js +50 -81
  124. package/dist/server/workers/build.js.map +1 -1
  125. package/dist/server/workers/chat.js +13 -6
  126. package/dist/server/workers/chat.js.map +1 -1
  127. package/dist/server/workers/followUp.js +53 -30
  128. package/dist/server/workers/followUp.js.map +1 -1
  129. package/dist/server/workers/review.js +2 -10
  130. package/dist/server/workers/review.js.map +1 -1
  131. package/package.json +1 -1
  132. package/dist/client/assets/ActivityView-Btpsv-5U.js +0 -1
  133. package/dist/client/assets/GitHubActionsDialog-DRlIMgSB.js +0 -1
  134. package/dist/client/assets/TestsDialog-DSWDkKPV.js +0 -4
  135. package/dist/client/assets/channel-Lic13vb5.js +0 -1
  136. package/dist/client/assets/classDiagram-4FO5ZUOK-DACLwQIJ.js +0 -1
  137. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CKi6c1d9.js +0 -1
  138. package/dist/client/assets/index-DQt4dcwc.css +0 -2
  139. package/dist/client/assets/index-W9OCqkMU.js +0 -273
  140. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-lhIQ8FTe.js +0 -1
@@ -28,8 +28,8 @@ import { emitMobileRealtime, getMobileAccessStatus, regenerateMobilePairingChall
28
28
  import { validateDesktopMobileAccessRequest } from './lib/mobileAccessSecurity.js';
29
29
  import { refreshChatSessionUsageSnapshot, refreshSpecSessionUsageSnapshot, stopAllBuildAgents, stopAllReviewAgents, stopBuildAgent, stopReviewAgent } from './lib/agent.js';
30
30
  import { runLightweightPrompt } from './lib/lightweightPrompt.js';
31
- import { startBuildChain, stopBuildChain } from './lib/buildChains.js';
32
- import { activateQueuedBuildSequenceTicket, compensateBuildSequenceAcceptance, enqueueBundledBuild, getDependencyOrderedBundleTicketIds, hasActiveSequenceForBundle, removeTicketFromBuildSequence, stopBuildSequence, } from './lib/buildSequences.js';
31
+ import { advanceBuildChain, startBuildChain, stopBuildChain } from './lib/buildChains.js';
32
+ import { activateQueuedBuildSequenceTicket, compensateBuildSequenceAcceptance, enqueueBundledBuild, getDependencyOrderedBundleTicketIds, getBuildSequenceIdForTicket, hasActiveSequenceForBundle, removeTicketFromBuildSequence, stopBuildSequence, } from './lib/buildSequences.js';
33
33
  import { prepareTicketForBuild } from './lib/buildFlow.js';
34
34
  import { getBundlePlanMutationBlockerById, shouldBlockPlanForBundleMutationById, } from './lib/bundleActivity.js';
35
35
  import { getDiscoveredModels, normalizeModelId, normalizeTicketModelId } from './lib/models.js';
@@ -47,12 +47,14 @@ import { exportProjectMemoryArchive, importProjectMemoryArchive } from './lib/pr
47
47
  import { buildReviewFindingsResponse, computeDiffSignature, getLatestReviewRunForContext, listReviewFindings } from './lib/reviewFindings.js';
48
48
  import { formatBuildOpenQuestionAnswersPrompt, getLatestBuildOpenQuestionBatchStatus, hasActiveBuildOpenQuestionBatch, listBuildOpenQuestionsForBundle, loadBuildOpenQuestionsByBundleIds, } from './lib/buildOpenQuestions.js';
49
49
  import { acceptBuildOpenQuestionResolution } from './lib/buildOpenQuestionOperations.js';
50
- import { completeAutomationProgression, createAutomationReviewFix, finishAutomationReviewFix, getAutomationProgression, recoverLegacyAutomationProgression, recoverStoppedSideEffectProgression, runAutomationProgression, stopAutomationProgression, waitAutomationProgression } from './lib/automationProgression.js';
50
+ import { completeAutomationProgression, createAutomationReviewFix, ensureAutomationProgression, finishAutomationReviewFix, getAutomationProgression, recoverLegacyAutomationProgression, recoverStoppedSideEffectProgression, runAutomationProgression, stopAutomationProgression, waitAutomationProgression } from './lib/automationProgression.js';
51
51
  import { reconcileAutomationStage } from './lib/automationReconciliation.js';
52
52
  import { pausedRunBelongsToProgression, resolveAutomationResumeDecision } from './lib/automationResumeDecision.js';
53
53
  import { getAutomationStageDetails, } from './lib/automationStageDetails.js';
54
54
  import { buildAutomationFixInstruction, clearAutomationPause, countBlockingFindings, depthAtLeast, getBundleAutomationState, isAutomationDepth, recordAutomationPause, resolveAutomationConfig, upsertBundleAutomationState, } from './lib/ticketAutomation.js';
55
- import { runAutomationCommitChain } from './lib/ticketAutomationCommit.js';
55
+ import { resolveAutomationCheckGateResume, runAutomationCommitChain, runAutomationCommitOperation } from './lib/ticketAutomationCommit.js';
56
+ import { canMutateBuildTicketCommitCheckpoint, resolveBuildTicketCommitCheckpointLineage } from './lib/buildTicketCommit.js';
57
+ import { advanceSuccessfulBuildTicketAfterCheckpoint, compensateBuildSequenceTicketActivation } from './lib/buildCompletionContinuation.js';
56
58
  import { getDiscoveredSkills, normalizeSkillNames } from './lib/skills.js';
57
59
  import { clearAllPreviewProxies } from './lib/previewProxy.js';
58
60
  import { createExecutionRun, addExecutionRunLink, addExecutionRunLinks, getExecutionRun, hasActiveBuildExecutionRunForTicket, getGitHubActionsExecutionDetails, getTestExecutionDetails, GitHubActionsDispatchConflictError, listExecutionRunLinks, listExecutionRuns, listLifecycleEvents, listProjectLifecycleEvents, toCanonicalLifecycleEvent, transitionExecutionRun, withExecutionRunTransaction, } from './lib/executionRuns.js';
@@ -61,7 +63,7 @@ import { listExecutionTimeline } from './lib/executionTimeline.js';
61
63
  import { loadExecutionArtifactSummariesForRuns, openExecutionArtifactForRetrieval, } from './lib/executionArtifacts.js';
62
64
  import { cleanupAllExecutionEvidence, EXECUTION_EVIDENCE_CLEANUP_INTERVAL_MS, resolveExecutionEvidenceRetentionDays, } from './lib/executionRunRetention.js';
63
65
  import { ExecutionArtifactCleanupError, reconcileExecutionArtifactRegistrationIntents, reconcileFinalizedExecutionArtifacts, reconcileProjectExecutionArtifacts, resolveArtifactCleanupIntervalMs, } from './lib/executionArtifactRetention.js';
64
- import { shutdownRealtimeServer } from './lib/shutdown.js';
66
+ import { closeHttpServer, shutdownRealtimeServer } from './lib/shutdown.js';
65
67
  import { resolveClientBuildPaths, shouldServeClientApp, STATIC_CLIENT_DIRECTORY_OPTIONS, STATIC_CLIENT_INDEX_OPTIONS, } from './lib/staticClient.js';
66
68
  import { deleteIntegrationConnection, getGitHubConnectionConfig, getJiraConnectionConfig, listIntegrationConnections, upsertIntegrationConnection, } from './lib/integrations/index.js';
67
69
  import { createGitHubPullRequest, createOrFindGitHubPullRequest, findOpenGitHubPullRequest, findOpenGitHubPullRequestByHeadBranch, getGitHubPullRequestComments, getGitHubPullRequestReviews, getGitHubPullRequest, GitHubApiError, hasGitHubRemoteBranch, resolveGitHubRepository, validateGitHubConnection, } from './lib/integrations/github.js';
@@ -75,9 +77,14 @@ import { PlanningReferenceError, resolveExternalPlanningDescription } from './li
75
77
  import { refreshIfNeeded as refreshAssignedIssues, clearCachedAssignedIssues } from './lib/integrations/assignedIssuesCache.js';
76
78
  import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage } from './lib/commitMessage.js';
77
79
  import { BUNDLE_PULL_REQUEST_SYNC_TTL_MS, getBundlePullRequestRecord, hasOpenBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequest, upsertBundlePullRequestFromGitHub, } from './lib/bundlePullRequests.js';
78
- import { countInboundProjectLinks, getEffectiveProject, getProjectById, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectPayload, } from './lib/projects.js';
80
+ import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectPayload, } from './lib/projects.js';
79
81
  import { getActiveRunStatus, getActiveRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runSetupCommandsInWorkspace, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
80
- import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, findProjectTestCommand, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
82
+ import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
83
+ import { closeCheckRunnerAdmissions, getCheckBatchAttempts, openCheckRunnerAdmissions, rerunCheckBatchAndSchedule, shutdownCheckRunner, startCheckBatch, stopCheckBatch } from './lib/checkRunner.js';
84
+ import { findActiveCheckBatchForTarget, getCheckAttemptDetails, getCheckBatchDetails, hasActiveProjectCheckBatch, isCheckAttemptCurrent, resolveCheckBatchRerunEligibility } from './lib/checkRuns.js';
85
+ import { getCheckDefinitionFingerprint } from './lib/checkDefinitions.js';
86
+ import { getCheckGateEvaluation } from './lib/checkGateEvaluations.js';
87
+ import { captureCheckWorkspaceIdentity } from './lib/checkWorkspaceIdentity.js';
81
88
  import { getBundleBuildBlockerResponse, shouldStartBundleBuildNow, transitionTicketState, } from './lib/ticketWorkflowOperations.js';
82
89
  import { submitPlanTicketFollowUp, submitTicketFollowUp, } from './lib/ticketFollowUpOperations.js';
83
90
  import { closePendingTicketPlanQuestionSet, listPendingTicketPlanQuestionSetMessageIds, resolveTicketPlanQuestionSet, restoreClosedTicketPlanQuestionSets, } from './lib/planQuestionOperations.js';
@@ -292,15 +299,90 @@ const serializeGitHubActionsExecutionDetail = (details) => {
292
299
  jobs_count: details.jobs_count,
293
300
  };
294
301
  };
295
- const buildExecutionRunDetailResponse = (project, run) => {
296
- const retryEligibility = getExecutionRetryEligibility(project.id, run.id);
302
+ const serializeCheckAttemptExecutionDetail = (projectId, runId) => {
303
+ const attempt = getCheckAttemptDetails(projectId, runId);
304
+ return {
305
+ kind: 'check_attempt',
306
+ available: true,
307
+ attempt,
308
+ batch: attempt ? getCheckBatchDetails(projectId, attempt.batch_run_id) : null,
309
+ };
310
+ };
311
+ const hydrateCheckAttemptCurrency = async (project, batch, attempts) => {
312
+ const terminalAttempts = attempts.filter(attempt => attempt.outcome !== null);
313
+ if (terminalAttempts.length === 0) {
314
+ return attempts.map(attempt => ({ ...attempt, currency: 'pending' }));
315
+ }
316
+ const workspace = resolveExplicitProjectTestWorkspace(project, batch.target_kind, batch.target_bundle_id);
317
+ if ('error' in workspace) {
318
+ return attempts.map(attempt => ({
319
+ ...attempt,
320
+ currency: attempt.outcome === null ? 'pending' : 'unavailable',
321
+ }));
322
+ }
323
+ const currentIdentity = await captureCheckWorkspaceIdentity(workspace.cwd);
324
+ const currentProject = getProjectById(project.id);
325
+ const definitionsById = new Map(getProjectCheckDefinitions(currentProject).map(definition => [definition.id, definition]));
326
+ return attempts.map(attempt => {
327
+ if (attempt.outcome === null) {
328
+ return { ...attempt, currency: 'pending' };
329
+ }
330
+ const definition = definitionsById.get(attempt.definition_id);
331
+ if (!definition || getCheckDefinitionFingerprint(definition) !== attempt.definition_fingerprint) {
332
+ return { ...attempt, currency: 'stale' };
333
+ }
334
+ const currency = currentIdentity.state === 'unavailable'
335
+ ? 'unavailable'
336
+ : isCheckAttemptCurrent(attempt, currentIdentity) ? 'current' : 'stale';
337
+ return { ...attempt, currency };
338
+ });
339
+ };
340
+ const getCheckBatchRerunEligibility = (project, batch, selection) => {
341
+ const workspace = resolveExplicitProjectTestWorkspace(project, batch.target_kind, batch.target_bundle_id);
342
+ return resolveCheckBatchRerunEligibility({
343
+ projectId: project.id,
344
+ batchRunId: batch.execution_run_id,
345
+ availableDefinitions: getProjectCheckDefinitions(project),
346
+ selection,
347
+ targetAvailable: !('error' in workspace && typeof workspace.error === 'string' && workspace.error.includes('no longer exists')),
348
+ workspaceAvailable: !('error' in workspace),
349
+ });
350
+ };
351
+ const buildExecutionRunDetailResponse = async (project, run) => {
352
+ const checkBatch = run.kind === 'check_batch' ? getCheckBatchDetails(project.id, run.id) : null;
353
+ const retryEligibility = checkBatch
354
+ ? getCheckBatchRerunEligibility(project, checkBatch)
355
+ : getExecutionRetryEligibility(project.id, run.id);
356
+ const checkBatchAttempts = checkBatch
357
+ ? await hydrateCheckAttemptCurrency(project, checkBatch, getCheckBatchAttempts(project.id, run.id))
358
+ : [];
359
+ const checkAttemptDetail = run.kind === 'check_attempt' ? serializeCheckAttemptExecutionDetail(project.id, run.id) : null;
360
+ const checkAttempt = checkAttemptDetail?.attempt && checkAttemptDetail.batch
361
+ ? (await hydrateCheckAttemptCurrency(project, checkAttemptDetail.batch, [checkAttemptDetail.attempt]))[0]
362
+ : checkAttemptDetail?.attempt ?? null;
297
363
  const details = run.kind === 'test'
298
364
  ? serializeTestExecutionDetail(getTestExecutionDetails(project.id, run.id), retryEligibility)
299
- : run.kind === 'github_actions'
300
- ? { ...serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id)), retry_eligibility: retryEligibility }
301
- : ['automation_commit', 'automation_push', 'automation_pull_request'].includes(run.kind)
302
- ? { kind: run.kind, available: true, automation_stage: getAutomationStageDetails(project.id, run.id), retry_eligibility: retryEligibility }
303
- : { kind: run.kind, available: true, retry_eligibility: retryEligibility };
365
+ : run.kind === 'check_batch'
366
+ ? {
367
+ kind: 'check_batch',
368
+ available: true,
369
+ batch: checkBatch,
370
+ attempts: checkBatchAttempts,
371
+ retry_eligibility: retryEligibility,
372
+ action_kind: retryEligibility.eligible ? 'rerun' : null,
373
+ }
374
+ : run.kind === 'check_attempt'
375
+ ? {
376
+ ...checkAttemptDetail,
377
+ attempt: checkAttempt,
378
+ retry_eligibility: retryEligibility,
379
+ action_kind: null,
380
+ }
381
+ : run.kind === 'github_actions'
382
+ ? { ...serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id)), retry_eligibility: retryEligibility }
383
+ : ['automation_commit', 'automation_push', 'automation_pull_request'].includes(run.kind)
384
+ ? { kind: run.kind, available: true, automation_stage: getAutomationStageDetails(project.id, run.id), retry_eligibility: retryEligibility }
385
+ : { kind: run.kind, available: true, retry_eligibility: retryEligibility };
304
386
  const artifacts = loadExecutionArtifactSummariesForRuns(project.id, [run.id], MAX_ARTIFACTS_PER_RUN).get(run.id)
305
387
  ?? { artifacts: [], artifact_count: 0 };
306
388
  return {
@@ -3628,6 +3710,9 @@ app.delete('/bundles/:id', (req, res) => {
3628
3710
  if (result.project.active_target_kind === 'bundle' && result.project.active_target_bundle_id === bundle.id) {
3629
3711
  return res.status(409).json({ error: 'Cannot delete the currently selected target bundle' });
3630
3712
  }
3713
+ if (findActiveCheckBatchForTarget(result.project.id, 'bundle', bundle.id)) {
3714
+ return res.status(409).json({ error: 'Cannot delete a bundle while checks are running' });
3715
+ }
3631
3716
  const affectedChatSessionIds = listChatSessions(result.project.id)
3632
3717
  .filter(chatSession => chatSession.target_bundle_id === bundle.id)
3633
3718
  .map(chatSession => chatSession.id);
@@ -3855,6 +3940,18 @@ app.post('/bundles/:id/build-open-questions/resolve', (req, res) => {
3855
3940
  io.emit('ticket:updated', ticket);
3856
3941
  }
3857
3942
  if (reviewResult && !reviewResult.ok) {
3943
+ if (reviewResult.code === 'bundle_build_incomplete') {
3944
+ completeAutomationProgression({
3945
+ projectId: result.project.id,
3946
+ bundleId: bundle.id,
3947
+ progressionRunId: accepted.progressionRunId,
3948
+ broadcaster: io,
3949
+ });
3950
+ for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
3951
+ io.emit('ticket:updated', ticket);
3952
+ }
3953
+ return res.json(serializeBundleDetails(result.project, bundle));
3954
+ }
3858
3955
  waitAutomationProgression({
3859
3956
  projectId: result.project.id,
3860
3957
  bundleId: bundle.id,
@@ -3868,8 +3965,8 @@ app.post('/bundles/:id/build-open-questions/resolve', (req, res) => {
3868
3965
  return res.status(reviewResult.status).json({ error: reviewResult.error });
3869
3966
  }
3870
3967
  return res.json(serializeBundleDetails(result.project, bundle, {
3871
- review_owner_ticket_id: reviewResult?.value?.id,
3872
- target_ticket_id: reviewResult?.value?.id,
3968
+ review_owner_ticket_id: reviewResult?.ok ? reviewResult.value?.id : undefined,
3969
+ target_ticket_id: reviewResult?.ok ? reviewResult.value?.id : undefined,
3873
3970
  }));
3874
3971
  }
3875
3972
  for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
@@ -3976,7 +4073,7 @@ app.post('/bundles/:id/follow-up', (req, res) => {
3976
4073
  return res.status(response.status).json(response.value);
3977
4074
  });
3978
4075
  const AUTOMATION_SIDE_EFFECT_KINDS = new Set(['automation_commit', 'automation_push', 'automation_pull_request']);
3979
- const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix']);
4076
+ const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix', 'check_batch']);
3980
4077
  app.post('/bundles/:id/resume-automation', async (req, res) => {
3981
4078
  const result = getRequiredProject(req);
3982
4079
  if ('error' in result) {
@@ -4052,6 +4149,194 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4052
4149
  if (!revalidateDecision()) {
4053
4150
  return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4054
4151
  }
4152
+ const checkpointLineage = resolveBuildTicketCommitCheckpointLineage({
4153
+ projectId: result.project.id,
4154
+ bundleId: bundle.id,
4155
+ progressionRunId,
4156
+ });
4157
+ if (checkpointLineage) {
4158
+ const owner = getTicket(decision.owner_ticket_id);
4159
+ if (!owner || owner.id !== checkpointLineage.ticket.id || owner.agent_status !== 'done') {
4160
+ return res.status(409).json({ error: 'Checkpoint progression owner is no longer the active Build ticket' });
4161
+ }
4162
+ let retryOfRunId;
4163
+ let commitAlreadyComplete = false;
4164
+ const stageRunId = bundleState.automation_paused_stage_run_id;
4165
+ if (stageRunId) {
4166
+ const stage = getExecutionRun(result.project.id, stageRunId);
4167
+ if (!stage
4168
+ || !['automation_commit', 'check_batch'].includes(stage.kind)
4169
+ || !pausedRunBelongsToProgression(result.project.id, bundle.id, progressionRunId, stageRunId)) {
4170
+ return res.status(409).json({ error: 'The paused checkpoint stage does not belong to the active progression' });
4171
+ }
4172
+ if (stage.kind === 'automation_commit') {
4173
+ let reconciled;
4174
+ try {
4175
+ reconciled = await reconcileAutomationStage({
4176
+ projectId: result.project.id,
4177
+ bundleId: bundle.id,
4178
+ progressionRunId,
4179
+ stageRunId,
4180
+ });
4181
+ }
4182
+ catch (error) {
4183
+ return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4184
+ }
4185
+ if (reconciled.domainStatus === 'ambiguous') {
4186
+ return res.status(409).json({ error: 'The paused checkpoint commit is ambiguous and cannot be resumed safely.' });
4187
+ }
4188
+ if (reconciled.status === 'completed' && reconciled.domainStatus === 'reconciled_completed') {
4189
+ commitAlreadyComplete = true;
4190
+ }
4191
+ else if (reconciled.status === 'interrupted' && reconciled.domainStatus === 'reconciled_absent') {
4192
+ retryOfRunId = stage.id;
4193
+ }
4194
+ else {
4195
+ return res.status(409).json({ error: 'The paused checkpoint commit has no safely resumable outcome.' });
4196
+ }
4197
+ }
4198
+ }
4199
+ try {
4200
+ if (!revalidateDecision()) {
4201
+ return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4202
+ }
4203
+ runAutomationProgression({
4204
+ projectId: result.project.id,
4205
+ bundleId: bundle.id,
4206
+ progressionRunId,
4207
+ broadcaster: io,
4208
+ });
4209
+ if (commitAlreadyComplete) {
4210
+ completeAutomationProgression({
4211
+ projectId: result.project.id,
4212
+ bundleId: bundle.id,
4213
+ progressionRunId,
4214
+ broadcaster: io,
4215
+ });
4216
+ }
4217
+ else {
4218
+ await runAutomationCommitOperation({
4219
+ ticket: owner,
4220
+ io,
4221
+ progressionRunId,
4222
+ retryOfRunId,
4223
+ mutationLabel: 'checkpoint commit',
4224
+ canCommitLive: canMutateBuildTicketCommitCheckpoint,
4225
+ shouldCompleteProgression: () => true,
4226
+ failureMode: 'throw',
4227
+ requiredProgressionPurpose: 'build_ticket_checkpoint',
4228
+ });
4229
+ }
4230
+ }
4231
+ catch (error) {
4232
+ return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4233
+ }
4234
+ const workflowContext = validateWorkflowContext(checkpointLineage.progression.metadata);
4235
+ const continuation = advanceSuccessfulBuildTicketAfterCheckpoint({
4236
+ ticket: owner,
4237
+ emitUpdatedTickets,
4238
+ });
4239
+ if (continuation.nextBuild) {
4240
+ const nextBuild = continuation.nextBuild;
4241
+ const nextSequenceId = continuation.nextSequenceId;
4242
+ const scheduling = startBuild(nextBuild, io, {
4243
+ initiation: {
4244
+ initiator: 'automation',
4245
+ causalParentRunId: checkpointLineage.parent.id,
4246
+ workflowContext,
4247
+ },
4248
+ sequenceId: nextSequenceId,
4249
+ accept: () => {
4250
+ const transition = prepareTicketForBuild(nextBuild.id, {
4251
+ ignoredBlockerTicketIds: continuation.ignoredBlockerTicketIds,
4252
+ });
4253
+ if (!transition.ok) {
4254
+ throw new Error(transition.error);
4255
+ }
4256
+ if (!nextSequenceId) {
4257
+ throw new Error('Build sequence no longer exists');
4258
+ }
4259
+ activateQueuedBuildSequenceTicket(nextSequenceId, transition.ticket.id);
4260
+ return {
4261
+ ticket: transition.ticket,
4262
+ sequenceId: nextSequenceId,
4263
+ compensate: () => compensateBuildSequenceTicketActivation(nextSequenceId, transition.ticket.id),
4264
+ };
4265
+ },
4266
+ });
4267
+ if (!scheduling.accepted) {
4268
+ const stoppedSequence = stopBuildSequence(nextBuild.id);
4269
+ emitUpdatedTickets(stoppedSequence.affectedTicketIds);
4270
+ }
4271
+ }
4272
+ else if (!continuation.nextTicketId && owner.project_id && owner.worktree_bundle_id) {
4273
+ const resumedOwnerForReview = getTicket(owner.id) ?? owner;
4274
+ const liveProject = getEffectiveProject(resumedOwnerForReview) ?? result.project;
4275
+ const automationConfig = resolveAutomationConfig(resumedOwnerForReview.bundle ?? null, liveProject);
4276
+ const autoReviewEnabled = depthAtLeast(automationConfig.depth, 'auto_review');
4277
+ const latestBundleState = getBundleAutomationState(owner.project_id, owner.worktree_bundle_id);
4278
+ const openQuestions = listBuildOpenQuestionsForBundle(owner.project_id, owner.worktree_bundle_id);
4279
+ const shouldPauseForOpenQuestions = autoReviewEnabled
4280
+ && automationConfig.pauseOnOpenQuestions
4281
+ && openQuestions.length > 0;
4282
+ if (shouldPauseForOpenQuestions) {
4283
+ const reviewProgression = ensureAutomationProgression({
4284
+ projectId: owner.project_id,
4285
+ bundleId: owner.worktree_bundle_id,
4286
+ ticketId: owner.id,
4287
+ ticketTitle: owner.title,
4288
+ parentRunId: checkpointLineage.parent.id,
4289
+ workflowContext,
4290
+ broadcaster: io,
4291
+ });
4292
+ waitAutomationProgression({
4293
+ projectId: owner.project_id,
4294
+ bundleId: owner.worktree_bundle_id,
4295
+ progressionRunId: reviewProgression.runId,
4296
+ pauseCode: 'open_questions',
4297
+ pauseReason: `${openQuestions.length} unresolved Build open question${openQuestions.length === 1 ? '' : 's'}`,
4298
+ reasonCode: 'precondition_failed',
4299
+ pausedStageRunId: checkpointLineage.parent.id,
4300
+ broadcaster: io,
4301
+ });
4302
+ }
4303
+ else if (autoReviewEnabled && (!latestBundleState?.automation_paused_reason || latestBundleState.automation_paused_code === 'open_questions')) {
4304
+ const reviewProgression = ensureAutomationProgression({
4305
+ projectId: owner.project_id,
4306
+ bundleId: owner.worktree_bundle_id,
4307
+ ticketId: owner.id,
4308
+ ticketTitle: owner.title,
4309
+ parentRunId: checkpointLineage.parent.id,
4310
+ workflowContext,
4311
+ broadcaster: io,
4312
+ });
4313
+ const reviewResult = transitionBundleToReview(owner.project_id, owner.worktree_bundle_id, io, {
4314
+ automatic: true,
4315
+ initiation: {
4316
+ causalParentRunId: reviewProgression.runId,
4317
+ workflowContext,
4318
+ },
4319
+ });
4320
+ if (!reviewResult.ok) {
4321
+ waitAutomationProgression({
4322
+ projectId: owner.project_id,
4323
+ bundleId: owner.worktree_bundle_id,
4324
+ progressionRunId: reviewProgression.runId,
4325
+ pauseCode: 'review',
4326
+ pauseReason: reviewResult.error,
4327
+ reasonCode: 'stage_failed',
4328
+ pausedStageRunId: checkpointLineage.parent.id,
4329
+ broadcaster: io,
4330
+ });
4331
+ }
4332
+ }
4333
+ }
4334
+ const resumedOwner = getTicket(owner.id);
4335
+ if (resumedOwner) {
4336
+ io.emit('ticket:updated', resumedOwner);
4337
+ }
4338
+ return res.json(serializeBundleDetails(result.project, bundle));
4339
+ }
4055
4340
  let sideEffectResume = null;
4056
4341
  if (bundleState.automation_paused_stage_run_id) {
4057
4342
  const stageRunId = bundleState.automation_paused_stage_run_id;
@@ -4272,7 +4557,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4272
4557
  return res.status(followUpResult.status).json({ error: followUpResult.error });
4273
4558
  }
4274
4559
  }
4275
- else if (decision.outcome === 'continue_commit_chain') {
4560
+ else if (decision.outcome === 'continue_commit_chain' || decision.outcome === 'rerun_required_checks') {
4276
4561
  try {
4277
4562
  if (!revalidateDecision()) {
4278
4563
  return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
@@ -4283,7 +4568,10 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4283
4568
  progressionRunId,
4284
4569
  broadcaster: io,
4285
4570
  });
4286
- await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId);
4571
+ const checkGateResume = decision.outcome === 'rerun_required_checks'
4572
+ ? resolveAutomationCheckGateResume(result.project.id, progressionRunId)
4573
+ : undefined;
4574
+ await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId, checkGateResume);
4287
4575
  }
4288
4576
  catch (error) {
4289
4577
  return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
@@ -4376,6 +4664,11 @@ app.post('/bundles/:id/dismiss-automation-pause', async (req, res) => {
4376
4664
  catch (error) {
4377
4665
  return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4378
4666
  }
4667
+ const checkpointLineage = resolveBuildTicketCommitCheckpointLineage({
4668
+ projectId: result.project.id,
4669
+ bundleId: bundle.id,
4670
+ progressionRunId,
4671
+ });
4379
4672
  if (bundleState.automation_paused_stage_run_id) {
4380
4673
  const stage = getExecutionRun(result.project.id, bundleState.automation_paused_stage_run_id);
4381
4674
  if (!stage
@@ -4411,6 +4704,10 @@ app.post('/bundles/:id/dismiss-automation-pause', async (req, res) => {
4411
4704
  broadcaster: io,
4412
4705
  stopLocalWorker: worker => (worker.kind === 'review' ? stopReviewAgent(worker.ticket_id) : stopBuildAgent(worker.ticket_id)),
4413
4706
  });
4707
+ if (checkpointLineage) {
4708
+ const stoppedSequence = stopBuildSequence(checkpointLineage.ticket.id);
4709
+ emitUpdatedTickets(stoppedSequence.affectedTicketIds);
4710
+ }
4414
4711
  for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
4415
4712
  io.emit('ticket:updated', ticket);
4416
4713
  }
@@ -5495,7 +5792,7 @@ app.post('/projects', (req, res) => {
5495
5792
  return res.status(400).json({ error: validation.error });
5496
5793
  }
5497
5794
  const projectCount = Number(db.prepare('SELECT COUNT(*) AS count FROM projects').get()?.count ?? 0);
5498
- const { name, repo_path, linked_project_ids, run_setup, run_services, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, commit_message_rules, auto_park_stale_tickets, memory_enabled, scan_include_globs_json, scan_exclude_globs_json, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_pause_on_open_questions, automation_review_severity, automation_review_max_iterations, } = validation.values;
5795
+ const { name, repo_path, linked_project_ids, run_setup, run_services, check_definitions, required_check_definition_ids, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, commit_message_rules, commit_after_each_ticket, auto_park_stale_tickets, memory_enabled, scan_include_globs_json, scan_exclude_globs_json, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_pause_on_open_questions, automation_review_severity, automation_review_max_iterations, } = validation.values;
5499
5796
  if (!repo_path) {
5500
5797
  return res.status(400).json({ error: 'repo_path must be a non-empty string' });
5501
5798
  }
@@ -5515,6 +5812,8 @@ app.post('/projects', (req, res) => {
5515
5812
  repo_path,
5516
5813
  run_setup,
5517
5814
  run_services,
5815
+ check_definitions,
5816
+ required_check_definition_ids,
5518
5817
  test_commands,
5519
5818
  run_ide,
5520
5819
  preview_service_name,
@@ -5523,6 +5822,7 @@ app.post('/projects', (req, res) => {
5523
5822
  helper_model,
5524
5823
  helper_variant,
5525
5824
  commit_message_rules,
5825
+ commit_after_each_ticket,
5526
5826
  auto_park_stale_tickets,
5527
5827
  memory_enabled,
5528
5828
  scan_include_globs_json,
@@ -5535,8 +5835,8 @@ app.post('/projects', (req, res) => {
5535
5835
  automation_review_severity,
5536
5836
  automation_review_max_iterations
5537
5837
  )
5538
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5539
- `).run(id, name, normalizedRepoPath, run_setup, run_services, test_commands, run_ide ?? null, preview_service_name ?? null, preview_path ?? null, preview_capability_mode ?? null, helper_model ?? null, helper_variant ?? null, commit_message_rules ?? null, auto_park_stale_tickets ?? 0, memory_enabled ?? 0, scan_include_globs_json ?? null, scan_exclude_globs_json ?? null, worktree_sync ?? null, spec_aware_code_review_enabled ?? 0, automation_depth ?? 'manual', automation_review_fix_enabled ?? 0, automation_pause_on_open_questions ?? 0, automation_review_severity ?? 'medium', automation_review_max_iterations ?? 3);
5838
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5839
+ `).run(id, name, normalizedRepoPath, run_setup, run_services, check_definitions, required_check_definition_ids ?? '[]', test_commands, run_ide ?? null, preview_service_name ?? null, preview_path ?? null, preview_capability_mode ?? null, helper_model ?? null, helper_variant ?? null, commit_message_rules ?? null, commit_after_each_ticket ?? 0, auto_park_stale_tickets ?? 0, memory_enabled ?? 0, scan_include_globs_json ?? null, scan_exclude_globs_json ?? null, worktree_sync ?? null, spec_aware_code_review_enabled ?? 0, automation_depth ?? 'manual', automation_review_fix_enabled ?? 0, automation_pause_on_open_questions ?? 0, automation_review_severity ?? 'medium', automation_review_max_iterations ?? 3);
5540
5840
  replaceProjectLinks(id, linked_project_ids ?? []);
5541
5841
  ensureProjectRootBundle(id);
5542
5842
  if (projectCount === 0) {
@@ -6702,13 +7002,15 @@ app.patch('/projects/:id', (req, res) => {
6702
7002
  });
6703
7003
  }
6704
7004
  }
6705
- const { name, repo_path, linked_project_ids, run_setup, run_services, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, commit_message_rules, auto_park_stale_tickets, memory_enabled, scan_include_globs_json, scan_exclude_globs_json, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_pause_on_open_questions, automation_review_severity, automation_review_max_iterations, } = validation.values;
7005
+ const { name, repo_path, linked_project_ids, run_setup, run_services, check_definitions, required_check_definition_ids, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, commit_message_rules, commit_after_each_ticket, auto_park_stale_tickets, memory_enabled, scan_include_globs_json, scan_exclude_globs_json, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_pause_on_open_questions, automation_review_severity, automation_review_max_iterations, } = validation.values;
6706
7006
  db.prepare(`
6707
7007
  UPDATE projects SET
6708
7008
  name = ?,
6709
7009
  repo_path = ?,
6710
7010
  run_setup = ?,
6711
7011
  run_services = ?,
7012
+ check_definitions = ?,
7013
+ required_check_definition_ids = ?,
6712
7014
  test_commands = ?,
6713
7015
  run_ide = ?,
6714
7016
  preview_service_name = ?,
@@ -6717,6 +7019,7 @@ app.patch('/projects/:id', (req, res) => {
6717
7019
  helper_model = ?,
6718
7020
  helper_variant = ?,
6719
7021
  commit_message_rules = ?,
7022
+ commit_after_each_ticket = ?,
6720
7023
  auto_park_stale_tickets = ?,
6721
7024
  memory_enabled = ?,
6722
7025
  scan_include_globs_json = ?,
@@ -6730,7 +7033,7 @@ app.patch('/projects/:id', (req, res) => {
6730
7033
  automation_review_max_iterations = ?,
6731
7034
  updated_at = CURRENT_TIMESTAMP
6732
7035
  WHERE id = ?
6733
- `).run(name ?? project.name, repo_path ?? project.repo_path, hasOwn(validation.values, 'run_setup') ? run_setup : project.run_setup, hasOwn(validation.values, 'run_services') ? run_services : project.run_services, hasOwn(validation.values, 'test_commands') ? test_commands : project.test_commands, hasOwn(validation.values, 'run_ide') ? run_ide : project.run_ide, hasOwn(validation.values, 'preview_service_name') ? preview_service_name : project.preview_service_name, hasOwn(validation.values, 'preview_path') ? preview_path : project.preview_path, hasOwn(validation.values, 'preview_capability_mode') ? preview_capability_mode : project.preview_capability_mode, hasOwn(validation.values, 'helper_model') ? helper_model : project.helper_model, hasOwn(validation.values, 'helper_variant') ? helper_variant : project.helper_variant, hasOwn(validation.values, 'commit_message_rules') ? commit_message_rules : project.commit_message_rules, hasOwn(validation.values, 'auto_park_stale_tickets') ? auto_park_stale_tickets : project.auto_park_stale_tickets, hasOwn(validation.values, 'memory_enabled') ? memory_enabled : project.memory_enabled, hasOwn(validation.values, 'scan_include_globs_json') ? scan_include_globs_json : project.scan_include_globs_json, hasOwn(validation.values, 'scan_exclude_globs_json') ? scan_exclude_globs_json : project.scan_exclude_globs_json, hasOwn(validation.values, 'worktree_sync') ? worktree_sync : project.worktree_sync, hasOwn(validation.values, 'spec_aware_code_review_enabled') ? spec_aware_code_review_enabled : project.spec_aware_code_review_enabled, hasOwn(validation.values, 'automation_depth') ? automation_depth : project.automation_depth, hasOwn(validation.values, 'automation_review_fix_enabled') ? automation_review_fix_enabled : project.automation_review_fix_enabled, hasOwn(validation.values, 'automation_pause_on_open_questions') ? automation_pause_on_open_questions : project.automation_pause_on_open_questions, hasOwn(validation.values, 'automation_review_severity') ? automation_review_severity : project.automation_review_severity, hasOwn(validation.values, 'automation_review_max_iterations') ? automation_review_max_iterations : project.automation_review_max_iterations, req.params.id);
7036
+ `).run(name ?? project.name, repo_path ?? project.repo_path, hasOwn(validation.values, 'run_setup') ? run_setup : project.run_setup, hasOwn(validation.values, 'run_services') ? run_services : project.run_services, hasOwn(validation.values, 'check_definitions') ? check_definitions : project.check_definitions, hasOwn(validation.values, 'required_check_definition_ids') ? required_check_definition_ids : project.required_check_definition_ids, hasOwn(validation.values, 'test_commands') ? test_commands : project.test_commands, hasOwn(validation.values, 'run_ide') ? run_ide : project.run_ide, hasOwn(validation.values, 'preview_service_name') ? preview_service_name : project.preview_service_name, hasOwn(validation.values, 'preview_path') ? preview_path : project.preview_path, hasOwn(validation.values, 'preview_capability_mode') ? preview_capability_mode : project.preview_capability_mode, hasOwn(validation.values, 'helper_model') ? helper_model : project.helper_model, hasOwn(validation.values, 'helper_variant') ? helper_variant : project.helper_variant, hasOwn(validation.values, 'commit_message_rules') ? commit_message_rules : project.commit_message_rules, hasOwn(validation.values, 'commit_after_each_ticket') ? commit_after_each_ticket : project.commit_after_each_ticket, hasOwn(validation.values, 'auto_park_stale_tickets') ? auto_park_stale_tickets : project.auto_park_stale_tickets, hasOwn(validation.values, 'memory_enabled') ? memory_enabled : project.memory_enabled, hasOwn(validation.values, 'scan_include_globs_json') ? scan_include_globs_json : project.scan_include_globs_json, hasOwn(validation.values, 'scan_exclude_globs_json') ? scan_exclude_globs_json : project.scan_exclude_globs_json, hasOwn(validation.values, 'worktree_sync') ? worktree_sync : project.worktree_sync, hasOwn(validation.values, 'spec_aware_code_review_enabled') ? spec_aware_code_review_enabled : project.spec_aware_code_review_enabled, hasOwn(validation.values, 'automation_depth') ? automation_depth : project.automation_depth, hasOwn(validation.values, 'automation_review_fix_enabled') ? automation_review_fix_enabled : project.automation_review_fix_enabled, hasOwn(validation.values, 'automation_pause_on_open_questions') ? automation_pause_on_open_questions : project.automation_pause_on_open_questions, hasOwn(validation.values, 'automation_review_severity') ? automation_review_severity : project.automation_review_severity, hasOwn(validation.values, 'automation_review_max_iterations') ? automation_review_max_iterations : project.automation_review_max_iterations, req.params.id);
6734
7037
  if (hasOwn(validation.values, 'linked_project_ids')) {
6735
7038
  replaceProjectLinks(project.id, linked_project_ids ?? []);
6736
7039
  }
@@ -6759,6 +7062,9 @@ const assertProjectDeletionEligible = (projectId, claimId) => {
6759
7062
  if (stats.ticketCount > 0 || stats.worktreeCount > 0 || stats.bundleCount > 0) {
6760
7063
  throw new ProjectDeletionError(409, 'Cannot delete a project with existing tickets, bundles, or worktrees');
6761
7064
  }
7065
+ if (hasActiveProjectCheckBatch(projectId)) {
7066
+ throw new ProjectDeletionError(409, 'Cannot delete a project while checks are active');
7067
+ }
6762
7068
  };
6763
7069
  const completeClaimedProjectDeletion = async (projectId, claimId) => {
6764
7070
  await runExclusiveExecutionArtifactMaintenance(() => reconcileProjectExecutionArtifacts(projectId, {
@@ -6785,6 +7091,19 @@ const completeClaimedProjectDeletion = async (projectId, claimId) => {
6785
7091
  db.prepare("DELETE FROM execution_artifacts WHERE project_id = ? AND availability = 'deleted'").run(projectId);
6786
7092
  deleteProjectAttachments(projectId);
6787
7093
  db.prepare(`
7094
+ DELETE FROM check_gate_evaluation_attempts
7095
+ WHERE evaluation_id IN (SELECT id FROM check_gate_evaluations WHERE project_id = ?)
7096
+ `).run(projectId);
7097
+ db.prepare('DELETE FROM check_gate_evaluations WHERE project_id = ?').run(projectId);
7098
+ db.prepare(`
7099
+ DELETE FROM check_attempt_details
7100
+ WHERE execution_run_id IN (SELECT id FROM execution_runs WHERE project_id = ?)
7101
+ `).run(projectId);
7102
+ db.prepare(`
7103
+ DELETE FROM check_batch_details
7104
+ WHERE execution_run_id IN (SELECT id FROM execution_runs WHERE project_id = ?)
7105
+ `).run(projectId);
7106
+ db.prepare(`
6788
7107
  UPDATE execution_runs
6789
7108
  SET parent_run_id = NULL, retry_of_run_id = NULL
6790
7109
  WHERE project_id = ?
@@ -7671,6 +7990,186 @@ app.post('/projects/:id/test-sessions', (req, res) => {
7671
7990
  targetLabel: workspace.target_label,
7672
7991
  }));
7673
7992
  });
7993
+ app.post('/projects/:id/check-batches', async (req, res) => {
7994
+ const project = getProjectById(req.params.id);
7995
+ if (!project) {
7996
+ return res.status(404).json({ error: 'Not found' });
7997
+ }
7998
+ const body = req.body && typeof req.body === 'object' && !Array.isArray(req.body) ? req.body : null;
7999
+ const targetKind = body?.target_kind === 'bundle'
8000
+ ? 'bundle'
8001
+ : body?.target_kind === 'repo_root'
8002
+ ? 'repo_root'
8003
+ : null;
8004
+ if (!targetKind) {
8005
+ return res.status(400).json({ error: 'target_kind must be repo_root or bundle' });
8006
+ }
8007
+ const definitionIds = parseCheckDefinitionIds(body?.definition_ids);
8008
+ if (!definitionIds) {
8009
+ return res.status(400).json({ error: 'definition_ids must contain at least one unique check definition ID' });
8010
+ }
8011
+ const definitions = getProjectCheckDefinitions(project);
8012
+ const definitionsById = new Map(definitions.map(definition => [definition.id, definition]));
8013
+ const selectedDefinitions = definitionIds.map(id => definitionsById.get(id)).filter((definition) => Boolean(definition));
8014
+ if (selectedDefinitions.length !== definitionIds.length) {
8015
+ return res.status(409).json({ error: 'One or more selected check definitions are stale or unavailable' });
8016
+ }
8017
+ const targetBundleId = typeof body?.target_bundle_id === 'string' ? body.target_bundle_id.trim() || null : null;
8018
+ const workspace = resolveExplicitProjectTestWorkspace(project, targetKind, targetBundleId);
8019
+ if ('error' in workspace) {
8020
+ return res.status(Number(workspace.status)).json({ error: workspace.error });
8021
+ }
8022
+ try {
8023
+ const batch = await startCheckBatch({
8024
+ projectId: project.id,
8025
+ cwd: workspace.cwd,
8026
+ targetKind: workspace.target_kind,
8027
+ targetBundleId: workspace.target_bundle_id,
8028
+ targetLabel: workspace.target_label,
8029
+ selectedDefinitions,
8030
+ availableDefinitions: definitions,
8031
+ sourceReference: { kind: 'project', id: project.id },
8032
+ }, io);
8033
+ return res.status(201).json({
8034
+ batch,
8035
+ attempts: getCheckBatchAttempts(project.id, batch.execution_run_id),
8036
+ });
8037
+ }
8038
+ catch (error) {
8039
+ return res.status(409).json({ error: getErrorMessage(error) });
8040
+ }
8041
+ });
8042
+ const parseCheckDefinitionIds = (value) => {
8043
+ if (!Array.isArray(value) || value.length === 0 || value.some(id => typeof id !== 'string' || id.trim() === '')) {
8044
+ return null;
8045
+ }
8046
+ const ids = value.map(id => id.trim());
8047
+ return ids.length === new Set(ids).size ? ids : null;
8048
+ };
8049
+ const parseCheckRerunSelection = (body) => {
8050
+ const payload = body && typeof body === 'object' && !Array.isArray(body) ? body : {};
8051
+ if (payload.mode === undefined || payload.mode === 'all') {
8052
+ return { mode: 'all' };
8053
+ }
8054
+ if (payload.mode === 'failed_only') {
8055
+ return { mode: 'failed' };
8056
+ }
8057
+ if (payload.mode !== 'selected') {
8058
+ return { error: 'mode must be all, failed_only, or selected' };
8059
+ }
8060
+ const definitionIds = parseCheckDefinitionIds(payload.definition_ids);
8061
+ if (!definitionIds) {
8062
+ return { error: 'definition_ids must contain at least one unique source definition ID for selected reruns' };
8063
+ }
8064
+ return { mode: 'selected', definitionIds };
8065
+ };
8066
+ const rerunProjectCheckBatch = async (project, sourceBatchRunId, body) => {
8067
+ const sourceBatch = getCheckBatchDetails(project.id, sourceBatchRunId);
8068
+ if (!sourceBatch) {
8069
+ return { ok: false, status: 404, error: 'Check batch not found', reason: 'status_not_retryable' };
8070
+ }
8071
+ const selection = parseCheckRerunSelection(body);
8072
+ if ('error' in selection) {
8073
+ return { ok: false, status: 400, error: selection.error, reason: 'retry_input_unavailable' };
8074
+ }
8075
+ const eligibility = getCheckBatchRerunEligibility(project, sourceBatch, selection);
8076
+ if (!eligibility.eligible) {
8077
+ return {
8078
+ ok: false,
8079
+ status: 409,
8080
+ error: 'Check batch cannot be rerun with its current target and definitions',
8081
+ reason: eligibility.reason,
8082
+ };
8083
+ }
8084
+ const workspace = resolveExplicitProjectTestWorkspace(project, sourceBatch.target_kind, sourceBatch.target_bundle_id);
8085
+ if ('error' in workspace) {
8086
+ return { ok: false, status: Number(workspace.status), error: workspace.error, reason: 'retry_workspace_unavailable' };
8087
+ }
8088
+ try {
8089
+ const value = await rerunCheckBatchAndSchedule({
8090
+ projectId: project.id,
8091
+ sourceBatchRunId,
8092
+ cwd: workspace.cwd,
8093
+ availableDefinitions: getProjectCheckDefinitions(project),
8094
+ selection,
8095
+ }, io);
8096
+ return { ok: true, value };
8097
+ }
8098
+ catch (error) {
8099
+ return { ok: false, status: 409, error: getErrorMessage(error), reason: 'retry_input_unavailable' };
8100
+ }
8101
+ };
8102
+ app.get('/projects/:id/check-batches', (req, res) => {
8103
+ const project = getProjectById(req.params.id);
8104
+ if (!project) {
8105
+ return res.status(404).json({ error: 'Not found' });
8106
+ }
8107
+ const query = parseLifecycleListQuery(req.query);
8108
+ if ('error' in query) {
8109
+ return res.status(400).json({ error: query.error });
8110
+ }
8111
+ try {
8112
+ return res.json(listExecutionTimeline({
8113
+ project,
8114
+ kind: 'check_batch',
8115
+ statuses: query.statuses,
8116
+ cursor: query.cursor,
8117
+ limit: query.limit,
8118
+ }));
8119
+ }
8120
+ catch (error) {
8121
+ if (error instanceof Error && error.message === 'Invalid execution run cursor') {
8122
+ return res.status(400).json({ error: 'cursor must be a valid execution run cursor' });
8123
+ }
8124
+ throw error;
8125
+ }
8126
+ });
8127
+ app.get('/projects/:id/check-batches/:batchRunId', async (req, res) => {
8128
+ const project = getProjectById(req.params.id);
8129
+ if (!project) {
8130
+ return res.status(404).json({ error: 'Not found' });
8131
+ }
8132
+ const batch = getCheckBatchDetails(req.params.id, req.params.batchRunId);
8133
+ if (!batch) {
8134
+ return res.status(404).json({ error: 'Check batch not found' });
8135
+ }
8136
+ return res.json({
8137
+ batch,
8138
+ attempts: await hydrateCheckAttemptCurrency(project, batch, getCheckBatchAttempts(req.params.id, req.params.batchRunId)),
8139
+ });
8140
+ });
8141
+ app.get('/projects/:id/check-gate-evaluations/:evaluationId', (req, res) => {
8142
+ const evaluation = getCheckGateEvaluation(req.params.id, req.params.evaluationId);
8143
+ if (!evaluation) {
8144
+ return res.status(404).json({ error: 'Check gate evaluation not found' });
8145
+ }
8146
+ return res.json(evaluation);
8147
+ });
8148
+ app.post('/projects/:id/check-batches/:batchRunId/stop', async (req, res) => {
8149
+ if (!getCheckBatchDetails(req.params.id, req.params.batchRunId)) {
8150
+ return res.status(404).json({ error: 'Check batch not found' });
8151
+ }
8152
+ try {
8153
+ const batch = await stopCheckBatch(req.params.id, req.params.batchRunId, io);
8154
+ return res.json({
8155
+ batch,
8156
+ attempts: getCheckBatchAttempts(req.params.id, req.params.batchRunId),
8157
+ });
8158
+ }
8159
+ catch (error) {
8160
+ return res.status(409).json({ error: getErrorMessage(error) });
8161
+ }
8162
+ });
8163
+ app.post('/projects/:id/check-batches/:batchRunId/rerun', async (req, res) => {
8164
+ const project = getProjectById(req.params.id);
8165
+ if (!project) {
8166
+ return res.status(404).json({ error: 'Not found' });
8167
+ }
8168
+ const result = await rerunProjectCheckBatch(project, req.params.batchRunId, req.body);
8169
+ return result.ok
8170
+ ? res.status(201).json(result.value)
8171
+ : res.status(result.status).json({ error: result.error });
8172
+ });
7674
8173
  app.get('/projects/:id/execution-runs', (req, res) => {
7675
8174
  const project = getProjectById(req.params.id);
7676
8175
  if (!project) {
@@ -7755,7 +8254,7 @@ app.get('/projects/:id/execution-timeline', (req, res) => {
7755
8254
  throw error;
7756
8255
  }
7757
8256
  });
7758
- app.get('/projects/:id/execution-timeline/:runId', (req, res) => {
8257
+ app.get('/projects/:id/execution-timeline/:runId', async (req, res) => {
7759
8258
  const project = getProjectById(req.params.id);
7760
8259
  if (!project) {
7761
8260
  return res.status(404).json({ error: 'Not found' });
@@ -7772,9 +8271,9 @@ app.get('/projects/:id/execution-timeline/:runId', (req, res) => {
7772
8271
  if (scope.scopeEntity && !links.some(link => link.entity_type === scope.scopeEntity?.type && link.entity_id === scope.scopeEntity.id)) {
7773
8272
  return res.status(404).json({ error: 'Execution run not found' });
7774
8273
  }
7775
- return res.json(buildExecutionRunDetailResponse(project, run));
8274
+ return res.json(await buildExecutionRunDetailResponse(project, run));
7776
8275
  });
7777
- app.get('/projects/:id/execution-runs/:runId', (req, res) => {
8276
+ app.get('/projects/:id/execution-runs/:runId', async (req, res) => {
7778
8277
  const project = getProjectById(req.params.id);
7779
8278
  if (!project) {
7780
8279
  return res.status(404).json({ error: 'Not found' });
@@ -7783,7 +8282,7 @@ app.get('/projects/:id/execution-runs/:runId', (req, res) => {
7783
8282
  if (!run) {
7784
8283
  return res.status(404).json({ error: 'Execution run not found' });
7785
8284
  }
7786
- return res.json(buildExecutionRunDetailResponse(project, run));
8285
+ return res.json(await buildExecutionRunDetailResponse(project, run));
7787
8286
  });
7788
8287
  app.get('/projects/:id/execution-runs/:runId/artifacts/:artifactId', async (req, res) => {
7789
8288
  const project = getProjectById(req.params.id);
@@ -7827,6 +8326,13 @@ app.post('/projects/:id/execution-runs/:runId/retry', async (req, res) => {
7827
8326
  if (!project) {
7828
8327
  return res.status(404).json({ error: 'Not found' });
7829
8328
  }
8329
+ const source = getExecutionRun(project.id, req.params.runId);
8330
+ if (source?.kind === 'check_batch') {
8331
+ const result = await rerunProjectCheckBatch(project, source.id, req.body);
8332
+ return result.ok
8333
+ ? res.status(201).json(result.value)
8334
+ : res.status(result.status).json({ error: result.error, reason: result.reason });
8335
+ }
7830
8336
  const resolution = resolveExecutionRetry(project.id, req.params.runId);
7831
8337
  const result = await dispatchExecutionRetry(resolution, io, async (retry) => {
7832
8338
  const session = createTestSession({
@@ -7892,7 +8398,7 @@ app.get('/test-sessions/:id/discovery', (req, res) => {
7892
8398
  if (!project) {
7893
8399
  return res.status(404).json({ error: 'Project not found' });
7894
8400
  }
7895
- command = findProjectTestCommand(project, commandId);
8401
+ command = getProjectTestCommands(project).find(item => item.id === commandId) ?? null;
7896
8402
  if (!command) {
7897
8403
  return res.status(404).json({ error: 'Test command not found' });
7898
8404
  }
@@ -7928,7 +8434,7 @@ app.post('/test-sessions/:id/run', async (req, res) => {
7928
8434
  if (!selection) {
7929
8435
  return res.status(400).json({ error: 'selection must be all, file, or case' });
7930
8436
  }
7931
- const command = findProjectTestCommand(project, commandId);
8437
+ const command = getProjectTestCommands(project).find(item => item.id === commandId) ?? null;
7932
8438
  if (!command) {
7933
8439
  return res.status(404).json({ error: 'Test command not found' });
7934
8440
  }
@@ -8305,6 +8811,7 @@ export const startServer = ({ port = DEFAULT_PORT, host } = {}) => {
8305
8811
  };
8306
8812
  httpServer.once('error', handleError);
8307
8813
  const { bindHost } = resolveDesktopServerHost(host);
8814
+ openCheckRunnerAdmissions();
8308
8815
  httpServer.listen(port, bindHost, handleListening);
8309
8816
  });
8310
8817
  }
@@ -8328,6 +8835,9 @@ export const shutdownServer = async (signal) => {
8328
8835
  return shutdownServerPromise;
8329
8836
  }
8330
8837
  shutdownRequested = true;
8838
+ closeCheckRunnerAdmissions();
8839
+ const httpServerClosePromise = httpServer.listening ? closeHttpServer(httpServer) : null;
8840
+ void httpServerClosePromise?.catch(() => undefined);
8331
8841
  const pendingStartup = startServerPromise;
8332
8842
  shutdownServerPromise = (async () => {
8333
8843
  await Promise.all([
@@ -8344,6 +8854,7 @@ export const shutdownServer = async (signal) => {
8344
8854
  }
8345
8855
  await executionArtifactCleanupPromise;
8346
8856
  await stopMobileAccess();
8857
+ await shutdownCheckRunner();
8347
8858
  await clearAllTestSessions(io);
8348
8859
  await clearAllGitHubActionsSessions();
8349
8860
  await shutdownRealtimeServer({
@@ -8356,6 +8867,7 @@ export const shutdownServer = async (signal) => {
8356
8867
  stopAllReviewAgents,
8357
8868
  stopAllPlanAgents,
8358
8869
  stopAllTickets,
8870
+ httpServerClosePromise,
8359
8871
  });
8360
8872
  })().finally(() => {
8361
8873
  shutdownRequested = false;