@straiffi/archon 1.7.6 → 1.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/dist/client/assets/ActivityView-DwfDSF4q.js +1 -0
  2. package/dist/client/assets/GitHubActionsDialog-3dMCYCSZ.js +1 -0
  3. package/dist/client/assets/VerificationDialog-CoTfXMyb.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-D0PXGvSL.js → architectureDiagram-3BPJPVTR-Bdnc97XK.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-BCLAxnp5.js → blockDiagram-GPEHLZMM-boxM3She.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-DVZ5yZWV.js → c4Diagram-AAUBKEIU-CJDdF7xd.js} +1 -1
  7. package/dist/client/assets/channel-BGqdAcIT.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-BGJYj1Lu.js → chunk-2J33WTMH-ClpcO-Om.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-DI5-yK3m.js → chunk-3OPIFGDE-CcuL_65J.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-Dnr5DDsb.js → chunk-5ZQYHXKU-CUGYZXkH.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-8HdFwn-P.js → chunk-727SXJPM-CApeLDyK.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-C7x9t2uJ.js → chunk-AQP2D5EJ-8FdSvF2D.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CrhwRPh5.js → chunk-CSCIHK7Q-CZoIOF3a.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-CdD62r5U.js → chunk-KSCS5N6A-BigFQI5y.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-CqQVvwc_.js → chunk-L5ZTLDWV-DIi3SHaT.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-DLHrqoJ4.js → chunk-LZXEDZCA-B-Z0BcQy.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-B0yOdiPF.js → chunk-ND2GUHAM-EBeba0mg.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-boM-MocY.js → chunk-NZK2D7GU-DJBI2y53.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-B0jgJn0t.js → chunk-O5CBEL6O-BqxdXhMd.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-C_S_v-m2.js → chunk-WU5MYG2G-Coe1_Cbb.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-D5VuKRcj.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B9GNqpRr.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-DKl6Y8ya.js → dagre-BM42HDAG-tuTmeA0Y.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-6Lyn9DBp.js → diagram-2AECGRRQ-ILGCpG2F.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-wdS1vEkN.js → diagram-5GNKFQAL-C8qBZsgK.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-CPih29jF.js → diagram-KO2AKTUF-BXlcm_Zo.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-Cr5Px74d.js → diagram-LMA3HP47-B8V5EVbD.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-i0PjSScd.js → diagram-OG6HWLK6-CWsdOqcP.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-Dt4yOC9J.js → erDiagram-TEJ5UH35-DW8pgly2.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-Bf6kvajC.js → flowDiagram-I6XJVG4X-Djn7fOVR.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DgxePMk_.js → ganttDiagram-6RSMTGT7-DZ421MEy.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BZq2cmFS.js → gitGraphDiagram-PVQCEYII-CCNzLPuP.js} +1 -1
  33. package/dist/client/assets/index-BRc02lbW.js +273 -0
  34. package/dist/client/assets/index-CZO9HL1Z.css +2 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-Chij69KN.js → infoDiagram-5YYISTIA-Bd1gYgMp.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-3qHCLFgD.js → ishikawaDiagram-YF4QCWOH-txyMoatJ.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-DvRW8tbA.js → journeyDiagram-JHISSGLW-DhT3bmX6.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-DjR2T5I1.js → kanban-definition-UN3LZRKU-BBHt2hfC.js} +1 -1
  39. package/dist/client/assets/{line-B9OzrQ1Y.js → line-KGRcT7Qz.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-DYcDor4d.js → mermaid-parser.core-CDzt4_zR.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-DG8QYo2u.js → mermaid.core-DD_-k1aB.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-f4QPF6b3.js → mindmap-definition-RKZ34NQL-DoctVTDl.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-CMkl2jBu.js → pieDiagram-4H26LBE5-DFlLEi70.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BXaUHzJi.js → quadrantDiagram-W4KKPZXB-3YHOQgl7.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-B-Inuj_6.js → requirementDiagram-4Y6WPE33-X4fZVL9M.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BjsmZFYI.js → sankeyDiagram-5OEKKPKP-BUPnMXeG.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B1PAm6sX.js → sequenceDiagram-3UESZ5HK-D6QU3M64.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-pLONNEN8.js → stateDiagram-AJRCARHV-c35bu4qZ.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ymy1_EHr.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-BLMkSXo_.js → timeline-definition-PNZ67QCA-CX2y6e_b.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-CEwGRegG.js → vennDiagram-CIIHVFJN-BDaWltjF.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-_md2IBXC.js → wardleyDiagram-YWT4CUSO-BpBRL2sb.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-DVzwfXHc.js → xychartDiagram-2RQKCTM6-Bqw7X0UT.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 +389 -47
  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 +266 -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 +16 -2
  66. package/dist/server/lib/automationReconciliation.js.map +1 -1
  67. package/dist/server/lib/automationResumeDecision.js +4 -1
  68. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  69. package/dist/server/lib/automationStageDetails.js +173 -41
  70. package/dist/server/lib/automationStageDetails.js.map +1 -1
  71. package/dist/server/lib/chatMessages.js +37 -8
  72. package/dist/server/lib/chatMessages.js.map +1 -1
  73. package/dist/server/lib/chatOperations.js +119 -68
  74. package/dist/server/lib/chatOperations.js.map +1 -1
  75. package/dist/server/lib/checkDefinitions.js +278 -0
  76. package/dist/server/lib/checkDefinitions.js.map +1 -0
  77. package/dist/server/lib/checkGateEvaluations.js +294 -0
  78. package/dist/server/lib/checkGateEvaluations.js.map +1 -0
  79. package/dist/server/lib/checkRunner.js +674 -0
  80. package/dist/server/lib/checkRunner.js.map +1 -0
  81. package/dist/server/lib/checkRuns.js +736 -0
  82. package/dist/server/lib/checkRuns.js.map +1 -0
  83. package/dist/server/lib/checkWorkspaceIdentity.js +207 -0
  84. package/dist/server/lib/checkWorkspaceIdentity.js.map +1 -0
  85. package/dist/server/lib/executionReconciliation.js +79 -2
  86. package/dist/server/lib/executionReconciliation.js.map +1 -1
  87. package/dist/server/lib/executionRetry.js +14 -6
  88. package/dist/server/lib/executionRetry.js.map +1 -1
  89. package/dist/server/lib/executionRetryContext.js +3 -3
  90. package/dist/server/lib/executionRetryContext.js.map +1 -1
  91. package/dist/server/lib/executionRunRetention.js +41 -3
  92. package/dist/server/lib/executionRunRetention.js.map +1 -1
  93. package/dist/server/lib/executionRuns.js +135 -2
  94. package/dist/server/lib/executionRuns.js.map +1 -1
  95. package/dist/server/lib/executionTimeline.js +52 -1
  96. package/dist/server/lib/executionTimeline.js.map +1 -1
  97. package/dist/server/lib/mobileAccess.js +44 -6
  98. package/dist/server/lib/mobileAccess.js.map +1 -1
  99. package/dist/server/lib/planModeQuestions.js +12 -7
  100. package/dist/server/lib/planModeQuestions.js.map +1 -1
  101. package/dist/server/lib/planQuestionOperations.js +246 -0
  102. package/dist/server/lib/planQuestionOperations.js.map +1 -0
  103. package/dist/server/lib/planQuestions.js +139 -0
  104. package/dist/server/lib/planQuestions.js.map +1 -0
  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 +87 -7
  108. package/dist/server/lib/projects.js.map +1 -1
  109. package/dist/server/lib/realtime.js +24 -1
  110. package/dist/server/lib/realtime.js.map +1 -1
  111. package/dist/server/lib/requiredCheckPolicy.js +68 -0
  112. package/dist/server/lib/requiredCheckPolicy.js.map +1 -0
  113. package/dist/server/lib/shutdown.js +4 -4
  114. package/dist/server/lib/shutdown.js.map +1 -1
  115. package/dist/server/lib/specArtifact.js +36 -38
  116. package/dist/server/lib/specArtifact.js.map +1 -1
  117. package/dist/server/lib/specMessages.js +31 -18
  118. package/dist/server/lib/specMessages.js.map +1 -1
  119. package/dist/server/lib/specOperations.js +129 -78
  120. package/dist/server/lib/specOperations.js.map +1 -1
  121. package/dist/server/lib/testCommandRunner.js +3 -2
  122. package/dist/server/lib/testCommandRunner.js.map +1 -1
  123. package/dist/server/lib/testSessions.js +2 -6
  124. package/dist/server/lib/testSessions.js.map +1 -1
  125. package/dist/server/lib/ticketAutomationCommit.js +185 -22
  126. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  127. package/dist/server/lib/ticketFollowUpOperations.js +19 -6
  128. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  129. package/dist/server/lib/ticketMessages.js +38 -9
  130. package/dist/server/lib/ticketMessages.js.map +1 -1
  131. package/dist/server/lib/ticketWorkflowOperations.js +10 -1
  132. package/dist/server/lib/ticketWorkflowOperations.js.map +1 -1
  133. package/dist/server/lib/tickets.js +21 -1
  134. package/dist/server/lib/tickets.js.map +1 -1
  135. package/dist/server/lib/types.js +2 -0
  136. package/dist/server/lib/types.js.map +1 -1
  137. package/dist/server/workers/chat.js +48 -2
  138. package/dist/server/workers/chat.js.map +1 -1
  139. package/dist/server/workers/followUp.js +50 -2
  140. package/dist/server/workers/followUp.js.map +1 -1
  141. package/dist/server/workers/plan.js +57 -4
  142. package/dist/server/workers/plan.js.map +1 -1
  143. package/dist/server/workers/planFollowUp.js +51 -2
  144. package/dist/server/workers/planFollowUp.js.map +1 -1
  145. package/dist/server/workers/review.js +2 -10
  146. package/dist/server/workers/review.js.map +1 -1
  147. package/dist/server/workers/specSession.js +55 -5
  148. package/dist/server/workers/specSession.js.map +1 -1
  149. package/package.json +1 -1
  150. package/dist/client/assets/ActivityView-BrZY8vYf.js +0 -1
  151. package/dist/client/assets/GitHubActionsDialog-CUfdVw97.js +0 -1
  152. package/dist/client/assets/TestsDialog-FXcnLOyf.js +0 -4
  153. package/dist/client/assets/channel-BCPCrThZ.js +0 -1
  154. package/dist/client/assets/classDiagram-4FO5ZUOK-BdJlc_1i.js +0 -1
  155. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Bidw5Zc1.js +0 -1
  156. package/dist/client/assets/index-BJGHMaMz.css +0 -2
  157. package/dist/client/assets/index-CjRMYO1U.js +0 -273
  158. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-kWPDF3wr.js +0 -1
@@ -8,11 +8,11 @@ import { createHash, randomUUID } from 'crypto';
8
8
  import cors from 'cors';
9
9
  import { adjectives, animals, uniqueNamesGenerator } from 'unique-names-generator';
10
10
  import db from './db.js';
11
- import { deleteChatSession, getChatSession, listChatSessions, updateChatSessionMode } from './lib/chats.js';
11
+ import { deleteChatSession, getChatSession, listChatSessions } from './lib/chats.js';
12
12
  import { deleteSpecSession, getCreatedTicketIdsForSnapshot, getSpecSession, getSpecSnapshot, listSpecSessions } from './lib/specs.js';
13
13
  import { createChatAttachment, createSpecAttachment, getAttachmentById, deleteChatAttachment, deleteProjectAttachments, deleteSpecSessionAttachment, getChatAttachmentForSession, getChatAttachmentLinkedMessageId, getSpecAttachmentForSession, getSpecAttachmentLinkedMessageId, MAX_CHAT_ATTACHMENTS_PER_TURN, MAX_CHAT_ATTACHMENT_SIZE_BYTES, MAX_SPEC_ATTACHMENTS_PER_TURN, MAX_SPEC_ATTACHMENT_SIZE_BYTES, readChatAttachmentContent, readSpecAttachmentContent, validateImageAttachment, } from './lib/attachments.js';
14
- import { createProjectChatSession, resolveChatMode, resolveChatTool, stopProjectChatSessionResponse, submitProjectChatSessionMessage, } from './lib/chatOperations.js';
15
- import { createProjectSpecSession, deleteProjectSpecSession, stopProjectSpecSessionResponse, submitProjectSpecSessionMessage, updateProjectSpecSessionMode, } from './lib/specOperations.js';
14
+ import { createProjectChatSession, resolveChatTool, resolveProjectChatPlanQuestionSet, stopProjectChatSessionResponse, submitProjectChatSessionMessage, updateProjectChatSessionMode, } from './lib/chatOperations.js';
15
+ import { createProjectSpecSession, deleteProjectSpecSession, resolveProjectSpecPlanQuestionSet, stopProjectSpecSessionResponse, submitProjectSpecSessionMessage, updateProjectSpecSessionMode, } from './lib/specOperations.js';
16
16
  import { buildProjectSpecReviewRevisionDraft, getProjectSpecReviewFindings, startProjectSpecReview, stopProjectSpecReview, } from './lib/specReviewOperations.js';
17
17
  import { resolveChatSessionTarget } from './lib/chatTargets.js';
18
18
  import { createChatMessage, getChatMessage, updateChatMessageContent } from './lib/chatMessages.js';
@@ -52,7 +52,7 @@ 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 } from './lib/ticketAutomationCommit.js';
56
56
  import { getDiscoveredSkills, normalizeSkillNames } from './lib/skills.js';
