@straiffi/archon 1.10.9 → 1.10.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.
- package/dist/client/assets/ActivityView-CRkM5TeN.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-KFUiR00H.js +1 -0
- package/dist/client/assets/VerificationDialog-BIDttgiW.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BAIGV9Mj.js → architectureDiagram-3BPJPVTR-u0PSZlNP.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-B-5v5jSZ.js → blockDiagram-GPEHLZMM-DFXLHRIk.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-C6wU415o.js → c4Diagram-AAUBKEIU-Dvic8tCt.js} +1 -1
- package/dist/client/assets/channel-Dhv_uSKx.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-COmvX2tP.js → chunk-2J33WTMH-t5CxhRXJ.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-BlANptdY.js → chunk-3OPIFGDE-YHT1dP8Q.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-ETyA2_GU.js → chunk-5ZQYHXKU-DcxuI7lb.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-D5-DudwF.js → chunk-727SXJPM-D1F6DmrT.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-k1AJ4sA3.js → chunk-AQP2D5EJ-BIx6Ohbk.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-jfksXwY9.js → chunk-CSCIHK7Q-B355h6HA.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-CHj0FDj9.js → chunk-KSCS5N6A-CLHGYOLQ.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-1ODGxXhG.js → chunk-L5ZTLDWV-DFLYcMx8.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-Bgh9X2W0.js → chunk-LZXEDZCA-DtljFujX.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-DDkb3sp4.js → chunk-ND2GUHAM-CIuUQ86K.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-B6B8PZ_-.js → chunk-NZK2D7GU-CIwqlxek.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-DhiUIFgj.js → chunk-O5CBEL6O-W42BEPu4.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-Dzd0fbP3.js → chunk-WU5MYG2G-C_zuaNxV.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-9XKvSSLq.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Dbr5_49Y.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-DoNDMCXI.js → dagre-BM42HDAG-UKfIUJTP.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-ETDXSa5q.js → diagram-2AECGRRQ-Xn6yojqq.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CgTUep7e.js → diagram-5GNKFQAL-BzpY1ZEl.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-BKTx2iRe.js → diagram-KO2AKTUF-Dyrvzvsg.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-9iEiScHT.js → diagram-LMA3HP47-AqImKwAr.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-UIZRh7Qa.js → diagram-OG6HWLK6-CaQQ46mi.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-BpNJmJva.js → erDiagram-TEJ5UH35-Dkw4Aa5u.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-C-rK0XZ1.js → flowDiagram-I6XJVG4X-CJTuGX9h.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DWyfPvv_.js → ganttDiagram-6RSMTGT7-CidgOdS-.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-x71bzdc_.js → gitGraphDiagram-PVQCEYII-Bj02mIQj.js} +1 -1
- package/dist/client/assets/index-ClRB9YWJ.css +2 -0
- package/dist/client/assets/index-DBMdsTvy.js +276 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-CbRus0Xu.js → infoDiagram-5YYISTIA-GwJWfo_r.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DvaMHrgN.js → ishikawaDiagram-YF4QCWOH-rbrlWbUn.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-BQBu4YGn.js → journeyDiagram-JHISSGLW-BLi_uzQM.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-8rU-U7L1.js → kanban-definition-UN3LZRKU-ynlywm-_.js} +1 -1
- package/dist/client/assets/{line-CSZnwjLA.js → line-BNyTZBB4.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-CTOl2slE.js → mermaid-parser.core-CzUOsZAD.js} +1 -1
- package/dist/client/assets/{mermaid.core-B9gr0Vqf.js → mermaid.core-K0QR6SzN.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-But1_PWQ.js → mindmap-definition-RKZ34NQL-BKcgqXm0.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-CnlVmfSG.js → pieDiagram-4H26LBE5-Bjx0lvDI.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Dww1ZGuR.js → quadrantDiagram-W4KKPZXB-CmUlgyUv.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-CNf_loe8.js → requirementDiagram-4Y6WPE33-Dj2mzg1y.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBhi37Jj.js → sankeyDiagram-5OEKKPKP-DC--H6qf.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B64hb4E4.js → sequenceDiagram-3UESZ5HK-BkNrt6Ra.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-DcS6oB-S.js → stateDiagram-AJRCARHV-BuxxRu1Y.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Cyo85GPN.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-Dq-4tXfh.js → timeline-definition-PNZ67QCA-Db3pCVSp.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CeDklSE_.js → vennDiagram-CIIHVFJN-BgBSGThQ.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-D2cJISMm.js → wardleyDiagram-YWT4CUSO-BJhPdjYS.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CBF-tTpF.js → xychartDiagram-2RQKCTM6-BY4Ra0kX.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +341 -4
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +172 -249
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agentRuns.js +14 -1
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/automationLoopProjection.js +55 -12
- package/dist/server/lib/automationLoopProjection.js.map +1 -1
- package/dist/server/lib/automationProgression.js +2 -5
- package/dist/server/lib/automationProgression.js.map +1 -1
- package/dist/server/lib/automationResumeDecision.js +19 -1
- package/dist/server/lib/automationResumeDecision.js.map +1 -1
- package/dist/server/lib/buildControlEligibility.js +4 -0
- package/dist/server/lib/buildControlEligibility.js.map +1 -0
- package/dist/server/lib/buildExecutionAdmission.js +76 -0
- package/dist/server/lib/buildExecutionAdmission.js.map +1 -0
- package/dist/server/lib/buildSequenceResume.js +79 -0
- package/dist/server/lib/buildSequenceResume.js.map +1 -0
- package/dist/server/lib/buildWorkload.js +11 -0
- package/dist/server/lib/buildWorkload.js.map +1 -0
- package/dist/server/lib/checkAutoFixDetails.js +36 -1
- package/dist/server/lib/checkAutoFixDetails.js.map +1 -1
- package/dist/server/lib/executionRetry.js +13 -1
- package/dist/server/lib/executionRetry.js.map +1 -1
- package/dist/server/lib/executionRunAuthority.js +2 -2
- package/dist/server/lib/executionRunAuthority.js.map +1 -1
- package/dist/server/lib/executionTargets.js +7 -4
- package/dist/server/lib/executionTargets.js.map +1 -1
- package/dist/server/lib/executionTimeline.js +8 -13
- package/dist/server/lib/executionTimeline.js.map +1 -1
- package/dist/server/lib/executionTimelineHierarchy.js +83 -0
- package/dist/server/lib/executionTimelineHierarchy.js.map +1 -0
- package/dist/server/lib/failedBuildSequenceRecovery.js +133 -0
- package/dist/server/lib/failedBuildSequenceRecovery.js.map +1 -0
- package/dist/server/lib/hostRunSession.js +62 -0
- package/dist/server/lib/hostRunSession.js.map +1 -0
- package/dist/server/lib/hostServiceRecovery.js +159 -0
- package/dist/server/lib/hostServiceRecovery.js.map +1 -0
- package/dist/server/lib/hostServiceRunProvider.js +32 -0
- package/dist/server/lib/hostServiceRunProvider.js.map +1 -0
- package/dist/server/lib/mobileAccess.js +6 -2
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/projectAutoConfig.js +5 -2
- package/dist/server/lib/projectAutoConfig.js.map +1 -1
- package/dist/server/lib/projectTargets.js +8 -2
- package/dist/server/lib/projectTargets.js.map +1 -1
- package/dist/server/lib/projects.js +25 -1
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/reviewFindingReconciliation.js +9 -2
- package/dist/server/lib/reviewFindingReconciliation.js.map +1 -1
- package/dist/server/lib/reviewFindings.js +82 -21
- package/dist/server/lib/reviewFindings.js.map +1 -1
- package/dist/server/lib/reviewRepairAssessmentRecords.js +41 -0
- package/dist/server/lib/reviewRepairAssessmentRecords.js.map +1 -0
- package/dist/server/lib/reviewRepairCompletion.js +78 -18
- package/dist/server/lib/reviewRepairCompletion.js.map +1 -1
- package/dist/server/lib/reviewRepairIterationRecords.js +4 -2
- package/dist/server/lib/reviewRepairIterationRecords.js.map +1 -1
- package/dist/server/lib/reviewRepairIterations.js +8 -2
- package/dist/server/lib/reviewRepairIterations.js.map +1 -1
- package/dist/server/lib/reviewRepairProgress.js +177 -0
- package/dist/server/lib/reviewRepairProgress.js.map +1 -0
- package/dist/server/lib/reviewRepairReconciliation.js +119 -140
- package/dist/server/lib/reviewRepairReconciliation.js.map +1 -1
- package/dist/server/lib/reviewRepairResumeOverride.js +55 -0
- package/dist/server/lib/reviewRepairResumeOverride.js.map +1 -0
- package/dist/server/lib/run.js +523 -504
- package/dist/server/lib/run.js.map +1 -1
- package/dist/server/lib/runProcessIdentity.js +71 -0
- package/dist/server/lib/runProcessIdentity.js.map +1 -0
- package/dist/server/lib/runProcesses.js +261 -0
- package/dist/server/lib/runProcesses.js.map +1 -0
- package/dist/server/lib/runServices.js +221 -0
- package/dist/server/lib/runServices.js.map +1 -0
- package/dist/server/lib/runSessionLifecycle.js +68 -0
- package/dist/server/lib/runSessionLifecycle.js.map +1 -0
- package/dist/server/lib/runStatus.js +7 -0
- package/dist/server/lib/runStatus.js.map +1 -0
- package/dist/server/lib/serviceRuns.js +273 -16
- package/dist/server/lib/serviceRuns.js.map +1 -1
- package/dist/server/lib/ticketAutomation.js +4 -27
- package/dist/server/lib/ticketAutomation.js.map +1 -1
- package/dist/server/lib/ticketRunOperations.js +103 -34
- package/dist/server/lib/ticketRunOperations.js.map +1 -1
- package/dist/server/lib/tickets.js +0 -13
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +44 -69
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/plan.js +0 -7
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +95 -146
- package/dist/server/workers/review.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ActivityView-CKvbDXeA.js +0 -1
- package/dist/client/assets/GitHubActionsDialog-Dzo0vtak.js +0 -1
- package/dist/client/assets/VerificationDialog-BJnOitrJ.js +0 -4
- package/dist/client/assets/channel-5mIy7nm1.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BiQ-X2Sg.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CF-wCYW-.js +0 -1
- package/dist/client/assets/index-CIAO5aE_.css +0 -2
- package/dist/client/assets/index-nCGyalrn.js +0 -276
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-PWeeL0tR.js +0 -1
- package/dist/server/lib/automationDeliveryDecision.js +0 -34
- package/dist/server/lib/automationDeliveryDecision.js.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -70,8 +70,8 @@ import { abandonAutomationPause, completeAutomationProgression, completeAutomati
|
|
|
70
70
|
import { reconcileAutomationStage } from './lib/automationReconciliation.js';
|
|
71
71
|
import { runAutomationReviewGateOrPause } from './lib/automationReviewGate.js';
|
|
72
72
|
import { pausedRunBelongsToProgression, resolveAutomationResumeDecision } from './lib/automationResumeDecision.js';
|
|
73
|
-
import {
|
|
74
|
-
import {
|
|
73
|
+
import { isLegacyReviewRepairMigrationPause, recoverCompletedReviewRepairVerification } from './lib/reviewRepairReconciliation.js';
|
|
74
|
+
import { overrideNoProgressReviewRepairInTransaction } from './lib/reviewRepairResumeOverride.js';
|
|
75
75
|
import { getAutomationStageDetails, } from './lib/automationStageDetails.js';
|
|
76
76
|
import { buildAutomationFixInstruction, clearAutomationPause, countBlockingFindings, depthAtLeast, getBundleAutomationState, isAutomationActive, isAutomationDepth, recordAutomationPause, resetBundleAutomationLoop, resolveAutomationConfig, } from './lib/ticketAutomation.js';
|
|
77
77
|
import { resolveAutomationCheckGateResume, runAutomationCommitChain, runAutomationCommitOperation } from './lib/ticketAutomationCommit.js';
|
|
@@ -80,7 +80,7 @@ import { continueAfterBuildCheckpoint } from './lib/automationBuildContinuation.
|
|
|
80
80
|
import { getDiscoveredSkills, normalizeSkillNames } from './lib/skills.js';
|
|
81
81
|
import { clearAllPreviewProxies } from './lib/previewProxy.js';
|
|
82
82
|
import { createExecutionRun, addExecutionRunLink, addExecutionRunLinks, getExecutionRun, getLifecycleEvent, hasActiveBuildExecutionRunForTicket, getGitHubActionsExecutionDetails, getTestExecutionDetails, GitHubActionsDispatchConflictError, listExecutionRunLinks, listExecutionRuns, listLifecycleEvents, listProjectLifecycleEvents, toCanonicalLifecycleEvent, transitionExecutionRun, withExecutionRunTransaction, } from './lib/executionRuns.js';
|
|
83
|
-
import { appendReviewRepairIterationDecisionInTransaction,
|
|
83
|
+
import { appendReviewRepairIterationDecisionInTransaction, attachReviewRepairActionEnvelopeInTransaction, attachReviewRepairActionInTransaction, claimReviewRepairIterationInTransaction, assertReviewRepairIterationDeletionEligible, deleteTerminalReviewRepairIterationsForTicketInTransaction, getActiveReviewRepairIteration, getReviewRepairIteration, pauseReviewRepairIteration, rewindStaleReviewRepairVerification, replaceReviewRepairActionEnvelopeRetryInTransaction, replaceReviewRepairActionRetryInTransaction, replaceReviewRepairVerificationRetryInTransaction, resetReviewRepairGateForRetry, resumePausedReviewRepairIteration, terminalizeReviewRepairIteration, terminalizeReviewRepairIterationInTransaction, } from './lib/reviewRepairIterations.js';
|
|
84
84
|
import { dispatchExecutionRetry, getExecutionRetryEligibility, resolveExecutionRetry } from './lib/executionRetry.js';
|
|
85
85
|
import { listExecutionTimeline } from './lib/executionTimeline.js';
|
|
86
86
|
import { loadReviewAutomationLoopDetails } from './lib/automationLoopProjection.js';
|
|
@@ -105,7 +105,9 @@ import { refreshIfNeeded as refreshAssignedIssues, clearCachedAssignedIssues } f
|
|
|
105
105
|
import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage } from './lib/commitMessage.js';
|
|
106
106
|
import { BUNDLE_PULL_REQUEST_SYNC_TTL_MS, getBundlePullRequestRecord, hasOpenBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequest, upsertBundlePullRequestFromGitHub, } from './lib/bundlePullRequests.js';
|
|
107
107
|
import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectRequiredCheckPolicyReferences, validateProjectPayload, } from './lib/projects.js';
|
|
108
|
-
import { getActiveRunStatus, getActiveRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
|
|
108
|
+
import { getActiveRunStatus, getActiveRunStatusForProject, getLastRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
|
|
109
|
+
import { hasActiveProjectServiceRun } from './lib/serviceRuns.js';
|
|
110
|
+
import { projectRunStatus } from './lib/runStatus.js';
|
|
109
111
|
import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
|
|
110
112
|
import { closeCheckRunnerAdmissions, finalizeProductionCheckApproval, getCheckBatchAttempts, openCheckRunnerAdmissions, rerunCheckBatchAndSchedule, resumeProductionCheckApproval, shutdownCheckRunner, startCheckBatch, stopCheckBatch } from './lib/checkRunner.js';
|
|
111
113
|
import { CheckRepairError, getCheckRepairCandidateState, getCheckRepairDetails, getLatestCheckRepairForBatch, retryCheckRepair, startCheckRepair, stopCheckRepair } from './lib/checkRepair.js';
|
|
@@ -421,7 +423,7 @@ const getCheckBatchRerunEligibility = (project, batch, selection) => {
|
|
|
421
423
|
workspaceAvailable: !('error' in workspace),
|
|
422
424
|
});
|
|
423
425
|
};
|
|
424
|
-
const buildExecutionRunDetailResponse = async (project, run) => {
|
|
426
|
+
const buildExecutionRunDetailResponse = async (project, run, scope) => {
|
|
425
427
|
const checkBatch = run.kind === 'check_batch' ? getCheckBatchDetails(project.id, run.id) : null;
|
|
426
428
|
const retryEligibility = checkBatch
|
|
427
429
|
? getCheckBatchRerunEligibility(project, checkBatch)
|
|
@@ -463,7 +465,7 @@ const buildExecutionRunDetailResponse = async (project, run) => {
|
|
|
463
465
|
? {
|
|
464
466
|
kind: 'check_auto_fix',
|
|
465
467
|
available: true,
|
|
466
|
-
check_auto_fix: getCheckAutoFixDetails(project.id, run.id),
|
|
468
|
+
check_auto_fix: getCheckAutoFixDetails(project.id, run.id, scope),
|
|
467
469
|
retry_eligibility: retryEligibility,
|
|
468
470
|
action_kind: null,
|
|
469
471
|
}
|
|
@@ -490,7 +492,7 @@ const buildExecutionRunDetailResponse = async (project, run) => {
|
|
|
490
492
|
...artifacts,
|
|
491
493
|
environment_context: getExecutionEnvironmentContextForRun(project.id, run.id),
|
|
492
494
|
automation_loops: run.kind === 'automation_progression'
|
|
493
|
-
? loadReviewAutomationLoopDetails(project.id, run.id)
|
|
495
|
+
? loadReviewAutomationLoopDetails(project.id, run.id, scope)
|
|
494
496
|
: [],
|
|
495
497
|
};
|
|
496
498
|
};
|
|
@@ -576,7 +578,26 @@ const cleanupWorktreeExecutionTarget = async ({ project, targetKind, targetKey,
|
|
|
576
578
|
}
|
|
577
579
|
return { branchWarning: buildBranchCleanupWarning(branch, deleteLocalBranch(branch, project)) };
|
|
578
580
|
};
|
|
581
|
+
const assertTicketServiceTargetMutable = (ticketId) => {
|
|
582
|
+
db.transaction(() => {
|
|
583
|
+
const authority = db.prepare(`SELECT 1 FROM active_project_service_authorities active
|
|
584
|
+
JOIN execution_runs run ON run.id = active.execution_run_id
|
|
585
|
+
WHERE run.source_kind = 'ticket' AND run.source_id = ? LIMIT 1`).get(ticketId);
|
|
586
|
+
if (authority) {
|
|
587
|
+
throw new HttpError(409, 'Ticket has active service authority. Stop the service session and verify cleanup before changing or deleting its target.');
|
|
588
|
+
}
|
|
589
|
+
}).immediate();
|
|
590
|
+
};
|
|
579
591
|
const deleteTicketRecord = async (ticket) => {
|
|
592
|
+
try {
|
|
593
|
+
assertTicketServiceTargetMutable(ticket.id);
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
if (error instanceof HttpError) {
|
|
597
|
+
return { ok: false, status: error.status, error: error.message };
|
|
598
|
+
}
|
|
599
|
+
throw error;
|
|
600
|
+
}
|
|
580
601
|
if (ticket.project_id) {
|
|
581
602
|
try {
|
|
582
603
|
assertReviewRepairIterationDeletionEligible(ticket.project_id, ticket.id);
|
|
@@ -667,6 +688,7 @@ const deleteTicketRecord = async (ticket) => {
|
|
|
667
688
|
}
|
|
668
689
|
try {
|
|
669
690
|
withExecutionRunTransaction(transaction => {
|
|
691
|
+
assertTicketServiceTargetMutable(ticket.id);
|
|
670
692
|
if (ticket.project_id) {
|
|
671
693
|
deleteTerminalReviewRepairIterationsForTicketInTransaction(transaction, ticket.project_id, ticket.id);
|
|
672
694
|
}
|
|
@@ -2799,11 +2821,9 @@ app.get('/run-config', (req, res) => {
|
|
|
2799
2821
|
});
|
|
2800
2822
|
app.get('/run-status', (req, res) => {
|
|
2801
2823
|
const requestedProjectId = getRequestedProjectId(req);
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
: getActiveRunStatus(),
|
|
2806
|
-
});
|
|
2824
|
+
const activeRun = requestedProjectId ? getActiveRunStatusForProject(requestedProjectId) : getActiveRunStatus();
|
|
2825
|
+
const durableAuthority = hasActiveProjectServiceRun(requestedProjectId);
|
|
2826
|
+
return res.json(projectRunStatus(activeRun, requestedProjectId ? getLastRunStatusForProject(requestedProjectId) : null, durableAuthority));
|
|
2807
2827
|
});
|
|
2808
2828
|
app.get('/preview-status', (req, res) => {
|
|
2809
2829
|
const projectId = getRequestedProjectId(req);
|
|
@@ -4592,7 +4612,25 @@ app.post('/bundles/:id/undo-review', (req, res) => {
|
|
|
4592
4612
|
}
|
|
4593
4613
|
return res.json(undoResult.details);
|
|
4594
4614
|
});
|
|
4595
|
-
const
|
|
4615
|
+
const parseServiceRunControl = (body) => {
|
|
4616
|
+
if (body === undefined) {
|
|
4617
|
+
return {};
|
|
4618
|
+
}
|
|
4619
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
4620
|
+
return { error: 'Run control body must be an object' };
|
|
4621
|
+
}
|
|
4622
|
+
const input = body;
|
|
4623
|
+
for (const field of ['service_id', 'session_id']) {
|
|
4624
|
+
if (input[field] !== undefined && (typeof input[field] !== 'string' || !input[field].trim())) {
|
|
4625
|
+
return { error: `${field} must be a non-empty string` };
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
return {
|
|
4629
|
+
serviceId: typeof input.service_id === 'string' ? input.service_id.trim() : undefined,
|
|
4630
|
+
sessionId: typeof input.session_id === 'string' ? input.session_id.trim() : undefined,
|
|
4631
|
+
};
|
|
4632
|
+
};
|
|
4633
|
+
const parseServiceExecutionProviderChoice = (body, allowServiceControl = false) => {
|
|
4596
4634
|
if (body === undefined || body === null) {
|
|
4597
4635
|
return 'project_default';
|
|
4598
4636
|
}
|
|
@@ -4600,7 +4638,8 @@ const parseServiceExecutionProviderChoice = (body) => {
|
|
|
4600
4638
|
return null;
|
|
4601
4639
|
}
|
|
4602
4640
|
const request = body;
|
|
4603
|
-
if (Object.keys(request).some(key => key !== 'execution_provider'
|
|
4641
|
+
if (Object.keys(request).some(key => key !== 'execution_provider'
|
|
4642
|
+
&& !(allowServiceControl && (key === 'service_id' || key === 'session_id')))) {
|
|
4604
4643
|
return null;
|
|
4605
4644
|
}
|
|
4606
4645
|
if (request.execution_provider === undefined || request.execution_provider === 'project_default') {
|
|
@@ -4659,8 +4698,17 @@ app.post('/bundles/:id/run', (req, res) => {
|
|
|
4659
4698
|
return res.status(409).json(activeRunConflict);
|
|
4660
4699
|
}
|
|
4661
4700
|
const profileSelection = createServiceExecutionProfileSelection(project.id, executionProvider);
|
|
4701
|
+
try {
|
|
4702
|
+
void runTicketInWorkspace(targetTicket, io, project, workspace.cwd, profileSelection)
|
|
4703
|
+
.catch(error => console.error('[run] admitted bundle service startup failed:', error));
|
|
4704
|
+
}
|
|
4705
|
+
catch (error) {
|
|
4706
|
+
return res.status(409).json({
|
|
4707
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4708
|
+
code: error instanceof Error && 'code' in error ? error.code : 'service_start_rejected',
|
|
4709
|
+
});
|
|
4710
|
+
}
|
|
4662
4711
|
emitRunStateForContext(targetTicket, true);
|
|
4663
|
-
runTicketInWorkspace(targetTicket, io, project, workspace.cwd, profileSelection);
|
|
4664
4712
|
return res.json(serializeBundleDetails(result.project, bundle));
|
|
4665
4713
|
});
|
|
4666
4714
|
app.post('/bundles/:id/stop', async (req, res) => {
|
|
@@ -4700,52 +4748,6 @@ app.post('/bundles/:id/follow-up', (req, res) => {
|
|
|
4700
4748
|
});
|
|
4701
4749
|
const AUTOMATION_SIDE_EFFECT_KINDS = new Set(['automation_commit', 'automation_push', 'automation_pull_request']);
|
|
4702
4750
|
const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix', 'check_auto_fix', 'check_repair', 'check_batch']);
|
|
4703
|
-
app.post('/bundles/:id/continue-delivery', async (req, res) => {
|
|
4704
|
-
const result = getRequiredProject(req);
|
|
4705
|
-
if ('error' in result) {
|
|
4706
|
-
return res.status(400).json({ error: result.error });
|
|
4707
|
-
}
|
|
4708
|
-
const bundle = getBundle(req.params.id, result.project.id);
|
|
4709
|
-
if (!bundle) {
|
|
4710
|
-
return res.status(404).json({ error: 'Bundle not found' });
|
|
4711
|
-
}
|
|
4712
|
-
const state = getBundleAutomationState(result.project.id, bundle.id);
|
|
4713
|
-
const tickets = listDetailedBundleTickets(result.project.id, bundle.id);
|
|
4714
|
-
if (!state) {
|
|
4715
|
-
return res.status(409).json({ error: 'Bundle delivery is not ready to continue' });
|
|
4716
|
-
}
|
|
4717
|
-
const automationConfig = resolveAutomationConfig(bundle, result.project);
|
|
4718
|
-
const decision = resolveAutomationDeliveryDecision({
|
|
4719
|
-
projectId: result.project.id,
|
|
4720
|
-
bundleId: bundle.id,
|
|
4721
|
-
state,
|
|
4722
|
-
config: automationConfig,
|
|
4723
|
-
tickets,
|
|
4724
|
-
});
|
|
4725
|
-
if (!decision) {
|
|
4726
|
-
return res.status(409).json({ error: 'Bundle delivery is not ready to continue' });
|
|
4727
|
-
}
|
|
4728
|
-
const owner = tickets.find(ticket => ticket.id === decision.owner_ticket_id);
|
|
4729
|
-
const reviewStageRunId = state.automation_delivery_ready_stage_run_id;
|
|
4730
|
-
if (!owner || !reviewStageRunId) {
|
|
4731
|
-
return res.status(409).json({ error: 'Bundle delivery owner is no longer available' });
|
|
4732
|
-
}
|
|
4733
|
-
try {
|
|
4734
|
-
const progression = ensureAutomationProgression({
|
|
4735
|
-
projectId: result.project.id,
|
|
4736
|
-
bundleId: bundle.id,
|
|
4737
|
-
ticketId: owner.id,
|
|
4738
|
-
ticketTitle: owner.title,
|
|
4739
|
-
parentRunId: reviewStageRunId,
|
|
4740
|
-
broadcaster: io,
|
|
4741
|
-
});
|
|
4742
|
-
await runAutomationCommitChain(owner, result.project, owner.bundle ?? bundle, automationConfig, io, progression.runId, undefined, reviewStageRunId);
|
|
4743
|
-
}
|
|
4744
|
-
catch (error) {
|
|
4745
|
-
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
4746
|
-
}
|
|
4747
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
4748
|
-
});
|
|
4749
4751
|
app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
4750
4752
|
const result = getRequiredProject(req);
|
|
4751
4753
|
if ('error' in result) {
|
|
@@ -4785,7 +4787,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4785
4787
|
: 'Bundle automation cannot be resumed in its current state';
|
|
4786
4788
|
return res.status(409).json({ error, automation_resume_decision: initialDecision });
|
|
4787
4789
|
}
|
|
4788
|
-
|
|
4790
|
+
const decision = initialDecision;
|
|
4789
4791
|
const revalidateDecision = () => {
|
|
4790
4792
|
const current = resolveCurrentDecision();
|
|
4791
4793
|
return current?.status === 'enabled'
|
|
@@ -5001,9 +5003,21 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5001
5003
|
}
|
|
5002
5004
|
const automationConfig = resolveAutomationConfig(owner.bundle ?? null, project);
|
|
5003
5005
|
const legacyReviewRepairPause = isLegacyReviewRepairMigrationPause(result.project.id, bundle.id, progressionRunId);
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5006
|
+
const completedVerificationIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
5007
|
+
if (completedVerificationIteration
|
|
5008
|
+
&& ['paused', 'verification_active'].includes(completedVerificationIteration.phase)
|
|
5009
|
+
&& completedVerificationIteration.stop_reason !== 'no_progress'
|
|
5010
|
+
&& completedVerificationIteration.resulting_review_execution_run_id
|
|
5011
|
+
&& getExecutionRun(result.project.id, completedVerificationIteration.resulting_review_execution_run_id)?.status === 'completed') {
|
|
5012
|
+
try {
|
|
5013
|
+
await recoverCompletedReviewRepairVerification(result.project.id, completedVerificationIteration.id, io, { resume: true });
|
|
5014
|
+
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5015
|
+
}
|
|
5016
|
+
catch (error) {
|
|
5017
|
+
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
if (completedVerificationIteration?.phase === 'paused' && completedVerificationIteration.stop_reason !== 'no_progress') {
|
|
5007
5021
|
withExecutionRunTransaction(transaction => {
|
|
5008
5022
|
appendReviewRepairIterationDecisionInTransaction(transaction, {
|
|
5009
5023
|
projectId: result.project.id,
|
|
@@ -5014,165 +5028,6 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5014
5028
|
});
|
|
5015
5029
|
}, io, 'immediate');
|
|
5016
5030
|
}
|
|
5017
|
-
if (completedVerificationIteration?.phase === 'paused') {
|
|
5018
|
-
const verificationRunId = completedVerificationIteration.resulting_review_execution_run_id;
|
|
5019
|
-
const verificationRun = verificationRunId ? getExecutionRun(result.project.id, verificationRunId) : null;
|
|
5020
|
-
if (verificationRun?.status === 'completed') {
|
|
5021
|
-
try {
|
|
5022
|
-
completedVerificationIteration = resumePausedReviewRepairIteration({
|
|
5023
|
-
projectId: result.project.id,
|
|
5024
|
-
iterationId: completedVerificationIteration.id,
|
|
5025
|
-
expectedCurrentRunId: verificationRunId,
|
|
5026
|
-
broadcaster: io,
|
|
5027
|
-
});
|
|
5028
|
-
}
|
|
5029
|
-
catch (error) {
|
|
5030
|
-
const current = getReviewRepairIteration(result.project.id, completedVerificationIteration.id);
|
|
5031
|
-
if (current?.resulting_review_execution_run_id === verificationRunId
|
|
5032
|
-
&& ['verification_active', 'terminal'].includes(current.phase)) {
|
|
5033
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5034
|
-
}
|
|
5035
|
-
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
5036
|
-
}
|
|
5037
|
-
}
|
|
5038
|
-
}
|
|
5039
|
-
if (completedVerificationIteration?.phase === 'verification_active'
|
|
5040
|
-
&& completedVerificationIteration.resulting_review_execution_run_id) {
|
|
5041
|
-
const verificationRunId = completedVerificationIteration.resulting_review_execution_run_id;
|
|
5042
|
-
const verificationRun = getExecutionRun(result.project.id, verificationRunId);
|
|
5043
|
-
const resultingReview = verificationRun?.status === 'completed'
|
|
5044
|
-
? db.prepare(`
|
|
5045
|
-
SELECT review.id
|
|
5046
|
-
FROM review_runs review
|
|
5047
|
-
JOIN execution_run_links result_link
|
|
5048
|
-
ON result_link.execution_run_id = ?
|
|
5049
|
-
AND result_link.project_id = review.project_id
|
|
5050
|
-
AND result_link.entity_type = 'review_run'
|
|
5051
|
-
AND result_link.entity_id = review.id
|
|
5052
|
-
AND result_link.relationship = 'result'
|
|
5053
|
-
WHERE review.project_id = ? AND review.ticket_id = ?
|
|
5054
|
-
AND review.worktree_bundle_id = ? AND review.status = 'completed'
|
|
5055
|
-
LIMIT 1
|
|
5056
|
-
`).get(verificationRunId, result.project.id, owner.id, bundle.id)
|
|
5057
|
-
: undefined;
|
|
5058
|
-
if (resultingReview) {
|
|
5059
|
-
reconciledSourceReviewRunId = resultingReview.id;
|
|
5060
|
-
const blockingCount = countBlockingFindings(listReviewFindings(resultingReview.id), automationConfig);
|
|
5061
|
-
const terminal = !isAutomationActive(automationConfig)
|
|
5062
|
-
? { outcome: 'interrupted', stopReason: 'configuration_changed' }
|
|
5063
|
-
: blockingCount === 0
|
|
5064
|
-
? { outcome: 'repaired', stopReason: 'no_blocking_findings' }
|
|
5065
|
-
: automationConfig.reviewFixEnabled
|
|
5066
|
-
? completedVerificationIteration.ordinal < automationConfig.reviewMaxIterations
|
|
5067
|
-
? { outcome: 'continuing', stopReason: null }
|
|
5068
|
-
: { outcome: 'exhausted', stopReason: 'max_iterations' }
|
|
5069
|
-
: { outcome: 'interrupted', stopReason: 'automation_disabled' };
|
|
5070
|
-
try {
|
|
5071
|
-
withExecutionRunTransaction(transaction => {
|
|
5072
|
-
if (!completedVerificationIteration.resulting_review_run_id) {
|
|
5073
|
-
attachReviewRepairResultInTransaction(transaction, {
|
|
5074
|
-
projectId: result.project.id,
|
|
5075
|
-
iterationId: completedVerificationIteration.id,
|
|
5076
|
-
expectedPhase: 'verification_active',
|
|
5077
|
-
expectedVerificationRunId: verificationRunId,
|
|
5078
|
-
resultingReviewRunId: resultingReview.id,
|
|
5079
|
-
});
|
|
5080
|
-
}
|
|
5081
|
-
appendReviewRepairIterationDecisionInTransaction(transaction, {
|
|
5082
|
-
projectId: result.project.id,
|
|
5083
|
-
iterationId: completedVerificationIteration.id,
|
|
5084
|
-
decisionPoint: 'verification_completion',
|
|
5085
|
-
effectiveAutomationConfig: automationConfig,
|
|
5086
|
-
decisionCode: 'verification_reconciled_on_resume',
|
|
5087
|
-
operationRunId: verificationRunId,
|
|
5088
|
-
});
|
|
5089
|
-
terminalizeReviewRepairIterationInTransaction(transaction, {
|
|
5090
|
-
projectId: result.project.id,
|
|
5091
|
-
iterationId: completedVerificationIteration.id,
|
|
5092
|
-
expectedPhase: 'verification_active',
|
|
5093
|
-
expectedCurrentRunId: verificationRunId,
|
|
5094
|
-
outcome: terminal.outcome,
|
|
5095
|
-
stopReason: terminal.stopReason,
|
|
5096
|
-
resultingBlockingCount: blockingCount,
|
|
5097
|
-
});
|
|
5098
|
-
if (automationConfig.depth === 'manual') {
|
|
5099
|
-
completeAutomationProgressionInTransaction(transaction, {
|
|
5100
|
-
projectId: result.project.id,
|
|
5101
|
-
bundleId: bundle.id,
|
|
5102
|
-
progressionRunId,
|
|
5103
|
-
domainStatus: 'manual_boundary_reached',
|
|
5104
|
-
});
|
|
5105
|
-
}
|
|
5106
|
-
if (terminal.outcome === 'continuing') {
|
|
5107
|
-
claimReviewRepairIterationInTransaction(transaction, {
|
|
5108
|
-
projectId: result.project.id,
|
|
5109
|
-
worktreeBundleId: bundle.id,
|
|
5110
|
-
ownerTicketId: owner.id,
|
|
5111
|
-
automationProgressionRunId: progressionRunId,
|
|
5112
|
-
triggeringReviewExecutionRunId: verificationRunId,
|
|
5113
|
-
triggeringReviewRunId: resultingReview.id,
|
|
5114
|
-
triggeringBlockingCount: blockingCount,
|
|
5115
|
-
effectiveAutomationConfig: automationConfig,
|
|
5116
|
-
decisionCode: 'resume_verification_repair_admitted',
|
|
5117
|
-
});
|
|
5118
|
-
}
|
|
5119
|
-
}, io, 'immediate');
|
|
5120
|
-
}
|
|
5121
|
-
catch (error) {
|
|
5122
|
-
const current = getReviewRepairIteration(result.project.id, completedVerificationIteration.id);
|
|
5123
|
-
if (current?.phase === 'terminal'
|
|
5124
|
-
&& current.resulting_review_execution_run_id === verificationRunId
|
|
5125
|
-
&& current.resulting_review_run_id === resultingReview.id) {
|
|
5126
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5127
|
-
}
|
|
5128
|
-
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
5129
|
-
}
|
|
5130
|
-
if (terminal.outcome === 'continuing') {
|
|
5131
|
-
const continuationDecision = resolveCurrentDecision();
|
|
5132
|
-
if (!continuationDecision || continuationDecision.status !== 'enabled') {
|
|
5133
|
-
return res.status(409).json({ error: 'Bundle automation state changed before repair continuation could start' });
|
|
5134
|
-
}
|
|
5135
|
-
decision = continuationDecision;
|
|
5136
|
-
}
|
|
5137
|
-
else if (terminal.outcome === 'repaired') {
|
|
5138
|
-
resetBundleAutomationLoop(result.project.id, bundle.id);
|
|
5139
|
-
runAutomationProgression({
|
|
5140
|
-
projectId: result.project.id,
|
|
5141
|
-
bundleId: bundle.id,
|
|
5142
|
-
progressionRunId,
|
|
5143
|
-
broadcaster: io,
|
|
5144
|
-
});
|
|
5145
|
-
if (depthAtLeast(automationConfig.depth, 'auto_commit')) {
|
|
5146
|
-
await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId, undefined, verificationRunId);
|
|
5147
|
-
}
|
|
5148
|
-
else {
|
|
5149
|
-
completeAutomationProgression({
|
|
5150
|
-
projectId: result.project.id,
|
|
5151
|
-
bundleId: bundle.id,
|
|
5152
|
-
progressionRunId,
|
|
5153
|
-
broadcaster: io,
|
|
5154
|
-
});
|
|
5155
|
-
}
|
|
5156
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5157
|
-
}
|
|
5158
|
-
else if (automationConfig.depth !== 'manual') {
|
|
5159
|
-
waitAutomationProgression({
|
|
5160
|
-
projectId: result.project.id,
|
|
5161
|
-
bundleId: bundle.id,
|
|
5162
|
-
progressionRunId,
|
|
5163
|
-
pauseCode: 'review',
|
|
5164
|
-
pauseReason: 'Blocking Review findings remain after repair verification.',
|
|
5165
|
-
reasonCode: 'precondition_failed',
|
|
5166
|
-
pausedStageRunId: verificationRunId,
|
|
5167
|
-
broadcaster: io,
|
|
5168
|
-
});
|
|
5169
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5170
|
-
}
|
|
5171
|
-
else {
|
|
5172
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5173
|
-
}
|
|
5174
|
-
}
|
|
5175
|
-
}
|
|
5176
5031
|
const restoreWaitingProgression = () => {
|
|
5177
5032
|
waitAutomationProgression({
|
|
5178
5033
|
projectId: result.project.id,
|
|
@@ -5365,7 +5220,16 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5365
5220
|
}
|
|
5366
5221
|
const workflowContext = validateWorkflowContext(progression.metadata);
|
|
5367
5222
|
let existingIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
5368
|
-
const
|
|
5223
|
+
const overridingNoProgress = existingIteration?.phase === 'paused' && existingIteration.stop_reason === 'no_progress';
|
|
5224
|
+
if (overridingNoProgress) {
|
|
5225
|
+
existingIteration = withExecutionRunTransaction(transaction => {
|
|
5226
|
+
if (!revalidateDecision()) {
|
|
5227
|
+
throw new Error('Bundle automation state changed before Resume override could start');
|
|
5228
|
+
}
|
|
5229
|
+
return overrideNoProgressReviewRepairInTransaction(transaction, result.project.id, existingIteration.id, io);
|
|
5230
|
+
}, io, 'immediate');
|
|
5231
|
+
}
|
|
5232
|
+
const sourceReviewRunId = existingIteration?.triggering_review_run_id ?? null;
|
|
5369
5233
|
if (!sourceReviewRunId) {
|
|
5370
5234
|
throw new Error('The completed review findings to resume are unavailable');
|
|
5371
5235
|
}
|
|
@@ -5374,12 +5238,14 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5374
5238
|
review_finding_run_id: sourceReviewRunId,
|
|
5375
5239
|
review_severity: automationConfig.reviewSeverity,
|
|
5376
5240
|
});
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5241
|
+
if (!overridingNoProgress) {
|
|
5242
|
+
runAutomationProgression({
|
|
5243
|
+
projectId: result.project.id,
|
|
5244
|
+
bundleId: bundle.id,
|
|
5245
|
+
progressionRunId,
|
|
5246
|
+
broadcaster: io,
|
|
5247
|
+
});
|
|
5248
|
+
}
|
|
5383
5249
|
if (existingIteration?.phase === 'paused') {
|
|
5384
5250
|
existingIteration = resumePausedReviewRepairIteration({
|
|
5385
5251
|
projectId: result.project.id,
|
|
@@ -5650,9 +5516,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5650
5516
|
});
|
|
5651
5517
|
}, io, 'immediate');
|
|
5652
5518
|
}
|
|
5653
|
-
|
|
5654
|
-
resetBundleAutomationLoop(result.project.id, bundle.id);
|
|
5655
|
-
}
|
|
5519
|
+
resetBundleAutomationLoop(result.project.id, bundle.id);
|
|
5656
5520
|
}
|
|
5657
5521
|
runAutomationProgression({
|
|
5658
5522
|
projectId: result.project.id,
|
|
@@ -8201,14 +8065,6 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8201
8065
|
if ('error' in roleModelValidation) {
|
|
8202
8066
|
return res.status(400).json({ error: roleModelValidation.error });
|
|
8203
8067
|
}
|
|
8204
|
-
if (hasOwn(validation.values, 'repo_path') && validation.values.repo_path !== project.repo_path) {
|
|
8205
|
-
const stats = getProjectTicketStats(project.id);
|
|
8206
|
-
if (stats.ticketCount > 0 || stats.worktreeCount > 0 || stats.bundleCount > 0) {
|
|
8207
|
-
return res.status(409).json({
|
|
8208
|
-
error: 'repo_path cannot be changed after tickets, bundles, or worktrees exist for this project',
|
|
8209
|
-
});
|
|
8210
|
-
}
|
|
8211
|
-
}
|
|
8212
8068
|
const { name, repo_path, linked_project_ids, run_setup, run_services, check_definitions, required_check_definition_ids, pre_review_check_definition_ids, post_build_check_definition_ids, auto_fix_check_definition_ids, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, helper_models_json, reviewer_model, reviewer_variant, reviewer_models_json, 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;
|
|
8213
8069
|
const updateProject = db.prepare(`
|
|
8214
8070
|
UPDATE projects SET
|
|
@@ -8266,7 +8122,7 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8266
8122
|
const nextReviewerVariant = nextReviewerModel
|
|
8267
8123
|
? (hasOwn(validation.values, 'reviewer_variant') ? reviewer_variant ?? null : project.reviewer_variant)
|
|
8268
8124
|
: null;
|
|
8269
|
-
const
|
|
8125
|
+
const applyUpdate = db.transaction(() => {
|
|
8270
8126
|
const currentProject = getProjectById(project.id);
|
|
8271
8127
|
if (!currentProject || JSON.stringify(currentProject) !== JSON.stringify(project)) {
|
|
8272
8128
|
return {
|
|
@@ -8274,6 +8130,15 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8274
8130
|
status: 409,
|
|
8275
8131
|
};
|
|
8276
8132
|
}
|
|
8133
|
+
if (hasOwn(validation.values, 'repo_path') && repo_path !== currentProject.repo_path) {
|
|
8134
|
+
if (currentProject.deletion_started_at || hasActiveProjectServiceRun(project.id) || getActiveRunStatusForProject(project.id)) {
|
|
8135
|
+
return { error: 'Cannot change project repository while service authority or deletion is active', status: 409 };
|
|
8136
|
+
}
|
|
8137
|
+
const stats = getProjectTicketStats(project.id);
|
|
8138
|
+
if (stats.ticketCount > 0 || stats.worktreeCount > 0 || stats.bundleCount > 0) {
|
|
8139
|
+
return { error: 'repo_path cannot be changed after tickets, bundles, or worktrees exist for this project', status: 409 };
|
|
8140
|
+
}
|
|
8141
|
+
}
|
|
8277
8142
|
const policyReferences = validateProjectRequiredCheckPolicyReferences({
|
|
8278
8143
|
projectId: project.id,
|
|
8279
8144
|
checkDefinitionsJson: nextCheckDefinitions,
|
|
@@ -8305,7 +8170,17 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8305
8170
|
replaceProjectLinks(project.id, linked_project_ids ?? []);
|
|
8306
8171
|
}
|
|
8307
8172
|
return { ok: true };
|
|
8308
|
-
})
|
|
8173
|
+
});
|
|
8174
|
+
let updateResult;
|
|
8175
|
+
try {
|
|
8176
|
+
updateResult = applyUpdate.immediate();
|
|
8177
|
+
}
|
|
8178
|
+
catch (error) {
|
|
8179
|
+
if (error instanceof Error && 'code' in error && error.code === 'SQLITE_CONSTRAINT_TRIGGER') {
|
|
8180
|
+
return res.status(409).json({ error: error.message });
|
|
8181
|
+
}
|
|
8182
|
+
throw error;
|
|
8183
|
+
}
|
|
8309
8184
|
if ('error' in updateResult) {
|
|
8310
8185
|
const status = 'status' in updateResult && typeof updateResult.status === 'number'
|
|
8311
8186
|
? updateResult.status
|
|
@@ -8316,6 +8191,9 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8316
8191
|
res.json(serializeProject(updated ?? null));
|
|
8317
8192
|
});
|
|
8318
8193
|
const assertProjectDeletionEligible = (projectId, claimId) => {
|
|
8194
|
+
if (hasActiveProjectServiceRun(projectId) || getActiveRunStatusForProject(projectId)) {
|
|
8195
|
+
throw new ProjectDeletionError(409, 'Cannot delete a project while services are active');
|
|
8196
|
+
}
|
|
8319
8197
|
const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
|
|
8320
8198
|
if (!project) {
|
|
8321
8199
|
throw new ProjectDeletionError(404, 'Not found');
|
|
@@ -8363,6 +8241,7 @@ const assertProjectDeletionEligible = (projectId, claimId) => {
|
|
|
8363
8241
|
}
|
|
8364
8242
|
};
|
|
8365
8243
|
const completeClaimedProjectDeletion = async (projectId, claimId) => {
|
|
8244
|
+
assertProjectDeletionEligible(projectId, claimId);
|
|
8366
8245
|
const marked = db.prepare(`
|
|
8367
8246
|
UPDATE projects
|
|
8368
8247
|
SET deletion_cleanup_started_at = COALESCE(deletion_cleanup_started_at, CURRENT_TIMESTAMP)
|
|
@@ -8599,6 +8478,17 @@ app.patch('/tickets/:id', async (req, res) => {
|
|
|
8599
8478
|
const nextProjectId = hasProjectField ? project_id : ticket.project_id;
|
|
8600
8479
|
const isProjectChanging = nextProjectId !== ticket.project_id;
|
|
8601
8480
|
const hasStandaloneWorktree = typeof ticket.branch === 'string' && ticket.branch.trim() !== '';
|
|
8481
|
+
if (isProjectChanging || (hasBundleField && req.body.worktree_bundle_id !== ticket.worktree_bundle_id)) {
|
|
8482
|
+
try {
|
|
8483
|
+
assertTicketServiceTargetMutable(ticket.id);
|
|
8484
|
+
}
|
|
8485
|
+
catch (error) {
|
|
8486
|
+
if (error instanceof HttpError) {
|
|
8487
|
+
return res.status(error.status).json({ error: error.message });
|
|
8488
|
+
}
|
|
8489
|
+
throw error;
|
|
8490
|
+
}
|
|
8491
|
+
}
|
|
8602
8492
|
if (isProjectChanging && hasStandaloneWorktree) {
|
|
8603
8493
|
return res.status(409).json({ error: 'project_id cannot be changed after a worktree has been created' });
|
|
8604
8494
|
}
|
|
@@ -8708,10 +8598,23 @@ app.patch('/tickets/:id', async (req, res) => {
|
|
|
8708
8598
|
}
|
|
8709
8599
|
}
|
|
8710
8600
|
}
|
|
8711
|
-
|
|
8712
|
-
|
|
8601
|
+
try {
|
|
8602
|
+
db.transaction(() => {
|
|
8603
|
+
if (bundleChanged) {
|
|
8604
|
+
assertTicketServiceTargetMutable(ticket.id);
|
|
8605
|
+
}
|
|
8606
|
+
if (isProjectChanging) {
|
|
8607
|
+
deleteTicketDependenciesForTicket(ticket.id);
|
|
8608
|
+
}
|
|
8609
|
+
db.prepare('UPDATE tickets SET title = ?, description = ?, model = ?, variant = ?, skills_json = ?, worktree_bundle_id = ?, project_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(title?.trim() ?? ticket.title, description ?? ticket.description, resolvedModel, resolvedVariant, JSON.stringify(resolvedSkills), resolvedBundleId, nextProjectId, req.params.id);
|
|
8610
|
+
}).immediate();
|
|
8611
|
+
}
|
|
8612
|
+
catch (error) {
|
|
8613
|
+
if (error instanceof HttpError) {
|
|
8614
|
+
return res.status(error.status).json({ error: error.message });
|
|
8615
|
+
}
|
|
8616
|
+
throw error;
|
|
8713
8617
|
}
|
|
8714
|
-
db.prepare('UPDATE tickets SET title = ?, description = ?, model = ?, variant = ?, skills_json = ?, worktree_bundle_id = ?, project_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(title?.trim() ?? ticket.title, description ?? ticket.description, resolvedModel, resolvedVariant, JSON.stringify(resolvedSkills), resolvedBundleId, nextProjectId, req.params.id);
|
|
8715
8618
|
upsertTicketDescriptionMessage(req.params.id, description ?? ticket.description);
|
|
8716
8619
|
const updated = getTicket(req.params.id);
|
|
8717
8620
|
if (bundleChanged && previousBundleId && previousProject) {
|
|
@@ -9133,7 +9036,12 @@ app.post('/tickets/:id/undo', (req, res) => {
|
|
|
9133
9036
|
return res.json(result.ticket);
|
|
9134
9037
|
});
|
|
9135
9038
|
app.post('/tickets/:id/run', (req, res) => {
|
|
9039
|
+
const control = parseServiceRunControl(req.body);
|
|
9040
|
+
if ('error' in control) {
|
|
9041
|
+
return res.status(400).json({ error: control.error });
|
|
9042
|
+
}
|
|
9136
9043
|
const result = runTicketRun({
|
|
9044
|
+
...control,
|
|
9137
9045
|
ticketId: req.params.id,
|
|
9138
9046
|
requestedProjectId: getRequestedProjectId(req),
|
|
9139
9047
|
broadcaster: io,
|
|
@@ -9394,11 +9302,16 @@ app.post('/projects/:id/open-ide', (req, res) => {
|
|
|
9394
9302
|
return res.json({ ok: true });
|
|
9395
9303
|
});
|
|
9396
9304
|
app.post('/projects/:id/run', (req, res) => {
|
|
9397
|
-
const
|
|
9305
|
+
const control = parseServiceRunControl(req.body);
|
|
9306
|
+
if ('error' in control) {
|
|
9307
|
+
return res.status(400).json({ error: control.error });
|
|
9308
|
+
}
|
|
9309
|
+
const executionProvider = parseServiceExecutionProviderChoice(req.body, true);
|
|
9398
9310
|
if (!executionProvider) {
|
|
9399
9311
|
return res.status(400).json({ error: 'execution_provider must be project_default or host' });
|
|
9400
9312
|
}
|
|
9401
9313
|
const result = runProjectTarget({
|
|
9314
|
+
...control,
|
|
9402
9315
|
projectId: req.params.id,
|
|
9403
9316
|
executionProvider,
|
|
9404
9317
|
});
|
|
@@ -9883,7 +9796,7 @@ app.get('/projects/:id/execution-timeline/:runId', async (req, res) => {
|
|
|
9883
9796
|
if (scope.scopeEntity && !links.some(link => link.entity_type === scope.scopeEntity?.type && link.entity_id === scope.scopeEntity.id)) {
|
|
9884
9797
|
return res.status(404).json({ error: 'Execution run not found' });
|
|
9885
9798
|
}
|
|
9886
|
-
return res.json(await buildExecutionRunDetailResponse(project, run));
|
|
9799
|
+
return res.json(await buildExecutionRunDetailResponse(project, run, scope.scopeEntity));
|
|
9887
9800
|
});
|
|
9888
9801
|
app.get('/projects/:id/execution-runs/:runId', async (req, res) => {
|
|
9889
9802
|
const project = getProjectById(req.params.id);
|
|
@@ -10131,7 +10044,12 @@ app.post('/terminals/:sessionId/destroy', (req, res) => {
|
|
|
10131
10044
|
return res.status(204).send();
|
|
10132
10045
|
});
|
|
10133
10046
|
app.post('/tickets/:id/stop', async (req, res) => {
|
|
10047
|
+
const control = parseServiceRunControl(req.body);
|
|
10048
|
+
if ('error' in control) {
|
|
10049
|
+
return res.status(400).json({ error: control.error });
|
|
10050
|
+
}
|
|
10134
10051
|
const result = await stopTicketRun({
|
|
10052
|
+
...control,
|
|
10135
10053
|
ticketId: req.params.id,
|
|
10136
10054
|
requestedProjectId: getRequestedProjectId(req),
|
|
10137
10055
|
broadcaster: io,
|
|
@@ -10142,7 +10060,12 @@ app.post('/tickets/:id/stop', async (req, res) => {
|
|
|
10142
10060
|
return res.status(result.status).json(result.value);
|
|
10143
10061
|
});
|
|
10144
10062
|
app.post('/projects/:id/stop-run', async (req, res) => {
|
|
10063
|
+
const control = parseServiceRunControl(req.body);
|
|
10064
|
+
if ('error' in control) {
|
|
10065
|
+
return res.status(400).json({ error: control.error });
|
|
10066
|
+
}
|
|
10145
10067
|
const result = await stopProjectTargetRun({
|
|
10068
|
+
...control,
|
|
10146
10069
|
projectId: req.params.id,
|
|
10147
10070
|
});
|
|
10148
10071
|
if (!result.ok) {
|