@straiffi/archon 1.7.9 → 1.8.0

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 (170) hide show
  1. package/dist/client/assets/{ActivityView-CDLQOXZm.js → ActivityView-EVy76nes.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-DYxLX2HS.js → GitHubActionsDialog-D53aA0Fv.js} +1 -1
  3. package/dist/client/assets/VerificationDialog-Dm5DcY8F.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-ByNXcCIO.js → architectureDiagram-3BPJPVTR-CmMRkze6.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-ByykLAaG.js → blockDiagram-GPEHLZMM-BgLsRgxM.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-CmWA4Pli.js → c4Diagram-AAUBKEIU-C3WJl13c.js} +1 -1
  7. package/dist/client/assets/channel-xqaERdN6.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-CTgbXRh4.js → chunk-2J33WTMH-BfH4_PkQ.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-D3FajVfq.js → chunk-3OPIFGDE-CkVGPLM4.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-CUMg_Vfo.js → chunk-5ZQYHXKU-CDTwT753.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-Oc9MgxZD.js → chunk-727SXJPM-CKEhBrr_.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-5JZboHeP.js → chunk-AQP2D5EJ-D5QPK_k7.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-BpkSMHjB.js → chunk-CSCIHK7Q-BU4Ysr_2.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-BPBMAswO.js → chunk-KSCS5N6A-B9lHLGg1.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-BA2sHmu7.js → chunk-L5ZTLDWV-BrNsmxtD.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-Blc9q-fQ.js → chunk-LZXEDZCA-CVgZc1nv.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-GlVS74JP.js → chunk-ND2GUHAM-DWWdi42a.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-fctBFnAm.js → chunk-NZK2D7GU-46-aRMvO.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-eGs4DDLJ.js → chunk-O5CBEL6O-BW6wHmzD.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-vxa50E7g.js → chunk-WU5MYG2G-Wfk15Qtf.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-BdwFrsZE.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DxAuXA1L.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-DXGXNVUp.js → dagre-BM42HDAG-CZ_BDahz.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-BRYr1GvB.js → diagram-2AECGRRQ-CQKXQvjO.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-BcwgMag4.js → diagram-5GNKFQAL-Dgsh82oA.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-Delk7lbr.js → diagram-KO2AKTUF-DUwmm3sV.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-BVtc_fjo.js → diagram-LMA3HP47-DLGz0g10.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-weIPvUz5.js → diagram-OG6HWLK6-CjS6v8Zz.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-DKde70dA.js → erDiagram-TEJ5UH35-DHaCFHn9.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-B7Jrk-6c.js → flowDiagram-I6XJVG4X-E4LSVqs6.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CDzWDgnA.js → ganttDiagram-6RSMTGT7-BWywa_xn.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DYZCryuN.js → gitGraphDiagram-PVQCEYII-BK4kWKuB.js} +1 -1
  33. package/dist/client/assets/index-CS2arNNP.js +274 -0
  34. package/dist/client/assets/index-Cb_ZVYaw.css +2 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-DmQOgVKK.js → infoDiagram-5YYISTIA-BX7HzDWX.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DCpu-q-Z.js → ishikawaDiagram-YF4QCWOH-CEnWCNes.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-DhBoglhI.js → journeyDiagram-JHISSGLW-MQPDgPxD.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-BSjvGFTY.js → kanban-definition-UN3LZRKU-PwVLKAnN.js} +1 -1
  39. package/dist/client/assets/{line-ClKBZwDr.js → line-BJMnG1sl.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-BRA4mJ40.js → mermaid-parser.core-Bwv8qN83.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-BZ7kSfEt.js → mermaid.core-DpFOXZmb.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DoUdLHgj.js → mindmap-definition-RKZ34NQL-Dt-9JT_z.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-NVnzqltb.js → pieDiagram-4H26LBE5-aD3cg6m3.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DOITgkan.js → quadrantDiagram-W4KKPZXB-DUh4hgmW.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-D0VPxrFE.js → requirementDiagram-4Y6WPE33-DOgz-iLT.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Bx0uU7xV.js → sankeyDiagram-5OEKKPKP-BqpDXW6-.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B4lFL6cO.js → sequenceDiagram-3UESZ5HK-BM0Gvqia.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-CsLVsMZ4.js → stateDiagram-AJRCARHV-XZMyDtEy.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C0Qe8FiL.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-BkcYpPFI.js → timeline-definition-PNZ67QCA-gqiDwxVu.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-Xc5f2sRQ.js → vennDiagram-CIIHVFJN-MxeqqqyG.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-n3Dpk02Q.js → wardleyDiagram-YWT4CUSO-pMgGO6cB.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BTMzF8zg.js → xychartDiagram-2RQKCTM6-_VK_NZDn.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +467 -11
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +356 -150
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/activeAutomationChild.js +6 -1
  60. package/dist/server/lib/activeAutomationChild.js.map +1 -1
  61. package/dist/server/lib/agent.js +5 -4
  62. package/dist/server/lib/agent.js.map +1 -1
  63. package/dist/server/lib/automationCheckGate.js +211 -59
  64. package/dist/server/lib/automationCheckGate.js.map +1 -1
  65. package/dist/server/lib/automationProgression.js +78 -10
  66. package/dist/server/lib/automationProgression.js.map +1 -1
  67. package/dist/server/lib/automationReconciliation.js +65 -0
  68. package/dist/server/lib/automationReconciliation.js.map +1 -1
  69. package/dist/server/lib/automationResumeDecision.js +82 -20
  70. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  71. package/dist/server/lib/automationReviewGate.js +147 -0
  72. package/dist/server/lib/automationReviewGate.js.map +1 -0
  73. package/dist/server/lib/bundleActivity.js +20 -0
  74. package/dist/server/lib/bundleActivity.js.map +1 -1
  75. package/dist/server/lib/bundleReviewOperations.js +36 -5
  76. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  77. package/dist/server/lib/chatTicketProposals.js +2 -1
  78. package/dist/server/lib/chatTicketProposals.js.map +1 -1
  79. package/dist/server/lib/checkDefinitions.js +11 -0
  80. package/dist/server/lib/checkDefinitions.js.map +1 -1
  81. package/dist/server/lib/checkDiagnosticParsers.js +301 -0
  82. package/dist/server/lib/checkDiagnosticParsers.js.map +1 -0
  83. package/dist/server/lib/checkDiagnostics.js +142 -0
  84. package/dist/server/lib/checkDiagnostics.js.map +1 -0
  85. package/dist/server/lib/checkGateEvaluations.js +92 -4
  86. package/dist/server/lib/checkGateEvaluations.js.map +1 -1
  87. package/dist/server/lib/checkRepair.js +1031 -0
  88. package/dist/server/lib/checkRepair.js.map +1 -0
  89. package/dist/server/lib/checkRepairLifecycle.js +56 -0
  90. package/dist/server/lib/checkRepairLifecycle.js.map +1 -0
  91. package/dist/server/lib/checkRepairReconciliation.js +145 -0
  92. package/dist/server/lib/checkRepairReconciliation.js.map +1 -0
  93. package/dist/server/lib/checkRunner.js +142 -25
  94. package/dist/server/lib/checkRunner.js.map +1 -1
  95. package/dist/server/lib/checkRuns.js +75 -9
  96. package/dist/server/lib/checkRuns.js.map +1 -1
  97. package/dist/server/lib/executionReconciliation.js +21 -11
  98. package/dist/server/lib/executionReconciliation.js.map +1 -1
  99. package/dist/server/lib/executionRetryPredicates.js +3 -1
  100. package/dist/server/lib/executionRetryPredicates.js.map +1 -1
  101. package/dist/server/lib/executionRunRetention.js +24 -3
  102. package/dist/server/lib/executionRunRetention.js.map +1 -1
  103. package/dist/server/lib/executionRuns.js +5 -2
  104. package/dist/server/lib/executionRuns.js.map +1 -1
  105. package/dist/server/lib/git.js +5 -1
  106. package/dist/server/lib/git.js.map +1 -1
  107. package/dist/server/lib/lightweightPrompt.js +2 -1
  108. package/dist/server/lib/lightweightPrompt.js.map +1 -1
  109. package/dist/server/lib/planModeQuestions.js +2 -0
  110. package/dist/server/lib/planModeQuestions.js.map +1 -1
  111. package/dist/server/lib/planQuestions.js +39 -7
  112. package/dist/server/lib/planQuestions.js.map +1 -1
  113. package/dist/server/lib/projectAutoConfig.js +2 -1
  114. package/dist/server/lib/projectAutoConfig.js.map +1 -1
  115. package/dist/server/lib/projectMemorySuggestions.js +2 -1
  116. package/dist/server/lib/projectMemorySuggestions.js.map +1 -1
  117. package/dist/server/lib/projects.js +57 -9
  118. package/dist/server/lib/projects.js.map +1 -1
  119. package/dist/server/lib/pullRequestContent.js +14 -0
  120. package/dist/server/lib/pullRequestContent.js.map +1 -1
  121. package/dist/server/lib/requiredCheckPolicy.js +88 -18
  122. package/dist/server/lib/requiredCheckPolicy.js.map +1 -1
  123. package/dist/server/lib/requiredCheckPolicyPrompt.js +46 -0
  124. package/dist/server/lib/requiredCheckPolicyPrompt.js.map +1 -0
  125. package/dist/server/lib/run.js +3 -2
  126. package/dist/server/lib/run.js.map +1 -1
  127. package/dist/server/lib/specDraft.js +6 -0
  128. package/dist/server/lib/specDraft.js.map +1 -1
  129. package/dist/server/lib/specResources.js +42 -0
  130. package/dist/server/lib/specResources.js.map +1 -0
  131. package/dist/server/lib/specs.js +9 -5
  132. package/dist/server/lib/specs.js.map +1 -1
  133. package/dist/server/lib/terminal.js +5 -7
  134. package/dist/server/lib/terminal.js.map +1 -1
  135. package/dist/server/lib/testSessions.js +2 -2
  136. package/dist/server/lib/testSessions.js.map +1 -1
  137. package/dist/server/lib/ticketAutomation.js +10 -3
  138. package/dist/server/lib/ticketAutomation.js.map +1 -1
  139. package/dist/server/lib/ticketAutomationCommit.js +37 -11
  140. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  141. package/dist/server/lib/ticketFollowUpOperations.js +24 -9
  142. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  143. package/dist/server/lib/tickets.js +18 -0
  144. package/dist/server/lib/tickets.js.map +1 -1
  145. package/dist/server/lib/types.js +1 -0
  146. package/dist/server/lib/types.js.map +1 -1
  147. package/dist/server/lib/workspaceProcessEnvironment.js +21 -0
  148. package/dist/server/lib/workspaceProcessEnvironment.js.map +1 -0
  149. package/dist/server/workers/build.js +41 -28
  150. package/dist/server/workers/build.js.map +1 -1
  151. package/dist/server/workers/chat.js +4 -3
  152. package/dist/server/workers/chat.js.map +1 -1
  153. package/dist/server/workers/followUp.js +99 -103
  154. package/dist/server/workers/followUp.js.map +1 -1
  155. package/dist/server/workers/plan.js +9 -4
  156. package/dist/server/workers/plan.js.map +1 -1
  157. package/dist/server/workers/planFollowUp.js +5 -6
  158. package/dist/server/workers/planFollowUp.js.map +1 -1
  159. package/dist/server/workers/review.js +38 -6
  160. package/dist/server/workers/review.js.map +1 -1
  161. package/dist/server/workers/specSession.js +10 -4
  162. package/dist/server/workers/specSession.js.map +1 -1
  163. package/package.json +1 -1
  164. package/dist/client/assets/VerificationDialog-DEKvZG75.js +0 -4
  165. package/dist/client/assets/channel-BvcRv90i.js +0 -1
  166. package/dist/client/assets/classDiagram-4FO5ZUOK-IuovJyxl.js +0 -1
  167. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BBJ3avX6.js +0 -1
  168. package/dist/client/assets/index-BlKOx9Vf.css +0 -2
  169. package/dist/client/assets/index-CmaBDvIB.js +0 -273
  170. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CvrvaIdF.js +0 -1
@@ -31,7 +31,7 @@ import { runLightweightPrompt } from './lib/lightweightPrompt.js';
31
31
  import { advanceBuildChain, startBuildChain, stopBuildChain } from './lib/buildChains.js';
32
32
  import { activateQueuedBuildSequenceTicket, compensateBuildSequenceAcceptance, enqueueBundledBuild, getDependencyOrderedBundleTicketIds, getBuildSequenceIdForTicket, hasActiveSequenceForBundle, removeTicketFromBuildSequence, stopBuildSequence, } from './lib/buildSequences.js';
33
33
  import { prepareTicketForBuild } from './lib/buildFlow.js';
34
- import { getBundlePlanMutationBlockerById, shouldBlockPlanForBundleMutationById, } from './lib/bundleActivity.js';
34
+ import { getActiveBundleRepairBlocker, getBundlePlanMutationBlockerById, shouldBlockPlanForBundleMutationById, } from './lib/bundleActivity.js';
35
35
  import { getDiscoveredModels, normalizeModelId, normalizeTicketModelId } from './lib/models.js';
36
36
  import { upgradeTool, UpgradeInProgressError } from './lib/toolUpgrade.js';
37
37
  import { acceptProjectMemorySuggestion, applyProjectMemorySuggestionToDecision, applyProjectMemorySuggestionToConvention, archiveProjectConvention, archiveProjectDecision, createProjectConvention, createProjectDecision, dismissProjectMemorySuggestion, getProjectMemoryById, getProjectMemorySuggestionById, isProjectMemoryEnabled, listProjectConventions, listLatestSuccessfulProjectContextArtifacts, listProjectDecisions, listProjectMemories, listProjectMemorySuggestions, restoreProjectConvention, restoreProjectDecision, serializeProjectMemoryFromConventionValue, serializeProjectMemoryFromDecisionValue, serializeProjectMemoryFromSuggestionValue, supersedeProjectDecision, supersedeProjectDecisionFromSuggestion, updateProjectConvention, updateProjectDecision, validateProjectConventionPayload, validateProjectDecisionPayload, } from './lib/projectMemory.js';
@@ -47,8 +47,10 @@ 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, ensureAutomationProgression, finishAutomationReviewFix, getAutomationProgression, recoverLegacyAutomationProgression, recoverStoppedSideEffectProgression, runAutomationProgression, stopAutomationProgression, waitAutomationProgression } from './lib/automationProgression.js';
50
+ import { areBundleTicketsReadyForAutomaticReview } from './lib/bundleAutomationReadiness.js';
51
+ import { abandonAutomationPause, completeAutomationProgression, createAutomationReviewFix, ensureAutomationProgression, finishAutomationReviewFix, getAutomationProgression, recoverLegacyAutomationProgression, recoverStoppedSideEffectProgression, runAutomationProgression, stopAutomationProgression, waitAutomationProgression } from './lib/automationProgression.js';
51
52
  import { reconcileAutomationStage } from './lib/automationReconciliation.js';