57
57
  import { clearAllPreviewProxies } from './lib/previewProxy.js';
58
58
  import { createExecutionRun, addExecutionRunLink, addExecutionRunLinks, getExecutionRun, hasActiveBuildExecutionRunForTicket, getGitHubActionsExecutionDetails, getTestExecutionDetails, GitHubActionsDispatchConflictError, listExecutionRunLinks, listExecutionRuns, listLifecycleEvents, listProjectLifecycleEvents, toCanonicalLifecycleEvent, transitionExecutionRun, withExecutionRunTransaction, } from './lib/executionRuns.js';
@@ -61,7 +61,7 @@ import { listExecutionTimeline } from './lib/executionTimeline.js';
61
61
  import { loadExecutionArtifactSummariesForRuns, openExecutionArtifactForRetrieval, } from './lib/executionArtifacts.js';
62
62
  import { cleanupAllExecutionEvidence, EXECUTION_EVIDENCE_CLEANUP_INTERVAL_MS, resolveExecutionEvidenceRetentionDays, } from './lib/executionRunRetention.js';
63
63
  import { ExecutionArtifactCleanupError, reconcileExecutionArtifactRegistrationIntents, reconcileFinalizedExecutionArtifacts, reconcileProjectExecutionArtifacts, resolveArtifactCleanupIntervalMs, } from './lib/executionArtifactRetention.js';
