@straiffi/archon 1.7.1 → 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.
Files changed (166) hide show
  1. package/dist/client/assets/ActivityView-DTEenQas.js +1 -0
  2. package/dist/client/assets/GitHubActionsDialog-BKWHonUS.js +1 -0
  3. package/dist/client/assets/TestsDialog-cBaoffCs.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-CJgwCv5O.js → architectureDiagram-3BPJPVTR-BzzGotac.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM--xjes8FA.js → blockDiagram-GPEHLZMM-DIcqLHKg.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-DHnp82NG.js → c4Diagram-AAUBKEIU-Hc1Sqmhs.js} +1 -1
  7. package/dist/client/assets/channel-Cym17waa.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-DAOyJCKy.js → chunk-2J33WTMH-CWSwjsgk.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-C9YI_tk4.js → chunk-3OPIFGDE-BnX3DljN.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-BW9khkin.js → chunk-5ZQYHXKU-EyyJlI-d.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-DL4t5Hpi.js → chunk-727SXJPM-Ci6CfxWj.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-C4VMKa_O.js → chunk-AQP2D5EJ-RKZNsJTm.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-DHP7zrZK.js → chunk-CSCIHK7Q-DBf0Euoj.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-CqZWek3t.js → chunk-KSCS5N6A-BmLQI_2S.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-D3q55pCI.js → chunk-L5ZTLDWV-DKwVAb8G.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-UDY_Lo6v.js → chunk-LZXEDZCA-axotKkbz.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-ZCiOxkYV.js → chunk-ND2GUHAM-BBlJ4nXC.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-CuryoBtd.js → chunk-NZK2D7GU-BKJGoZZt.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-PnPsz2TB.js → chunk-O5CBEL6O-C-Ghigo1.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-BzOopX-U.js → chunk-WU5MYG2G-CCzVWYcd.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-BcigvvIC.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-3AdSuE0N.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-5LBwQ83I.js → dagre-BM42HDAG-rwIdPcjI.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-_4pckiGR.js → diagram-2AECGRRQ-DJWxOQsg.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-CqElv5rJ.js → diagram-5GNKFQAL-C-mQeFtd.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-DKv4zS4F.js → diagram-KO2AKTUF-BAu6u4sR.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-iLmTH4UP.js → diagram-LMA3HP47-U6CbvBn0.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-DpFYIa0o.js → diagram-OG6HWLK6-DNTwFcs1.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-DnfbvbKs.js → erDiagram-TEJ5UH35-DaHX6Xw5.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-C2MN70Qv.js → flowDiagram-I6XJVG4X-B9hKqL2l.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-yonpliOJ.js → ganttDiagram-6RSMTGT7-BQ1HPHVO.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CaKDWx7y.js → gitGraphDiagram-PVQCEYII-BBRcsmMK.js} +1 -1
  33. package/dist/client/assets/index-CEMaJCPS.js +273 -0
  34. package/dist/client/assets/index-lWftq65U.css +2 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-82-8njnx.js → infoDiagram-5YYISTIA-DUoNiSGC.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BMiyLWwK.js → ishikawaDiagram-YF4QCWOH-DJN1y2w8.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-C1qBnR37.js → journeyDiagram-JHISSGLW-BxLLMT70.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-zByEuCzn.js → kanban-definition-UN3LZRKU-BdCPistJ.js} +1 -1
  39. package/dist/client/assets/{line-EpLqyzw7.js → line-4mIgXsZd.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-o124REPN.js → mermaid-parser.core-Pu1QGWJ-.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-DgiAn2-k.js → mermaid.core-DnH9DrgZ.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-UK6V0Bxq.js → mindmap-definition-RKZ34NQL-DqE4ba3C.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-B2s9oGFl.js → pieDiagram-4H26LBE5-Crjy9ejf.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-d5H7-zcc.js → quadrantDiagram-W4KKPZXB-BUSZ2VB7.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DlwN9Vcv.js → requirementDiagram-4Y6WPE33-CBhFKEoV.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-9dGyAxu5.js → sankeyDiagram-5OEKKPKP-CgdbY41v.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DKwRjEym.js → sequenceDiagram-3UESZ5HK-hkp_H7AE.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-D_6cuqsX.js → stateDiagram-AJRCARHV-C6x45RWp.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DVzPjRPN.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-ChqD4j8I.js → timeline-definition-PNZ67QCA-CjnZWttQ.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-CSAZk0IW.js → vennDiagram-CIIHVFJN-DU5ZyYju.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BUE6IF4q.js → wardleyDiagram-YWT4CUSO-CaCEnCB0.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-MOkC53m_.js → xychartDiagram-2RQKCTM6-Ce3xa37U.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +471 -39
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +1267 -213
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/agent.js +281 -76
  60. package/dist/server/lib/agent.js.map +1 -1
  61. package/dist/server/lib/agentRuns.js +205 -38
  62. package/dist/server/lib/agentRuns.js.map +1 -1
  63. package/dist/server/lib/automationProgression.js +464 -0
  64. package/dist/server/lib/automationProgression.js.map +1 -0
  65. package/dist/server/lib/automationReconciliation.js +498 -0
  66. package/dist/server/lib/automationReconciliation.js.map +1 -0
  67. package/dist/server/lib/automationStageDetails.js +232 -0
  68. package/dist/server/lib/automationStageDetails.js.map +1 -0
  69. package/dist/server/lib/buildOpenQuestionOperations.js +223 -0
  70. package/dist/server/lib/buildOpenQuestionOperations.js.map +1 -0
  71. package/dist/server/lib/buildOpenQuestions.js +52 -0
  72. package/dist/server/lib/buildOpenQuestions.js.map +1 -1
  73. package/dist/server/lib/buildSequences.js +130 -46
  74. package/dist/server/lib/buildSequences.js.map +1 -1
  75. package/dist/server/lib/bundleReviewOperations.js +82 -9
  76. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  77. package/dist/server/lib/chatOperations.js +31 -0
  78. package/dist/server/lib/chatOperations.js.map +1 -1
  79. package/dist/server/lib/executionReconciliation.js +518 -0
  80. package/dist/server/lib/executionReconciliation.js.map +1 -0
  81. package/dist/server/lib/executionRunRetention.js +141 -0
  82. package/dist/server/lib/executionRunRetention.js.map +1 -0
  83. package/dist/server/lib/executionRuns.js +1264 -0
  84. package/dist/server/lib/executionRuns.js.map +1 -0
  85. package/dist/server/lib/git.js +385 -6
  86. package/dist/server/lib/git.js.map +1 -1
  87. package/dist/server/lib/integrations/github.js +43 -0
  88. package/dist/server/lib/integrations/github.js.map +1 -1
  89. package/dist/server/lib/integrations/githubActions.js +1 -0
  90. package/dist/server/lib/integrations/githubActions.js.map +1 -1
  91. package/dist/server/lib/integrations/githubActionsSessions.js +337 -114
  92. package/dist/server/lib/integrations/githubActionsSessions.js.map +1 -1
  93. package/dist/server/lib/projectMemorySelections.js +7 -3
  94. package/dist/server/lib/projectMemorySelections.js.map +1 -1
  95. package/dist/server/lib/shutdown.js +2 -2
  96. package/dist/server/lib/shutdown.js.map +1 -1
  97. package/dist/server/lib/specArtifact.js +3 -0
  98. package/dist/server/lib/specArtifact.js.map +1 -1
  99. package/dist/server/lib/specFormatting.js +1 -1
  100. package/dist/server/lib/specFormatting.js.map +1 -1
  101. package/dist/server/lib/specOperations.js +31 -0
  102. package/dist/server/lib/specOperations.js.map +1 -1
  103. package/dist/server/lib/specReviewContext.js +44 -14
  104. package/dist/server/lib/specReviewContext.js.map +1 -1
  105. package/dist/server/lib/specReviewDraft.js +3 -2
  106. package/dist/server/lib/specReviewDraft.js.map +1 -1
  107. package/dist/server/lib/specReviewOperations.js +44 -7
  108. package/dist/server/lib/specReviewOperations.js.map +1 -1
  109. package/dist/server/lib/specReviewValidation.js +217 -0
  110. package/dist/server/lib/specReviewValidation.js.map +1 -0
  111. package/dist/server/lib/specReviews.js +30 -2
  112. package/dist/server/lib/specReviews.js.map +1 -1
  113. package/dist/server/lib/specs.js +18 -1
  114. package/dist/server/lib/specs.js.map +1 -1
  115. package/dist/server/lib/stageSnapshots.js +197 -0
  116. package/dist/server/lib/stageSnapshots.js.map +1 -0
  117. package/dist/server/lib/testSessions.js +164 -19
  118. package/dist/server/lib/testSessions.js.map +1 -1
  119. package/dist/server/lib/ticketAutomation.js +80 -4
  120. package/dist/server/lib/ticketAutomation.js.map +1 -1
  121. package/dist/server/lib/ticketAutomationCommit.js +466 -91
  122. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  123. package/dist/server/lib/ticketFollowUpOperations.js +214 -20
  124. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  125. package/dist/server/lib/ticketOpenQuestions.js +30 -14
  126. package/dist/server/lib/ticketOpenQuestions.js.map +1 -1
  127. package/dist/server/lib/ticketRunOperations.js +75 -7
  128. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  129. package/dist/server/lib/ticketUndo.js +77 -168
  130. package/dist/server/lib/ticketUndo.js.map +1 -1
  131. package/dist/server/lib/ticketWorkflowOperations.js +132 -28
  132. package/dist/server/lib/ticketWorkflowOperations.js.map +1 -1
  133. package/dist/server/lib/tickets.js +46 -29
  134. package/dist/server/lib/tickets.js.map +1 -1
  135. package/dist/server/lib/transcriptRuns.js +3 -3
  136. package/dist/server/lib/transcriptRuns.js.map +1 -1
  137. package/dist/server/lib/turnExecutionErrors.js +12 -0
  138. package/dist/server/lib/turnExecutionErrors.js.map +1 -0
  139. package/dist/server/lib/types.js +57 -1
  140. package/dist/server/lib/types.js.map +1 -1
  141. package/dist/server/workers/build.js +596 -184
  142. package/dist/server/workers/build.js.map +1 -1
  143. package/dist/server/workers/chat.js +261 -130
  144. package/dist/server/workers/chat.js.map +1 -1
  145. package/dist/server/workers/followUp.js +720 -195
  146. package/dist/server/workers/followUp.js.map +1 -1
  147. package/dist/server/workers/plan.js +527 -188
  148. package/dist/server/workers/plan.js.map +1 -1
  149. package/dist/server/workers/planFollowUp.js +231 -69
  150. package/dist/server/workers/planFollowUp.js.map +1 -1
  151. package/dist/server/workers/review.js +796 -239
  152. package/dist/server/workers/review.js.map +1 -1
  153. package/dist/server/workers/specReview.js +211 -45
  154. package/dist/server/workers/specReview.js.map +1 -1
  155. package/dist/server/workers/specSession.js +425 -252
  156. package/dist/server/workers/specSession.js.map +1 -1
  157. package/package.json +1 -1
  158. package/dist/client/assets/GitHubActionsDialog-BrUq-zO1.js +0 -1
  159. package/dist/client/assets/TestsDialog-D9N4GD77.js +0 -4
  160. package/dist/client/assets/TicketChangesModal-bYHJSO6d.js +0 -1
  161. package/dist/client/assets/channel-B4kCvLCt.js +0 -1
  162. package/dist/client/assets/classDiagram-4FO5ZUOK-C-eRRNNj.js +0 -1
  163. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B3vxoc73.js +0 -1
  164. package/dist/client/assets/index-9q1GXkUw.js +0 -270
  165. package/dist/client/assets/index-BxTc5qQT.css +0 -2
  166. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BkMKz90T.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 { commitGitChanges, getGitStatus, getReviewDiffData, getSelectedGitUnifiedDiff, pushCurrentBranch, resolveWorktreePath, } from './git.js';
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 { createOrFindGitHubPullRequest, hasGitHubRemoteBranch } from './integrations/github.js';
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 { hasOpenBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequestFromGitHub, } from './bundlePullRequests.js';
10
- import { recordAutomationPause } from './ticketAutomation.js';
11
- import { getTicket } from './tickets.js';
12
- const getErrorMessage = (error) => {
13
- return error instanceof Error ? error.message : String(error);
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, existingTrackedPr) => {
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
- if (project.helper_model) {
41
- return {
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
- const rawMessage = await runLightweightPrompt(buildCommitExecution(project), prompt, cwd);
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 rawOutput = await runLightweightPrompt(buildCommitExecution(project), prompt, cwd);
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
- // Fall back to ticket fields on any failure
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
- export const runAutomationCommitChain = async (ticket, project, bundle, automationConfig, io) => {
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 branch = bundle?.branch ?? null;
101
- if (!branch) {
102
- recordAutomationPause(projectId, bundleId, 'commit', 'automation commit chain skipped: bundle has no branch');
103
- emitUpdated(io, ticket.id);
279
+ const progression = progressionRunId
280
+ ? getAutomationProgression(projectId, bundleId)
281
+ : null;
282
+ if (!progression || progression.id !== progressionRunId) {
104
283
  return;
105
284
  }
106
- const cwd = bundle?.kind === 'project_root'
107
- ? project.repo_path
108
- : resolveWorktreePath(branch, project);
109
- const emitPaused = (code, reason) => {
110
- recordAutomationPause(projectId, bundleId, code, reason);
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
- // Commit leg (auto_commit and deeper)
114
- try {
115
- const gitStatus = getGitStatus(cwd, branch, project);
116
- const commitCheck = evaluateCommitPrecondition(gitStatus);
117
- if (!commitCheck.ok) {
118
- emitPaused('commit', commitCheck.reason);
119
- return;
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
- if (gitStatus.files.length > 0) {
122
- const selectedPaths = gitStatus.files.map(file => file.path);
123
- const message = await generateCommitMessage(cwd, project, selectedPaths);
124
- if (!message) {
125
- emitPaused('commit', 'commit message generation produced an empty result');
126
- return;
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
- await commitGitChanges(cwd, branch, project, message, selectedPaths);
380
+ emitUpdated(io, ticket.id);
381
+ return;
129
382
  }
130
383
  }
131
- catch (error) {
132
- emitPaused('commit', `commit failed: ${getErrorMessage(error)}`);
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 === 'auto_commit') {
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
- // Push leg (auto_push and deeper). pushCurrentBranch is idempotent — a no-op
140
- // when the branch is already up to date — so no separate precondition is needed.
141
- try {
142
- await pushCurrentBranch(cwd, branch);
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
- catch (error) {
145
- emitPaused('push', `push failed: ${getErrorMessage(error)}`);
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 (automationConfig.depth === 'auto_push') {
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
- // PR leg (auto_pr)
463
+ let pullRequestRunId = null;
153
464
  try {
154
465
  const githubConfig = getGitHubConnectionConfig(projectId);
155
- const prGitStatus = getGitStatus(cwd, branch, project);
156
- const hasRemoteBranch = hasGitHubRemoteBranch(cwd, branch);
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 (!githubConfig) {
170
- emitPaused('pull_request', 'GitHub integration not connected');
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 baseBranch = githubConfig.default_base_branch ?? '';
174
- const prContent = await generatePullRequestContent(cwd, project, ticket, branch);
175
- const nextPullRequest = await createOrFindGitHubPullRequest(githubConfig, {
176
- title: prContent.title,
177
- body: prContent.body,
178
- baseBranch,
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
- emitPaused('pull_request', `pull request failed: ${getErrorMessage(error)}`);
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