@todoist/doistbot-cli 0.0.0-stage → 1.0.16

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 (185) hide show
  1. package/dist/actions/auth.js +225 -0
  2. package/dist/actions/doctor.js +39 -0
  3. package/dist/actions/review-usage.js +25 -0
  4. package/dist/actions/review.js +311 -0
  5. package/dist/actions/setup.js +14 -0
  6. package/dist/auth.js +202 -0
  7. package/dist/config.js +145 -0
  8. package/dist/env.js +10 -0
  9. package/dist/file-store.js +112 -0
  10. package/dist/index.js +164 -0
  11. package/dist/input.js +40 -0
  12. package/dist/runtime.js +105 -0
  13. package/dist/secure-store.js +148 -0
  14. package/dist/terminal.js +185 -0
  15. package/dist/test-utils/secure-store-mocks.js +29 -0
  16. package/dist/update-cache-entrypoint.js +16 -0
  17. package/dist/update-check.js +235 -0
  18. package/package.json +65 -5
  19. package/sandbox/_node_modules/doistbot-logging/dist/index.d.ts +23 -0
  20. package/sandbox/_node_modules/doistbot-logging/dist/index.js +51 -0
  21. package/sandbox/_node_modules/doistbot-logging/package.json +12 -0
  22. package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +93 -0
  23. package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +197 -0
  24. package/sandbox/_node_modules/doistbot-repo-config/package.json +12 -0
  25. package/sandbox/dist/core/autofix-resources.js +58 -0
  26. package/sandbox/dist/core/autofix-service-query.js +69 -0
  27. package/sandbox/dist/core/autofix-test-policy.js +11 -0
  28. package/sandbox/dist/core/autofix-verification.js +355 -0
  29. package/sandbox/dist/core/bot-logins.js +14 -0
  30. package/sandbox/dist/core/check-run.js +158 -0
  31. package/sandbox/dist/core/datadog-metrics.js +113 -0
  32. package/sandbox/dist/core/environment.js +49 -0
  33. package/sandbox/dist/core/git-auth.js +17 -0
  34. package/sandbox/dist/core/github-history.js +4 -0
  35. package/sandbox/dist/core/github-issues.js +18 -0
  36. package/sandbox/dist/core/label-parsing.js +1 -0
  37. package/sandbox/dist/core/langfuse-observation.js +33 -0
  38. package/sandbox/dist/core/langfuse-session.js +39 -0
  39. package/sandbox/dist/core/logger-span-processor.js +90 -0
  40. package/sandbox/dist/core/logging.js +18 -0
  41. package/sandbox/dist/core/pi-autofix-extension.js +188 -0
  42. package/sandbox/dist/core/pi-execution-limits-extension.js +79 -0
  43. package/sandbox/dist/core/pi-progress-logging.js +51 -0
  44. package/sandbox/dist/core/pi-tool-output-filter-extension.js +65 -0
  45. package/sandbox/dist/core/pi.js +870 -0
  46. package/sandbox/dist/core/pii-scrubber.js +31 -0
  47. package/sandbox/dist/core/prompt-template.js +63 -0
  48. package/sandbox/dist/core/reactions.js +36 -0
  49. package/sandbox/dist/core/regexp.js +3 -0
  50. package/sandbox/dist/core/repository-map.js +1 -0
  51. package/sandbox/dist/core/repository-resolver.js +1 -0
  52. package/sandbox/dist/core/retry.js +29 -0
  53. package/sandbox/dist/core/s3-utils.js +42 -0
  54. package/sandbox/dist/core/shared.js +124 -0
  55. package/sandbox/dist/core/snapshot-helpers.js +17 -0
  56. package/sandbox/dist/core/span-test-helpers.js +13 -0
  57. package/sandbox/dist/core/tool-environment.js +83 -0
  58. package/sandbox/dist/core/tool-output.js +18 -0
  59. package/sandbox/dist/core/tracing-setup.js +92 -0
  60. package/sandbox/dist/core/tracing.js +81 -0
  61. package/sandbox/dist/main.js +51 -0
  62. package/sandbox/dist/providers/config.js +23 -0
  63. package/sandbox/dist/providers/credentials.js +40 -0
  64. package/sandbox/dist/providers/gemini.js +28 -0
  65. package/sandbox/dist/providers/helpers.js +253 -0
  66. package/sandbox/dist/providers/index.js +27 -0
  67. package/sandbox/dist/providers/openai.js +23 -0
  68. package/sandbox/dist/providers/openrouter-pi.js +25 -0
  69. package/sandbox/dist/providers/pi-review.js +301 -0
  70. package/sandbox/dist/providers/process.js +34 -0
  71. package/sandbox/dist/providers/schemas.js +61 -0
  72. package/sandbox/dist/providers/types.js +4 -0
  73. package/sandbox/dist/tasks/chat/chat.js +440 -0
  74. package/sandbox/dist/tasks/chat/prompt.js +20 -0
  75. package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +304 -0
  76. package/sandbox/dist/tasks/issue-summarize/context.js +21 -0
  77. package/sandbox/dist/tasks/issue-summarize/media.js +335 -0
  78. package/sandbox/dist/tasks/issue-summarize/model.js +230 -0
  79. package/sandbox/dist/tasks/issue-summarize/output.js +45 -0
  80. package/sandbox/dist/tasks/issue-summarize/prompt.js +11 -0
  81. package/sandbox/dist/tasks/issue-summarize/summarize.js +238 -0
  82. package/sandbox/dist/tasks/issue-summarize/thread.js +123 -0
  83. package/sandbox/dist/tasks/issue-summarize/types.js +1 -0
  84. package/sandbox/dist/tasks/issue-triage/assignee-review.js +114 -0
  85. package/sandbox/dist/tasks/issue-triage/autofix-capabilities.js +22 -0
  86. package/sandbox/dist/tasks/issue-triage/autofix-checkpoint.js +122 -0
  87. package/sandbox/dist/tasks/issue-triage/autofix-policy.js +36 -0
  88. package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
  89. package/sandbox/dist/tasks/issue-triage/context.js +57 -0
  90. package/sandbox/dist/tasks/issue-triage/diff-validator.js +102 -0
  91. package/sandbox/dist/tasks/issue-triage/fix-attempt.js +617 -0
  92. package/sandbox/dist/tasks/issue-triage/fix-context.js +5 -0
  93. package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +263 -0
  94. package/sandbox/dist/tasks/issue-triage/fix-failure-reasons.js +2 -0
  95. package/sandbox/dist/tasks/issue-triage/fix-loop/effects.js +526 -0
  96. package/sandbox/dist/tasks/issue-triage/fix-loop/effects.types.js +1 -0
  97. package/sandbox/dist/tasks/issue-triage/fix-loop/fake-effects.js +65 -0
  98. package/sandbox/dist/tasks/issue-triage/fix-loop/promotion-task.js +108 -0
  99. package/sandbox/dist/tasks/issue-triage/fix-loop/recovery.js +512 -0
  100. package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +233 -0
  101. package/sandbox/dist/tasks/issue-triage/fix-loop/state-machine.js +171 -0
  102. package/sandbox/dist/tasks/issue-triage/github-request.js +2 -0
  103. package/sandbox/dist/tasks/issue-triage/hero-group-map.js +206 -0
  104. package/sandbox/dist/tasks/issue-triage/markers.js +6 -0
  105. package/sandbox/dist/tasks/issue-triage/model.js +124 -0
  106. package/sandbox/dist/tasks/issue-triage/output.js +164 -0
  107. package/sandbox/dist/tasks/issue-triage/pr-creator.js +585 -0
  108. package/sandbox/dist/tasks/issue-triage/pr-readiness.js +25 -0
  109. package/sandbox/dist/tasks/issue-triage/prompt.js +63 -0
  110. package/sandbox/dist/tasks/issue-triage/repo-config.js +47 -0
  111. package/sandbox/dist/tasks/issue-triage/repo-prompts.js +77 -0
  112. package/sandbox/dist/tasks/issue-triage/report-fixture.js +53 -0
  113. package/sandbox/dist/tasks/issue-triage/report-history.js +113 -0
  114. package/sandbox/dist/tasks/issue-triage/report-schema.js +63 -0
  115. package/sandbox/dist/tasks/issue-triage/report.js +111 -0
  116. package/sandbox/dist/tasks/issue-triage/resolution.js +30 -0
  117. package/sandbox/dist/tasks/issue-triage/routing.js +169 -0
  118. package/sandbox/dist/tasks/issue-triage/sandbox-environment.js +59 -0
  119. package/sandbox/dist/tasks/issue-triage/shared.js +86 -0
  120. package/sandbox/dist/tasks/issue-triage/side-effects.js +102 -0
  121. package/sandbox/dist/tasks/issue-triage/triage.js +442 -0
  122. package/sandbox/dist/tasks/issue-triage/types.js +1 -0
  123. package/sandbox/dist/tasks/persist-logs/context.js +19 -0
  124. package/sandbox/dist/tasks/persist-logs/dd-proxy.js +116 -0
  125. package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +156 -0
  126. package/sandbox/dist/tasks/persist-logs/persist-logs.js +491 -0
  127. package/sandbox/dist/tasks/refresh-s3-urls/refresh-s3-urls.js +214 -0
  128. package/sandbox/dist/tasks/review/check-run.js +326 -0
  129. package/sandbox/dist/tasks/review/comment-mapper.js +46 -0
  130. package/sandbox/dist/tasks/review/conversation-context.js +477 -0
  131. package/sandbox/dist/tasks/review/diff.js +95 -0
  132. package/sandbox/dist/tasks/review/engines/dedupe.js +291 -0
  133. package/sandbox/dist/tasks/review/engines/multi-focus.js +419 -0
  134. package/sandbox/dist/tasks/review/engines/shared.js +87 -0
  135. package/sandbox/dist/tasks/review/engines/types.js +1 -0
  136. package/sandbox/dist/tasks/review/finding-output.js +18 -0
  137. package/sandbox/dist/tasks/review/format.js +17 -0
  138. package/sandbox/dist/tasks/review/github-comment-format.js +94 -0
  139. package/sandbox/dist/tasks/review/local-git.js +374 -0
  140. package/sandbox/dist/tasks/review/local.js +118 -0
  141. package/sandbox/dist/tasks/review/multi-focus-prompt.js +59 -0
  142. package/sandbox/dist/tasks/review/prompt.js +81 -0
  143. package/sandbox/dist/tasks/review/review-feedback.js +48 -0
  144. package/sandbox/dist/tasks/review/review-mode-github.js +58 -0
  145. package/sandbox/dist/tasks/review/review-mode-policy.js +232 -0
  146. package/sandbox/dist/tasks/review/review-mode-types.js +1 -0
  147. package/sandbox/dist/tasks/review/review-mode.js +296 -0
  148. package/sandbox/dist/tasks/review/review-summary.js +123 -0
  149. package/sandbox/dist/tasks/review/review.js +391 -0
  150. package/sandbox/dist/tasks/review/runner.js +255 -0
  151. package/sandbox/dist/tasks/review/severity.js +5 -0
  152. package/sandbox/dist/tasks/review/summary-model.js +271 -0
  153. package/sandbox/dist/tasks/review/thinking-level.js +14 -0
  154. package/sandbox/dist/tasks/review/translation-review-policy.js +37 -0
  155. package/sandbox/dist/tasks/review/types.js +1 -0
  156. package/sandbox/dist/tasks/review-slice-plan/context.js +66 -0
  157. package/sandbox/dist/tasks/review-slice-plan/plan.js +210 -0
  158. package/sandbox/dist/tasks/review-slice-plan/prompt.js +59 -0
  159. package/sandbox/dist/tasks/review-slice-plan/review-slice-plan.js +367 -0
  160. package/sandbox/dist/tasks/sentry-triage/store.js +133 -0
  161. package/sandbox/dist/tasks/sentry-triage/triage.js +217 -0
  162. package/sandbox/node_modules/doistbot-logging/dist/index.d.ts +23 -0
  163. package/sandbox/node_modules/doistbot-logging/dist/index.js +51 -0
  164. package/sandbox/node_modules/doistbot-logging/package.json +12 -0
  165. package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +93 -0
  166. package/sandbox/node_modules/doistbot-repo-config/dist/index.js +197 -0
  167. package/sandbox/node_modules/doistbot-repo-config/package.json +12 -0
  168. package/sandbox/src/review/prompts/ai-internal-tools.md +254 -0
  169. package/sandbox/src/review/prompts/backend-repository-standards.md +102 -0
  170. package/sandbox/src/review/prompts/observability-standard.md +169 -0
  171. package/sandbox/src/review/prompts/outline-map.json +26 -0
  172. package/sandbox/src/review/prompts/secrets-management.md +286 -0
  173. package/sandbox/src/review/prompts/service-production-readiness.md +122 -0
  174. package/sandbox/src/review/prompts/workload-placement.md +345 -0
  175. package/sandbox/src/tasks/review/prompts/review-focus-prompts/efficiency.md +16 -0
  176. package/sandbox/src/tasks/review/prompts/review-focus-prompts/general.md +32 -0
  177. package/sandbox/src/tasks/review/prompts/review-focus-prompts/quality.md +20 -0
  178. package/sandbox/src/tasks/review/prompts/review-focus-prompts/reuse.md +13 -0
  179. package/sandbox/src/tasks/review/prompts/review-focus-prompts/security.md +18 -0
  180. package/sandbox/src/tasks/review/prompts/review-focus-prompts/standards.md +21 -0
  181. package/sandbox/src/tasks/review/prompts/review-focus-prompts/tests.md +19 -0
  182. package/sandbox/src/tasks/review/prompts/review-mode.md +40 -0
  183. package/sandbox/src/tasks/review/prompts/review-multi-focus-base-prompt.md +142 -0
  184. package/scripts/restore-runtime-assets.mjs +21 -0
  185. package/README.md +0 -3