64
- import { shutdownRealtimeServer } from './lib/shutdown.js';
64
+ import { closeHttpServer, shutdownRealtimeServer } from './lib/shutdown.js';
65
65
  import { resolveClientBuildPaths, shouldServeClientApp, STATIC_CLIENT_DIRECTORY_OPTIONS, STATIC_CLIENT_INDEX_OPTIONS, } from './lib/staticClient.js';
66
66
  import { deleteIntegrationConnection, getGitHubConnectionConfig, getJiraConnectionConfig, listIntegrationConnections, upsertIntegrationConnection, } from './lib/integrations/index.js';
67
67
  import { createGitHubPullRequest, createOrFindGitHubPullRequest, findOpenGitHubPullRequest, findOpenGitHubPullRequestByHeadBranch, getGitHubPullRequestComments, getGitHubPullRequestReviews, getGitHubPullRequest, GitHubApiError, hasGitHubRemoteBranch, resolveGitHubRepository, validateGitHubConnection, } from './lib/integrations/github.js';
@@ -75,11 +75,18 @@ import { PlanningReferenceError, resolveExternalPlanningDescription } from './li
75
75
  import { refreshIfNeeded as refreshAssignedIssues, clearCachedAssignedIssues } from './lib/integrations/assignedIssuesCache.js';
76
76
  import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage } from './lib/commitMessage.js';
77
77
  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';
78
+ import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectPayload, } from './lib/projects.js';
79
79
  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';
80
+ import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
81
+ import { closeCheckRunnerAdmissions, getCheckBatchAttempts, openCheckRunnerAdmissions, rerunCheckBatchAndSchedule, shutdownCheckRunner, startCheckBatch, stopCheckBatch } from './lib/checkRunner.js';
82
+ import { findActiveCheckBatchForTarget, getCheckAttemptDetails, getCheckBatchDetails, hasActiveProjectCheckBatch, isCheckAttemptCurrent, resolveCheckBatchRerunEligibility } from './lib/checkRuns.js';
83
+ import { getCheckDefinitionFingerprint } from './lib/checkDefinitions.js';
84
+ import { getCheckGateEvaluation } from './lib/checkGateEvaluations.js';
85
+ import { captureCheckWorkspaceIdentity } from './lib/checkWorkspaceIdentity.js';
81
86
  import { getBundleBuildBlockerResponse, shouldStartBundleBuildNow, transitionTicketState, } from './lib/ticketWorkflowOperations.js';
82
87
  import { submitPlanTicketFollowUp, submitTicketFollowUp, } from './lib/ticketFollowUpOperations.js';
88
+ import { closePendingTicketPlanQuestionSet, listPendingTicketPlanQuestionSetMessageIds, resolveTicketPlanQuestionSet, restoreClosedTicketPlanQuestionSets, } from './lib/planQuestionOperations.js';
89
+ import { hasPendingAskQuestionSet } from './lib/planQuestions.js';
83
90
  import { canUseBuildControls, formatActiveRunConflict, getTicketRunReadinessError, resumeBuildTicketRun, runProjectTarget, runTicketRun, stopBuildTicketRun, stopReviewTicketRun, stopProjectTargetRun, stopTicketRun, } from './lib/ticketRunOperations.js';
84
91
  import { isTicketAgentSettingsPayload, updateTicketAgentSettings, } from './lib/ticketSettingsOperations.js';
85
92
  import { rerunTicketReviewOperation, resolveBundleReviewOwner, transitionBundleToReview } from './lib/bundleReviewOperations.js';
@@ -290,15 +297,90 @@ const serializeGitHubActionsExecutionDetail = (details) => {
290
297
  jobs_count: details.jobs_count,
291
298
  };
292
299
  };