53
+ import { runAutomationReviewGateOrPause } from './lib/automationReviewGate.js';
52
54
  import { pausedRunBelongsToProgression, resolveAutomationResumeDecision } from './lib/automationResumeDecision.js';
53
55
  import { getAutomationStageDetails, } from './lib/automationStageDetails.js';
54
56
  import { buildAutomationFixInstruction, clearAutomationPause, countBlockingFindings, depthAtLeast, getBundleAutomationState, isAutomationDepth, recordAutomationPause, resolveAutomationConfig, upsertBundleAutomationState, } from './lib/ticketAutomation.js';
@@ -77,14 +79,17 @@ import { PlanningReferenceError, resolveExternalPlanningDescription } from './li
77
79
  import { refreshIfNeeded as refreshAssignedIssues, clearCachedAssignedIssues } from './lib/integrations/assignedIssuesCache.js';
78
80
  import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage } from './lib/commitMessage.js';
79
81
  import { BUNDLE_PULL_REQUEST_SYNC_TTL_MS, getBundlePullRequestRecord, hasOpenBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequest, upsertBundlePullRequestFromGitHub, } from './lib/bundlePullRequests.js';
80
- import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectPayload, } from './lib/projects.js';
82
+ import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectRequiredCheckPolicyReferences, validateProjectPayload, } from './lib/projects.js';
81
83
  import { getActiveRunStatus, getActiveRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runSetupCommandsInWorkspace, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
82
84
  import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
83
85
  import { closeCheckRunnerAdmissions, getCheckBatchAttempts, openCheckRunnerAdmissions, rerunCheckBatchAndSchedule, shutdownCheckRunner, startCheckBatch, stopCheckBatch } from './lib/checkRunner.js';
86
+ import { CheckRepairError, getCheckRepairCandidateState, getCheckRepairDetails, getLatestCheckRepairForBatch, retryCheckRepair, startCheckRepair, stopCheckRepair } from './lib/checkRepair.js';
84
87
  import { findActiveCheckBatchForTarget, getCheckAttemptDetails, getCheckBatchDetails, hasActiveProjectCheckBatch, isCheckAttemptCurrent, resolveCheckBatchRerunEligibility } from './lib/checkRuns.js';
88
+ import { getCheckAttemptDiagnosticResponse, getCheckBatchDiagnosticResponse } from './lib/checkDiagnostics.js';
85
89
  import { getCheckDefinitionFingerprint } from './lib/checkDefinitions.js';
86
- import { getCheckGateEvaluation } from './lib/checkGateEvaluations.js';
90
+ import { getCheckGateEvaluation, hydrateCheckGatePresentApplicability } from './lib/checkGateEvaluations.js';
87
91
  import { captureCheckWorkspaceIdentity } from './lib/checkWorkspaceIdentity.js';
92
+ import { resolveEffectiveRequiredCheckPolicyMembership, resolveRequiredCheckPolicy } from './lib/requiredCheckPolicy.js';
88
93
  import { getBundleBuildBlockerResponse, shouldStartBundleBuildNow, transitionTicketState, } from './lib/ticketWorkflowOperations.js';
89
94
  import { submitPlanTicketFollowUp, submitTicketFollowUp, } from './lib/ticketFollowUpOperations.js';
90
95
  import { closePendingTicketPlanQuestionSet, listPendingTicketPlanQuestionSetMessageIds, resolveTicketPlanQuestionSet, restoreClosedTicketPlanQuestionSets, } from './lib/planQuestionOperations.js';
@@ -101,9 +106,9 @@ import { HttpError } from './lib/httpError.js';
101
106
  import { parseIncomingExternalSource } from './lib/externalSource.js';
102
107
  import { materializeTicketsWithDependencies } from './lib/ticketBatch.js';
103
108
  import { IMPLEMENTATION_READY_DESCRIPTION_SECTION_HEADINGS } from './lib/ticketDescriptionContract.js';
104
- import { buildSpecImplementationDraft, buildVisualizationAppendix } from './lib/specDraft.js';
109
+ import { buildImplementationResourcesAppendix, buildSpecImplementationDraft, buildVisualizationAppendix } from './lib/specDraft.js';
105
110
  import { exportSpecToDocs, checkSpecExportCollision } from './lib/specExport.js';
106
- import { createTicketDependency, deleteTicketDependenciesForTicket, deleteTicketDependency, getTicketDependency, isTicketRunSetupReady, listTicketDependencies, validateCreateTicketDependency, } from './lib/ticketDependencies.js';
111
+ import { createTicketDependency, deleteTicketDependenciesForTicket, deleteTicketDependency, getTicketDependency, isTicketBuildComplete, isTicketRunSetupReady, listTicketDependencies, validateCreateTicketDependency, } from './lib/ticketDependencies.js';
107
112
  import { startBuild } from './workers/build.js';
108
113
  import { expandPlanningTicketOutline, generatePlanningTicketOutline, generatePlanText, prepareLinkedPlanningResearch, startPlan, stopAllPlanAgents, } from './workers/plan.js';
109
114
  import { EXECUTION_RUN_KINDS, MAX_ARTIFACTS_PER_RUN, validateWorkflowContext } from './lib/types.js';
@@ -378,11 +383,19 @@ const buildExecutionRunDetailResponse = async (project, run) => {
378
383
  retry_eligibility: retryEligibility,
379
384
  action_kind: null,
380
385
  }
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 };
386
+ : run.kind === 'check_repair'
387
+ ? {
388
+ kind: 'check_repair',
389
+ available: true,
390
+ check_repair: getCheckRepairDetails(project.id, run.id),
391
+ retry_eligibility: retryEligibility,
392
+ action_kind: null,
393
+ }
394
+ : run.kind === 'github_actions'
395
+ ? { ...serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id)), retry_eligibility: retryEligibility }
396
+ : ['automation_commit', 'automation_push', 'automation_pull_request'].includes(run.kind)
397
+ ? { kind: run.kind, available: true, automation_stage: getAutomationStageDetails(project.id, run.id), retry_eligibility: retryEligibility }
398
+ : { kind: run.kind, available: true, retry_eligibility: retryEligibility };
386
399
  const artifacts = loadExecutionArtifactSummariesForRuns(project.id, [run.id], MAX_ARTIFACTS_PER_RUN).get(run.id)