@@ -0,0 +1,263 @@
1
+ import { SpanStatusCode } from '@opentelemetry/api';
2
+ import { repositoryKey } from 'doistbot-issue-routing';
3
+ import { logger } from '../../core/logging.js';
4
+ import { withPhase } from '../../core/tracing.js';
5
+ import { DefaultLoopEffects } from './fix-loop/effects.js';
6
+ import { IssueFixRecovery } from './fix-loop/recovery.js';
7
+ import { runFixLoop } from './fix-loop/runners.js';
8
+ import { buildFixAssessmentSkipReason, shouldAttemptFix } from './fix-attempt.js';
9
+ import { fixSource } from './fix-context.js';
10
+ import { FIX_LOOP_FAILED_REASON_PREFIX } from './fix-failure-reasons.js';
11
+ function fallbackRepository(input) {
12
+ return (input.fixAssessment.targetRepo ??
13
+ (input.clonedRepos.length === 1
14
+ ? input.clonedRepos[0].repository.fullName
15
+ : `${input.ctx.owner}/${input.ctx.repo}`));
16
+ }
17
+ function resolveFixTarget(input) {
18
+ if (input.clonedRepos.length === 0) {
19
+ return {
20
+ ok: false,
21
+ repository: fallbackRepository(input),
22
+ reason: 'No cloned repositories available for fix attempt.',
23
+ };
24
+ }
25
+ if (input.fixAssessment.targetRepo) {
26
+ const targetRepo = input.clonedRepos.find((repo) => repositoryKey(repo.repository.fullName) ===
27
+ repositoryKey(input.fixAssessment.targetRepo));
28
+ if (targetRepo) {
29
+ return { ok: true, targetRepo };
30
+ }
31
+ return {
32
+ ok: false,
33
+ repository: fallbackRepository(input),
34
+ reason: `Unable to resolve a unique target repository from assessment: ${input.fixAssessment.targetRepo}`,
35
+ };
36
+ }
37
+ if (input.clonedRepos.length === 1) {
38
+ return { ok: true, targetRepo: input.clonedRepos[0] };
39
+ }
40
+ return {
41
+ ok: false,
42
+ repository: fallbackRepository(input),
43
+ reason: 'Unable to resolve a unique target repository from assessment: missing targetRepo',
44
+ };
45
+ }
46
+ function stateLogMeta(state) {
47
+ return {
48
+ kind: state.kind,
49
+ ...(state.kind !== 'AWAITING_FIRST_FIX' && 'iteration' in state
50
+ ? { iteration: state.iteration }
51
+ : {}),
52
+ ...('branch' in state && state.branch !== undefined ? { branch: state.branch } : {}),
53
+ ...('prNumber' in state && state.prNumber !== undefined
54
+ ? { pr_number: state.prNumber }
55
+ : {}),
56
+ };
57
+ }
58
+ function logDryRunTransition({ from, event, to }) {
59
+ logger.info('DRY RUN: fix loop state transition', {
60
+ task: 'fix-loop',
61
+ from: stateLogMeta(from),
62
+ event: event.kind,
63
+ to: stateLogMeta(to),
64
+ });
65
+ }
66
+ function errorReason(error) {
67
+ return error instanceof Error ? error.message : String(error);
68
+ }
69
+ function prUrlFor(targetRepo, prNumber) {
70
+ return `https://github.com/${targetRepo.repository.fullName}/pull/${prNumber}`;
71
+ }
72
+ export async function runConfiguredFix(input) {
73
+ return withPhase(input.phaseName ?? 'triage.fix-attempt', { 'gen_ai.operation.name': 'invoke_agent', 'gen_ai.agent.name': 'codex.fix' }, async (span) => {
74
+ function finish(result) {
75
+ const prCreated = 'prCreated' in result ? result.prCreated : false;
76
+ // Only a real PR number counts as PR-evidence — dry-run loop
77
+ // states can carry a placeholder prNumber of 0.
78
+ const prNumber = 'prNumber' in result && result.prNumber !== undefined && result.prNumber > 0
79
+ ? result.prNumber
80
+ : undefined;
81
+ // Dry-run skips are expected outcomes, not failures — signaled
82
+ // by the stable dryRun flag on the result, never by parsing
83
+ // the human-readable reason copy.
84
+ const isDryRunSkip = 'dryRun' in result && result.dryRun === true;
85
+ let outcome;
86
+ if (prCreated) {
87
+ outcome = 'pr-created';
88
+ }
89
+ else if (isDryRunSkip) {
90
+ outcome = 'dry-run';
91
+ }
92
+ else if (prNumber !== undefined) {
93
+ // The loop opened a PR earlier (REVIEWING/ITERATING) but
94
+ // ended needing a human: a PR exists, the autonomous
95
+ // attempt did not finish.
96
+ outcome = 'pr-opened-needs-human';
97
+ }
98
+ else if (result.attempted) {
99
+ outcome = 'attempted-no-pr';
100
+ }
101
+ else {
102
+ outcome = 'not-attempted';
103
+ }
104
+ span.setAttributes({
105
+ 'fix.attempted': result.attempted,
106
+ 'fix.repository': result.repository,
107
+ 'fix.pr_created': prCreated,
108
+ ...(prNumber !== undefined ? { 'fix.pr_number': prNumber } : {}),
109
+ 'fix.outcome': outcome,
110
+ });
111
+ // A real attempt that did not deliver a ready PR is a failure
112
+ // (including pr-opened-needs-human — someone must take over);
113
+ // a legitimate skip or a dry-run is not. withPhase does not
114
+ // force OK on a normal return, so this ERROR survives to
115
+ // phase.end.
116
+ if (result.attempted && !prCreated && !isDryRunSkip && 'reason' in result) {
117
+ span.setStatus({ code: SpanStatusCode.ERROR, message: result.reason });
118
+ }
119
+ return result;
120
+ }
121
+ const target = resolveFixTarget(input);
122
+ if (!target.ok) {
123
+ return finish({
124
+ attempted: false,
125
+ repository: target.repository,
126
+ reason: target.reason,
127
+ });
128
+ }
129
+ const targetRepo = target.targetRepo;
130
+ if (!input.retry && !shouldAttemptFix(input.fixAssessment)) {
131
+ return finish({
132
+ attempted: false,
133
+ repository: targetRepo.repository.fullName,
134
+ reason: buildFixAssessmentSkipReason(input.fixAssessment),
135
+ });
136
+ }
137
+ let recovery;
138
+ let initial;
139
+ try {
140
+ if (!input.ctx.dryRun) {
141
+ recovery = new IssueFixRecovery({
142
+ issueUrl: input.issueUrl,
143
+ ...fixSource(input.ctx),
144
+ triageOutput: input.triageOutput,
145
+ targetRepo,
146
+ githubToken: input.ctx.githubToken,
147
+ octokit: input.octokit,
148
+ bucket: process.env.LOG_SNAPSHOT_BUCKET,
149
+ });
150
+ initial = await recovery.prepare(input.retry === true);
151
+ }
152
+ }
153
+ catch (error) {
154
+ return finish({
155
+ attempted: true,
156
+ prCreated: false,
157
+ repository: targetRepo.repository.fullName,
158
+ reason: `Could not restore saved fix: ${errorReason(error)}`,
159
+ recoveryRefused: true,
160
+ ...(recovery?.existingPR ? { prNumber: recovery.existingPR } : {}),
161
+ });
162
+ }
163
+ const session = {
164
+ kind: 'fix-loop',
165
+ ctx: input.ctx,
166
+ octokit: input.octokit,
167
+ triageOutput: input.triageOutput,
168
+ fixAssessment: input.fixAssessment,
169
+ targetRepo,
170
+ issueTitle: input.issueTitle,
171
+ issueUrl: input.issueUrl,
172
+ ghToken: input.ghToken,
173
+ pushToken: input.ctx.githubToken,
174
+ assigneeReview: input.assigneeReview,
175
+ manualRetry: input.retry,
176
+ modelPhaseName: `${input.phaseName ?? 'triage.fix-attempt'}.model`,
177
+ apiKeys: {
178
+ openai: input.ctx.openaiApiKey,
179
+ ...(input.ctx.geminiApiKey !== undefined && { gemini: input.ctx.geminiApiKey }),
180
+ },
181
+ };
182
+ const effects = new DefaultLoopEffects(session, { dryRun: input.ctx.dryRun, recovery });
183
+ let recoverySaved = false;
184
+ const saveRecovery = recovery
185
+ ? async (state) => {
186
+ await recovery.beforeStep(state);
187
+ if (!recoverySaved) {
188
+ if (input.onRecoverySaved && !recovery.shared)
189
+ throw new Error('Could not save the shared fix checkpoint');
190
+ await input.onRecoverySaved?.();
191
+ recoverySaved = true;
192
+ }
193
+ }
194
+ : undefined;
195
+ let finalState;
196
+ try {
197
+ finalState = await runFixLoop({
198
+ effects,
199
+ initial,
200
+ beforeStep: saveRecovery,
201
+ beforeFinish: saveRecovery
202
+ ? (state) => effects.interruption ? Promise.resolve() : saveRecovery(state)
203
+ : undefined,
204
+ ...(input.ctx.dryRun ? { onTransition: logDryRunTransition } : {}),
205
+ });
206
+ }
207
+ catch (error) {
208
+ return finish({
209
+ attempted: true,
210
+ repository: targetRepo.repository.fullName,
211
+ prCreated: false,
212
+ reason: `${FIX_LOOP_FAILED_REASON_PREFIX} ${errorReason(error)}`,
213
+ ...(recovery && 'prNumber' in recovery.state
214
+ ? { prNumber: recovery.state.prNumber }
215
+ : {}),
216
+ ...(recovery
217
+ ? {
218
+ interruption: {
219
+ stage: recovery.state.kind === 'REVIEWING' ? 'review' : 'fix',
220
+ saved: recovery.shared,
221
+ },
222
+ }
223
+ : {}),
224
+ });
225
+ }
226
+ if (finalState.kind === 'PUSHED_AWAITING_CI') {
227
+ if (input.ctx.dryRun) {
228
+ return finish({
229
+ attempted: true,
230
+ repository: targetRepo.repository.fullName,
231
+ prCreated: false,
232
+ reason: 'Dry run: PR not created.',
233
+ dryRun: true,
234
+ });
235
+ }
236
+ const prNumber = finalState.prNumber;
237
+ const prUrl = prUrlFor(targetRepo, prNumber);
238
+ return finish({
239
+ attempted: true,
240
+ repository: targetRepo.repository.fullName,
241
+ prCreated: true,
242
+ prUrl,
243
+ prNumber,
244
+ });
245
+ }
246
+ const reason = finalState.kind === 'DONE_NEEDS_HUMAN'
247
+ ? finalState.reason
248
+ : `Fix loop ended in ${finalState.kind}`;
249
+ return finish({
250
+ attempted: true,
251
+ repository: targetRepo.repository.fullName,
252
+ prCreated: false,
253
+ reason,
254
+ ...(effects.interruption ? { interruption: effects.interruption } : {}),
255
+ // If a PR was opened earlier in the loop (REVIEWING/ITERATING reached
256
+ // before DONE_NEEDS_HUMAN), surface it so callers can distinguish a
257
+ // silent failure from an in-progress PR.
258
+ ...('prNumber' in finalState && finalState.prNumber !== undefined
259
+ ? { prNumber: finalState.prNumber }
260
+ : {}),
261
+ });
262
+ });
263
+ }
@@ -0,0 +1,2 @@
1
+ export const CODEX_CANNOT_FIX_REASON = 'Codex determined fix cannot be applied automatically.';
2
+ export const FIX_LOOP_FAILED_REASON_PREFIX = 'Fix loop failed:';