293
- const buildExecutionRunDetailResponse = (project, run) => {
294
- const retryEligibility = getExecutionRetryEligibility(project.id, run.id);
300
+ const serializeCheckAttemptExecutionDetail = (projectId, runId) => {
301
+ const attempt = getCheckAttemptDetails(projectId, runId);
302
+ return {
303
+ kind: 'check_attempt',
304
+ available: true,
305
+ attempt,
306
+ batch: attempt ? getCheckBatchDetails(projectId, attempt.batch_run_id) : null,
307
+ };
308
+ };
309
+ const hydrateCheckAttemptCurrency = async (project, batch, attempts) => {
310
+ const terminalAttempts = attempts.filter(attempt => attempt.outcome !== null);
311
+ if (terminalAttempts.length === 0) {
312
+ return attempts.map(attempt => ({ ...attempt, currency: 'pending' }));
313
+ }
314
+ const workspace = resolveExplicitProjectTestWorkspace(project, batch.target_kind, batch.target_bundle_id);
315
+ if ('error' in workspace) {
316
+ return attempts.map(attempt => ({
317
+ ...attempt,
318
+ currency: attempt.outcome === null ? 'pending' : 'unavailable',
319
+ }));
320
+ }
321
+ const currentIdentity = await captureCheckWorkspaceIdentity(workspace.cwd);
322
+ const currentProject = getProjectById(project.id);
323
+ const definitionsById = new Map(getProjectCheckDefinitions(currentProject).map(definition => [definition.id, definition]));
324
+ return attempts.map(attempt => {
325
+ if (attempt.outcome === null) {
326
+ return { ...attempt, currency: 'pending' };
327
+ }
328
+ const definition = definitionsById.get(attempt.definition_id);
329
+ if (!definition || getCheckDefinitionFingerprint(definition) !== attempt.definition_fingerprint) {
330
+ return { ...attempt, currency: 'stale' };
331
+ }
332
+ const currency = currentIdentity.state === 'unavailable'
333
+ ? 'unavailable'
334
+ : isCheckAttemptCurrent(attempt, currentIdentity) ? 'current' : 'stale';
335
+ return { ...attempt, currency };
336
+ });
337
+ };
338
+ const getCheckBatchRerunEligibility = (project, batch, selection) => {
339
+ const workspace = resolveExplicitProjectTestWorkspace(project, batch.target_kind, batch.target_bundle_id);
340
+ return resolveCheckBatchRerunEligibility({
341
+ projectId: project.id,
342
+ batchRunId: batch.execution_run_id,
343
+ availableDefinitions: getProjectCheckDefinitions(project),
344
+ selection,
345
+ targetAvailable: !('error' in workspace && typeof workspace.error === 'string' && workspace.error.includes('no longer exists')),
346
+ workspaceAvailable: !('error' in workspace),
347
+ });
348
+ };
349
+ const buildExecutionRunDetailResponse = async (project, run) => {
350
+ const checkBatch = run.kind === 'check_batch' ? getCheckBatchDetails(project.id, run.id) : null;
351
+ const retryEligibility = checkBatch
352
+ ? getCheckBatchRerunEligibility(project, checkBatch)
353
+ : getExecutionRetryEligibility(project.id, run.id);
354
+ const checkBatchAttempts = checkBatch
355
+ ? await hydrateCheckAttemptCurrency(project, checkBatch, getCheckBatchAttempts(project.id, run.id))
356
+ : [];
357
+ const checkAttemptDetail = run.kind === 'check_attempt' ? serializeCheckAttemptExecutionDetail(project.id, run.id) : null;
358
+ const checkAttempt = checkAttemptDetail?.attempt && checkAttemptDetail.batch
359
+ ? (await hydrateCheckAttemptCurrency(project, checkAttemptDetail.batch, [checkAttemptDetail.attempt]))[0]
360
+ : checkAttemptDetail?.attempt ?? null;
295
361
  const details = run.kind === 'test'
296
362
  ? serializeTestExecutionDetail(getTestExecutionDetails(project.id, run.id), retryEligibility)
297
- : run.kind === 'github_actions'
298
- ? { ...serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id)), retry_eligibility: retryEligibility }
299
- : ['automation_commit', 'automation_push', 'automation_pull_request'].includes(run.kind)
300
- ? { kind: run.kind, available: true, automation_stage: getAutomationStageDetails(project.id, run.id), retry_eligibility: retryEligibility }
301
- : { kind: run.kind, available: true, retry_eligibility: retryEligibility };
363
+ : run.kind === 'check_batch'
364
+ ? {
365
+ kind: 'check_batch',
366
+ available: true,
367
+ batch: checkBatch,
368
+ attempts: checkBatchAttempts,
369
+ retry_eligibility: retryEligibility,
370
+ action_kind: retryEligibility.eligible ? 'rerun' : null,
371
+ }
372
+ : run.kind === 'check_attempt'
373
+ ? {
374
+ ...checkAttemptDetail,
375
+ attempt: checkAttempt,
376
+ retry_eligibility: retryEligibility,
377
+ action_kind: null,
378
+ }
379
+ : run.kind === 'github_actions'
380
+ ? { ...serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id)), retry_eligibility: retryEligibility }
381
+ : ['automation_commit', 'automation_push', 'automation_pull_request'].includes(run.kind)
382
+ ? { kind: run.kind, available: true, automation_stage: getAutomationStageDetails(project.id, run.id), retry_eligibility: retryEligibility }
383
+ : { kind: run.kind, available: true, retry_eligibility: retryEligibility };
302
384
  const artifacts = loadExecutionArtifactSummariesForRuns(project.id, [run.id], MAX_ARTIFACTS_PER_RUN).get(run.id)
303
385
  ?? { artifacts: [], artifact_count: 0 };
304
386
  return {
@@ -1752,6 +1834,10 @@ const buildBundleTickets = (projectId, bundleId, broadcaster) => {
1752
1834
  }
1753
1835
  }
1754
1836
  const movedTickets = [];
1837
+ const pendingQuestionMessageIds = new Map(orderedPlanTickets.map(ticket => [
1838
+ ticket.id,
1839
+ listPendingTicketPlanQuestionSetMessageIds(ticket.id),
1840
+ ]));
1755
1841
  const ticketSnapshots = orderedPlanTickets.map(ticket => db.prepare(`
1756
1842
  SELECT id, project_id, state, tool, agent_status, agent_log, streaming_response, branch,
1757
1843
  build_completed_at, run_setup_status, auto_park_dismissed_at, planning_context_json, lane_order
@@ -1765,6 +1851,7 @@ const buildBundleTickets = (projectId, bundleId, broadcaster) => {
1765
1851
  const acceptBundleBuild = () => {
1766
1852
  let activeSequenceId = null;
1767
1853
  for (const [index, planTicket] of orderedPlanTickets.entries()) {
1854
+ closePendingTicketPlanQuestionSet(planTicket.id);
1768
1855
  const buildTransition = prepareTicketForBuild(planTicket.id, {
1769
1856
  ignoredBlockerTicketIds: bundleTicketIds,
1770
1857
  });
@@ -1797,6 +1884,7 @@ const buildBundleTickets = (projectId, bundleId, broadcaster) => {
1797
1884
  planning_context_json = ?, lane_order = ?, updated_at = CURRENT_TIMESTAMP
1798
1885
  WHERE id = ? AND project_id = ?
1799
1886
  `).run(snapshot.state, snapshot.tool, snapshot.agent_status, snapshot.agent_log, snapshot.streaming_response, snapshot.branch, snapshot.build_completed_at, snapshot.run_setup_status, snapshot.auto_park_dismissed_at, snapshot.planning_context_json, snapshot.lane_order, snapshot.id, snapshot.project_id);
1887
+ restoreClosedTicketPlanQuestionSets(snapshot.id, pendingQuestionMessageIds.get(snapshot.id) ?? []);
1800
1888
  }
1801
1889
  },
1802
1890
  }
@@ -2406,20 +2494,10 @@ app.get('/projects/:id/chat-sessions/:sessionId/agent-runs', (req, res) => {
2406
2494
  return res.json({ runs: listAgentRunsForParent('chat', chatSession.id) });
2407
2495
  });
2408
2496
  app.patch('/projects/:id/chat-sessions/:sessionId', (req, res) => {
2409
- const project = getProjectById(req.params.id);
2410
- if (!project) {
2411
- return res.status(404).json({ error: 'Not found' });
2412
- }
2413
- const existing = getProjectChatSession(project.id, req.params.sessionId);
2414
- if (!existing) {
2415
- return res.status(404).json({ error: 'Not found' });
2416
- }
2417
- const updated = updateChatSessionMode(existing.id, resolveChatMode(req.body.mode));
2418
- if (!updated) {
2419
- return res.status(404).json({ error: 'Not found' });
2420
- }
2421
- io.emit('chat-session:updated', updated);
2422
- return res.json(updated);
2497
+ const result = updateProjectChatSessionMode({ projectId: req.params.id, chatSessionId: req.params.sessionId, payload: req.body, broadcaster: io });
2498
+ return result.ok
2499
+ ? res.status(result.status).json(result.value)
2500
+ : res.status(result.status).json({ error: result.error, ...(result.value ? { chat_session: result.value } : {}) });
2423
2501
  });
