@straiffi/archon 1.7.2 → 1.7.3
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-DTEenQas.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-BKWHonUS.js +1 -0
- package/dist/client/assets/TestsDialog-cBaoffCs.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-Do9nbC8v.js → architectureDiagram-3BPJPVTR-BzzGotac.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-DGGOLHF8.js → blockDiagram-GPEHLZMM-DIcqLHKg.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-Bz7tQybs.js → c4Diagram-AAUBKEIU-Hc1Sqmhs.js} +1 -1
- package/dist/client/assets/channel-Cym17waa.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-CJO-GmWJ.js → chunk-2J33WTMH-CWSwjsgk.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DPAVagpC.js → chunk-3OPIFGDE-BnX3DljN.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-Cof75Mu7.js → chunk-5ZQYHXKU-EyyJlI-d.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-COuPqQ8_.js → chunk-727SXJPM-Ci6CfxWj.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-CFj5KabW.js → chunk-AQP2D5EJ-RKZNsJTm.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-C4M6w5cT.js → chunk-CSCIHK7Q-DBf0Euoj.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-ByaiwVue.js → chunk-KSCS5N6A-BmLQI_2S.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-Cx5jeot9.js → chunk-L5ZTLDWV-DKwVAb8G.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-DmGtztud.js → chunk-LZXEDZCA-axotKkbz.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-i3yuspqL.js → chunk-ND2GUHAM-BBlJ4nXC.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-CQ4E_jP4.js → chunk-NZK2D7GU-BKJGoZZt.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-D2g4VDCd.js → chunk-O5CBEL6O-C-Ghigo1.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-exIu-zQ2.js → chunk-WU5MYG2G-CCzVWYcd.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BcigvvIC.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-3AdSuE0N.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-DvF75yLZ.js → dagre-BM42HDAG-rwIdPcjI.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-DFv0HxLL.js → diagram-2AECGRRQ-DJWxOQsg.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-C_LRir0-.js → diagram-5GNKFQAL-C-mQeFtd.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DGZxRDRy.js → diagram-KO2AKTUF-BAu6u4sR.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-UXt0uti4.js → diagram-LMA3HP47-U6CbvBn0.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-CFbZS4n5.js → diagram-OG6HWLK6-DNTwFcs1.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-CfAwZDdf.js → erDiagram-TEJ5UH35-DaHX6Xw5.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-Cdgi5z3n.js → flowDiagram-I6XJVG4X-B9hKqL2l.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DOrkFkhu.js → ganttDiagram-6RSMTGT7-BQ1HPHVO.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-cEox0peD.js → gitGraphDiagram-PVQCEYII-BBRcsmMK.js} +1 -1
- package/dist/client/assets/index-CEMaJCPS.js +273 -0
- package/dist/client/assets/index-lWftq65U.css +2 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-gm51u2Nn.js → infoDiagram-5YYISTIA-DUoNiSGC.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CjnoOIja.js → ishikawaDiagram-YF4QCWOH-DJN1y2w8.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-Dlg4DsiO.js → journeyDiagram-JHISSGLW-BxLLMT70.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BU0XZaSd.js → kanban-definition-UN3LZRKU-BdCPistJ.js} +1 -1
- package/dist/client/assets/{line-Bert22-_.js → line-4mIgXsZd.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-BvQSGDmL.js → mermaid-parser.core-Pu1QGWJ-.js} +1 -1
- package/dist/client/assets/{mermaid.core--qSAHJRj.js → mermaid.core-DnH9DrgZ.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-BgFIvWWD.js → mindmap-definition-RKZ34NQL-DqE4ba3C.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DGcbXD0M.js → pieDiagram-4H26LBE5-Crjy9ejf.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-ClkBUeia.js → quadrantDiagram-W4KKPZXB-BUSZ2VB7.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-B8CwGToU.js → requirementDiagram-4Y6WPE33-CBhFKEoV.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DTk3jL6B.js → sankeyDiagram-5OEKKPKP-CgdbY41v.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BQVrcnKf.js → sequenceDiagram-3UESZ5HK-hkp_H7AE.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-DD4yMZPB.js → stateDiagram-AJRCARHV-C6x45RWp.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DVzPjRPN.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-BnCBVqcd.js → timeline-definition-PNZ67QCA-CjnZWttQ.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-BDmLMNXS.js → vennDiagram-CIIHVFJN-DU5ZyYju.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DSLuQiEv.js → wardleyDiagram-YWT4CUSO-CaCEnCB0.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-DAq_LA4r.js → xychartDiagram-2RQKCTM6-Ce3xa37U.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +471 -39
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +1256 -214
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +228 -71
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentRuns.js +205 -38
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/automationProgression.js +464 -0
- package/dist/server/lib/automationProgression.js.map +1 -0
- package/dist/server/lib/automationReconciliation.js +498 -0
- package/dist/server/lib/automationReconciliation.js.map +1 -0
- package/dist/server/lib/automationStageDetails.js +232 -0
- package/dist/server/lib/automationStageDetails.js.map +1 -0
- package/dist/server/lib/buildOpenQuestionOperations.js +223 -0
- package/dist/server/lib/buildOpenQuestionOperations.js.map +1 -0
- package/dist/server/lib/buildOpenQuestions.js +44 -0
- package/dist/server/lib/buildOpenQuestions.js.map +1 -1
- package/dist/server/lib/buildSequences.js +130 -46
- package/dist/server/lib/buildSequences.js.map +1 -1
- package/dist/server/lib/bundleReviewOperations.js +82 -9
- package/dist/server/lib/bundleReviewOperations.js.map +1 -1
- package/dist/server/lib/chatOperations.js +31 -0
- package/dist/server/lib/chatOperations.js.map +1 -1
- package/dist/server/lib/executionReconciliation.js +518 -0
- package/dist/server/lib/executionReconciliation.js.map +1 -0
- package/dist/server/lib/executionRunRetention.js +141 -0
- package/dist/server/lib/executionRunRetention.js.map +1 -0
- package/dist/server/lib/executionRuns.js +1264 -0
- package/dist/server/lib/executionRuns.js.map +1 -0
- package/dist/server/lib/git.js +385 -6
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/integrations/github.js +43 -0
- package/dist/server/lib/integrations/github.js.map +1 -1
- package/dist/server/lib/integrations/githubActions.js +1 -0
- package/dist/server/lib/integrations/githubActions.js.map +1 -1
- package/dist/server/lib/integrations/githubActionsSessions.js +337 -114
- package/dist/server/lib/integrations/githubActionsSessions.js.map +1 -1
- package/dist/server/lib/projectMemorySelections.js +7 -3
- package/dist/server/lib/projectMemorySelections.js.map +1 -1
- package/dist/server/lib/shutdown.js +2 -2
- package/dist/server/lib/shutdown.js.map +1 -1
- package/dist/server/lib/specArtifact.js +3 -0
- package/dist/server/lib/specArtifact.js.map +1 -1
- package/dist/server/lib/specFormatting.js +1 -1
- package/dist/server/lib/specFormatting.js.map +1 -1
- package/dist/server/lib/specOperations.js +31 -0
- package/dist/server/lib/specOperations.js.map +1 -1
- package/dist/server/lib/specReviewContext.js +44 -14
- package/dist/server/lib/specReviewContext.js.map +1 -1
- package/dist/server/lib/specReviewDraft.js +3 -2
- package/dist/server/lib/specReviewDraft.js.map +1 -1
- package/dist/server/lib/specReviewOperations.js +44 -7
- package/dist/server/lib/specReviewOperations.js.map +1 -1
- package/dist/server/lib/specReviewValidation.js +217 -0
- package/dist/server/lib/specReviewValidation.js.map +1 -0
- package/dist/server/lib/specReviews.js +30 -2
- package/dist/server/lib/specReviews.js.map +1 -1
- package/dist/server/lib/specs.js +18 -1
- package/dist/server/lib/specs.js.map +1 -1
- package/dist/server/lib/stageSnapshots.js +197 -0
- package/dist/server/lib/stageSnapshots.js.map +1 -0
- package/dist/server/lib/testSessions.js +164 -19
- package/dist/server/lib/testSessions.js.map +1 -1
- package/dist/server/lib/ticketAutomation.js +80 -4
- package/dist/server/lib/ticketAutomation.js.map +1 -1
- package/dist/server/lib/ticketAutomationCommit.js +466 -91
- package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
- package/dist/server/lib/ticketFollowUpOperations.js +214 -20
- package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
- package/dist/server/lib/ticketRunOperations.js +51 -6
- package/dist/server/lib/ticketRunOperations.js.map +1 -1
- package/dist/server/lib/ticketUndo.js +77 -168
- package/dist/server/lib/ticketUndo.js.map +1 -1
- package/dist/server/lib/ticketWorkflowOperations.js +132 -28
- package/dist/server/lib/ticketWorkflowOperations.js.map +1 -1
- package/dist/server/lib/tickets.js +41 -27
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/transcriptRuns.js +3 -3
- package/dist/server/lib/transcriptRuns.js.map +1 -1
- package/dist/server/lib/turnExecutionErrors.js +12 -0
- package/dist/server/lib/turnExecutionErrors.js.map +1 -0
- package/dist/server/lib/types.js +57 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +596 -184
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/chat.js +261 -130
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/followUp.js +716 -203
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +527 -188
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/planFollowUp.js +231 -69
- package/dist/server/workers/planFollowUp.js.map +1 -1
- package/dist/server/workers/review.js +790 -297
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +211 -45
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +425 -252
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/GitHubActionsDialog-DAD5REmY.js +0 -1
- package/dist/client/assets/TestsDialog-B5NVrroq.js +0 -4
- package/dist/client/assets/TicketChangesModal-By-_gTGd.js +0 -1
- package/dist/client/assets/channel-Dd5Len0X.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-B7KUg6r8.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CQm-z5c4.js +0 -1
- package/dist/client/assets/index-BHq6-Eir.js +0 -270
- package/dist/client/assets/index-BxTc5qQT.css +0 -2
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C8yO6POT.js +0 -1
|
@@ -1,17 +1,26 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import db from '../db.js';
|
|
1
3
|
import config from './config.js';
|
|
2
4
|
import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage, } from './commitMessage.js';
|
|
3
|
-
import {
|
|
5
|
+
import { commitPreparedAutomationChanges, computeGitTreeDeltaDigest, fetchRemoteBranchState, getAutomationCommitPaths, getAutomationRemoteIdentity, getGitHeadIdentity, getGitIndexTreeSha, getGitStatus, getReviewDiffData, getSelectedGitUnifiedDiff, prepareAutomationCommit, pushAutomationTarget, resolveWorktreePath, } from './git.js';
|
|
4
6
|
import { runLightweightPrompt } from './lightweightPrompt.js';
|
|
5
7
|
import { buildPullRequestContentPrompt, parsePullRequestContent, } from './pullRequestContent.js';
|
|
6
|
-
import { isProjectRootBundle } from './tickets.js';
|
|
7
|
-
import {
|
|
8
|
+
import { getBundle, getTicket, isProjectRootBundle } from './tickets.js';
|
|
9
|
+
import { createGitHubPullRequest, observeGitHubPullRequest, } from './integrations/github.js';
|
|
8
10
|
import { getGitHubConnectionConfig } from './integrations/index.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import { upsertBundlePullRequestFromGitHub, } from './bundlePullRequests.js';
|
|
12
|
+
import { depthAtLeast, getBundleAutomationState, resolveAutomationConfig, } from './ticketAutomation.js';
|
|
13
|
+
import { completeAutomationProgressionInTransaction, completeAutomationProgression, getAutomationProgression, waitAutomationProgressionInTransaction, waitAutomationProgression, } from './automationProgression.js';
|
|
14
|
+
import { AUTOMATION_STAGE_DETAILS_SCHEMA_VERSION, createAutomationStageDetails, ensureAutomationStageResult, } from './automationStageDetails.js';
|
|
15
|
+
import { getProjectById } from './projects.js';
|
|
16
|
+
import { getExecutionRun, withExecutionRunTransaction } from './executionRuns.js';
|
|
17
|
+
const EMPTY_MESSAGE_DIGEST = `sha256:${createHash('sha256').update('').digest('hex')}`;
|
|
18
|
+
const getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
19
|
+
const digestCommitMessage = (message) => {
|
|
20
|
+
const normalized = message.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n+$/, '');
|
|
21
|
+
return `sha256:${createHash('sha256').update(normalized, 'utf8').digest('hex')}`;
|
|
14
22
|
};
|
|
23
|
+
const asBroadcaster = (io) => io;
|
|
15
24
|
export const evaluateCommitPrecondition = (gitStatus) => {
|
|
16
25
|
if (gitStatus.files.length === 0) {
|
|
17
26
|
return { ok: true };
|
|
@@ -21,7 +30,7 @@ export const evaluateCommitPrecondition = (gitStatus) => {
|
|
|
21
30
|
}
|
|
22
31
|
return { ok: true };
|
|
23
32
|
};
|
|
24
|
-
export const evaluatePrPrecondition = (connectionConfig, bundle, gitStatus, hasRemoteBranch,
|
|
33
|
+
export const evaluatePrPrecondition = (connectionConfig, bundle, gitStatus, hasRemoteBranch, _existingTrackedPr) => {
|
|
25
34
|
if (!connectionConfig) {
|
|
26
35
|
return { ok: false, reason: 'GitHub integration not connected' };
|
|
27
36
|
}
|
|
@@ -36,29 +45,20 @@ export const evaluatePrPrecondition = (connectionConfig, bundle, gitStatus, hasR
|
|
|
36
45
|
}
|
|
37
46
|
return { ok: true };
|
|
38
47
|
};
|
|
39
|
-
const buildCommitExecution = (project) =>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
tool: 'opencode',
|
|
43
|
-
model: project.helper_model,
|
|
44
|
-
variant: project.helper_variant,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return { tool: config.tool ?? 'opencode' };
|
|
48
|
-
};
|
|
48
|
+
const buildCommitExecution = (project) => project.helper_model
|
|
49
|
+
? { tool: 'opencode', model: project.helper_model, variant: project.helper_variant }
|
|
50
|
+
: { tool: config.tool ?? 'opencode' };
|
|
49
51
|
const generateCommitMessage = async (cwd, project, selectedPaths) => {
|
|
50
52
|
const gitStatus = getGitStatus(cwd, null, project);
|
|
51
53
|
const selectedPathSet = new Set(selectedPaths);
|
|
52
|
-
const selectedFiles = gitStatus.files
|
|
53
|
-
.filter(file => selectedPathSet.has(file.path))
|
|
54
|
-
.map(file => `${file.status}: ${file.path}`);
|
|
55
54
|
const prompt = buildCommitMessagePrompt({
|
|
56
55
|
commitMessageRules: project.commit_message_rules,
|
|
57
|
-
selectedFiles
|
|
56
|
+
selectedFiles: gitStatus.files
|
|
57
|
+
.filter(file => selectedPathSet.has(file.path))
|
|
58
|
+
.map(file => `${file.status}: ${file.path}`),
|
|
58
59
|
diff: getSelectedGitUnifiedDiff(cwd, selectedPaths),
|
|
59
60
|
});
|
|
60
|
-
|
|
61
|
-
return normalizeGeneratedCommitMessage(rawMessage);
|
|
61
|
+
return normalizeGeneratedCommitMessage(await runLightweightPrompt(buildCommitExecution(project), prompt, cwd));
|
|
62
62
|
};
|
|
63
63
|
const generatePullRequestContent = async (cwd, project, ticket, branch) => {
|
|
64
64
|
const diffData = getReviewDiffData({ cwd, branch, project });
|
|
@@ -67,21 +67,19 @@ const generatePullRequestContent = async (cwd, project, ticket, branch) => {
|
|
|
67
67
|
}
|
|
68
68
|
try {
|
|
69
69
|
const githubConfig = getGitHubConnectionConfig(ticket.project_id ?? '');
|
|
70
|
-
const baseBranch = githubConfig?.default_base_branch ?? 'main';
|
|
71
70
|
const prompt = buildPullRequestContentPrompt({
|
|
72
71
|
ticketTitle: ticket.title,
|
|
73
72
|
ticketDescription: ticket.description,
|
|
74
73
|
diff: diffData.diff,
|
|
75
|
-
baseBranch,
|
|
74
|
+
baseBranch: githubConfig?.default_base_branch ?? 'main',
|
|
76
75
|
});
|
|
77
|
-
const
|
|
78
|
-
const parsed = parsePullRequestContent(rawOutput);
|
|
76
|
+
const parsed = parsePullRequestContent(await runLightweightPrompt(buildCommitExecution(project), prompt, cwd));
|
|
79
77
|
if (parsed) {
|
|
80
78
|
return parsed;
|
|
81
79
|
}
|
|
82
80
|
}
|
|
83
81
|
catch {
|
|
84
|
-
//
|
|
82
|
+
// Ticket content is a bounded fallback when helper generation is unavailable.
|
|
85
83
|
}
|
|
86
84
|
return { title: ticket.title, body: ticket.description ?? '' };
|
|
87
85
|
};
|
|
@@ -91,98 +89,475 @@ const emitUpdated = (io, ticketId) => {
|
|
|
91
89
|
io.emit('ticket:updated', updated);
|
|
92
90
|
}
|
|
93
91
|
};
|
|
94
|
-
|
|
92
|
+
const clearStoppedStagePointer = (projectId, bundleId, runId) => {
|
|
93
|
+
db.prepare(`
|
|
94
|
+
UPDATE bundle_automation_state
|
|
95
|
+
SET automation_paused_stage_run_id = NULL,
|
|
96
|
+
automation_pause_reason_code = NULL,
|
|
97
|
+
automation_paused_code = NULL,
|
|
98
|
+
automation_paused_reason = NULL,
|
|
99
|
+
automation_paused_at = NULL,
|
|
100
|
+
updated_at = CURRENT_TIMESTAMP
|
|
101
|
+
WHERE project_id = ? AND worktree_bundle_id = ?
|
|
102
|
+
AND automation_progression_run_id IS NULL AND automation_paused_stage_run_id = ?
|
|
103
|
+
`).run(projectId, bundleId, runId);
|
|
104
|
+
};
|
|
105
|
+
const loadLiveState = (projectId, bundleId) => {
|
|
106
|
+
const project = getProjectById(projectId);
|
|
107
|
+
const bundle = getBundle(bundleId, projectId);
|
|
108
|
+
if (!project || !bundle) {
|
|
109
|
+
throw new Error('Automation project or bundle no longer exists');
|
|
110
|
+
}
|
|
111
|
+
return { project, bundle, automationConfig: resolveAutomationConfig(bundle, project) };
|
|
112
|
+
};
|
|
113
|
+
const assertStageCanMutate = (projectId, bundleId, progressionRunId, requiredDepth) => {
|
|
114
|
+
const live = loadLiveState(projectId, bundleId);
|
|
115
|
+
const progression = getAutomationProgression(projectId, bundleId);
|
|
116
|
+
const state = getBundleAutomationState(projectId, bundleId);
|
|
117
|
+
if (!progression
|
|
118
|
+
|| progression.id !== progressionRunId
|
|
119
|
+
|| progression.status !== 'running'
|
|
120
|
+
|| state?.automation_progression_run_id !== progressionRunId
|
|
121
|
+
|| state.automation_paused_reason !== null
|
|
122
|
+
|| !depthAtLeast(live.automationConfig.depth, requiredDepth)) {
|
|
123
|
+
throw new Error(`automation no longer permits ${requiredDepth === 'auto_push' ? 'push' : 'pull-request'} mutation`);
|
|
124
|
+
}
|
|
125
|
+
return live;
|
|
126
|
+
};
|
|
127
|
+
const githubConfigMatchesIntent = (githubConfig, intent) => (githubConfig.host.toLowerCase() === intent.repoHost.toLowerCase()
|
|
128
|
+
&& githubConfig.repo_owner.toLowerCase() === intent.repoOwner.toLowerCase()
|
|
129
|
+
&& githubConfig.repo_name.toLowerCase() === intent.repoName.toLowerCase()
|
|
130
|
+
&& githubConfig.default_base_branch === intent.baseBranch);
|
|
131
|
+
const startStage = ({ projectId, bundleId, ticketId, ticketTitle, progressionRunId, allowedActiveChildRunId, stageKind, intent, broadcaster, retryOfRunId, }) => withExecutionRunTransaction(transaction => {
|
|
132
|
+
const progression = getAutomationProgression(projectId, bundleId);
|
|
133
|
+
if (!progression || progression.id !== progressionRunId) {
|
|
134
|
+
throw new Error('Automation stage has no active progression owner');
|
|
135
|
+
}
|
|
136
|
+
const activeChild = db.prepare(`
|
|
137
|
+
SELECT id
|
|
138
|
+
FROM execution_runs
|
|
139
|
+
WHERE project_id = ? AND parent_run_id = ? AND id <> ?
|
|
140
|
+
AND kind IN ('review', 'automation_review_fix', 'automation_commit', 'automation_push', 'automation_pull_request')
|
|
141
|
+
AND status IN ('queued', 'running', 'waiting')
|
|
142
|
+
LIMIT 1
|
|
143
|
+
`).get(projectId, progressionRunId, allowedActiveChildRunId ?? '');
|
|
144
|
+
if (activeChild) {
|
|
145
|
+
throw new Error('Automation progression already has an active child');
|
|
146
|
+
}
|
|
147
|
+
const progressionTicketLink = db.prepare(`
|
|
148
|
+
SELECT 1 FROM execution_run_links
|
|
149
|
+
WHERE execution_run_id = ? AND project_id = ? AND entity_type = 'ticket' AND entity_id = ?
|
|
150
|
+
`).get(progressionRunId, projectId, ticketId);
|
|
151
|
+
if (!progressionTicketLink) {
|
|
152
|
+
transaction.addExecutionRunLink({
|
|
153
|
+
projectId,
|
|
154
|
+
runId: progressionRunId,
|
|
155
|
+
link: { entityType: 'ticket', entityId: ticketId, relationship: 'context', label: ticketTitle },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const createRun = retryOfRunId
|
|
159
|
+
? transaction.createAutomationRetryExecutionRun
|
|
160
|
+
: transaction.createExecutionRun;
|
|
161
|
+
const run = createRun({
|
|
162
|
+
projectId,
|
|
163
|
+
kind: stageKind,
|
|
164
|
+
domainStatus: 'starting',
|
|
165
|
+
sourceReference: { kind: 'bundle', id: bundleId },
|
|
166
|
+
links: [{ entityType: 'ticket', entityId: ticketId, relationship: 'context', label: ticketTitle }],
|
|
167
|
+
initiatedBy: 'automation',
|
|
168
|
+
parentRunId: progressionRunId,
|
|
169
|
+
retryOfRunId,
|
|
170
|
+
eventType: `${stageKind}_created`,
|
|
171
|
+
});
|
|
172
|
+
createAutomationStageDetails({
|
|
173
|
+
projectId,
|
|
174
|
+
bundleId,
|
|
175
|
+
runId: run.id,
|
|
176
|
+
stageKind,
|
|
177
|
+
schemaVersion: AUTOMATION_STAGE_DETAILS_SCHEMA_VERSION,
|
|
178
|
+
intent: intent,
|
|
179
|
+
});
|
|
180
|
+
transaction.transitionExecutionRun({
|
|
181
|
+
projectId,
|
|
182
|
+
runId: run.id,
|
|
183
|
+
status: 'running',
|
|
184
|
+
domainStatus: 'running',
|
|
185
|
+
eventType: `${stageKind}_started`,
|
|
186
|
+
});
|
|
187
|
+
return run.id;
|
|
188
|
+
}, broadcaster);
|
|
189
|
+
const finishStage = ({ projectId, bundleId, progressionRunId, runId, stageKind, result, outcome, completeProgression, pullRequest, githubConfig, broadcaster, }) => {
|
|
190
|
+
const existing = getExecutionRun(projectId, runId);
|
|
191
|
+
if (!existing || !['queued', 'running', 'waiting'].includes(existing.status)) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
return withExecutionRunTransaction(transaction => {
|
|
195
|
+
ensureAutomationStageResult({
|
|
196
|
+
projectId,
|
|
197
|
+
bundleId,
|
|
198
|
+
runId,
|
|
199
|
+
stageKind,
|
|
200
|
+
schemaVersion: AUTOMATION_STAGE_DETAILS_SCHEMA_VERSION,
|
|
201
|
+
result: result,
|
|
202
|
+
});
|
|
203
|
+
if (pullRequest && githubConfig) {
|
|
204
|
+
const record = upsertBundlePullRequestFromGitHub(projectId, bundleId, githubConfig, pullRequest);
|
|
205
|
+
if (!record) {
|
|
206
|
+
throw new Error('Pull-request domain result could not be persisted');
|
|
207
|
+
}
|
|
208
|
+
transaction.addExecutionRunLink({
|
|
209
|
+
projectId,
|
|
210
|
+
runId,
|
|
211
|
+
link: {
|
|
212
|
+
entityType: 'bundle_pull_request',
|
|
213
|
+
entityId: record.id,
|
|
214
|
+
relationship: 'result',
|
|
215
|
+
expectedParent: { entityType: 'bundle', entityId: bundleId },
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
transaction.transitionExecutionRun({
|
|
220
|
+
projectId,
|
|
221
|
+
runId,
|
|
222
|
+
status: 'completed',
|
|
223
|
+
domainStatus: outcome,
|
|
224
|
+
eventType: `${stageKind}_completed`,
|
|
225
|
+
});
|
|
226
|
+
clearStoppedStagePointer(projectId, bundleId, runId);
|
|
227
|
+
const progression = getAutomationProgression(projectId, bundleId);
|
|
228
|
+
if (completeProgression && progression?.id === progressionRunId) {
|
|
229
|
+
completeAutomationProgressionInTransaction(transaction, { projectId, bundleId, progressionRunId });
|
|
230
|
+
}
|
|
231
|
+
}, broadcaster);
|
|
232
|
+
};
|
|
233
|
+
const failStage = ({ projectId, bundleId, progressionRunId, runId, stageKind, pauseCode, reason, ambiguous, broadcaster, }) => withExecutionRunTransaction(transaction => {
|
|
234
|
+
const existing = getExecutionRun(projectId, runId);
|
|
235
|
+
if (!existing || !['queued', 'running', 'waiting'].includes(existing.status)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
transaction.transitionExecutionRun({
|
|
239
|
+
projectId,
|
|
240
|
+
runId,
|
|
241
|
+
status: 'failed',
|
|
242
|
+
domainStatus: ambiguous ? 'ambiguous' : 'failed',
|
|
243
|
+
failure: { code: ambiguous ? 'side_effect_ambiguous' : 'stage_failed', message: reason },
|
|
244
|
+
eventType: `${stageKind}_failed`,
|
|
245
|
+
});
|
|
246
|
+
if (!ambiguous) {
|
|
247
|
+
clearStoppedStagePointer(projectId, bundleId, runId);
|
|
248
|
+
}
|
|
249
|
+
const progression = getAutomationProgression(projectId, bundleId);
|
|
250
|
+
if (progression?.id === progressionRunId) {
|
|
251
|
+
waitAutomationProgressionInTransaction(transaction, {
|
|
252
|
+
projectId,
|
|
253
|
+
bundleId,
|
|
254
|
+
progressionRunId,
|
|
255
|
+
pauseCode,
|
|
256
|
+
pauseReason: reason,
|
|
257
|
+
reasonCode: ambiguous ? 'side_effect_ambiguous' : 'stage_failed',
|
|
258
|
+
pausedStageRunId: runId,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}, broadcaster);
|
|
262
|
+
const pauseBeforeStage = ({ projectId, bundleId, progressionRunId, pauseCode, reason, ambiguous, broadcaster, }) => waitAutomationProgression({
|
|
263
|
+
projectId,
|
|
264
|
+
bundleId,
|
|
265
|
+
progressionRunId,
|
|
266
|
+
pauseCode,
|
|
267
|
+
pauseReason: reason,
|
|
268
|
+
reasonCode: ambiguous ? 'side_effect_ambiguous' : 'precondition_failed',
|
|
269
|
+
pausedStageRunId: null,
|
|
270
|
+
broadcaster,
|
|
271
|
+
});
|
|
272
|
+
const remoteStatesEqual = (left, right) => (left.state === right.state && (left.state === 'absent' || (right.state === 'observed' && left.sha === right.sha)));
|
|
273
|
+
export const runAutomationCommitChain = async (ticket, _project, _bundle, _automationConfig, io, progressionRunId, resumeFrom, triggeringReviewRunId) => {
|
|
95
274
|
if (!ticket.project_id || !ticket.worktree_bundle_id) {
|
|
96
275
|
return;
|
|
97
276
|
}
|
|
98
277
|
const projectId = ticket.project_id;
|
|
99
278
|
const bundleId = ticket.worktree_bundle_id;
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
279
|
+
const progression = progressionRunId
|
|
280
|
+
? getAutomationProgression(projectId, bundleId)
|
|
281
|
+
: null;
|
|
282
|
+
if (!progression || progression.id !== progressionRunId) {
|
|
104
283
|
return;
|
|
105
284
|
}
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
285
|
+
const broadcaster = asBroadcaster(io);
|
|
286
|
+
const isRetry = Boolean(resumeFrom?.retryOfRunId);
|
|
287
|
+
const skipCommit = Boolean(resumeFrom && (resumeFrom.stageKind !== 'automation_commit' || !isRetry));
|
|
288
|
+
const skipPush = Boolean(resumeFrom && (resumeFrom.stageKind === 'automation_pull_request'
|
|
289
|
+
|| (resumeFrom.stageKind === 'automation_push' && !isRetry)));
|
|
290
|
+
let expectedHeadSha = resumeFrom?.expectedHeadSha ?? null;
|
|
291
|
+
let live = loadLiveState(projectId, bundleId);
|
|
292
|
+
if (!depthAtLeast(live.automationConfig.depth, 'auto_commit')) {
|
|
293
|
+
completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
|
|
111
294
|
emitUpdated(io, ticket.id);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const branch = live.bundle.branch;
|
|
298
|
+
const cwd = live.bundle.kind === 'project_root'
|
|
299
|
+
? live.project.repo_path
|
|
300
|
+
: resolveWorktreePath(branch, live.project);
|
|
301
|
+
let commitRunId = null;
|
|
302
|
+
if (!skipCommit) {
|
|
303
|
+
try {
|
|
304
|
+
const status = getGitStatus(cwd, branch, live.project);
|
|
305
|
+
const precondition = evaluateCommitPrecondition(status);
|
|
306
|
+
if (!precondition.ok) {
|
|
307
|
+
throw new Error(precondition.reason);
|
|
308
|
+
}
|
|
309
|
+
if (status.files.length === 0) {
|
|
310
|
+
const head = getGitHeadIdentity(cwd);
|
|
311
|
+
const intent = {
|
|
312
|
+
branch,
|
|
313
|
+
preActionHeadSha: head.headSha,
|
|
314
|
+
preActionIndexTreeSha: getGitIndexTreeSha(cwd),
|
|
315
|
+
intendedTreeSha: head.treeSha,
|
|
316
|
+
commitMessageDigest: EMPTY_MESSAGE_DIGEST,
|
|
317
|
+
selectedContentDigest: computeGitTreeDeltaDigest(cwd, head.headSha, head.treeSha),
|
|
318
|
+
};
|
|
319
|
+
commitRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_commit', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_commit' ? resumeFrom.retryOfRunId : undefined });
|
|
320
|
+
live = loadLiveState(projectId, bundleId);
|
|
321
|
+
finishStage({
|
|
322
|
+
projectId,
|
|
323
|
+
bundleId,
|
|
324
|
+
progressionRunId,
|
|
325
|
+
runId: commitRunId,
|
|
326
|
+
stageKind: 'automation_commit',
|
|
327
|
+
result: { outcome: 'no_changes', headSha: head.headSha, treeSha: head.treeSha },
|
|
328
|
+
outcome: 'no_changes',
|
|
329
|
+
completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_push'),
|
|
330
|
+
broadcaster,
|
|
331
|
+
});
|
|
332
|
+
expectedHeadSha = head.headSha;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
const selectedPaths = getAutomationCommitPaths(status.files);
|
|
336
|
+
const message = await generateCommitMessage(cwd, live.project, selectedPaths);
|
|
337
|
+
live = loadLiveState(projectId, bundleId);
|
|
338
|
+
if (!message) {
|
|
339
|
+
throw new Error('commit message generation produced an empty result');
|
|
340
|
+
}
|
|
341
|
+
if (!depthAtLeast(live.automationConfig.depth, 'auto_commit')) {
|
|
342
|
+
completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
|
|
343
|
+
emitUpdated(io, ticket.id);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const prepared = prepareAutomationCommit(cwd, branch, live.project, selectedPaths);
|
|
347
|
+
const intent = {
|
|
348
|
+
branch,
|
|
349
|
+
preActionHeadSha: prepared.preActionHeadSha,
|
|
350
|
+
preActionIndexTreeSha: prepared.preActionIndexTreeSha,
|
|
351
|
+
intendedTreeSha: prepared.intendedTreeSha,
|
|
352
|
+
commitMessageDigest: digestCommitMessage(message),
|
|
353
|
+
selectedContentDigest: prepared.selectedContentDigest,
|
|
354
|
+
};
|
|
355
|
+
commitRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_commit', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_commit' ? resumeFrom.retryOfRunId : undefined });
|
|
356
|
+
const result = await commitPreparedAutomationChanges(cwd, branch, live.project, message, prepared);
|
|
357
|
+
live = loadLiveState(projectId, bundleId);
|
|
358
|
+
finishStage({
|
|
359
|
+
projectId,
|
|
360
|
+
bundleId,
|
|
361
|
+
progressionRunId,
|
|
362
|
+
runId: commitRunId,
|
|
363
|
+
stageKind: 'automation_commit',
|
|
364
|
+
result,
|
|
365
|
+
outcome: 'committed',
|
|
366
|
+
completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_push'),
|
|
367
|
+
broadcaster,
|
|
368
|
+
});
|
|
369
|
+
expectedHeadSha = result.commitSha;
|
|
370
|
+
}
|
|
120
371
|
}
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
372
|
+
catch (error) {
|
|
373
|
+
const reason = `commit failed: ${getErrorMessage(error)}`;
|
|
374
|
+
if (commitRunId) {
|
|
375
|
+
failStage({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', pauseCode: 'commit', reason, ambiguous: true, broadcaster });
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
pauseBeforeStage({ projectId, bundleId, progressionRunId, pauseCode: 'commit', reason, broadcaster });
|
|
127
379
|
}
|
|
128
|
-
|
|
380
|
+
emitUpdated(io, ticket.id);
|
|
381
|
+
return;
|
|
129
382
|
}
|
|
130
383
|
}
|
|
131
|
-
|
|
132
|
-
|
|
384
|
+
live = loadLiveState(projectId, bundleId);
|
|
385
|
+
if (getAutomationProgression(projectId, bundleId)?.id !== progressionRunId) {
|
|
386
|
+
emitUpdated(io, ticket.id);
|
|
133
387
|
return;
|
|
134
388
|
}
|
|
135
|
-
if (automationConfig.depth
|
|
389
|
+
if (!depthAtLeast(live.automationConfig.depth, 'auto_push')) {
|
|
390
|
+
completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
|
|
136
391
|
emitUpdated(io, ticket.id);
|
|
137
392
|
return;
|
|
138
393
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
394
|
+
let pushRunId = null;
|
|
395
|
+
if (!skipPush) {
|
|
396
|
+
try {
|
|
397
|
+
if (!expectedHeadSha || getGitHeadIdentity(cwd).headSha !== expectedHeadSha) {
|
|
398
|
+
throw new Error('HEAD no longer matches the exact automation commit result');
|
|
399
|
+
}
|
|
400
|
+
const target = expectedHeadSha;
|
|
401
|
+
const originIdentity = getAutomationRemoteIdentity(cwd);
|
|
402
|
+
const preActionRemote = await fetchRemoteBranchState(cwd, branch, originIdentity);
|
|
403
|
+
live = loadLiveState(projectId, bundleId);
|
|
404
|
+
if (!depthAtLeast(live.automationConfig.depth, 'auto_push')) {
|
|
405
|
+
completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
|
|
406
|
+
emitUpdated(io, ticket.id);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (getGitHeadIdentity(cwd).headSha !== target) {
|
|
410
|
+
throw new Error('HEAD changed while the push stage was being prepared');
|
|
411
|
+
}
|
|
412
|
+
const intent = { branch, originIdentity, targetLocalSha: target, preActionRemote };
|
|
413
|
+
pushRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_push', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_push' ? resumeFrom.retryOfRunId : undefined });
|
|
414
|
+
const current = await fetchRemoteBranchState(cwd, branch, originIdentity);
|
|
415
|
+
live = loadLiveState(projectId, bundleId);
|
|
416
|
+
if (current.state === 'observed' && current.sha === target) {
|
|
417
|
+
finishStage({ projectId, bundleId, progressionRunId, runId: pushRunId, stageKind: 'automation_push', result: { remoteSha: target }, outcome: 'already_current', completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_pr'), broadcaster });
|
|
418
|
+
expectedHeadSha = target;
|
|
419
|
+
}
|
|
420
|
+
else if (remoteStatesEqual(current, preActionRemote)) {
|
|
421
|
+
live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_push');
|
|
422
|
+
await pushAutomationTarget(cwd, branch, target, originIdentity);
|
|
423
|
+
live = loadLiveState(projectId, bundleId);
|
|
424
|
+
const observed = await fetchRemoteBranchState(cwd, branch, originIdentity);
|
|
425
|
+
live = loadLiveState(projectId, bundleId);
|
|
426
|
+
if (observed.state !== 'observed' || observed.sha !== target) {
|
|
427
|
+
throw new Error('remote changed after push and cannot be attributed to this stage');
|
|
428
|
+
}
|
|
429
|
+
finishStage({ projectId, bundleId, progressionRunId, runId: pushRunId, stageKind: 'automation_push', result: { remoteSha: target }, outcome: 'pushed', completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_pr'), broadcaster });
|
|
430
|
+
expectedHeadSha = target;
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
throw new Error('remote changed after push intent was persisted');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
const reason = `push ambiguous: ${getErrorMessage(error)}`;
|
|
438
|
+
if (pushRunId) {
|
|
439
|
+
failStage({ projectId, bundleId, progressionRunId, runId: pushRunId, stageKind: 'automation_push', pauseCode: 'push', reason, ambiguous: true, broadcaster });
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
pauseBeforeStage({ projectId, bundleId, progressionRunId, pauseCode: 'push', reason, ambiguous: true, broadcaster });
|
|
443
|
+
}
|
|
444
|
+
emitUpdated(io, ticket.id);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
143
447
|
}
|
|
144
|
-
|
|
145
|
-
|
|
448
|
+
live = loadLiveState(projectId, bundleId);
|
|
449
|
+
if (getAutomationProgression(projectId, bundleId)?.id !== progressionRunId) {
|
|
450
|
+
emitUpdated(io, ticket.id);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (!depthAtLeast(live.automationConfig.depth, 'auto_pr')) {
|
|
454
|
+
completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
|
|
455
|
+
emitUpdated(io, ticket.id);
|
|
146
456
|
return;
|
|
147
457
|
}
|
|
148
|
-
if (
|
|
458
|
+
if (resumeFrom?.stageKind === 'automation_pull_request' && !isRetry) {
|
|
459
|
+
completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
|
|
149
460
|
emitUpdated(io, ticket.id);
|
|
150
461
|
return;
|
|
151
462
|
}
|
|
152
|
-
|
|
463
|
+
let pullRequestRunId = null;
|
|
153
464
|
try {
|
|
154
465
|
const githubConfig = getGitHubConnectionConfig(projectId);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const tracked = bundle
|
|
158
|
-
? await syncTrackedBundlePullRequestIfNeeded(project, bundle, { force: true })
|
|
159
|
-
: null;
|
|
160
|
-
const prCheck = evaluatePrPrecondition(githubConfig, bundle, prGitStatus, hasRemoteBranch, tracked);
|
|
161
|
-
if (!prCheck.ok) {
|
|
162
|
-
emitPaused('pull_request', prCheck.reason);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (hasOpenBundlePullRequest(tracked)) {
|
|
166
|
-
emitUpdated(io, ticket.id);
|
|
167
|
-
return;
|
|
466
|
+
if (!githubConfig?.default_base_branch || isProjectRootBundle(live.bundle)) {
|
|
467
|
+
throw new Error(!githubConfig ? 'GitHub integration not connected' : 'pull-request configuration is incomplete');
|
|
168
468
|
}
|
|
169
|
-
if (!
|
|
170
|
-
|
|
171
|
-
return;
|
|
469
|
+
if (!expectedHeadSha || getGitHeadIdentity(cwd).headSha !== expectedHeadSha) {
|
|
470
|
+
throw new Error('HEAD no longer matches the exact automation push result');
|
|
172
471
|
}
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
472
|
+
const headSha = expectedHeadSha;
|
|
473
|
+
const intent = {
|
|
474
|
+
provider: 'github',
|
|
475
|
+
repoHost: githubConfig.host,
|
|
476
|
+
repoOwner: githubConfig.repo_owner,
|
|
477
|
+
repoName: githubConfig.repo_name,
|
|
478
|
+
baseBranch: githubConfig.default_base_branch,
|
|
179
479
|
headBranch: branch,
|
|
480
|
+
headSha,
|
|
481
|
+
};
|
|
482
|
+
pullRequestRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_pull_request', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_pull_request' ? resumeFrom.retryOfRunId : undefined });
|
|
483
|
+
let observation = await observeGitHubPullRequest(githubConfig, intent);
|
|
484
|
+
live = loadLiveState(projectId, bundleId);
|
|
485
|
+
let pullRequest;
|
|
486
|
+
let outcome;
|
|
487
|
+
if (observation.outcome === 'exact') {
|
|
488
|
+
pullRequest = observation.pullRequest;
|
|
489
|
+
outcome = 'already_exists';
|
|
490
|
+
}
|
|
491
|
+
else if (observation.outcome === 'absent') {
|
|
492
|
+
const content = await generatePullRequestContent(cwd, live.project, ticket, branch);
|
|
493
|
+
live = loadLiveState(projectId, bundleId);
|
|
494
|
+
const currentConfig = getGitHubConnectionConfig(projectId);
|
|
495
|
+
if (!currentConfig || !githubConfigMatchesIntent(currentConfig, intent)) {
|
|
496
|
+
throw new Error('GitHub configuration changed while the pull-request stage was active');
|
|
497
|
+
}
|
|
498
|
+
live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_pr');
|
|
499
|
+
if (getGitHeadIdentity(cwd).headSha !== intent.headSha) {
|
|
500
|
+
throw new Error('HEAD changed while the pull-request stage was active');
|
|
501
|
+
}
|
|
502
|
+
const remote = await fetchRemoteBranchState(cwd, branch);
|
|
503
|
+
if (remote.state !== 'observed' || remote.sha !== intent.headSha) {
|
|
504
|
+
throw new Error('remote branch no longer matches the persisted pull-request head');
|
|
505
|
+
}
|
|
506
|
+
const mutationConfig = getGitHubConnectionConfig(projectId);
|
|
507
|
+
if (!mutationConfig || !githubConfigMatchesIntent(mutationConfig, intent)) {
|
|
508
|
+
throw new Error('GitHub configuration changed while the pull-request stage was active');
|
|
509
|
+
}
|
|
510
|
+
live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_pr');
|
|
511
|
+
if (getGitHeadIdentity(cwd).headSha !== intent.headSha) {
|
|
512
|
+
throw new Error('HEAD changed before pull-request creation');
|
|
513
|
+
}
|
|
514
|
+
pullRequest = await createGitHubPullRequest(mutationConfig, {
|
|
515
|
+
title: content.title,
|
|
516
|
+
body: content.body,
|
|
517
|
+
baseBranch: intent.baseBranch,
|
|
518
|
+
headBranch: branch,
|
|
519
|
+
});
|
|
520
|
+
live = loadLiveState(projectId, bundleId);
|
|
521
|
+
observation = await observeGitHubPullRequest(mutationConfig, intent);
|
|
522
|
+
live = loadLiveState(projectId, bundleId);
|
|
523
|
+
if (observation.outcome !== 'exact' || observation.pullRequest.number !== pullRequest.number) {
|
|
524
|
+
throw new Error('created pull request could not be observed with its persisted head identity');
|
|
525
|
+
}
|
|
526
|
+
pullRequest = observation.pullRequest;
|
|
527
|
+
outcome = 'created';
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
throw new Error(observation.reason);
|
|
531
|
+
}
|
|
532
|
+
const result = {
|
|
533
|
+
pullRequestNumber: pullRequest.number,
|
|
534
|
+
providerId: pullRequest.nodeId,
|
|
535
|
+
url: pullRequest.url,
|
|
536
|
+
state: pullRequest.state,
|
|
537
|
+
};
|
|
538
|
+
finishStage({
|
|
539
|
+
projectId,
|
|
540
|
+
bundleId,
|
|
541
|
+
progressionRunId,
|
|
542
|
+
runId: pullRequestRunId,
|
|
543
|
+
stageKind: 'automation_pull_request',
|
|
544
|
+
result,
|
|
545
|
+
outcome,
|
|
546
|
+
completeProgression: true,
|
|
547
|
+
pullRequest,
|
|
548
|
+
githubConfig,
|
|
549
|
+
broadcaster,
|
|
180
550
|
});
|
|
181
|
-
upsertBundlePullRequestFromGitHub(projectId, bundleId, githubConfig, nextPullRequest);
|
|
182
|
-
emitUpdated(io, ticket.id);
|
|
183
551
|
}
|
|
184
552
|
catch (error) {
|
|
185
|
-
|
|
553
|
+
const reason = `pull request ambiguous: ${getErrorMessage(error)}`;
|
|
554
|
+
if (pullRequestRunId) {
|
|
555
|
+
failStage({ projectId, bundleId, progressionRunId, runId: pullRequestRunId, stageKind: 'automation_pull_request', pauseCode: 'pull_request', reason, ambiguous: true, broadcaster });
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
pauseBeforeStage({ projectId, bundleId, progressionRunId, pauseCode: 'pull_request', reason, ambiguous: true, broadcaster });
|
|
559
|
+
}
|
|
186
560
|
}
|
|
561
|
+
emitUpdated(io, ticket.id);
|
|
187
562
|
};
|
|
188
563
|
//# sourceMappingURL=ticketAutomationCommit.js.map
|