@straiffi/archon 1.7.8 → 1.7.10

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 (131) hide show
  1. package/dist/client/assets/{ActivityView-DwfDSF4q.js → ActivityView-IUWr92uj.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-3dMCYCSZ.js → GitHubActionsDialog-0YtWvbFk.js} +1 -1
  3. package/dist/client/assets/VerificationDialog-B4Pyz1LV.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-Bdnc97XK.js → architectureDiagram-3BPJPVTR-LLMyOR-6.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-boxM3She.js → blockDiagram-GPEHLZMM-aa1HitBJ.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-CJDdF7xd.js → c4Diagram-AAUBKEIU-BpSt5sdj.js} +1 -1
  7. package/dist/client/assets/channel-DWPRC-jg.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-ClpcO-Om.js → chunk-2J33WTMH-D8pf3RNr.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-CcuL_65J.js → chunk-3OPIFGDE-_yx1N2iT.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-CUGYZXkH.js → chunk-5ZQYHXKU-CYURGSbL.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-CApeLDyK.js → chunk-727SXJPM-Ci2Mu1zz.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-8FdSvF2D.js → chunk-AQP2D5EJ-CqVgrKze.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CZoIOF3a.js → chunk-CSCIHK7Q-CT8aPk-o.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-BigFQI5y.js → chunk-KSCS5N6A-yM1i0J-p.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-DIi3SHaT.js → chunk-L5ZTLDWV-BKMGr31Z.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-B-Z0BcQy.js → chunk-LZXEDZCA-A7oUDETP.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-EBeba0mg.js → chunk-ND2GUHAM-Cagc-XAG.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-DJBI2y53.js → chunk-NZK2D7GU-pe8tKLWI.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-BqxdXhMd.js → chunk-O5CBEL6O-BahwghWW.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-Coe1_Cbb.js → chunk-WU5MYG2G-BfSAFufr.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-DBZaZLLc.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B7HsVFoJ.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-tuTmeA0Y.js → dagre-BM42HDAG-DCma6-hS.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-ILGCpG2F.js → diagram-2AECGRRQ-DUeggZrO.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-C8qBZsgK.js → diagram-5GNKFQAL-CdjOKGtl.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-BXlcm_Zo.js → diagram-KO2AKTUF-CYjXhgh_.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-B8V5EVbD.js → diagram-LMA3HP47-DYBCh4cm.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-CWsdOqcP.js → diagram-OG6HWLK6-D3HPQt3z.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-DW8pgly2.js → erDiagram-TEJ5UH35-mG8Yztg-.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-Djn7fOVR.js → flowDiagram-I6XJVG4X-4FvjuoSZ.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DZ421MEy.js → ganttDiagram-6RSMTGT7-ByCQI8ft.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CCNzLPuP.js → gitGraphDiagram-PVQCEYII-CzEUA7nB.js} +1 -1
  33. package/dist/client/assets/index-D-miHTTb.js +273 -0
  34. package/dist/client/assets/index-TAQIlNdJ.css +2 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-Bd1gYgMp.js → infoDiagram-5YYISTIA-Q9CqnXO1.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-txyMoatJ.js → ishikawaDiagram-YF4QCWOH-CO8BToGf.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-DhT3bmX6.js → journeyDiagram-JHISSGLW-DtOyWvDJ.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-BBHt2hfC.js → kanban-definition-UN3LZRKU-DIn9bYpk.js} +1 -1
  39. package/dist/client/assets/{line-KGRcT7Qz.js → line-H-k0M9_s.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-CDzt4_zR.js → mermaid-parser.core-CZOUS5zK.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-DD_-k1aB.js → mermaid.core-Ck71Qr7h.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DoctVTDl.js → mindmap-definition-RKZ34NQL-BRFEcJw_.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-DFlLEi70.js → pieDiagram-4H26LBE5-CwihXXcJ.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-3YHOQgl7.js → quadrantDiagram-W4KKPZXB-moiRw4Cm.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-X4fZVL9M.js → requirementDiagram-4Y6WPE33-tshvM_vV.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BUPnMXeG.js → sankeyDiagram-5OEKKPKP-rtKIuBEp.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-D6QU3M64.js → sequenceDiagram-3UESZ5HK-lkTCo4wE.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-c35bu4qZ.js → stateDiagram-AJRCARHV-CFQhVUSd.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-rrN4DgWp.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-CX2y6e_b.js → timeline-definition-PNZ67QCA-BmlQ7Wiy.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-BDaWltjF.js → vennDiagram-CIIHVFJN-CZyZepcT.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BpBRL2sb.js → wardleyDiagram-YWT4CUSO-C2b52gni.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-Bqw7X0UT.js → xychartDiagram-2RQKCTM6-FqnROUHq.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +23 -10
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +330 -25
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/agent.js +5 -4
  60. package/dist/server/lib/agent.js.map +1 -1
  61. package/dist/server/lib/automationCheckGate.js +114 -20
  62. package/dist/server/lib/automationCheckGate.js.map +1 -1
  63. package/dist/server/lib/automationProgression.js +2 -1
  64. package/dist/server/lib/automationProgression.js.map +1 -1
  65. package/dist/server/lib/automationReconciliation.js +19 -0
  66. package/dist/server/lib/automationReconciliation.js.map +1 -1
  67. package/dist/server/lib/automationResumeDecision.js +82 -0
  68. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  69. package/dist/server/lib/automationReviewGate.js +142 -0
  70. package/dist/server/lib/automationReviewGate.js.map +1 -0
  71. package/dist/server/lib/automationStageDetails.js +98 -5
  72. package/dist/server/lib/automationStageDetails.js.map +1 -1
  73. package/dist/server/lib/buildCompletionContinuation.js +109 -0
  74. package/dist/server/lib/buildCompletionContinuation.js.map +1 -0
  75. package/dist/server/lib/buildSequences.js +81 -0
  76. package/dist/server/lib/buildSequences.js.map +1 -1
  77. package/dist/server/lib/buildTicketCommit.js +219 -0
  78. package/dist/server/lib/buildTicketCommit.js.map +1 -0
  79. package/dist/server/lib/bundleAutomationReadiness.js +7 -0
  80. package/dist/server/lib/bundleAutomationReadiness.js.map +1 -0
  81. package/dist/server/lib/bundleReviewOperations.js +17 -5
  82. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  83. package/dist/server/lib/chatOperations.js +2 -2
  84. package/dist/server/lib/chatOperations.js.map +1 -1
  85. package/dist/server/lib/checkDefinitions.js +11 -0
  86. package/dist/server/lib/checkDefinitions.js.map +1 -1
  87. package/dist/server/lib/checkGateEvaluations.js +4 -3
  88. package/dist/server/lib/checkGateEvaluations.js.map +1 -1
  89. package/dist/server/lib/checkRunner.js +27 -4
  90. package/dist/server/lib/checkRunner.js.map +1 -1
  91. package/dist/server/lib/checkRuns.js +38 -6
  92. package/dist/server/lib/checkRuns.js.map +1 -1
  93. package/dist/server/lib/git.js +5 -1
  94. package/dist/server/lib/git.js.map +1 -1
  95. package/dist/server/lib/lightweightPrompt.js +2 -1
  96. package/dist/server/lib/lightweightPrompt.js.map +1 -1
  97. package/dist/server/lib/projectAutoConfig.js +2 -1
  98. package/dist/server/lib/projectAutoConfig.js.map +1 -1
  99. package/dist/server/lib/projects.js +95 -10
  100. package/dist/server/lib/projects.js.map +1 -1
  101. package/dist/server/lib/requiredCheckPolicy.js +150 -19
  102. package/dist/server/lib/requiredCheckPolicy.js.map +1 -1
  103. package/dist/server/lib/run.js +3 -2
  104. package/dist/server/lib/run.js.map +1 -1
  105. package/dist/server/lib/terminal.js +5 -7
  106. package/dist/server/lib/terminal.js.map +1 -1
  107. package/dist/server/lib/testSessions.js +2 -2
  108. package/dist/server/lib/testSessions.js.map +1 -1
  109. package/dist/server/lib/ticketAutomationCommit.js +251 -160
  110. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  111. package/dist/server/lib/tickets.js +45 -4
  112. package/dist/server/lib/tickets.js.map +1 -1
  113. package/dist/server/lib/types.js.map +1 -1
  114. package/dist/server/lib/workspaceProcessEnvironment.js +21 -0
  115. package/dist/server/lib/workspaceProcessEnvironment.js.map +1 -0
  116. package/dist/server/workers/build.js +57 -97
  117. package/dist/server/workers/build.js.map +1 -1
  118. package/dist/server/workers/chat.js +13 -6
  119. package/dist/server/workers/chat.js.map +1 -1
  120. package/dist/server/workers/followUp.js +92 -116
  121. package/dist/server/workers/followUp.js.map +1 -1
  122. package/dist/server/workers/plan.js +4 -1
  123. package/dist/server/workers/plan.js.map +1 -1
  124. package/package.json +1 -1
  125. package/dist/client/assets/VerificationDialog-CoTfXMyb.js +0 -4
  126. package/dist/client/assets/channel-BGqdAcIT.js +0 -1
  127. package/dist/client/assets/classDiagram-4FO5ZUOK-D5VuKRcj.js +0 -1
  128. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B9GNqpRr.js +0 -1
  129. package/dist/client/assets/index-BRc02lbW.js +0 -273
  130. package/dist/client/assets/index-CZO9HL1Z.css +0 -2
  131. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ymy1_EHr.js +0 -1
@@ -28,8 +28,8 @@ import { emitMobileRealtime, getMobileAccessStatus, regenerateMobilePairingChall
28
28
  import { validateDesktopMobileAccessRequest } from './lib/mobileAccessSecurity.js';
29
29
  import { refreshChatSessionUsageSnapshot, refreshSpecSessionUsageSnapshot, stopAllBuildAgents, stopAllReviewAgents, stopBuildAgent, stopReviewAgent } from './lib/agent.js';
30
30
  import { runLightweightPrompt } from './lib/lightweightPrompt.js';
31
- import { startBuildChain, stopBuildChain } from './lib/buildChains.js';
32
- import { activateQueuedBuildSequenceTicket, compensateBuildSequenceAcceptance, enqueueBundledBuild, getDependencyOrderedBundleTicketIds, hasActiveSequenceForBundle, removeTicketFromBuildSequence, stopBuildSequence, } from './lib/buildSequences.js';
31
+ import { advanceBuildChain, startBuildChain, stopBuildChain } from './lib/buildChains.js';
32
+ import { activateQueuedBuildSequenceTicket, compensateBuildSequenceAcceptance, enqueueBundledBuild, getDependencyOrderedBundleTicketIds, getBuildSequenceIdForTicket, hasActiveSequenceForBundle, removeTicketFromBuildSequence, stopBuildSequence, } from './lib/buildSequences.js';
33
33
  import { prepareTicketForBuild } from './lib/buildFlow.js';
34
34
  import { getBundlePlanMutationBlockerById, shouldBlockPlanForBundleMutationById, } from './lib/bundleActivity.js';
35
35
  import { getDiscoveredModels, normalizeModelId, normalizeTicketModelId } from './lib/models.js';
@@ -47,12 +47,15 @@ import { exportProjectMemoryArchive, importProjectMemoryArchive } from './lib/pr
47
47
  import { buildReviewFindingsResponse, computeDiffSignature, getLatestReviewRunForContext, listReviewFindings } from './lib/reviewFindings.js';
48
48
  import { formatBuildOpenQuestionAnswersPrompt, getLatestBuildOpenQuestionBatchStatus, hasActiveBuildOpenQuestionBatch, listBuildOpenQuestionsForBundle, loadBuildOpenQuestionsByBundleIds, } from './lib/buildOpenQuestions.js';
49
49
  import { acceptBuildOpenQuestionResolution } from './lib/buildOpenQuestionOperations.js';
50
- import { completeAutomationProgression, createAutomationReviewFix, finishAutomationReviewFix, getAutomationProgression, recoverLegacyAutomationProgression, recoverStoppedSideEffectProgression, runAutomationProgression, stopAutomationProgression, waitAutomationProgression } from './lib/automationProgression.js';
50
+ import { completeAutomationProgression, createAutomationReviewFix, ensureAutomationProgression, finishAutomationReviewFix, getAutomationProgression, recoverLegacyAutomationProgression, recoverStoppedSideEffectProgression, runAutomationProgression, stopAutomationProgression, waitAutomationProgression } from './lib/automationProgression.js';
51
51
  import { reconcileAutomationStage } from './lib/automationReconciliation.js';
52
+ import { runAutomationReviewGateOrPause } from './lib/automationReviewGate.js';
52
53
  import { pausedRunBelongsToProgression, resolveAutomationResumeDecision } from './lib/automationResumeDecision.js';
53
54
  import { getAutomationStageDetails, } from './lib/automationStageDetails.js';
54
55
  import { buildAutomationFixInstruction, clearAutomationPause, countBlockingFindings, depthAtLeast, getBundleAutomationState, isAutomationDepth, recordAutomationPause, resolveAutomationConfig, upsertBundleAutomationState, } from './lib/ticketAutomation.js';
55
- import { resolveAutomationCheckGateResume, runAutomationCommitChain } from './lib/ticketAutomationCommit.js';
56
+ import { resolveAutomationCheckGateResume, runAutomationCommitChain, runAutomationCommitOperation } from './lib/ticketAutomationCommit.js';
57
+ import { canMutateBuildTicketCommitCheckpoint, resolveBuildTicketCommitCheckpointLineage } from './lib/buildTicketCommit.js';
58
+ import { advanceSuccessfulBuildTicketAfterCheckpoint, compensateBuildSequenceTicketActivation } from './lib/buildCompletionContinuation.js';
56
59
  import { getDiscoveredSkills, normalizeSkillNames } from './lib/skills.js';
57
60
  import { clearAllPreviewProxies } from './lib/previewProxy.js';
58
61
  import { createExecutionRun, addExecutionRunLink, addExecutionRunLinks, getExecutionRun, hasActiveBuildExecutionRunForTicket, getGitHubActionsExecutionDetails, getTestExecutionDetails, GitHubActionsDispatchConflictError, listExecutionRunLinks, listExecutionRuns, listLifecycleEvents, listProjectLifecycleEvents, toCanonicalLifecycleEvent, transitionExecutionRun, withExecutionRunTransaction, } from './lib/executionRuns.js';
@@ -75,14 +78,15 @@ import { PlanningReferenceError, resolveExternalPlanningDescription } from './li
75
78
  import { refreshIfNeeded as refreshAssignedIssues, clearCachedAssignedIssues } from './lib/integrations/assignedIssuesCache.js';
76
79
  import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage } from './lib/commitMessage.js';
77
80
  import { BUNDLE_PULL_REQUEST_SYNC_TTL_MS, getBundlePullRequestRecord, hasOpenBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequest, upsertBundlePullRequestFromGitHub, } from './lib/bundlePullRequests.js';
78
- import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectPayload, } from './lib/projects.js';
81
+ import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectRequiredCheckPolicyReferences, validateProjectPayload, } from './lib/projects.js';
79
82
  import { getActiveRunStatus, getActiveRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runSetupCommandsInWorkspace, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
80
83
  import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
81
84
  import { closeCheckRunnerAdmissions, getCheckBatchAttempts, openCheckRunnerAdmissions, rerunCheckBatchAndSchedule, shutdownCheckRunner, startCheckBatch, stopCheckBatch } from './lib/checkRunner.js';
82
85
  import { findActiveCheckBatchForTarget, getCheckAttemptDetails, getCheckBatchDetails, hasActiveProjectCheckBatch, isCheckAttemptCurrent, resolveCheckBatchRerunEligibility } from './lib/checkRuns.js';
83
- import { getCheckDefinitionFingerprint } from './lib/checkDefinitions.js';
86
+ import { getCheckDefinitionFingerprint, validateRequiredCheckPolicySnapshotSize } from './lib/checkDefinitions.js';
84
87
  import { getCheckGateEvaluation } from './lib/checkGateEvaluations.js';
85
88
  import { captureCheckWorkspaceIdentity } from './lib/checkWorkspaceIdentity.js';
89
+ import { canonicalizeBundleRequiredCheckPolicyOverride, parseBundleRequiredCheckPolicyOverride, resolveEffectiveRequiredCheckPolicyMembership, } from './lib/requiredCheckPolicy.js';
86
90
  import { getBundleBuildBlockerResponse, shouldStartBundleBuildNow, transitionTicketState, } from './lib/ticketWorkflowOperations.js';
87
91
  import { submitPlanTicketFollowUp, submitTicketFollowUp, } from './lib/ticketFollowUpOperations.js';
88
92
  import { closePendingTicketPlanQuestionSet, listPendingTicketPlanQuestionSetMessageIds, resolveTicketPlanQuestionSet, restoreClosedTicketPlanQuestionSets, } from './lib/planQuestionOperations.js';
@@ -1460,6 +1464,10 @@ const serializeBundleRow = (projectId, bundle, openQuestions = bundle ? loadBuil
1460
1464
  const hasPreparedWorktree = bundle.kind === 'project_root'
1461
1465
  ? true
1462
1466
  : Boolean(project && resolveExistingWorktreePath(bundle.branch, project));
1467
+ const parsedOverride = bundle.kind === 'project_root'
1468
+ ? { value: null }
1469
+ : parseBundleRequiredCheckPolicyOverride(bundle.required_check_policy_override);
1470
+ const effectivePolicy = resolveEffectiveRequiredCheckPolicyMembership(project, bundle);
1463
1471
  return {
1464
1472
  ...bundle,
1465
1473
  has_prepared_worktree: hasPreparedWorktree,
@@ -1467,6 +1475,15 @@ const serializeBundleRow = (projectId, bundle, openQuestions = bundle ? loadBuil
1467
1475
  ? serializeBundlePullRequest(getBundlePullRequestRecord(bundle.id, projectId))
1468
1476
  : null,
1469
1477
  automation_config: resolveAutomationConfig(bundle, project),
1478
+ required_check_policy_override: 'error' in parsedOverride ? null : parsedOverride.value,
1479
+ required_check_policy: 'error' in effectivePolicy
1480
+ ? undefined
1481
+ : {
1482
+ source: effectivePolicy.source,
1483
+ required_check_definition_ids: effectivePolicy.required_check_definition_ids,
1484
+ pre_review_check_definition_ids: effectivePolicy.pre_review_check_definition_ids,
1485
+ },
1486
+ required_check_policy_error: 'error' in parsedOverride ? parsedOverride.error : ('error' in effectivePolicy ? effectivePolicy.error : null),
1470
1487
  open_questions: openQuestions,
1471
1488
  build_open_question_batch_status: getLatestBuildOpenQuestionBatchStatus(projectId, bundle.id),
1472
1489
  };
@@ -3741,11 +3758,14 @@ app.patch('/bundles/:id', (req, res) => {
3741
3758
  return res.status(404).json({ error: 'Bundle not found' });
3742
3759
  }
3743
3760
  const hasNameUpdate = hasOwn(req.body, 'name');
3761
+ const hasRequiredCheckPolicyOverrideUpdate = hasOwn(req.body, 'required_check_policy_override');
3744
3762
  if (isProjectRootBundle(bundle)) {
3745
3763
  return res.status(409).json({
3746
3764
  error: hasNameUpdate
3747
3765
  ? 'Project root bundles cannot be renamed'
3748
- : 'Project root bundle automation cannot be overridden',
3766
+ : hasRequiredCheckPolicyOverrideUpdate
3767
+ ? 'Project root bundles cannot override required-check policy'
3768
+ : 'Project root bundle automation cannot be overridden',
3749
3769
  });
3750
3770
  }
3751
3771
  let nextName = bundle.name;
@@ -3818,6 +3838,39 @@ app.patch('/bundles/:id', (req, res) => {
3818
3838
  return res.status(400).json({ error: 'automation_pause_on_open_questions must be a boolean' });
3819
3839
  }
3820
3840
  }
3841
+ let requiredCheckPolicyOverride;
3842
+ let requiredCheckPolicyOverrideValue;
3843
+ if (hasRequiredCheckPolicyOverrideUpdate) {
3844
+ const value = req.body.required_check_policy_override;
3845
+ if (value == null) {
3846
+ requiredCheckPolicyOverride = null;
3847
+ requiredCheckPolicyOverrideValue = null;
3848
+ }
3849
+ else {
3850
+ const result = canonicalizeBundleRequiredCheckPolicyOverride(value);
3851
+ if ('error' in result) {
3852
+ return res.status(400).json({ error: result.error });
3853
+ }
3854
+ requiredCheckPolicyOverrideValue = result.value;
3855
+ requiredCheckPolicyOverride = result.value ? JSON.stringify(result.value) : null;
3856
+ }
3857
+ }
3858
+ if (requiredCheckPolicyOverrideValue) {
3859
+ const definitions = getProjectCheckDefinitions(result.project);
3860
+ const definitionIds = new Set(definitions.map(definition => definition.id));
3861
+ const missingId = [
3862
+ ...requiredCheckPolicyOverrideValue.required_check_definition_ids,
3863
+ ...requiredCheckPolicyOverrideValue.pre_review_check_definition_ids,
3864
+ ].find(id => !definitionIds.has(id));
3865
+ if (missingId) {
3866
+ return res.status(400).json({ error: `required_check_policy_override references missing check definition ${missingId}` });
3867
+ }
3868
+ const requiredDefinitions = definitions.filter(definition => requiredCheckPolicyOverrideValue.required_check_definition_ids.includes(definition.id));
3869
+ const policySnapshotSizeError = validateRequiredCheckPolicySnapshotSize(requiredCheckPolicyOverrideValue.required_check_definition_ids, requiredDefinitions);
3870
+ if (policySnapshotSizeError) {
3871
+ return res.status(400).json({ error: policySnapshotSizeError });
3872
+ }
3873
+ }
3821
3874
  let updated = null;
3822
3875
  try {
3823
3876
  updated = db.transaction(() => {
@@ -3835,6 +3888,7 @@ app.patch('/bundles/:id', (req, res) => {
3835
3888
  automationDepth,
3836
3889
  automationReviewFixEnabled,
3837
3890
  automationPauseOnOpenQuestions,
3891
+ requiredCheckPolicyOverride,
3838
3892
  });
3839
3893
  })();
3840
3894
  }
@@ -3938,6 +3992,18 @@ app.post('/bundles/:id/build-open-questions/resolve', (req, res) => {
3938
3992
  io.emit('ticket:updated', ticket);
3939
3993
  }
3940
3994
  if (reviewResult && !reviewResult.ok) {
3995
+ if (reviewResult.code === 'bundle_build_incomplete') {
3996
+ completeAutomationProgression({
3997
+ projectId: result.project.id,
3998
+ bundleId: bundle.id,
3999
+ progressionRunId: accepted.progressionRunId,
4000
+ broadcaster: io,
4001
+ });
4002
+ for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
4003
+ io.emit('ticket:updated', ticket);
4004
+ }
4005
+ return res.json(serializeBundleDetails(result.project, bundle));
4006
+ }
3941
4007
  waitAutomationProgression({
3942
4008
  projectId: result.project.id,
3943
4009
  bundleId: bundle.id,
@@ -3951,8 +4017,8 @@ app.post('/bundles/:id/build-open-questions/resolve', (req, res) => {
3951
4017
  return res.status(reviewResult.status).json({ error: reviewResult.error });
3952
4018
  }
3953
4019
  return res.json(serializeBundleDetails(result.project, bundle, {
3954
- review_owner_ticket_id: reviewResult?.value?.id,
3955
- target_ticket_id: reviewResult?.value?.id,
4020
+ review_owner_ticket_id: reviewResult?.ok ? reviewResult.value?.id : undefined,
4021
+ target_ticket_id: reviewResult?.ok ? reviewResult.value?.id : undefined,
3956
4022
  }));
3957
4023
  }
3958
4024
  for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
@@ -4135,6 +4201,183 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4135
4201
  if (!revalidateDecision()) {
4136
4202
  return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4137
4203
  }
4204
+ const checkpointLineage = resolveBuildTicketCommitCheckpointLineage({
4205
+ projectId: result.project.id,
4206
+ bundleId: bundle.id,
4207
+ progressionRunId,
4208
+ });
4209
+ if (checkpointLineage) {
4210
+ const owner = getTicket(decision.owner_ticket_id);
4211
+ if (!owner || owner.id !== checkpointLineage.ticket.id || owner.agent_status !== 'done') {
4212
+ return res.status(409).json({ error: 'Checkpoint progression owner is no longer the active Build ticket' });
4213
+ }
4214
+ let retryOfRunId;
4215
+ let commitAlreadyComplete = false;
4216
+ const stageRunId = bundleState.automation_paused_stage_run_id;
4217
+ if (stageRunId) {
4218
+ const stage = getExecutionRun(result.project.id, stageRunId);
4219
+ if (!stage
4220
+ || !['automation_commit', 'check_batch'].includes(stage.kind)
4221
+ || !pausedRunBelongsToProgression(result.project.id, bundle.id, progressionRunId, stageRunId)) {
4222
+ return res.status(409).json({ error: 'The paused checkpoint stage does not belong to the active progression' });
4223
+ }
4224
+ if (stage.kind === 'automation_commit') {
4225
+ let reconciled;
4226
+ try {
4227
+ reconciled = await reconcileAutomationStage({
4228
+ projectId: result.project.id,
4229
+ bundleId: bundle.id,
4230
+ progressionRunId,
4231
+ stageRunId,
4232
+ });
4233
+ }
4234
+ catch (error) {
4235
+ return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4236
+ }
4237
+ if (reconciled.domainStatus === 'ambiguous') {
4238
+ return res.status(409).json({ error: 'The paused checkpoint commit is ambiguous and cannot be resumed safely.' });
4239
+ }
4240
+ if (reconciled.status === 'completed' && reconciled.domainStatus === 'reconciled_completed') {
4241
+ commitAlreadyComplete = true;
4242
+ }
4243
+ else if (reconciled.status === 'interrupted' && reconciled.domainStatus === 'reconciled_absent') {
4244
+ retryOfRunId = stage.id;
4245
+ }
4246
+ else {
4247
+ return res.status(409).json({ error: 'The paused checkpoint commit has no safely resumable outcome.' });
4248
+ }
4249
+ }
4250
+ }
4251
+ try {
4252
+ if (!revalidateDecision()) {
4253
+ return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4254
+ }
4255
+ runAutomationProgression({
4256
+ projectId: result.project.id,
4257
+ bundleId: bundle.id,
4258
+ progressionRunId,
4259
+ broadcaster: io,
4260
+ });
4261
+ if (commitAlreadyComplete) {
4262
+ completeAutomationProgression({
4263
+ projectId: result.project.id,
4264
+ bundleId: bundle.id,
4265
+ progressionRunId,
4266
+ broadcaster: io,
4267
+ });
4268
+ }
4269
+ else {
4270
+ await runAutomationCommitOperation({
4271
+ ticket: owner,
4272
+ io,
4273
+ progressionRunId,
4274
+ retryOfRunId,
4275
+ mutationLabel: 'checkpoint commit',
4276
+ canCommitLive: canMutateBuildTicketCommitCheckpoint,
4277
+ shouldCompleteProgression: () => true,
4278
+ failureMode: 'throw',
4279
+ requiredProgressionPurpose: 'build_ticket_checkpoint',
4280
+ });
4281
+ }
4282
+ }
4283
+ catch (error) {
4284
+ return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4285
+ }
4286
+ const workflowContext = validateWorkflowContext(checkpointLineage.progression.metadata);
4287
+ const continuation = advanceSuccessfulBuildTicketAfterCheckpoint({
4288
+ ticket: owner,
4289
+ emitUpdatedTickets,
4290
+ });
4291
+ if (continuation.nextBuild) {
4292
+ const nextBuild = continuation.nextBuild;
4293
+ const nextSequenceId = continuation.nextSequenceId;
4294
+ const scheduling = startBuild(nextBuild, io, {
4295
+ initiation: {
4296
+ initiator: 'automation',
4297
+ causalParentRunId: checkpointLineage.parent.id,
4298
+ workflowContext,
4299
+ },
4300
+ sequenceId: nextSequenceId,
4301
+ accept: () => {
4302
+ const transition = prepareTicketForBuild(nextBuild.id, {
4303
+ ignoredBlockerTicketIds: continuation.ignoredBlockerTicketIds,
4304
+ });
4305
+ if (!transition.ok) {
4306
+ throw new Error(transition.error);
4307
+ }
4308
+ if (!nextSequenceId) {
4309
+ throw new Error('Build sequence no longer exists');
4310
+ }
4311
+ activateQueuedBuildSequenceTicket(nextSequenceId, transition.ticket.id);
4312
+ return {
4313
+ ticket: transition.ticket,
4314
+ sequenceId: nextSequenceId,
4315
+ compensate: () => compensateBuildSequenceTicketActivation(nextSequenceId, transition.ticket.id),
4316
+ };
4317
+ },
4318
+ });
4319
+ if (!scheduling.accepted) {
4320
+ const stoppedSequence = stopBuildSequence(nextBuild.id);
4321
+ emitUpdatedTickets(stoppedSequence.affectedTicketIds);
4322
+ }
4323
+ }
4324
+ else if (!continuation.nextTicketId && owner.project_id && owner.worktree_bundle_id) {
4325
+ const resumedOwnerForReview = getTicket(owner.id) ?? owner;
4326
+ const liveProject = getEffectiveProject(resumedOwnerForReview) ?? result.project;
4327
+ const automationConfig = resolveAutomationConfig(resumedOwnerForReview.bundle ?? null, liveProject);
4328
+ const autoReviewEnabled = depthAtLeast(automationConfig.depth, 'auto_review');
4329
+ const latestBundleState = getBundleAutomationState(owner.project_id, owner.worktree_bundle_id);
4330
+ const openQuestions = listBuildOpenQuestionsForBundle(owner.project_id, owner.worktree_bundle_id);
4331
+ const shouldPauseForOpenQuestions = autoReviewEnabled
4332
+ && automationConfig.pauseOnOpenQuestions
4333
+ && openQuestions.length > 0;
4334
+ if (shouldPauseForOpenQuestions) {
4335
+ const reviewProgression = ensureAutomationProgression({
4336
+ projectId: owner.project_id,
4337
+ bundleId: owner.worktree_bundle_id,
4338
+ ticketId: owner.id,
4339
+ ticketTitle: owner.title,
4340
+ parentRunId: checkpointLineage.parent.id,
4341
+ workflowContext,
4342
+ broadcaster: io,
4343
+ });
4344
+ waitAutomationProgression({
4345
+ projectId: owner.project_id,
4346
+ bundleId: owner.worktree_bundle_id,
4347
+ progressionRunId: reviewProgression.runId,
4348
+ pauseCode: 'open_questions',
4349
+ pauseReason: `${openQuestions.length} unresolved Build open question${openQuestions.length === 1 ? '' : 's'}`,
4350
+ reasonCode: 'precondition_failed',
4351
+ pausedStageRunId: checkpointLineage.parent.id,
4352
+ broadcaster: io,
4353
+ });
4354
+ }
4355
+ else if (autoReviewEnabled && (!latestBundleState?.automation_paused_reason || latestBundleState.automation_paused_code === 'open_questions')) {
4356
+ const reviewProgression = ensureAutomationProgression({
4357
+ projectId: owner.project_id,
4358
+ bundleId: owner.worktree_bundle_id,
4359
+ ticketId: owner.id,
4360
+ ticketTitle: owner.title,
4361
+ parentRunId: checkpointLineage.parent.id,
4362
+ workflowContext,
4363
+ broadcaster: io,
4364
+ });
4365
+ await runAutomationReviewGateOrPause({
4366
+ projectId: owner.project_id,
4367
+ bundleId: owner.worktree_bundle_id,
4368
+ progressionRunId: reviewProgression.runId,
4369
+ broadcaster: io,
4370
+ pausedStageRunId: checkpointLineage.parent.id,
4371
+ initiation: { workflowContext },
4372
+ });
4373
+ }
4374
+ }
4375
+ const resumedOwner = getTicket(owner.id);
4376
+ if (resumedOwner) {
4377
+ io.emit('ticket:updated', resumedOwner);
4378
+ }
4379
+ return res.json(serializeBundleDetails(result.project, bundle));
4380
+ }
4138
4381
  let sideEffectResume = null;
4139
4382
  if (bundleState.automation_paused_stage_run_id) {
4140
4383
  const stageRunId = bundleState.automation_paused_stage_run_id;
@@ -4392,17 +4635,43 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4392
4635
  return res.status(409).json({ error: 'The selected Resume outcome no longer matches the bundle state' });
4393
4636
  }
4394
4637
  }
4395
- else if (owner.state === 'build' && owner.agent_status === 'done' && decision.outcome === 'start_review') {
4638
+ else if (owner.state === 'build' && owner.agent_status === 'done' && (decision.outcome === 'start_review' || decision.outcome === 'rerun_review_checks')) {
4396
4639
  if (!revalidateDecision()) {
4397
4640
  return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4398
4641
  }
4399
- const reviewResult = transitionBundleToReview(result.project.id, bundle.id, io, {
4400
- automatic: true,
4401
- initiation: { causalParentRunId: progressionRunId },
4402
- accept: acceptResumedReview,
4403
- });
4404
- if (!reviewResult.ok) {
4405
- return res.status(reviewResult.status).json({ error: reviewResult.error });
4642
+ const progression = getAutomationProgression(result.project.id, bundle.id);
4643
+ if (!progression || progression.id !== progressionRunId) {
4644
+ return res.status(409).json({ error: 'Automation progression ownership is invalid' });
4645
+ }
4646
+ const workflowContext = validateWorkflowContext(progression.metadata);
4647
+ let reviewStartFailureStatus = null;
4648
+ let reviewStartFailureError = null;
4649
+ try {
4650
+ runAutomationProgression({
4651
+ projectId: result.project.id,
4652
+ bundleId: bundle.id,
4653
+ progressionRunId,
4654
+ broadcaster: io,
4655
+ });
4656
+ await runAutomationReviewGateOrPause({
4657
+ projectId: result.project.id,
4658
+ bundleId: bundle.id,
4659
+ progressionRunId,
4660
+ broadcaster: io,
4661
+ pausedStageRunId: bundleState.automation_paused_stage_run_id,
4662
+ initiation: { workflowContext },
4663
+ onReviewStartFailure: failure => {
4664
+ reviewStartFailureStatus = failure.status;
4665
+ reviewStartFailureError = failure.error;
4666
+ restoreWaitingProgression();
4667
+ },
4668
+ });
4669
+ }
4670
+ catch (error) {
4671
+ return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4672
+ }
4673
+ if (reviewStartFailureStatus !== null && reviewStartFailureError !== null) {
4674
+ return res.status(reviewStartFailureStatus).json({ error: reviewStartFailureError });
4406
4675
  }
4407
4676
  }
4408
4677
  else {
@@ -4462,6 +4731,11 @@ app.post('/bundles/:id/dismiss-automation-pause', async (req, res) => {
4462
4731
  catch (error) {
4463
4732
  return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4464
4733
  }
4734
+ const checkpointLineage = resolveBuildTicketCommitCheckpointLineage({
4735
+ projectId: result.project.id,
4736
+ bundleId: bundle.id,
4737
+ progressionRunId,
4738
+ });
4465
4739
  if (bundleState.automation_paused_stage_run_id) {
4466
4740
  const stage = getExecutionRun(result.project.id, bundleState.automation_paused_stage_run_id);
4467
4741
  if (!stage
@@ -4497,6 +4771,10 @@ app.post('/bundles/:id/dismiss-automation-pause', async (req, res) => {
4497
4771
  broadcaster: io,
4498
4772
  stopLocalWorker: worker => (worker.kind === 'review' ? stopReviewAgent(worker.ticket_id) : stopBuildAgent(worker.ticket_id)),
4499
4773
  });
4774
+ if (checkpointLineage) {
4775
+ const stoppedSequence = stopBuildSequence(checkpointLineage.ticket.id);
4776
+ emitUpdatedTickets(stoppedSequence.affectedTicketIds);
4777
+ }
4500
4778
  for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
4501
4779
  io.emit('ticket:updated', ticket);
4502
4780
  }
@@ -5581,7 +5859,7 @@ app.post('/projects', (req, res) => {
5581
5859
  return res.status(400).json({ error: validation.error });
5582
5860
  }
5583
5861
  const projectCount = Number(db.prepare('SELECT COUNT(*) AS count FROM projects').get()?.count ?? 0);
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;
5862
+ 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;
5585
5863
  if (!repo_path) {
5586
5864
  return res.status(400).json({ error: 'repo_path must be a non-empty string' });
5587
5865
  }
@@ -5603,6 +5881,7 @@ app.post('/projects', (req, res) => {
5603
5881
  run_services,
5604
5882
  check_definitions,
5605
5883
  required_check_definition_ids,
5884
+ pre_review_check_definition_ids,
5606
5885
  test_commands,
5607
5886
  run_ide,
5608
5887
  preview_service_name,
@@ -5611,6 +5890,7 @@ app.post('/projects', (req, res) => {
5611
5890
  helper_model,
5612
5891
  helper_variant,
5613
5892
  commit_message_rules,
5893
+ commit_after_each_ticket,
5614
5894
  auto_park_stale_tickets,
5615
5895
  memory_enabled,
5616
5896
  scan_include_globs_json,
@@ -5623,8 +5903,8 @@ app.post('/projects', (req, res) => {
5623
5903
  automation_review_severity,
5624
5904
  automation_review_max_iterations
5625
5905
  )
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);
5906
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5907
+ `).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);
5628
5908
  replaceProjectLinks(id, linked_project_ids ?? []);
5629
5909
  ensureProjectRootBundle(id);
5630
5910
  if (projectCount === 0) {
@@ -6790,8 +7070,8 @@ app.patch('/projects/:id', (req, res) => {
6790
7070
  });
6791
7071
  }
6792
7072
  }
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;
6794
- db.prepare(`
7073
+ 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;
7074
+ const updateProject = db.prepare(`
6795
7075
  UPDATE projects SET
6796
7076
  name = ?,
6797
7077
  repo_path = ?,
@@ -6799,6 +7079,7 @@ app.patch('/projects/:id', (req, res) => {
6799
7079
  run_services = ?,
6800
7080
  check_definitions = ?,
6801
7081
  required_check_definition_ids = ?,
7082
+ pre_review_check_definition_ids = ?,
6802
7083
  test_commands = ?,
6803
7084
  run_ide = ?,
6804
7085
  preview_service_name = ?,
@@ -6807,6 +7088,7 @@ app.patch('/projects/:id', (req, res) => {
6807
7088
  helper_model = ?,
6808
7089
  helper_variant = ?,
6809
7090
  commit_message_rules = ?,
7091
+ commit_after_each_ticket = ?,
6810
7092
  auto_park_stale_tickets = ?,
6811
7093
  memory_enabled = ?,
6812
7094
  scan_include_globs_json = ?,
@@ -6820,9 +7102,32 @@ app.patch('/projects/:id', (req, res) => {
6820
7102
  automation_review_max_iterations = ?,
6821
7103
  updated_at = CURRENT_TIMESTAMP
6822
7104
  WHERE 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);
6824
- if (hasOwn(validation.values, 'linked_project_ids')) {
6825
- replaceProjectLinks(project.id, linked_project_ids ?? []);
7105
+ `);
7106
+ const nextCheckDefinitions = hasOwn(validation.values, 'check_definitions') ? check_definitions ?? null : project.check_definitions;
7107
+ const nextRequiredCheckDefinitionIds = hasOwn(validation.values, 'required_check_definition_ids')
7108
+ ? required_check_definition_ids ?? '[]'
7109
+ : project.required_check_definition_ids;
7110
+ const nextPreReviewCheckDefinitionIds = hasOwn(validation.values, 'pre_review_check_definition_ids')
7111
+ ? pre_review_check_definition_ids ?? '[]'
7112
+ : project.pre_review_check_definition_ids;
7113
+ const updateResult = db.transaction(() => {
7114
+ const policyReferences = validateProjectRequiredCheckPolicyReferences({
7115
+ projectId: project.id,
7116
+ checkDefinitionsJson: nextCheckDefinitions,
7117
+ requiredCheckDefinitionIdsJson: nextRequiredCheckDefinitionIds,
7118
+ preReviewCheckDefinitionIdsJson: nextPreReviewCheckDefinitionIds,
7119
+ });
7120
+ if ('error' in policyReferences) {
7121
+ return policyReferences;
7122
+ }
7123
+ 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);
7124
+ if (hasOwn(validation.values, 'linked_project_ids')) {
7125
+ replaceProjectLinks(project.id, linked_project_ids ?? []);
7126
+ }
7127
+ return { ok: true };
7128
+ })();
7129
+ if ('error' in updateResult) {
7130
+ return res.status(400).json({ error: updateResult.error });
6826
7131
  }
6827
7132
  const updated = db.prepare('SELECT * FROM projects WHERE id = ?').get(req.params.id);
6828
7133
  res.json(serializeProject(updated ?? null));