387
400
  ?? { artifacts: [], artifact_count: 0 };
388
401
  return {
@@ -442,7 +455,14 @@ const getParkTicketError = (ticket) => {
442
455
  const getBundlePlanBlockerError = () => {
443
456
  return 'AI planning is blocked because this bundle worktree is currently being modified.';
444
457
  };
458
+ const hasActiveBundleCheckWork = (projectId, bundleId) => Boolean(getActiveBundleRepairBlocker(bundleId)
459
+ || findActiveCheckBatchForTarget(projectId, 'bundle', bundleId));
445
460
  const deleteTicketRecord = async (ticket) => {
461
+ if (ticket.project_id
462
+ && ticket.worktree_bundle_id
463
+ && hasActiveBundleCheckWork(ticket.project_id, ticket.worktree_bundle_id)) {
464
+ return { ok: false, status: 409, error: 'Cannot delete a ticket while its bundle has active check work' };
465
+ }
446
466
  if (ticket.agent_status === 'running'
447
467
  || (ticket.project_id && hasActiveBuildExecutionRunForTicket(ticket.project_id, ticket.id))) {
448
468
  return { ok: false, status: 409, error: 'Cannot delete a ticket while the agent is running' };
@@ -810,6 +830,9 @@ const cleanupBundleIfEmpty = (bundleId, project) => {
810
830
  if (isProjectRootBundle(bundle)) {
811
831
  return;
812
832
  }
833
+ if (hasActiveBundleCheckWork(project.id, bundle.id)) {
834
+ throw new HttpError(409, 'Cannot delete a bundle while check work is active');
835
+ }
813
836
  try {
814
837
  deleteWorktree(bundle.branch, project);
815
838
  }
@@ -1462,6 +1485,7 @@ const serializeBundleRow = (projectId, bundle, openQuestions = bundle ? loadBuil
1462
1485
  const hasPreparedWorktree = bundle.kind === 'project_root'
1463
1486
  ? true
1464
1487
  : Boolean(project && resolveExistingWorktreePath(bundle.branch, project));
1488
+ const effectivePolicy = resolveEffectiveRequiredCheckPolicyMembership(project);
1465
1489
  return {
1466
1490
  ...bundle,
1467
1491
  has_prepared_worktree: hasPreparedWorktree,
@@ -1469,6 +1493,13 @@ const serializeBundleRow = (projectId, bundle, openQuestions = bundle ? loadBuil
1469
1493
  ? serializeBundlePullRequest(getBundlePullRequestRecord(bundle.id, projectId))
1470
1494
  : null,
1471
1495
  automation_config: resolveAutomationConfig(bundle, project),
1496
+ required_check_policy: 'error' in effectivePolicy
1497
+ ? undefined
1498
+ : {
1499
+ source: effectivePolicy.source,
1500
+ required_check_definition_ids: effectivePolicy.required_check_definition_ids,
1501
+ pre_review_check_definition_ids: effectivePolicy.pre_review_check_definition_ids,
1502
+ },
1472
1503
  open_questions: openQuestions,
1473
1504
  build_open_question_batch_status: getLatestBuildOpenQuestionBatchStatus(projectId, bundle.id),
1474
1505
  };
@@ -3034,11 +3065,14 @@ app.post('/projects/:id/spec-sessions/:sessionId/snapshots/:snapshotId/create-ti
3034
3065
  ? createAutoBundleForName(bundleName, project.id)
3035
3066
  : createAutoPlanningBundle(project.id);
3036
3067
  const ticketsForMaterialization = snapshot.tickets.map(ticket => {
3037
- const appendix = buildVisualizationAppendix(ticket.relevant_visualizations ?? [], snapshot);
3068
+ const appendices = [
3069
+ buildVisualizationAppendix(ticket.relevant_visualizations ?? [], snapshot),
3070
+ buildImplementationResourcesAppendix(snapshot.resource_urls),
3071
+ ].filter(Boolean);
3038
3072
  return {
3039
3073
  key: ticket.key,
3040
3074
  title: ticket.title,
3041
- description: appendix ? `${ticket.description}\n\n${appendix}` : ticket.description,
3075
+ description: appendices.length > 0 ? `${ticket.description}\n\n${appendices.join('\n\n')}` : ticket.description,
3042
3076
  };
3043
3077
  });
3044
3078
  const { createdTickets, createdDependencies } = materializeTicketsWithDependencies({
@@ -3704,15 +3738,15 @@ app.delete('/bundles/:id', (req, res) => {
3704
3738
  if (isProjectRootBundle(bundle)) {
3705
3739
  return res.status(409).json({ error: 'Project root cannot be deleted' });
3706
3740
  }
3741
+ if (hasActiveBundleCheckWork(result.project.id, bundle.id)) {
3742
+ return res.status(409).json({ error: 'Cannot delete a bundle while check work is active' });
3743
+ }
3707
3744
  if (countBundleTickets(bundle.id, result.project.id) > 0) {
3708
3745
  return res.status(409).json({ error: 'Cannot delete a bundle that is still used by tickets' });
3709
3746
  }
3710
3747
  if (result.project.active_target_kind === 'bundle' && result.project.active_target_bundle_id === bundle.id) {
3711
3748
  return res.status(409).json({ error: 'Cannot delete the currently selected target bundle' });
3712
3749
  }
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
- }
3716
3750
  const affectedChatSessionIds = listChatSessions(result.project.id)
3717
3751
  .filter(chatSession => chatSession.target_bundle_id === bundle.id)
3718
3752
  .map(chatSession => chatSession.id);
@@ -3743,6 +3777,9 @@ app.patch('/bundles/:id', (req, res) => {
3743
3777
  return res.status(404).json({ error: 'Bundle not found' });
3744
3778
  }
3745
3779
  const hasNameUpdate = hasOwn(req.body, 'name');
3780
+ if (hasOwn(req.body, 'required_check_policy_override')) {
3781
+ return res.status(400).json({ error: 'Bundle required-check policy overrides are no longer supported; configure required checks on the project' });
3782
+ }
3746
3783
  if (isProjectRootBundle(bundle)) {
3747
3784
  return res.status(409).json({
3748
3785
  error: hasNameUpdate
@@ -3876,7 +3913,7 @@ app.post('/bundles/:id/review', (req, res) => {
3876
3913
  }
3877
3914
  return res.json(reviewResult.details);
3878
3915
  });
3879
- app.post('/bundles/:id/build-open-questions/resolve', (req, res) => {
3916
+ app.post('/bundles/:id/build-open-questions/resolve', async (req, res) => {
3880
3917
  const result = getRequiredProject(req);
3881
3918
  if ('error' in result) {
3882
3919
  return res.status(400).json({ error: result.error });
@@ -3919,54 +3956,70 @@ app.post('/bundles/:id/build-open-questions/resolve', (req, res) => {
3919
3956
  }
3920
3957
  if (accepted.kind === 'dismissed') {
3921
3958
  const automationConfig = resolveAutomationConfig(bundle, result.project);
3922
- const reviewResult = depthAtLeast(automationConfig.depth, 'auto_review')
3923
- ? transitionBundleToReview(result.project.id, bundle.id, io, {
3924
- automatic: true,
3925
- initiation: {
3926
- causalParentRunId: accepted.progressionRunId,
3927
- workflowContext: validateWorkflowContext({ build_open_question_batch_id: accepted.batchId }),
3928
- },
3929
- })
3930
- : null;
3931
- if (!reviewResult) {
3959
+ if (!depthAtLeast(automationConfig.depth, 'auto_review')) {
3932
3960
  completeAutomationProgression({
3933
3961
  projectId: result.project.id,
3934
3962
  bundleId: bundle.id,
3935
3963
  progressionRunId: accepted.progressionRunId,
3936
3964
  broadcaster: io,
3937
3965
  });
3966
+ for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
3967
+ io.emit('ticket:updated', ticket);
3968
+ }
3969
+ return res.json(serializeBundleDetails(result.project, bundle));
3938
3970
  }
3939
- for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
3940
- io.emit('ticket:updated', ticket);
3941
- }
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));
3971
+ const ticketsBeforeReview = listDetailedBundleTickets(result.project.id, bundle.id);
3972
+ if (!areBundleTicketsReadyForAutomaticReview(ticketsBeforeReview, bundle.id)) {
3973
+ completeAutomationProgression({
3974
+ projectId: result.project.id,
3975
+ bundleId: bundle.id,
3976
+ progressionRunId: accepted.progressionRunId,
3977
+ broadcaster: io,
3978
+ });
3979
+ for (const ticket of ticketsBeforeReview) {
3980
+ io.emit('ticket:updated', ticket);
3954
3981
  }
3982
+ return res.json(serializeBundleDetails(result.project, bundle));
3983
+ }
3984
+ let reviewStartFailureStatus = null;
3985
+ let reviewStartFailureError = null;
3986
+ const reviewStarted = await runAutomationReviewGateOrPause({
3987
+ projectId: result.project.id,
3988
+ bundleId: bundle.id,
3989
+ progressionRunId: accepted.progressionRunId,
3990
+ broadcaster: io,
3991
+ pausedStageRunId: null,
3992
+ initiation: {
3993
+ workflowContext: validateWorkflowContext({ build_open_question_batch_id: accepted.batchId }),
3994
+ },
3995
+ onReviewStartFailure: failure => {
3996
+ reviewStartFailureStatus = failure.status;
3997
+ reviewStartFailureError = failure.error;
3998
+ },
3999
+ });
4000
+ if (reviewStartFailureStatus !== null && reviewStartFailureError !== null) {
3955
4001
  waitAutomationProgression({
3956
4002
  projectId: result.project.id,
3957
4003
  bundleId: bundle.id,
3958
4004
  progressionRunId: accepted.progressionRunId,
3959
4005
  pauseCode: 'review',
3960
- pauseReason: reviewResult.error,
4006
+ pauseReason: reviewStartFailureError,
3961
4007
  reasonCode: 'stage_failed',
3962
4008
  pausedStageRunId: null,
3963
4009
  broadcaster: io,
3964
4010
  });
3965
- return res.status(reviewResult.status).json({ error: reviewResult.error });
4011
+ return res.status(reviewStartFailureStatus).json({ error: reviewStartFailureError });
4012
+ }
4013
+ const updatedTickets = listDetailedBundleTickets(result.project.id, bundle.id);
4014
+ for (const ticket of updatedTickets) {
4015
+ io.emit('ticket:updated', ticket);
3966
4016
  }
4017
+ const reviewOwner = reviewStarted
4018
+ ? updatedTickets.find(ticket => ticket.state === 'review' && ticket.parked_at === null)
4019
+ : null;
3967
4020
  return res.json(serializeBundleDetails(result.project, bundle, {
3968
- review_owner_ticket_id: reviewResult?.ok ? reviewResult.value?.id : undefined,
3969
- target_ticket_id: reviewResult?.ok ? reviewResult.value?.id : undefined,
4021
+ review_owner_ticket_id: reviewOwner?.id,
4022
+ target_ticket_id: reviewOwner?.id,
3970
4023
  }));
3971
4024
  }
3972
4025
  for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
@@ -4073,7 +4126,7 @@ app.post('/bundles/:id/follow-up', (req, res) => {
4073
4126
  return res.status(response.status).json(response.value);
4074
4127
  });
4075
4128
  const AUTOMATION_SIDE_EFFECT_KINDS = new Set(['automation_commit', 'automation_push', 'automation_pull_request']);
4076
- const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix', 'check_batch']);
4129
+ const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix', 'check_repair', 'check_batch']);
4077
4130
  app.post('/bundles/:id/resume-automation', async (req, res) => {
4078
4131
  const result = getRequiredProject(req);
4079
4132
  if ('error' in result) {
@@ -4156,7 +4209,11 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4156
4209
  });
4157
4210
  if (checkpointLineage) {
4158
4211
  const owner = getTicket(decision.owner_ticket_id);
4159
- if (!owner || owner.id !== checkpointLineage.ticket.id || owner.agent_status !== 'done') {
4212
+ if (!owner
4213
+ || owner.id !== checkpointLineage.ticket.id
4214
+ || owner.state !== 'build'
4215
+ || owner.parked_at !== null
4216
+ || !isTicketBuildComplete(owner)) {
4160
4217
  return res.status(409).json({ error: 'Checkpoint progression owner is no longer the active Build ticket' });
4161
4218
  }
4162
4219
  let retryOfRunId;
@@ -4310,25 +4367,14 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4310
4367
  workflowContext,
4311
4368
  broadcaster: io,
4312
4369
  });
4313
- const reviewResult = transitionBundleToReview(owner.project_id, owner.worktree_bundle_id, io, {
4314
- automatic: true,
4315
- initiation: {
4316
- causalParentRunId: reviewProgression.runId,
4317
- workflowContext,
4318
- },
4370
+ await runAutomationReviewGateOrPause({
4371
+ projectId: owner.project_id,
4372
+ bundleId: owner.worktree_bundle_id,
4373
+ progressionRunId: reviewProgression.runId,
4374
+ broadcaster: io,
4375
+ pausedStageRunId: checkpointLineage.parent.id,
4376
+ initiation: { workflowContext },
4319
4377
  });
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
4378
  }
4333
4379
  }
4334
4380
  const resumedOwner = getTicket(owner.id);
@@ -4422,6 +4468,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4422
4468
  pauseReason: bundleState.automation_paused_reason ?? 'Automation paused',
4423
4469
  reasonCode: bundleState.automation_pause_reason_code ?? 'manual_transition',
4424
4470
  pausedStageRunId: bundleState.automation_paused_stage_run_id,
4471
+ pausedBoundaryPurpose: bundleState.automation_paused_boundary_purpose,
4425
4472
  broadcaster: io,
4426
4473
  });
4427
4474
  };
@@ -4459,7 +4506,62 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4459
4506
  }
4460
4507
  return res.json(serializeBundleDetails(result.project, bundle));
4461
4508
  }
4462
- if (owner.state === 'review' && owner.agent_status !== 'running') {
4509
+ if (decision.outcome === 'complete_manual_boundary') {
4510
+ if (!revalidateDecision()) {
4511
+ return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4512
+ }
4513
+ completeAutomationProgression({
4514
+ projectId: result.project.id,
4515
+ bundleId: bundle.id,
4516
+ progressionRunId,
4517
+ domainStatus: 'manual_boundary_reached',
4518
+ broadcaster: io,
4519
+ });
4520
+ return res.json(serializeBundleDetails(result.project, bundle));
4521
+ }
4522
+ if ((decision.outcome === 'start_review' || decision.outcome === 'rerun_review_checks')
4523
+ && owner.parked_at === null
4524
+ && isTicketBuildComplete(owner)
4525
+ && (owner.state === 'build' || (decision.outcome === 'rerun_review_checks' && owner.state === 'review'))) {
4526
+ if (!revalidateDecision()) {
4527
+ return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4528
+ }
4529
+ const progression = getAutomationProgression(result.project.id, bundle.id);
4530
+ if (!progression || progression.id !== progressionRunId) {
4531
+ return res.status(409).json({ error: 'Automation progression ownership is invalid' });
4532
+ }
4533
+ const workflowContext = validateWorkflowContext(progression.metadata);
4534
+ let reviewStartFailureStatus = null;
4535
+ let reviewStartFailureError = null;
4536
+ try {
4537
+ runAutomationProgression({
4538
+ projectId: result.project.id,
4539
+ bundleId: bundle.id,
4540
+ progressionRunId,
4541
+ broadcaster: io,
4542
+ });
4543
+ await runAutomationReviewGateOrPause({
4544
+ projectId: result.project.id,
4545
+ bundleId: bundle.id,
4546
+ progressionRunId,
4547
+ broadcaster: io,
4548
+ pausedStageRunId: bundleState.automation_paused_stage_run_id,
4549
+ initiation: { workflowContext },
4550
+ onReviewStartFailure: failure => {
4551
+ reviewStartFailureStatus = failure.status;
4552
+ reviewStartFailureError = failure.error;
4553
+ restoreWaitingProgression();
4554
+ },
4555
+ });
4556
+ }
4557
+ catch (error) {
4558
+ return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4559
+ }
4560
+ if (reviewStartFailureStatus !== null && reviewStartFailureError !== null) {
4561
+ return res.status(reviewStartFailureStatus).json({ error: reviewStartFailureError });
4562
+ }
4563
+ }
4564
+ else if (owner.state === 'review' && owner.agent_status !== 'running') {
4463
4565
  const iteration = bundleState.review_iteration_count ?? 0;
4464
4566
  if (decision.outcome === 'start_review_fix') {
4465
4567
  let reviewFixRunId = null;
@@ -4569,7 +4671,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4569
4671
  broadcaster: io,
4570
4672
  });
4571
4673
  const checkGateResume = decision.outcome === 'rerun_required_checks'
4572
- ? resolveAutomationCheckGateResume(result.project.id, progressionRunId)
4674
+ ? resolveAutomationCheckGateResume(result.project.id, progressionRunId, owner.bundle?.kind === 'project_root' ? project.repo_path : resolveWorktreePath(bundle.branch, project))
4573
4675
  : undefined;
4574
4676
  await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId, checkGateResume);
4575
4677
  }
@@ -4594,19 +4696,6 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4594
4696
  return res.status(409).json({ error: 'The selected Resume outcome no longer matches the bundle state' });
4595
4697
  }
4596
4698
  }
4597
- else if (owner.state === 'build' && owner.agent_status === 'done' && decision.outcome === 'start_review') {
4598
- if (!revalidateDecision()) {
4599
- return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4600
- }
4601
- const reviewResult = transitionBundleToReview(result.project.id, bundle.id, io, {
4602
- automatic: true,
4603
- initiation: { causalParentRunId: progressionRunId },
4604
- accept: acceptResumedReview,
4605
- });
4606
- if (!reviewResult.ok) {
4607
- return res.status(reviewResult.status).json({ error: reviewResult.error });
4608
- }
4609
- }
4610
4699
  else {
4611
4700
  return res.status(409).json({ error: 'Ticket is not in a resumable state' });
4612
4701
  }
@@ -4637,72 +4726,31 @@ app.post('/bundles/:id/dismiss-automation-pause', async (req, res) => {
4637
4726
  && listBuildOpenQuestionsForBundle(result.project.id, bundle.id).length > 0) {
4638
4727
  return res.status(409).json({ error: 'Resolve or dismiss all Build open questions before dismissing this pause' });
4639
4728
  }
4640
- let progressionRunId = bundleState.automation_progression_run_id;
4641
- if (!progressionRunId) {
4729
+ const progressionRunId = bundleState.automation_progression_run_id;
4730
+ const checkpointLineage = progressionRunId
4731
+ ? resolveBuildTicketCommitCheckpointLineage({
4732
+ projectId: result.project.id,
4733
+ bundleId: bundle.id,
4734
+ progressionRunId,
4735
+ })
4736
+ : null;
4737
+ if (progressionRunId) {
4642
4738
  try {
4643
- progressionRunId = (bundleState.automation_paused_stage_run_id
4644
- ? recoverStoppedSideEffectProgression({
4645
- projectId: result.project.id,
4646
- bundleId: bundle.id,
4647
- broadcaster: io,
4648
- })
4649
- : recoverLegacyAutomationProgression({
4650
- projectId: result.project.id,
4651
- bundleId: bundle.id,
4652
- broadcaster: io,
4653
- })).runId;
4654
- }
4655
- catch (error) {
4656
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4657
- }
4658
- }
4659
- try {
4660
- if (getAutomationProgression(result.project.id, bundle.id)?.id !== progressionRunId) {
4661
- return res.status(409).json({ error: 'Automation progression ownership is invalid' });
4662
- }
4663
- }
4664
- catch (error) {
4665
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4666
- }
4667
- const checkpointLineage = resolveBuildTicketCommitCheckpointLineage({
4668
- projectId: result.project.id,
4669
- bundleId: bundle.id,
4670
- progressionRunId,
4671
- });
4672
- if (bundleState.automation_paused_stage_run_id) {
4673
- const stage = getExecutionRun(result.project.id, bundleState.automation_paused_stage_run_id);
4674
- if (!stage
4675
- || (!AUTOMATION_SIDE_EFFECT_KINDS.has(stage.kind) && !AUTOMATION_WORKER_PAUSE_KINDS.has(stage.kind))
4676
- || !pausedRunBelongsToProgression(result.project.id, bundle.id, progressionRunId, stage.id)) {
4677
- return res.status(409).json({ error: 'The paused automation stage does not belong to the active progression' });
4739
+ await stopAutomationProgression({
4740
+ projectId: result.project.id,
4741
+ bundleId: bundle.id,
4742
+ broadcaster: io,
4743
+ stopLocalWorker: worker => (worker.kind === 'review' ? stopReviewAgent(worker.ticket_id) : stopBuildAgent(worker.ticket_id)),
4744
+ });
4678
4745
  }
4679
- if (AUTOMATION_SIDE_EFFECT_KINDS.has(stage.kind)) {
4680
- let reconciled;
4681
- try {
4682
- reconciled = await reconcileAutomationStage({
4683
- projectId: result.project.id,
4684
- bundleId: bundle.id,
4685
- progressionRunId,
4686
- stageRunId: stage.id,
4687
- });
4688
- }
4689
- catch (error) {
4690
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4691
- }
4692
- if (reconciled.domainStatus === 'ambiguous') {
4693
- return res.status(409).json({ error: 'The paused automation side effect is ambiguous and cannot be dismissed safely.' });
4694
- }
4695
- if (!(reconciled.status === 'completed' && reconciled.domainStatus === 'reconciled_completed')
4696
- && !(reconciled.status === 'interrupted' && reconciled.domainStatus === 'reconciled_absent')) {
4697
- return res.status(409).json({ error: 'The paused automation side effect has no safely dismissible outcome.' });
4698
- }
4746
+ catch {
4747
+ // Explicit abandonment also repairs a stale progression pointer.
4699
4748
  }
4700
4749
  }
4701
- await stopAutomationProgression({
4750
+ abandonAutomationPause({
4702
4751
  projectId: result.project.id,
4703
4752
  bundleId: bundle.id,
4704
4753
  broadcaster: io,
4705
- stopLocalWorker: worker => (worker.kind === 'review' ? stopReviewAgent(worker.ticket_id) : stopBuildAgent(worker.ticket_id)),
4706
4754
  });
4707
4755
  if (checkpointLineage) {
4708
4756
  const stoppedSequence = stopBuildSequence(checkpointLineage.ticket.id);
@@ -5792,7 +5840,7 @@ app.post('/projects', (req, res) => {
5792
5840
  return res.status(400).json({ error: validation.error });
5793
5841
  }
5794
5842
  const projectCount = Number(db.prepare('SELECT COUNT(*) AS count FROM projects').get()?.count ?? 0);
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;
5843
+ const { name, repo_path, linked_project_ids, run_setup, run_services, check_definitions, required_check_definition_ids, pre_review_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;
5796
5844
  if (!repo_path) {
5797
5845
  return res.status(400).json({ error: 'repo_path must be a non-empty string' });
5798
5846
  }
@@ -5814,6 +5862,7 @@ app.post('/projects', (req, res) => {
5814
5862
  run_services,
5815
5863
  check_definitions,
5816
5864
  required_check_definition_ids,
5865
+ pre_review_check_definition_ids,
5817
5866
  test_commands,
5818
5867
  run_ide,
5819
5868
  preview_service_name,
@@ -5835,8 +5884,8 @@ app.post('/projects', (req, res) => {
5835
5884
  automation_review_severity,
5836
5885
  automation_review_max_iterations
5837
5886
  )
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);
5887
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5888
+ `).run(id, name, normalizedRepoPath, run_setup, run_services, check_definitions, required_check_definition_ids ?? '[]', pre_review_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);
5840
5889
  replaceProjectLinks(id, linked_project_ids ?? []);
5841
5890
  ensureProjectRootBundle(id);
5842
5891
  if (projectCount === 0) {
@@ -7002,8 +7051,8 @@ app.patch('/projects/:id', (req, res) => {
7002
7051
  });
7003
7052
  }
7004
7053
  }
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;
7006
- db.prepare(`
7054
+ const { name, repo_path, linked_project_ids, run_setup, run_services, check_definitions, required_check_definition_ids, pre_review_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;
7055
+ const updateProject = db.prepare(`
7007
7056
  UPDATE projects SET
7008
7057
  name = ?,
7009
7058
  repo_path = ?,
@@ -7011,6 +7060,7 @@ app.patch('/projects/:id', (req, res) => {
7011
7060
  run_services = ?,
7012
7061
  check_definitions = ?,
7013
7062
  required_check_definition_ids = ?,
7063
+ pre_review_check_definition_ids = ?,
7014
7064
  test_commands = ?,
7015
7065
  run_ide = ?,
7016
7066
  preview_service_name = ?,
@@ -7033,9 +7083,32 @@ app.patch('/projects/:id', (req, res) => {
7033
7083
  automation_review_max_iterations = ?,
7034
7084
  updated_at = CURRENT_TIMESTAMP
7035
7085
  WHERE 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);
7037
- if (hasOwn(validation.values, 'linked_project_ids')) {
7038
- replaceProjectLinks(project.id, linked_project_ids ?? []);
7086
+ `);
7087
+ const nextCheckDefinitions = hasOwn(validation.values, 'check_definitions') ? check_definitions ?? null : project.check_definitions;
7088
+ const nextRequiredCheckDefinitionIds = hasOwn(validation.values, 'required_check_definition_ids')
7089
+ ? required_check_definition_ids ?? '[]'
7090
+ : project.required_check_definition_ids;
7091
+ const nextPreReviewCheckDefinitionIds = hasOwn(validation.values, 'pre_review_check_definition_ids')
7092
+ ? pre_review_check_definition_ids ?? '[]'
7093
+ : project.pre_review_check_definition_ids;
7094
+ const updateResult = db.transaction(() => {
7095
+ const policyReferences = validateProjectRequiredCheckPolicyReferences({
7096
+ projectId: project.id,
7097
+ checkDefinitionsJson: nextCheckDefinitions,
7098
+ requiredCheckDefinitionIdsJson: nextRequiredCheckDefinitionIds,
7099
+ preReviewCheckDefinitionIdsJson: nextPreReviewCheckDefinitionIds,
7100
+ });
7101
+ if ('error' in policyReferences) {
7102
+ return policyReferences;
7103
+ }
7104
+ updateProject.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, nextCheckDefinitions, nextRequiredCheckDefinitionIds, nextPreReviewCheckDefinitionIds, 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);
7105
+ if (hasOwn(validation.values, 'linked_project_ids')) {
7106
+ replaceProjectLinks(project.id, linked_project_ids ?? []);
7107
+ }
7108
+ return { ok: true };
7109
+ })();
7110
+ if ('error' in updateResult) {
7111
+ return res.status(400).json({ error: updateResult.error });
7039
7112
  }
7040
7113
  const updated = db.prepare('SELECT * FROM projects WHERE id = ?').get(req.params.id);
7041
7114
  res.json(serializeProject(updated ?? null));
@@ -7095,6 +7168,12 @@ const completeClaimedProjectDeletion = async (projectId, claimId) => {
7095
7168
  WHERE evaluation_id IN (SELECT id FROM check_gate_evaluations WHERE project_id = ?)
7096
7169
  `).run(projectId);
7097
7170
  db.prepare('DELETE FROM check_gate_evaluations WHERE project_id = ?').run(projectId);
7171
+ if (db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'check_repair_details'").get()) {
7172
+ db.prepare(`
7173
+ DELETE FROM check_repair_details
7174
+ WHERE execution_run_id IN (SELECT id FROM execution_runs WHERE project_id = ?)
7175
+ `).run(projectId);
7176
+ }
7098
7177
  db.prepare(`
7099
7178
  DELETE FROM check_attempt_details
7100
7179
  WHERE execution_run_id IN (SELECT id FROM execution_runs WHERE project_id = ?)
@@ -8039,6 +8118,98 @@ app.post('/projects/:id/check-batches', async (req, res) => {
8039
8118
  return res.status(409).json({ error: getErrorMessage(error) });
8040
8119
  }
8041
8120
  });
8121
+ app.post('/projects/:id/check-repairs', async (req, res) => {
8122
+ const project = getProjectById(req.params.id);
8123
+ if (!project) {
8124
+ return res.status(404).json({ error: 'Not found' });
8125
+ }
8126
+ const body = req.body && typeof req.body === 'object' && !Array.isArray(req.body)
8127
+ ? req.body
8128
+ : null;
8129
+ if (!body || Object.keys(body).some(key => !['diagnostic_ids', 'raw_output_attempt_run_ids', 'ticket_id'].includes(key))) {
8130
+ return res.status(400).json({ error: 'Only diagnostic_ids, raw_output_attempt_run_ids, and optional ticket_id are accepted' });
8131
+ }
8132
+ const diagnosticIds = body.diagnostic_ids === undefined
8133
+ ? []
8134
+ : Array.isArray(body.diagnostic_ids)
8135
+ && body.diagnostic_ids.length <= 32
8136
+ && body.diagnostic_ids.every(id => typeof id === 'string' && id.trim().length > 0 && id.trim().length <= 100)
8137
+ ? body.diagnostic_ids.map(id => id.trim())
8138
+ : null;
8139
+ const rawOutputAttemptRunIds = body.raw_output_attempt_run_ids === undefined
8140
+ ? []
8141
+ : Array.isArray(body.raw_output_attempt_run_ids)
8142
+ && body.raw_output_attempt_run_ids.length <= 8
8143
+ && body.raw_output_attempt_run_ids.every(id => typeof id === 'string' && id.trim().length > 0 && id.trim().length <= 100)
8144
+ ? body.raw_output_attempt_run_ids.map(id => id.trim())
8145
+ : null;
8146
+ if (!diagnosticIds
8147
+ || diagnosticIds.length !== new Set(diagnosticIds).size
8148
+ || !rawOutputAttemptRunIds
8149
+ || rawOutputAttemptRunIds.length !== new Set(rawOutputAttemptRunIds).size
8150
+ || diagnosticIds.length + rawOutputAttemptRunIds.length === 0) {
8151
+ return res.status(400).json({ error: 'At least one unique diagnostic_ids or raw_output_attempt_run_ids evidence reference is required' });
8152
+ }
8153
+ if (body.ticket_id !== undefined && (typeof body.ticket_id !== 'string'
8154
+ || body.ticket_id.trim().length === 0
8155
+ || body.ticket_id.trim().length > 100)) {
8156
+ return res.status(400).json({ error: 'ticket_id must be a non-empty opaque ticket ID' });
8157
+ }
8158
+ try {
8159
+ return res.status(202).json(await startCheckRepair({
8160
+ projectId: project.id,
8161
+ diagnosticIds,
8162
+ rawOutputAttemptRunIds,
8163
+ ticketId: typeof body.ticket_id === 'string' ? body.ticket_id.trim() : null,
8164
+ io,
8165
+ }));
8166
+ }
8167
+ catch (error) {
8168
+ if (error instanceof CheckRepairError) {
8169
+ return res.status(error.status).json({ error: error.message });
8170
+ }
8171
+ return res.status(409).json({ error: getErrorMessage(error) });
8172
+ }
8173
+ });
8174
+ app.get('/projects/:id/check-repairs/:repairRunId', (req, res) => {
8175
+ const project = getProjectById(req.params.id);
8176
+ if (!project) {
8177
+ return res.status(404).json({ error: 'Not found' });
8178
+ }
8179
+ const repair = getCheckRepairDetails(project.id, req.params.repairRunId);
8180
+ return repair ? res.json(repair) : res.status(404).json({ error: 'Check repair not found' });
8181
+ });
8182
+ app.post('/projects/:id/check-repairs/:repairRunId/stop', async (req, res) => {
8183
+ if (!getProjectById(req.params.id)) {
8184
+ return res.status(404).json({ error: 'Not found' });
8185
+ }
8186
+ try {
8187
+ return res.json(await stopCheckRepair(req.params.id, req.params.repairRunId, io));
8188
+ }
8189
+ catch (error) {
8190
+ if (error instanceof CheckRepairError) {
8191
+ return res.status(error.status).json({ error: error.message });
8192
+ }
8193
+ return res.status(409).json({ error: getErrorMessage(error) });
8194
+ }
8195
+ });
8196
+ app.post('/projects/:id/check-repairs/:repairRunId/retry', async (req, res) => {
8197
+ if (!getProjectById(req.params.id)) {
8198
+ return res.status(404).json({ error: 'Not found' });
8199
+ }
8200
+ if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {
8201
+ return res.status(400).json({ error: 'Check repair retries do not accept client-supplied repair context' });
8202
+ }
8203
+ try {
8204
+ return res.status(202).json(await retryCheckRepair(req.params.id, req.params.repairRunId, io));
8205
+ }
8206
+ catch (error) {
8207
+ if (error instanceof CheckRepairError) {
8208
+ return res.status(error.status).json({ error: error.message });
8209
+ }
8210
+ return res.status(409).json({ error: getErrorMessage(error) });
8211
+ }
8212
+ });
8042
8213
  const parseCheckDefinitionIds = (value) => {
8043
8214
  if (!Array.isArray(value) || value.length === 0 || value.some(id => typeof id !== 'string' || id.trim() === '')) {
8044
8215
  return null;
@@ -8138,12 +8309,47 @@ app.get('/projects/:id/check-batches/:batchRunId', async (req, res) => {
8138
8309
  attempts: await hydrateCheckAttemptCurrency(project, batch, getCheckBatchAttempts(req.params.id, req.params.batchRunId)),
8139
8310
  });
8140
8311
  });
8141
- app.get('/projects/:id/check-gate-evaluations/:evaluationId', (req, res) => {
8312
+ app.get('/projects/:id/check-batches/:batchRunId/diagnostics', async (req, res) => {
8313
+ const project = getProjectById(req.params.id);
8314
+ if (!project) {
8315
+ return res.status(404).json({ error: 'Not found' });
8316
+ }
8317
+ const diagnostics = getCheckBatchDiagnosticResponse(project.id, req.params.batchRunId);
8318
+ if (!diagnostics) {
8319
+ return res.status(404).json({ error: 'Check batch not found' });
8320
+ }
8321
+ return res.json({
8322
+ ...diagnostics,
8323
+ repair: await getCheckRepairCandidateState(project.id, req.params.batchRunId),
8324
+ latest_repair: getLatestCheckRepairForBatch(project.id, req.params.batchRunId),
8325
+ });
8326
+ });
8327
+ app.get('/projects/:id/check-attempts/:attemptRunId/diagnostics', (req, res) => {
8328
+ const project = getProjectById(req.params.id);
8329
+ if (!project) {
8330
+ return res.status(404).json({ error: 'Not found' });
8331
+ }
8332
+ const diagnostics = getCheckAttemptDiagnosticResponse(project.id, req.params.attemptRunId);
8333
+ if (!diagnostics) {
8334
+ return res.status(404).json({ error: 'Check attempt diagnostics not found' });
8335
+ }
8336
+ return res.json(diagnostics);
8337
+ });
8338
+ app.get('/projects/:id/check-gate-evaluations/:evaluationId', async (req, res) => {
8339
+ const project = getProjectById(req.params.id);
8340
+ if (!project) {
8341
+ return res.status(404).json({ error: 'Not found' });
8342
+ }
8142
8343
  const evaluation = getCheckGateEvaluation(req.params.id, req.params.evaluationId);
8143
8344
  if (!evaluation) {
8144
8345
  return res.status(404).json({ error: 'Check gate evaluation not found' });
8145
8346
  }
8146
- return res.json(evaluation);
8347
+ const workspace = resolveExplicitProjectTestWorkspace(project, evaluation.target.targetKind, evaluation.target.targetBundleId);
8348
+ const currentWorkspaceIdentity = 'error' in workspace
8349
+ ? null
8350
+ : await captureCheckWorkspaceIdentity(workspace.cwd);
8351
+ const currentPolicyResolution = resolveRequiredCheckPolicy(project, { boundaryKind: evaluation.boundaryKind });
8352
+ return res.json(hydrateCheckGatePresentApplicability(evaluation, currentWorkspaceIdentity, currentPolicyResolution));
8147
8353
  });
8148
8354
  app.post('/projects/:id/check-batches/:batchRunId/stop', async (req, res) => {
8149
8355
  if (!getCheckBatchDetails(req.params.id, req.params.batchRunId)) {