2424
2502
  app.post('/projects/:id/chat-sessions/:sessionId/messages', (req, res) => {
2425
2503
  const result = submitProjectChatSessionMessage({
@@ -2429,10 +2507,22 @@ app.post('/projects/:id/chat-sessions/:sessionId/messages', (req, res) => {
2429
2507
  broadcaster: io,
2430
2508
  });
2431
2509
  if (!result.ok) {
2432
- return res.status(result.status).json({ error: result.error });
2510
+ return res.status(result.status).json({ error: result.error, ...(result.value ? { chat_session: result.value } : {}) });
2433
2511
  }
2434
2512
  return res.status(result.status).json(result.value);
2435
2513
  });
2514
+ app.post('/projects/:id/chat-sessions/:sessionId/plan-questions/:messageId/resolve', (req, res) => {
2515
+ const result = resolveProjectChatPlanQuestionSet({
2516
+ projectId: req.params.id,
2517
+ chatSessionId: req.params.sessionId,
2518
+ messageId: req.params.messageId,
2519
+ payload: req.body,
2520
+ broadcaster: io,
2521
+ });
2522
+ return result.ok
2523
+ ? res.status(result.status).json(result.value)
2524
+ : res.status(result.status).json({ error: result.error, ...(result.value ? { chat_session: result.value } : {}) });
2525
+ });
2436
2526
  app.post('/projects/:id/chat-sessions/:sessionId/attachments', (req, res) => {
2437
2527
  const project = getProjectById(req.params.id);
2438
2528
  if (!project) {
@@ -2720,7 +2810,7 @@ app.patch('/projects/:id/spec-sessions/:sessionId', (req, res) => {
2720
2810
  broadcaster: io,
2721
2811
  });
2722
2812
  if (!result.ok) {
2723
- return res.status(result.status).json({ error: result.error });
2813
+ return res.status(result.status).json({ error: result.error, ...(result.value ? { spec_session: result.value } : {}) });
2724
2814
  }
2725
2815
  return res.status(result.status).json(result.value);
2726
2816
  });
@@ -2732,10 +2822,22 @@ app.post('/projects/:id/spec-sessions/:sessionId/messages', (req, res) => {
2732
2822
  broadcaster: io,
2733
2823
  });
2734
2824
  if (!result.ok) {
2735
- return res.status(result.status).json({ error: result.error });
2825
+ return res.status(result.status).json({ error: result.error, ...(result.value ? { spec_session: result.value } : {}) });
2736
2826
  }
2737
2827
  return res.status(result.status).json(result.value);
2738
2828
  });
2829
+ app.post('/projects/:id/spec-sessions/:sessionId/plan-questions/:messageId/resolve', (req, res) => {
2830
+ const result = resolveProjectSpecPlanQuestionSet({
2831
+ projectId: req.params.id,
2832
+ specSessionId: req.params.sessionId,
2833
+ messageId: req.params.messageId,
2834
+ payload: req.body,
2835
+ broadcaster: io,
2836
+ });
2837
+ return result.ok
2838
+ ? res.status(result.status).json(result.value)
2839
+ : res.status(result.status).json({ error: result.error, ...(result.value ? { spec_session: result.value } : {}) });
2840
+ });
2739
2841
  app.post('/projects/:id/spec-sessions/:sessionId/attachments', (req, res) => {
2740
2842
  const project = getProjectById(req.params.id);
2741
2843
  if (!project) {
@@ -3606,6 +3708,9 @@ app.delete('/bundles/:id', (req, res) => {
3606
3708
  if (result.project.active_target_kind === 'bundle' && result.project.active_target_bundle_id === bundle.id) {
3607
3709
  return res.status(409).json({ error: 'Cannot delete the currently selected target bundle' });
3608
3710
  }
3711
+ if (findActiveCheckBatchForTarget(result.project.id, 'bundle', bundle.id)) {
3712
+ return res.status(409).json({ error: 'Cannot delete a bundle while checks are running' });
3713
+ }
3609
3714
  const affectedChatSessionIds = listChatSessions(result.project.id)
3610
3715
  .filter(chatSession => chatSession.target_bundle_id === bundle.id)
3611
3716
  .map(chatSession => chatSession.id);
@@ -3954,7 +4059,7 @@ app.post('/bundles/:id/follow-up', (req, res) => {
3954
4059
  return res.status(response.status).json(response.value);
3955
4060
  });
3956
4061
  const AUTOMATION_SIDE_EFFECT_KINDS = new Set(['automation_commit', 'automation_push', 'automation_pull_request']);
3957
- const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix']);
4062
+ const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix', 'check_batch']);
3958
4063
  app.post('/bundles/:id/resume-automation', async (req, res) => {
3959
4064
  const result = getRequiredProject(req);
3960
4065
  if ('error' in result) {
@@ -4250,7 +4355,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4250
4355
  return res.status(followUpResult.status).json({ error: followUpResult.error });
4251
4356
  }
4252
4357
  }
4253
- else if (decision.outcome === 'continue_commit_chain') {
4358
+ else if (decision.outcome === 'continue_commit_chain' || decision.outcome === 'rerun_required_checks') {
4254
4359
  try {
4255
4360
  if (!revalidateDecision()) {
4256
4361
  return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
@@ -4261,7 +4366,10 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4261
4366
  progressionRunId,
4262
4367
  broadcaster: io,
4263
4368
  });
4264
- await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId);
4369
+ const checkGateResume = decision.outcome === 'rerun_required_checks'
4370
+ ? resolveAutomationCheckGateResume(result.project.id, progressionRunId)
4371
+ : undefined;
4372
+ await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId, checkGateResume);
4265
4373
  }
4266
4374
  catch (error) {
4267
4375
  return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
@@ -5473,7 +5581,7 @@ app.post('/projects', (req, res) => {
5473
5581
  return res.status(400).json({ error: validation.error });
5474
5582
  }
5475
5583
  const projectCount = Number(db.prepare('SELECT COUNT(*) AS count FROM projects').get()?.count ?? 0);
5476
- 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;
5584
+ 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, 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;
5477
5585
  if (!repo_path) {
5478
5586
  return res.status(400).json({ error: 'repo_path must be a non-empty string' });
5479
5587
  }
@@ -5493,6 +5601,8 @@ app.post('/projects', (req, res) => {
5493
5601
  repo_path,
5494
5602
  run_setup,
5495
5603
  run_services,
5604
+ check_definitions,
5605
+ required_check_definition_ids,
5496
5606
  test_commands,
5497
5607
  run_ide,
5498
5608
  preview_service_name,
@@ -5513,8 +5623,8 @@ app.post('/projects', (req, res) => {
5513
5623
  automation_review_severity,
5514
5624
  automation_review_max_iterations
5515
5625
  )
5516
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5517
- `).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);
5626
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5627
+ `).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, 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);
5518
5628
  replaceProjectLinks(id, linked_project_ids ?? []);
5519
5629
  ensureProjectRootBundle(id);
5520
5630
  if (projectCount === 0) {
@@ -6680,13 +6790,15 @@ app.patch('/projects/:id', (req, res) => {
6680
6790
  });
6681
6791
  }
6682
6792
  }
6683
- 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;
6793
+ 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, 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;
6684
6794
  db.prepare(`
6685
6795
  UPDATE projects SET
6686
6796
  name = ?,
6687
6797
  repo_path = ?,
6688
6798
  run_setup = ?,
6689
6799
  run_services = ?,
6800
+ check_definitions = ?,
6801
+ required_check_definition_ids = ?,
6690
6802
  test_commands = ?,
6691
6803
  run_ide = ?,
6692
6804
  preview_service_name = ?,
@@ -6708,7 +6820,7 @@ app.patch('/projects/:id', (req, res) => {
6708
6820
  automation_review_max_iterations = ?,
6709
6821
  updated_at = CURRENT_TIMESTAMP
6710
6822
  WHERE id = ?
6711
- `).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);
6823
+ `).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, '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);
6712
6824
  if (hasOwn(validation.values, 'linked_project_ids')) {
6713
6825
  replaceProjectLinks(project.id, linked_project_ids ?? []);
6714
6826
  }
@@ -6737,6 +6849,9 @@ const assertProjectDeletionEligible = (projectId, claimId) => {
6737
6849
  if (stats.ticketCount > 0 || stats.worktreeCount > 0 || stats.bundleCount > 0) {
6738
6850
  throw new ProjectDeletionError(409, 'Cannot delete a project with existing tickets, bundles, or worktrees');
6739
6851
  }
6852
+ if (hasActiveProjectCheckBatch(projectId)) {
6853
+ throw new ProjectDeletionError(409, 'Cannot delete a project while checks are active');
6854
+ }
6740
6855
  };
6741
6856
  const completeClaimedProjectDeletion = async (projectId, claimId) => {
6742
6857
  await runExclusiveExecutionArtifactMaintenance(() => reconcileProjectExecutionArtifacts(projectId, {
@@ -6763,6 +6878,19 @@ const completeClaimedProjectDeletion = async (projectId, claimId) => {
6763
6878
  db.prepare("DELETE FROM execution_artifacts WHERE project_id = ? AND availability = 'deleted'").run(projectId);
6764
6879
  deleteProjectAttachments(projectId);
6765
6880
  db.prepare(`
6881
+ DELETE FROM check_gate_evaluation_attempts
6882
+ WHERE evaluation_id IN (SELECT id FROM check_gate_evaluations WHERE project_id = ?)
6883
+ `).run(projectId);
6884
+ db.prepare('DELETE FROM check_gate_evaluations WHERE project_id = ?').run(projectId);
6885
+ db.prepare(`
6886
+ DELETE FROM check_attempt_details
6887
+ WHERE execution_run_id IN (SELECT id FROM execution_runs WHERE project_id = ?)
6888
+ `).run(projectId);
6889
+ db.prepare(`
6890
+ DELETE FROM check_batch_details
6891
+ WHERE execution_run_id IN (SELECT id FROM execution_runs WHERE project_id = ?)
6892
+ `).run(projectId);
6893
+ db.prepare(`
6766
6894
  UPDATE execution_runs
6767
6895
  SET parent_run_id = NULL, retry_of_run_id = NULL
6768
6896
  WHERE project_id = ?
@@ -7649,6 +7777,186 @@ app.post('/projects/:id/test-sessions', (req, res) => {
7649
7777
  targetLabel: workspace.target_label,
7650
7778
  }));
7651
7779
  });
7780
+ app.post('/projects/:id/check-batches', async (req, res) => {
7781
+ const project = getProjectById(req.params.id);
7782
+ if (!project) {
7783
+ return res.status(404).json({ error: 'Not found' });
7784
+ }
7785
+ const body = req.body && typeof req.body === 'object' && !Array.isArray(req.body) ? req.body : null;
7786
+ const targetKind = body?.target_kind === 'bundle'
7787
+ ? 'bundle'
7788
+ : body?.target_kind === 'repo_root'
7789
+ ? 'repo_root'
7790
+ : null;
7791
+ if (!targetKind) {
7792
+ return res.status(400).json({ error: 'target_kind must be repo_root or bundle' });
7793
+ }
7794
+ const definitionIds = parseCheckDefinitionIds(body?.definition_ids);
7795
+ if (!definitionIds) {
7796
+ return res.status(400).json({ error: 'definition_ids must contain at least one unique check definition ID' });
7797
+ }
7798
+ const definitions = getProjectCheckDefinitions(project);
7799
+ const definitionsById = new Map(definitions.map(definition => [definition.id, definition]));
7800
+ const selectedDefinitions = definitionIds.map(id => definitionsById.get(id)).filter((definition) => Boolean(definition));
7801
+ if (selectedDefinitions.length !== definitionIds.length) {
7802
+ return res.status(409).json({ error: 'One or more selected check definitions are stale or unavailable' });
7803
+ }
7804
+ const targetBundleId = typeof body?.target_bundle_id === 'string' ? body.target_bundle_id.trim() || null : null;
7805
+ const workspace = resolveExplicitProjectTestWorkspace(project, targetKind, targetBundleId);
7806
+ if ('error' in workspace) {
7807
+ return res.status(Number(workspace.status)).json({ error: workspace.error });
7808
+ }
7809
+ try {
7810
+ const batch = await startCheckBatch({
7811
+ projectId: project.id,
7812
+ cwd: workspace.cwd,
7813
+ targetKind: workspace.target_kind,
7814
+ targetBundleId: workspace.target_bundle_id,
7815
+ targetLabel: workspace.target_label,
7816
+ selectedDefinitions,
7817
+ availableDefinitions: definitions,
7818
+ sourceReference: { kind: 'project', id: project.id },
7819
+ }, io);
7820
+ return res.status(201).json({
7821
+ batch,
7822
+ attempts: getCheckBatchAttempts(project.id, batch.execution_run_id),
7823
+ });
7824
+ }
7825
+ catch (error) {
7826
+ return res.status(409).json({ error: getErrorMessage(error) });
7827
+ }
7828
+ });
7829
+ const parseCheckDefinitionIds = (value) => {
7830
+ if (!Array.isArray(value) || value.length === 0 || value.some(id => typeof id !== 'string' || id.trim() === '')) {
7831
+ return null;
7832
+ }
7833
+ const ids = value.map(id => id.trim());
7834
+ return ids.length === new Set(ids).size ? ids : null;
7835
+ };
7836
+ const parseCheckRerunSelection = (body) => {
7837
+ const payload = body && typeof body === 'object' && !Array.isArray(body) ? body : {};
7838
+ if (payload.mode === undefined || payload.mode === 'all') {
7839
+ return { mode: 'all' };
7840
+ }
7841
+ if (payload.mode === 'failed_only') {
7842
+ return { mode: 'failed' };
7843
+ }
7844
+ if (payload.mode !== 'selected') {
7845
+ return { error: 'mode must be all, failed_only, or selected' };
7846
+ }
7847
+ const definitionIds = parseCheckDefinitionIds(payload.definition_ids);
7848
+ if (!definitionIds) {
7849
+ return { error: 'definition_ids must contain at least one unique source definition ID for selected reruns' };
7850
+ }
7851
+ return { mode: 'selected', definitionIds };
7852
+ };
7853
+ const rerunProjectCheckBatch = async (project, sourceBatchRunId, body) => {
7854
+ const sourceBatch = getCheckBatchDetails(project.id, sourceBatchRunId);
7855
+ if (!sourceBatch) {
7856
+ return { ok: false, status: 404, error: 'Check batch not found', reason: 'status_not_retryable' };
7857
+ }
7858
+ const selection = parseCheckRerunSelection(body);
7859
+ if ('error' in selection) {
7860
+ return { ok: false, status: 400, error: selection.error, reason: 'retry_input_unavailable' };
7861
+ }
7862
+ const eligibility = getCheckBatchRerunEligibility(project, sourceBatch, selection);
7863
+ if (!eligibility.eligible) {
7864
+ return {
7865
+ ok: false,
7866
+ status: 409,
7867
+ error: 'Check batch cannot be rerun with its current target and definitions',
7868
+ reason: eligibility.reason,
7869
+ };
7870
+ }
7871
+ const workspace = resolveExplicitProjectTestWorkspace(project, sourceBatch.target_kind, sourceBatch.target_bundle_id);
7872
+ if ('error' in workspace) {
7873
+ return { ok: false, status: Number(workspace.status), error: workspace.error, reason: 'retry_workspace_unavailable' };
7874
+ }
7875
+ try {
7876
+ const value = await rerunCheckBatchAndSchedule({
7877
+ projectId: project.id,
7878
+ sourceBatchRunId,
7879
+ cwd: workspace.cwd,
7880
+ availableDefinitions: getProjectCheckDefinitions(project),
7881
+ selection,
7882
+ }, io);
7883
+ return { ok: true, value };
7884
+ }
7885
+ catch (error) {
7886
+ return { ok: false, status: 409, error: getErrorMessage(error), reason: 'retry_input_unavailable' };
7887
+ }
7888
+ };
7889
+ app.get('/projects/:id/check-batches', (req, res) => {
7890
+ const project = getProjectById(req.params.id);
7891
+ if (!project) {
7892
+ return res.status(404).json({ error: 'Not found' });
7893
+ }
7894
+ const query = parseLifecycleListQuery(req.query);
7895
+ if ('error' in query) {
7896
+ return res.status(400).json({ error: query.error });
7897
+ }
7898
+ try {
7899
+ return res.json(listExecutionTimeline({
7900
+ project,
7901
+ kind: 'check_batch',
7902
+ statuses: query.statuses,
7903
+ cursor: query.cursor,
7904
+ limit: query.limit,
7905
+ }));
7906
+ }
7907
+ catch (error) {
7908
+ if (error instanceof Error && error.message === 'Invalid execution run cursor') {
7909
+ return res.status(400).json({ error: 'cursor must be a valid execution run cursor' });
7910
+ }
7911
+ throw error;
7912
+ }
7913
+ });
7914
+ app.get('/projects/:id/check-batches/:batchRunId', async (req, res) => {
7915
+ const project = getProjectById(req.params.id);
7916
+ if (!project) {
7917
+ return res.status(404).json({ error: 'Not found' });
7918
+ }
7919
+ const batch = getCheckBatchDetails(req.params.id, req.params.batchRunId);
7920
+ if (!batch) {
7921
+ return res.status(404).json({ error: 'Check batch not found' });
7922
+ }
7923
+ return res.json({
7924
+ batch,
7925
+ attempts: await hydrateCheckAttemptCurrency(project, batch, getCheckBatchAttempts(req.params.id, req.params.batchRunId)),
7926
+ });
7927
+ });
7928
+ app.get('/projects/:id/check-gate-evaluations/:evaluationId', (req, res) => {
7929
+ const evaluation = getCheckGateEvaluation(req.params.id, req.params.evaluationId);
7930
+ if (!evaluation) {
7931
+ return res.status(404).json({ error: 'Check gate evaluation not found' });
7932
+ }
7933
+ return res.json(evaluation);
7934
+ });
7935
+ app.post('/projects/:id/check-batches/:batchRunId/stop', async (req, res) => {
7936
+ if (!getCheckBatchDetails(req.params.id, req.params.batchRunId)) {
7937
+ return res.status(404).json({ error: 'Check batch not found' });
7938
+ }
7939
+ try {
7940
+ const batch = await stopCheckBatch(req.params.id, req.params.batchRunId, io);
7941
+ return res.json({
7942
+ batch,
7943
+ attempts: getCheckBatchAttempts(req.params.id, req.params.batchRunId),
7944
+ });
7945
+ }
7946
+ catch (error) {
7947
+ return res.status(409).json({ error: getErrorMessage(error) });
7948
+ }
7949
+ });
7950
+ app.post('/projects/:id/check-batches/:batchRunId/rerun', async (req, res) => {
7951
+ const project = getProjectById(req.params.id);
7952
+ if (!project) {
7953
+ return res.status(404).json({ error: 'Not found' });
7954
+ }
7955
+ const result = await rerunProjectCheckBatch(project, req.params.batchRunId, req.body);
7956
+ return result.ok
7957
+ ? res.status(201).json(result.value)
7958
+ : res.status(result.status).json({ error: result.error });
7959
+ });
7652
7960
  app.get('/projects/:id/execution-runs', (req, res) => {
7653
7961
  const project = getProjectById(req.params.id);
7654
7962
  if (!project) {
@@ -7733,7 +8041,7 @@ app.get('/projects/:id/execution-timeline', (req, res) => {
7733
8041
  throw error;
7734
8042
  }
7735
8043
  });
7736
- app.get('/projects/:id/execution-timeline/:runId', (req, res) => {
8044
+ app.get('/projects/:id/execution-timeline/:runId', async (req, res) => {
7737
8045
  const project = getProjectById(req.params.id);
7738
8046
  if (!project) {
7739
8047
  return res.status(404).json({ error: 'Not found' });
@@ -7750,9 +8058,9 @@ app.get('/projects/:id/execution-timeline/:runId', (req, res) => {
7750
8058
  if (scope.scopeEntity && !links.some(link => link.entity_type === scope.scopeEntity?.type && link.entity_id === scope.scopeEntity.id)) {
7751
8059
  return res.status(404).json({ error: 'Execution run not found' });
7752
8060
  }
7753
- return res.json(buildExecutionRunDetailResponse(project, run));
8061
+ return res.json(await buildExecutionRunDetailResponse(project, run));
7754
8062
  });
7755
- app.get('/projects/:id/execution-runs/:runId', (req, res) => {
8063
+ app.get('/projects/:id/execution-runs/:runId', async (req, res) => {
7756
8064
  const project = getProjectById(req.params.id);
7757
8065
  if (!project) {
7758
8066
  return res.status(404).json({ error: 'Not found' });
@@ -7761,7 +8069,7 @@ app.get('/projects/:id/execution-runs/:runId', (req, res) => {
7761
8069
  if (!run) {
7762
8070
  return res.status(404).json({ error: 'Execution run not found' });
7763
8071
  }
7764
- return res.json(buildExecutionRunDetailResponse(project, run));
8072
+ return res.json(await buildExecutionRunDetailResponse(project, run));
7765
8073
  });
7766
8074
  app.get('/projects/:id/execution-runs/:runId/artifacts/:artifactId', async (req, res) => {
7767
8075
  const project = getProjectById(req.params.id);
@@ -7805,6 +8113,13 @@ app.post('/projects/:id/execution-runs/:runId/retry', async (req, res) => {
7805
8113
  if (!project) {
7806
8114
  return res.status(404).json({ error: 'Not found' });
7807
8115
  }
8116
+ const source = getExecutionRun(project.id, req.params.runId);
8117
+ if (source?.kind === 'check_batch') {
8118
+ const result = await rerunProjectCheckBatch(project, source.id, req.body);
8119
+ return result.ok
8120
+ ? res.status(201).json(result.value)
8121
+ : res.status(result.status).json({ error: result.error, reason: result.reason });
8122
+ }
7808
8123
  const resolution = resolveExecutionRetry(project.id, req.params.runId);
7809
8124
  const result = await dispatchExecutionRetry(resolution, io, async (retry) => {
7810
8125
  const session = createTestSession({
@@ -7870,7 +8185,7 @@ app.get('/test-sessions/:id/discovery', (req, res) => {
7870
8185
  if (!project) {
7871
8186
  return res.status(404).json({ error: 'Project not found' });
7872
8187
  }
7873
- command = findProjectTestCommand(project, commandId);
8188
+ command = getProjectTestCommands(project).find(item => item.id === commandId) ?? null;
7874
8189
  if (!command) {
7875
8190
  return res.status(404).json({ error: 'Test command not found' });
7876
8191
  }
@@ -7906,7 +8221,7 @@ app.post('/test-sessions/:id/run', async (req, res) => {
7906
8221
  if (!selection) {
7907
8222
  return res.status(400).json({ error: 'selection must be all, file, or case' });
7908
8223
  }
7909
- const command = findProjectTestCommand(project, commandId);
8224
+ const command = getProjectTestCommands(project).find(item => item.id === commandId) ?? null;
7910
8225
  if (!command) {
7911
8226
  return res.status(404).json({ error: 'Test command not found' });
7912
8227
  }
@@ -8049,7 +8364,7 @@ app.post('/tickets/:id/follow-up', (req, res) => {
8049
8364
  broadcaster: io,
8050
8365
  });
8051
8366
  if (!result.ok) {
8052
- return res.status(result.status).json({ error: result.error });
8367
+ return res.status(result.status).json({ error: result.error, ...(result.value ? { ticket: result.value } : {}) });
8053
8368
  }
8054
8369
  return res.status(result.status).json(result.value);
8055
8370
  });
@@ -8061,10 +8376,25 @@ app.post('/tickets/:id/plan-follow-up', (req, res) => {
8061
8376
  broadcaster: io,
8062
8377
  });
8063
8378
  if (!result.ok) {
8064
- return res.status(result.status).json({ error: result.error });
8379
+ return res.status(result.status).json({ error: result.error, ...(result.value ? { ticket: result.value } : {}) });
8065
8380
  }
8066
8381
  return res.status(result.status).json(result.value);
8067
8382
  });
8383
+ app.post('/tickets/:id/plan-questions/:messageId/resolve', (req, res) => {
8384
+ const result = resolveTicketPlanQuestionSet({
8385
+ ticketId: req.params.id,
8386
+ requestedProjectId: getRequestedProjectId(req),
8387
+ messageId: req.params.messageId,
8388
+ setId: typeof req.body?.set_id === 'string' ? req.body.set_id : '',
8389
+ rawResolutions: req.body?.resolutions,
8390
+ dismissAll: req.body?.dismiss_all === true,
8391
+ broadcaster: io,
8392
+ });
8393
+ if (!result.ok) {
8394
+ return res.status(result.status).json({ error: result.error, ...(result.value ? { ticket: result.value } : {}) });
8395
+ }
8396
+ return res.status(result.status).json({ ticket: result.value, kind: result.kind, execution_context: result.executionContext });
8397
+ });
8068
8398
  app.post('/tickets/:id/plan', (req, res) => {
8069
8399
  const ticket = getTicket(req.params.id);
8070
8400
  if (!ticket) {
@@ -8076,6 +8406,12 @@ app.post('/tickets/:id/plan', (req, res) => {
8076
8406
  if (ticket.state !== 'plan') {
8077
8407
  return res.status(400).json({ error: 'Only plan-state tickets can be planned' });
8078
8408
  }
8409
+ if (hasPendingAskQuestionSet(ticket.messages)) {
8410
+ return res.status(409).json({
8411
+ error: 'Resolve or dismiss the pending question set before continuing.',
8412
+ ticket,
8413
+ });
8414
+ }
8079
8415
  const bundleSelection = resolveRequestedBundleSelection(req.body, ticket.project_id);
8080
8416
  if ('error' in bundleSelection) {
8081
8417
  return res.status(bundleSelection.status).json({ error: bundleSelection.error });
@@ -8262,6 +8598,7 @@ export const startServer = ({ port = DEFAULT_PORT, host } = {}) => {
8262
8598
  };
8263
8599
  httpServer.once('error', handleError);
8264
8600
  const { bindHost } = resolveDesktopServerHost(host);
8601
+ openCheckRunnerAdmissions();
8265
8602
  httpServer.listen(port, bindHost, handleListening);
8266
8603
  });
8267
8604
  }
@@ -8285,6 +8622,9 @@ export const shutdownServer = async (signal) => {
8285
8622
  return shutdownServerPromise;
8286
8623
  }
8287
8624
  shutdownRequested = true;
8625
+ closeCheckRunnerAdmissions();
8626
+ const httpServerClosePromise = httpServer.listening ? closeHttpServer(httpServer) : null;
8627
+ void httpServerClosePromise?.catch(() => undefined);
8288
8628
  const pendingStartup = startServerPromise;
8289
8629
  shutdownServerPromise = (async () => {
8290
8630
  await Promise.all([
@@ -8301,6 +8641,7 @@ export const shutdownServer = async (signal) => {
8301
8641
  }
8302
8642
  await executionArtifactCleanupPromise;
8303
8643
  await stopMobileAccess();
8644
+ await shutdownCheckRunner();
8304
8645
  await clearAllTestSessions(io);
8305
8646
  await clearAllGitHubActionsSessions();
8306
8647
  await shutdownRealtimeServer({
@@ -8313,6 +8654,7 @@ export const shutdownServer = async (signal) => {
8313
8654
  stopAllReviewAgents,
8314
8655
  stopAllPlanAgents,
8315
8656
  stopAllTickets,
8657
+ httpServerClosePromise,
8316
8658
  });
8317
8659
  })().finally(() => {
8318
8660
  shutdownRequested = false;