@loopover/engine 1.0.0

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 (272) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/LICENSE +661 -0
  3. package/README.md +676 -0
  4. package/dist/advisory/gate-advisory.d.ts +151 -0
  5. package/dist/advisory/gate-advisory.js +512 -0
  6. package/dist/ai-policy-map.d.ts +97 -0
  7. package/dist/ai-policy-map.js +377 -0
  8. package/dist/ams-policy-spec.d.ts +56 -0
  9. package/dist/ams-policy-spec.js +165 -0
  10. package/dist/calibration-dashboard.d.ts +23 -0
  11. package/dist/calibration-dashboard.js +65 -0
  12. package/dist/calibration-trend.d.ts +40 -0
  13. package/dist/calibration-trend.js +60 -0
  14. package/dist/contributor-fit.d.ts +24 -0
  15. package/dist/contributor-fit.js +50 -0
  16. package/dist/discovery-index-contract.d.ts +74 -0
  17. package/dist/discovery-index-contract.js +184 -0
  18. package/dist/discovery-soft-claim.d.ts +35 -0
  19. package/dist/discovery-soft-claim.js +50 -0
  20. package/dist/duplicate-winner.d.ts +57 -0
  21. package/dist/duplicate-winner.js +98 -0
  22. package/dist/feasibility.d.ts +27 -0
  23. package/dist/feasibility.js +86 -0
  24. package/dist/finding-severity-calibration.d.ts +116 -0
  25. package/dist/finding-severity-calibration.js +457 -0
  26. package/dist/fleet-run-manifest.d.ts +41 -0
  27. package/dist/fleet-run-manifest.js +151 -0
  28. package/dist/focus-manifest/guidance.d.ts +21 -0
  29. package/dist/focus-manifest/guidance.js +222 -0
  30. package/dist/focus-manifest.d.ts +826 -0
  31. package/dist/focus-manifest.js +2730 -0
  32. package/dist/gate-verdict-calibration.d.ts +111 -0
  33. package/dist/gate-verdict-calibration.js +411 -0
  34. package/dist/github/constants.d.ts +1 -0
  35. package/dist/github/constants.js +1 -0
  36. package/dist/github/sanitize-public-comment.d.ts +1 -0
  37. package/dist/github/sanitize-public-comment.js +26 -0
  38. package/dist/goal-model.d.ts +7 -0
  39. package/dist/goal-model.js +87 -0
  40. package/dist/governor/action-mode.d.ts +55 -0
  41. package/dist/governor/action-mode.js +74 -0
  42. package/dist/governor/budget-cap.d.ts +49 -0
  43. package/dist/governor/budget-cap.js +34 -0
  44. package/dist/governor/chokepoint.d.ts +58 -0
  45. package/dist/governor/chokepoint.js +269 -0
  46. package/dist/governor/kill-switch.d.ts +38 -0
  47. package/dist/governor/kill-switch.js +62 -0
  48. package/dist/governor/rate-limit.d.ts +40 -0
  49. package/dist/governor/rate-limit.js +63 -0
  50. package/dist/governor/reputation-throttle.d.ts +50 -0
  51. package/dist/governor/reputation-throttle.js +94 -0
  52. package/dist/governor/run-halt.d.ts +39 -0
  53. package/dist/governor/run-halt.js +83 -0
  54. package/dist/governor/self-plagiarism.d.ts +47 -0
  55. package/dist/governor/self-plagiarism.js +200 -0
  56. package/dist/governor/write-rate-limit.d.ts +52 -0
  57. package/dist/governor/write-rate-limit.js +132 -0
  58. package/dist/governor-ledger.d.ts +25 -0
  59. package/dist/governor-ledger.js +91 -0
  60. package/dist/index.d.ts +99 -0
  61. package/dist/index.js +120 -0
  62. package/dist/issue-plan-decomposition.d.ts +18 -0
  63. package/dist/issue-plan-decomposition.js +56 -0
  64. package/dist/issue-rag-query.d.ts +12 -0
  65. package/dist/issue-rag-query.js +35 -0
  66. package/dist/local-scorer.d.ts +34 -0
  67. package/dist/local-scorer.js +36 -0
  68. package/dist/metadata-best-min-score.d.ts +9 -0
  69. package/dist/metadata-best-min-score.js +9 -0
  70. package/dist/metadata-best-pick.d.ts +9 -0
  71. package/dist/metadata-best-pick.js +9 -0
  72. package/dist/metadata-min-score.d.ts +9 -0
  73. package/dist/metadata-min-score.js +13 -0
  74. package/dist/metadata-top-min-score.d.ts +9 -0
  75. package/dist/metadata-top-min-score.js +13 -0
  76. package/dist/metadata-top-pick.d.ts +9 -0
  77. package/dist/metadata-top-pick.js +13 -0
  78. package/dist/miner/acceptance-criteria.d.ts +43 -0
  79. package/dist/miner/acceptance-criteria.js +77 -0
  80. package/dist/miner/agent-sdk-driver.d.ts +39 -0
  81. package/dist/miner/agent-sdk-driver.js +162 -0
  82. package/dist/miner/attempt-log.d.ts +29 -0
  83. package/dist/miner/attempt-log.js +100 -0
  84. package/dist/miner/attempt-metering.d.ts +39 -0
  85. package/dist/miner/attempt-metering.js +67 -0
  86. package/dist/miner/cli-subprocess-driver.d.ts +68 -0
  87. package/dist/miner/cli-subprocess-driver.js +272 -0
  88. package/dist/miner/coding-agent-driver.d.ts +33 -0
  89. package/dist/miner/coding-agent-driver.js +36 -0
  90. package/dist/miner/coding-agent-invoke.d.ts +11 -0
  91. package/dist/miner/coding-agent-invoke.js +80 -0
  92. package/dist/miner/coding-agent-mode.d.ts +25 -0
  93. package/dist/miner/coding-agent-mode.js +43 -0
  94. package/dist/miner/driver-factory.d.ts +82 -0
  95. package/dist/miner/driver-factory.js +169 -0
  96. package/dist/miner/harness-submission-trigger.d.ts +37 -0
  97. package/dist/miner/harness-submission-trigger.js +46 -0
  98. package/dist/miner/iterate-loop.d.ts +97 -0
  99. package/dist/miner/iterate-loop.js +278 -0
  100. package/dist/miner/iterate-policy.d.ts +98 -0
  101. package/dist/miner/iterate-policy.js +98 -0
  102. package/dist/miner/lint-guard.d.ts +50 -0
  103. package/dist/miner/lint-guard.js +68 -0
  104. package/dist/miner/local-write-tools.d.ts +81 -0
  105. package/dist/miner/local-write-tools.js +113 -0
  106. package/dist/miner/loop-reentry-policy.d.ts +36 -0
  107. package/dist/miner/loop-reentry-policy.js +53 -0
  108. package/dist/miner/repo-map.d.ts +51 -0
  109. package/dist/miner/repo-map.js +268 -0
  110. package/dist/miner/self-review-adapter.d.ts +106 -0
  111. package/dist/miner/self-review-adapter.js +83 -0
  112. package/dist/miner/submission-gate.d.ts +40 -0
  113. package/dist/miner/submission-gate.js +89 -0
  114. package/dist/miner/worktree-allocator.d.ts +59 -0
  115. package/dist/miner/worktree-allocator.js +60 -0
  116. package/dist/miner/worktree-pool.d.ts +55 -0
  117. package/dist/miner/worktree-pool.js +65 -0
  118. package/dist/miner-goal-lane-fit.d.ts +18 -0
  119. package/dist/miner-goal-lane-fit.js +76 -0
  120. package/dist/miner-goal-spec.d.ts +139 -0
  121. package/dist/miner-goal-spec.js +281 -0
  122. package/dist/miner-prediction-metrics.d.ts +16 -0
  123. package/dist/miner-prediction-metrics.js +61 -0
  124. package/dist/miner-telemetry.d.ts +32 -0
  125. package/dist/miner-telemetry.js +87 -0
  126. package/dist/objective-anchor.d.ts +125 -0
  127. package/dist/objective-anchor.js +401 -0
  128. package/dist/opportunity-competition.d.ts +6 -0
  129. package/dist/opportunity-competition.js +29 -0
  130. package/dist/opportunity-freshness.d.ts +18 -0
  131. package/dist/opportunity-freshness.js +55 -0
  132. package/dist/opportunity-metadata.d.ts +53 -0
  133. package/dist/opportunity-metadata.js +204 -0
  134. package/dist/opportunity-ranker.d.ts +46 -0
  135. package/dist/opportunity-ranker.js +74 -0
  136. package/dist/pairwise-calibration.d.ts +49 -0
  137. package/dist/pairwise-calibration.js +106 -0
  138. package/dist/phase7-calibration-loop.d.ts +136 -0
  139. package/dist/phase7-calibration-loop.js +437 -0
  140. package/dist/plan-blocked.d.ts +6 -0
  141. package/dist/plan-blocked.js +24 -0
  142. package/dist/plan-completed.d.ts +5 -0
  143. package/dist/plan-completed.js +6 -0
  144. package/dist/plan-completion.d.ts +6 -0
  145. package/dist/plan-completion.js +7 -0
  146. package/dist/plan-empty.d.ts +5 -0
  147. package/dist/plan-empty.js +6 -0
  148. package/dist/plan-export.d.ts +27 -0
  149. package/dist/plan-export.js +86 -0
  150. package/dist/plan-failure.d.ts +5 -0
  151. package/dist/plan-failure.js +6 -0
  152. package/dist/plan-overall-status.d.ts +6 -0
  153. package/dist/plan-overall-status.js +25 -0
  154. package/dist/plan-pending.d.ts +5 -0
  155. package/dist/plan-pending.js +6 -0
  156. package/dist/plan-progress-complete.d.ts +7 -0
  157. package/dist/plan-progress-complete.js +10 -0
  158. package/dist/plan-ready.d.ts +6 -0
  159. package/dist/plan-ready.js +12 -0
  160. package/dist/plan-running.d.ts +5 -0
  161. package/dist/plan-running.js +6 -0
  162. package/dist/plan-skipped.d.ts +5 -0
  163. package/dist/plan-skipped.js +6 -0
  164. package/dist/plan-step-count.d.ts +5 -0
  165. package/dist/plan-step-count.js +6 -0
  166. package/dist/plan-step-stats.d.ts +5 -0
  167. package/dist/plan-step-stats.js +6 -0
  168. package/dist/plan-templates.d.ts +25 -0
  169. package/dist/plan-templates.js +107 -0
  170. package/dist/plan-terminated.d.ts +6 -0
  171. package/dist/plan-terminated.js +9 -0
  172. package/dist/portfolio/non-convergence.d.ts +37 -0
  173. package/dist/portfolio/non-convergence.js +57 -0
  174. package/dist/portfolio/queue.d.ts +30 -0
  175. package/dist/portfolio/queue.js +150 -0
  176. package/dist/predicted-gate.d.ts +103 -0
  177. package/dist/predicted-gate.js +218 -0
  178. package/dist/prompt-packet.d.ts +11 -0
  179. package/dist/prompt-packet.js +34 -0
  180. package/dist/ranked-opportunity-best-min-score.d.ts +8 -0
  181. package/dist/ranked-opportunity-best-min-score.js +9 -0
  182. package/dist/ranked-opportunity-best-pick.d.ts +8 -0
  183. package/dist/ranked-opportunity-best-pick.js +9 -0
  184. package/dist/ranked-opportunity-min-score.d.ts +8 -0
  185. package/dist/ranked-opportunity-min-score.js +13 -0
  186. package/dist/ranked-opportunity-top-min-score.d.ts +8 -0
  187. package/dist/ranked-opportunity-top-min-score.js +13 -0
  188. package/dist/review/advisory-ai-routing-config.d.ts +8 -0
  189. package/dist/review/advisory-ai-routing-config.js +40 -0
  190. package/dist/review/check-names.d.ts +1 -0
  191. package/dist/review/check-names.js +1 -0
  192. package/dist/review/cla-check.d.ts +49 -0
  193. package/dist/review/cla-check.js +71 -0
  194. package/dist/review/diff-file-priority.d.ts +1 -0
  195. package/dist/review/diff-file-priority.js +12 -0
  196. package/dist/review/enrichment-analyzer-names.d.ts +3 -0
  197. package/dist/review/enrichment-analyzer-names.js +63 -0
  198. package/dist/review/guardrail-config.d.ts +10 -0
  199. package/dist/review/guardrail-config.js +62 -0
  200. package/dist/review/linked-issue-hard-rules-config.d.ts +4 -0
  201. package/dist/review/linked-issue-hard-rules-config.js +79 -0
  202. package/dist/review/linked-issue-label-propagation.d.ts +9 -0
  203. package/dist/review/linked-issue-label-propagation.js +114 -0
  204. package/dist/review/pre-merge-checks.d.ts +28 -0
  205. package/dist/review/pre-merge-checks.js +66 -0
  206. package/dist/review/review-thread-findings.d.ts +1 -0
  207. package/dist/review/review-thread-findings.js +1 -0
  208. package/dist/review/safe-url.d.ts +5 -0
  209. package/dist/review/safe-url.js +137 -0
  210. package/dist/review/screenshot-table-gate.d.ts +87 -0
  211. package/dist/review/screenshot-table-gate.js +340 -0
  212. package/dist/review/unlinked-issue-guardrail-config.d.ts +9 -0
  213. package/dist/review/unlinked-issue-guardrail-config.js +44 -0
  214. package/dist/reviewer-consensus-calibration.d.ts +113 -0
  215. package/dist/reviewer-consensus-calibration.js +493 -0
  216. package/dist/reward-risk.d.ts +223 -0
  217. package/dist/reward-risk.js +618 -0
  218. package/dist/scoring/label-match.d.ts +4 -0
  219. package/dist/scoring/label-match.js +145 -0
  220. package/dist/scoring/model.d.ts +31 -0
  221. package/dist/scoring/model.js +157 -0
  222. package/dist/scoring/pending-pr-scenarios.d.ts +44 -0
  223. package/dist/scoring/pending-pr-scenarios.js +145 -0
  224. package/dist/scoring/preview.d.ts +246 -0
  225. package/dist/scoring/preview.js +1108 -0
  226. package/dist/scoring/types.d.ts +131 -0
  227. package/dist/scoring/types.js +7 -0
  228. package/dist/settings/auto-close-exempt.d.ts +10 -0
  229. package/dist/settings/auto-close-exempt.js +58 -0
  230. package/dist/settings/autonomy.d.ts +33 -0
  231. package/dist/settings/autonomy.js +80 -0
  232. package/dist/settings/command-authorization.d.ts +49 -0
  233. package/dist/settings/command-authorization.js +222 -0
  234. package/dist/settings/contributor-blacklist.d.ts +17 -0
  235. package/dist/settings/contributor-blacklist.js +86 -0
  236. package/dist/settings/moderation-rules.d.ts +51 -0
  237. package/dist/settings/moderation-rules.js +104 -0
  238. package/dist/settings/pr-type-label.d.ts +70 -0
  239. package/dist/settings/pr-type-label.js +149 -0
  240. package/dist/signals/change-guardrail.d.ts +49 -0
  241. package/dist/signals/change-guardrail.js +164 -0
  242. package/dist/signals/check-summary.d.ts +9 -0
  243. package/dist/signals/check-summary.js +12 -0
  244. package/dist/signals/contributor-calibration.d.ts +23 -0
  245. package/dist/signals/contributor-calibration.js +47 -0
  246. package/dist/signals/duplicate-winner.d.ts +9 -0
  247. package/dist/signals/duplicate-winner.js +9 -0
  248. package/dist/signals/path-matchers.d.ts +36 -0
  249. package/dist/signals/path-matchers.js +371 -0
  250. package/dist/signals/predicted-gate-engine.d.ts +62 -0
  251. package/dist/signals/predicted-gate-engine.js +895 -0
  252. package/dist/signals/preflight-limits.d.ts +14 -0
  253. package/dist/signals/preflight-limits.js +14 -0
  254. package/dist/signals/slop.d.ts +56 -0
  255. package/dist/signals/slop.js +324 -0
  256. package/dist/signals/test-evidence.d.ts +25 -0
  257. package/dist/signals/test-evidence.js +108 -0
  258. package/dist/subprocess-env.d.ts +23 -0
  259. package/dist/subprocess-env.js +74 -0
  260. package/dist/track-record-summary.d.ts +92 -0
  261. package/dist/track-record-summary.js +301 -0
  262. package/dist/types/manifest-deps-types.d.ts +514 -0
  263. package/dist/types/manifest-deps-types.js +4 -0
  264. package/dist/types/predicted-gate-types.d.ts +338 -0
  265. package/dist/types/predicted-gate-types.js +3 -0
  266. package/dist/types/reward-risk-types.d.ts +319 -0
  267. package/dist/types/reward-risk-types.js +11 -0
  268. package/dist/utils/json.d.ts +1 -0
  269. package/dist/utils/json.js +3 -0
  270. package/dist/version.d.ts +2 -0
  271. package/dist/version.js +3 -0
  272. package/package.json +80 -0
@@ -0,0 +1,80 @@
1
+ // Mode-gated `CodingAgentDriver` invocation (#4313). Single call site that applies `CodingAgentExecutionMode`,
2
+ // writes attempt-log events (#4294), and never spawns the underlying agent unless mode is `live`.
3
+ import { codingAgentModeExecutes, } from "./coding-agent-mode.js";
4
+ function shadowSummary(task) {
5
+ return `dry-run: would invoke coding agent in ${task.workingDirectory} (≤${task.maxTurns} turns, criteria ${task.acceptanceCriteriaPath})`;
6
+ }
7
+ /**
8
+ * Invoke a driver under the resolved execution mode. `paused` and `dry_run` never call `driver.run()` — see
9
+ * `coding-agent-mode.ts` for the dry-run tradeoff documentation.
10
+ */
11
+ export async function invokeCodingAgentDriver(driver, mode, task, log) {
12
+ const base = { attemptId: task.attemptId, actionClass: "codegen", mode };
13
+ if (mode === "paused") {
14
+ log?.append({
15
+ eventType: "attempt_aborted",
16
+ ...base,
17
+ reason: "coding_agent_paused",
18
+ payload: { workingDirectory: task.workingDirectory },
19
+ });
20
+ return {
21
+ ok: false,
22
+ changedFiles: [],
23
+ summary: "coding agent paused",
24
+ error: "coding_agent_paused",
25
+ };
26
+ }
27
+ if (!codingAgentModeExecutes(mode)) {
28
+ log?.append({
29
+ eventType: "attempt_shadow",
30
+ ...base,
31
+ reason: "dry-run: would invoke coding agent without spawning underlying session",
32
+ payload: {
33
+ workingDirectory: task.workingDirectory,
34
+ acceptanceCriteriaPath: task.acceptanceCriteriaPath,
35
+ maxTurns: task.maxTurns,
36
+ },
37
+ });
38
+ return {
39
+ ok: true,
40
+ changedFiles: [],
41
+ summary: shadowSummary(task),
42
+ turnsUsed: 0,
43
+ };
44
+ }
45
+ log?.append({
46
+ eventType: "attempt_started",
47
+ ...base,
48
+ reason: "live coding-agent invocation",
49
+ payload: { workingDirectory: task.workingDirectory, maxTurns: task.maxTurns },
50
+ });
51
+ try {
52
+ const result = await driver.run(task);
53
+ log?.append({
54
+ eventType: result.ok ? "attempt_succeeded" : "attempt_failed",
55
+ ...base,
56
+ reason: result.summary,
57
+ payload: {
58
+ changedFiles: [...result.changedFiles],
59
+ turnsUsed: result.turnsUsed ?? null,
60
+ error: result.error ?? null,
61
+ },
62
+ });
63
+ return result;
64
+ }
65
+ catch (error) {
66
+ const message = error instanceof Error ? error.message : "unknown error";
67
+ log?.append({
68
+ eventType: "attempt_failed",
69
+ ...base,
70
+ reason: message,
71
+ payload: { thrown: true },
72
+ });
73
+ return {
74
+ ok: false,
75
+ changedFiles: [],
76
+ summary: "coding agent invocation failed",
77
+ error: message,
78
+ };
79
+ }
80
+ }
@@ -0,0 +1,25 @@
1
+ /** Whether a coding-agent attempt actually spawns/queries the underlying session. */
2
+ export type CodingAgentExecutionMode = "paused" | "dry_run" | "live";
3
+ /** Global kill-switch for miner coding-agent invocations (`MINER_CODING_AGENT_PAUSED`). Same truthy-string
4
+ * convention as `isGlobalAgentPause` (`AGENT_ACTIONS_PAUSED`). */
5
+ export declare function isGlobalMinerCodingAgentPause(env: {
6
+ MINER_CODING_AGENT_PAUSED?: string | undefined;
7
+ }): boolean;
8
+ /** THE single gate before invoking a `CodingAgentDriver`. Precedence (safest wins): global OR per-config pause
9
+ * → `paused`; else per-config dry-run → `dry_run`; else `live`. Pure. */
10
+ export declare function resolveCodingAgentExecutionMode(input: {
11
+ globalPaused: boolean;
12
+ agentPaused?: boolean | null | undefined;
13
+ agentDryRun?: boolean | null | undefined;
14
+ }): CodingAgentExecutionMode;
15
+ /** Resolve mode from env + optional per-run overrides (mirrors `resolveAgentActionMode` call sites). */
16
+ export declare function resolveCodingAgentModeFromConfig(config: {
17
+ env?: {
18
+ MINER_CODING_AGENT_PAUSED?: string | undefined;
19
+ } | undefined;
20
+ agentPaused?: boolean | null | undefined;
21
+ agentDryRun?: boolean | null | undefined;
22
+ }): CodingAgentExecutionMode;
23
+ /** True only for `live` — the only mode that performs a real driver `run()`. `paused` does nothing; `dry_run`
24
+ * records a shadow result without spawning the underlying agent. */
25
+ export declare function codingAgentModeExecutes(mode: CodingAgentExecutionMode): boolean;
@@ -0,0 +1,43 @@
1
+ // Coding-agent execution mode (#4313). Mirrors `AgentActionMode` at `src/settings/agent-execution.ts:23-48`:
2
+ // three states (`paused` | `dry_run` | `live`), deny-toward-safety precedence (global/per-repo pause beats
3
+ // dry-run beats live), and a single `codingAgentModeExecutes` boolean for callers.
4
+ //
5
+ // Why three states instead of a boolean? The maintainer-action layer already proved the shape: `paused` is an
6
+ // operator kill-switch (attempt never starts), `dry_run` is an observe/shadow path (record intent without the
7
+ // expensive/dangerous work), and `live` is the only mode that actually spawns/queries the underlying agent.
8
+ // A boolean would collapse `paused` into `dry_run`, losing the distinction between "halt entirely" and
9
+ // "shadow what would happen".
10
+ //
11
+ // Dry-run semantics for a CODING agent (#4313): at the driver invocation boundary, `dry_run` is a **pure no-op**
12
+ // — the underlying CLI/SDK session is never spawned. Tradeoff documented here:
13
+ // • Chosen: never spawn (cheapest, safest, mirrors `agentActionModeExecutes` skipping GitHub mutations).
14
+ // • Deferred alternative: run inside an isolated worktree (#4269) but suppress commit/push/PR downstream so
15
+ // file edits remain inspectable — requires the worktree primitive and orchestrator gating on create-phase
16
+ // steps; the attempt log records `attempt_shadow` with mode=`dry_run` so either path stays auditable.
17
+ /** Global kill-switch for miner coding-agent invocations (`MINER_CODING_AGENT_PAUSED`). Same truthy-string
18
+ * convention as `isGlobalAgentPause` (`AGENT_ACTIONS_PAUSED`). */
19
+ export function isGlobalMinerCodingAgentPause(env) {
20
+ return /^(1|true|yes|on)$/i.test(env.MINER_CODING_AGENT_PAUSED ?? "");
21
+ }
22
+ /** THE single gate before invoking a `CodingAgentDriver`. Precedence (safest wins): global OR per-config pause
23
+ * → `paused`; else per-config dry-run → `dry_run`; else `live`. Pure. */
24
+ export function resolveCodingAgentExecutionMode(input) {
25
+ if (input.globalPaused || input.agentPaused === true)
26
+ return "paused";
27
+ if (input.agentDryRun === true)
28
+ return "dry_run";
29
+ return "live";
30
+ }
31
+ /** Resolve mode from env + optional per-run overrides (mirrors `resolveAgentActionMode` call sites). */
32
+ export function resolveCodingAgentModeFromConfig(config) {
33
+ return resolveCodingAgentExecutionMode({
34
+ globalPaused: isGlobalMinerCodingAgentPause(config.env ?? {}),
35
+ agentPaused: config.agentPaused,
36
+ agentDryRun: config.agentDryRun,
37
+ });
38
+ }
39
+ /** True only for `live` — the only mode that performs a real driver `run()`. `paused` does nothing; `dry_run`
40
+ * records a shadow result without spawning the underlying agent. */
41
+ export function codingAgentModeExecutes(mode) {
42
+ return mode === "live";
43
+ }
@@ -0,0 +1,82 @@
1
+ import { type CodingAgentDriver } from "./coding-agent-driver.js";
2
+ import { type AttemptLogSink } from "./coding-agent-invoke.js";
3
+ import { type CodingAgentExecutionMode } from "./coding-agent-mode.js";
4
+ import type { CodingAgentDriverResult, CodingAgentDriverTask } from "./coding-agent-driver.js";
5
+ import { type LintGuardOptions, type LintGuardResult } from "./lint-guard.js";
6
+ import { type CliSubprocessSpawnFn } from "./cli-subprocess-driver.js";
7
+ import { type AgentSdkHooks, type AgentSdkQueryFn } from "./agent-sdk-driver.js";
8
+ /** Provider names the factory resolves: the two concrete drivers from #4266/#4267 (`claude-cli`/`codex-cli`
9
+ * spawn the respective CLI; `agent-sdk` runs in-process via the Agent SDK) plus the `noop` stub. All are
10
+ * locally-authenticated (no API-key env requirement), mirroring how `isConfiguredSelfHostProvider` treats
11
+ * `claude-code`/`codex` as always-configured. */
12
+ export declare const CODING_AGENT_DRIVER_NAMES: readonly ["noop", "claude-cli", "codex-cli", "agent-sdk"];
13
+ export type CodingAgentDriverName = (typeof CODING_AGENT_DRIVER_NAMES)[number];
14
+ /** Per-provider env keys for coding-agent configuration (mirrors `SELF_HOST_REVIEWER_MODEL_ENV`). Every key
15
+ * declared here is CONSUMED by `createCodingAgentDriver` below — a declared-but-unread entry is dead,
16
+ * misleading config-as-code surface. Deliberately NOT declared: a max-turns key (the turn budget is task-level
17
+ * input — `CodingAgentDriverTask.maxTurns` — set by the orchestrator per attempt, not per-provider config) and
18
+ * an agent-sdk model key (the SDK session uses the account/CLI default; it exposes no model option on the
19
+ * driver today). */
20
+ export declare const CODING_AGENT_DRIVER_CONFIG_ENV: Readonly<Record<CodingAgentDriverName, {
21
+ model?: string;
22
+ timeoutMs?: string;
23
+ }>>;
24
+ /** True when `name` is a known, configured coding-agent driver. Unknown names → false (deny-by-default). */
25
+ export declare function isConfiguredCodingAgentDriver(name: string, _env: Record<string, string | undefined>): boolean;
26
+ export declare function resolveConfiguredCodingAgentDriverNames(env: Record<string, string | undefined>): string[];
27
+ /** Primary-then-fallback resolution over `MINER_CODING_AGENT_PROVIDER`'s comma-separated list (the same
28
+ * fallback-chain semantic `AiRunOptions.fallback` gives reviewers): the FIRST configured name wins; unknown
29
+ * names are skipped (deny-by-default), and an all-unknown/empty list resolves to undefined so the caller
30
+ * fails closed rather than falling through to some implicit default driver. */
31
+ export declare function resolveFirstConfiguredCodingAgentDriverName(env: Record<string, string | undefined>): string | undefined;
32
+ export type CreateCodingAgentDriverOptions = {
33
+ providerName: string;
34
+ env?: Record<string, string | undefined> | undefined;
35
+ /** Test seam — inject a fake driver instead of constructing the named provider. */
36
+ driver?: CodingAgentDriver | undefined;
37
+ /** Subprocess runner for the CLI providers (`claude-cli`/`codex-cli`). REQUIRED for those providers — the
38
+ * engine package ships no default spawn, so constructing a CLI driver without one fails closed rather than
39
+ * producing a driver that can never run. */
40
+ spawn?: CliSubprocessSpawnFn | undefined;
41
+ /** Optional injected `query()` loop for the `agent-sdk` provider (defaults to the real SDK import). */
42
+ query?: AgentSdkQueryFn | undefined;
43
+ /** Forwarded to the `agent-sdk` provider's session (#2343's PreToolUse interception point). */
44
+ hooks?: AgentSdkHooks | undefined;
45
+ /** Optional injected changed-file enumerator for the `agent-sdk` provider (defaults to a real `git diff`/
46
+ * `git ls-files` read over the task's working directory — a test harness pointing at a non-git fake path
47
+ * should inject one, same seam as `query`). */
48
+ listChangedFiles?: ((cwd: string) => Promise<string[]>) | undefined;
49
+ /** Known secret values the CLI providers strip from surfaced output, on top of the token-shape patterns. */
50
+ knownSecrets?: readonly string[] | undefined;
51
+ };
52
+ /** Resolve a concrete driver for `providerName`. Throws on unknown/unconfigured providers (fail-closed). */
53
+ export declare function createCodingAgentDriver(options: CreateCodingAgentDriverOptions): CodingAgentDriver;
54
+ export type RunCodingAgentAttemptOptions = {
55
+ providerName: string;
56
+ env?: Record<string, string | undefined> | undefined;
57
+ agentPaused?: boolean | null | undefined;
58
+ agentDryRun?: boolean | null | undefined;
59
+ task: CodingAgentDriverTask;
60
+ log?: AttemptLogSink | undefined;
61
+ driver?: CodingAgentDriver | undefined;
62
+ /** Provider dependencies, forwarded to `createCodingAgentDriver` (see `CreateCodingAgentDriverOptions`). */
63
+ spawn?: CliSubprocessSpawnFn | undefined;
64
+ query?: AgentSdkQueryFn | undefined;
65
+ hooks?: AgentSdkHooks | undefined;
66
+ /** Optional injected changed-file enumerator for the `agent-sdk` provider (see `CreateCodingAgentDriverOptions`). */
67
+ listChangedFiles?: ((cwd: string) => Promise<string[]>) | undefined;
68
+ knownSecrets?: readonly string[] | undefined;
69
+ /** When supplied, the driver result is run through the lint guard (#4276) before being returned, so a
70
+ * live coding-agent edit that fails its own package's typecheck/node --check never reads as `ok: true`. */
71
+ lintGuard?: LintGuardOptions | undefined;
72
+ };
73
+ /** End-to-end entry: resolve mode from config, pick the driver, invoke under mode gating + attempt log, then
74
+ * (when `lintGuard` is supplied) run the changed files through the lint guard before the caller sees the result. */
75
+ export declare function runCodingAgentAttempt(options: RunCodingAgentAttemptOptions): Promise<{
76
+ mode: CodingAgentExecutionMode;
77
+ result: CodingAgentDriverResult & {
78
+ lintGuard?: LintGuardResult;
79
+ };
80
+ }>;
81
+ /** Exported for parity tests — wraps a driver without changing its behavior (identity helper). */
82
+ export declare function createFakeCodingAgentDriverForFactory(): CodingAgentDriver;
@@ -0,0 +1,169 @@
1
+ // CodingAgentDriver factory + provider-style config resolution (#4289). Mirrors `src/selfhost/ai-config.ts:41-74`:
2
+ // parse a comma-separated provider list, validate each name against what is actually configured, deny-by-default
3
+ // on unknown/unconfigured names, and expose a model/effort config map analogous to `SELF_HOST_REVIEWER_MODEL_ENV`.
4
+ import { createFakeCodingAgentDriver, createNoopCodingAgentDriver, } from "./coding-agent-driver.js";
5
+ import { invokeCodingAgentDriver, } from "./coding-agent-invoke.js";
6
+ import { codingAgentModeExecutes, resolveCodingAgentModeFromConfig, } from "./coding-agent-mode.js";
7
+ import { guardCodingAgentDriverResult } from "./lint-guard.js";
8
+ import { createCliSubprocessCodingAgentDriver, defaultClaudeCliArgs, defaultCodexCliArgs, } from "./cli-subprocess-driver.js";
9
+ import { createAgentSdkCodingAgentDriver, } from "./agent-sdk-driver.js";
10
+ /** Provider names the factory resolves: the two concrete drivers from #4266/#4267 (`claude-cli`/`codex-cli`
11
+ * spawn the respective CLI; `agent-sdk` runs in-process via the Agent SDK) plus the `noop` stub. All are
12
+ * locally-authenticated (no API-key env requirement), mirroring how `isConfiguredSelfHostProvider` treats
13
+ * `claude-code`/`codex` as always-configured. */
14
+ export const CODING_AGENT_DRIVER_NAMES = Object.freeze(["noop", "claude-cli", "codex-cli", "agent-sdk"]);
15
+ /** Per-provider env keys for coding-agent configuration (mirrors `SELF_HOST_REVIEWER_MODEL_ENV`). Every key
16
+ * declared here is CONSUMED by `createCodingAgentDriver` below — a declared-but-unread entry is dead,
17
+ * misleading config-as-code surface. Deliberately NOT declared: a max-turns key (the turn budget is task-level
18
+ * input — `CodingAgentDriverTask.maxTurns` — set by the orchestrator per attempt, not per-provider config) and
19
+ * an agent-sdk model key (the SDK session uses the account/CLI default; it exposes no model option on the
20
+ * driver today). */
21
+ export const CODING_AGENT_DRIVER_CONFIG_ENV = Object.freeze({
22
+ noop: {},
23
+ "claude-cli": { model: "MINER_CODING_AGENT_CLAUDE_MODEL", timeoutMs: "MINER_CODING_AGENT_TIMEOUT_MS" },
24
+ "codex-cli": { model: "MINER_CODING_AGENT_CODEX_MODEL", timeoutMs: "MINER_CODING_AGENT_TIMEOUT_MS" },
25
+ "agent-sdk": {},
26
+ });
27
+ /** `firstConfigured` (src/selfhost/ai.ts:117-134) pattern: a set-and-non-empty env value, else undefined. */
28
+ function firstConfiguredEnvValue(value) {
29
+ const trimmed = value?.trim();
30
+ return trimmed ? trimmed : undefined;
31
+ }
32
+ /** Positive-integer env parse for the CLI wall-clock ceiling; anything else defers to the driver default. */
33
+ function configuredTimeoutMs(env) {
34
+ const raw = Number(firstConfiguredEnvValue(env.MINER_CODING_AGENT_TIMEOUT_MS));
35
+ return Number.isFinite(raw) && Number.isInteger(raw) && raw > 0 ? raw : undefined;
36
+ }
37
+ function parseDriverNames(env) {
38
+ return (env.MINER_CODING_AGENT_PROVIDER ?? "")
39
+ .split(",")
40
+ .map((value) => value.trim().toLowerCase())
41
+ .filter(Boolean);
42
+ }
43
+ /** True when `name` is a known, configured coding-agent driver. Unknown names → false (deny-by-default). */
44
+ export function isConfiguredCodingAgentDriver(name, _env) {
45
+ switch (name) {
46
+ case "noop":
47
+ case "claude-cli":
48
+ case "codex-cli":
49
+ case "agent-sdk":
50
+ return true;
51
+ default:
52
+ return false;
53
+ }
54
+ }
55
+ export function resolveConfiguredCodingAgentDriverNames(env) {
56
+ return parseDriverNames(env).filter((name) => isConfiguredCodingAgentDriver(name, env));
57
+ }
58
+ /** Primary-then-fallback resolution over `MINER_CODING_AGENT_PROVIDER`'s comma-separated list (the same
59
+ * fallback-chain semantic `AiRunOptions.fallback` gives reviewers): the FIRST configured name wins; unknown
60
+ * names are skipped (deny-by-default), and an all-unknown/empty list resolves to undefined so the caller
61
+ * fails closed rather than falling through to some implicit default driver. */
62
+ export function resolveFirstConfiguredCodingAgentDriverName(env) {
63
+ return resolveConfiguredCodingAgentDriverNames(env)[0];
64
+ }
65
+ /** Build a CLI provider's argv: the driver's own real default argv contract, with the CONFIGURED model
66
+ * flag inserted at the RIGHT position for that command — this is where that declared config is actually
67
+ * consumed. claude's `--model` is a top-level flag (prefixed before everything else is fine); codex's
68
+ * `-m`/`--model` is scoped to the `exec` subcommand (per `codex exec --help`) and must be inserted AFTER
69
+ * the leading `"exec"` token, not before it — a single shared prefix-everything scheme would silently
70
+ * misparse for codex. */
71
+ function buildCliArgsWithConfiguredModel(command, model) {
72
+ if (model === undefined)
73
+ return undefined;
74
+ if (command === "claude") {
75
+ return (task) => ["--model", model, ...defaultClaudeCliArgs(task)];
76
+ }
77
+ return (task) => {
78
+ const [subcommand, ...rest] = defaultCodexCliArgs(task);
79
+ return [subcommand, "--model", model, ...rest];
80
+ };
81
+ }
82
+ function createCliProvider(command, modelEnvKey, options, env) {
83
+ if (!options.spawn) {
84
+ // Fail-closed (resolveAutonomy's deny-by-default precedent): a CLI provider without a spawn dependency is
85
+ // unconfigured in the way that matters — never hand back a driver whose every run() would throw.
86
+ throw new Error(`unconfigured_coding_agent_driver_missing_spawn:${command}-cli`);
87
+ }
88
+ if (options.hooks !== undefined) {
89
+ // CLI subprocess providers have no hook-registration surface. If a caller supplied house-rule hooks, treating
90
+ // them as "best effort" would silently run prompt-influenced local code without the policy the caller asked
91
+ // for, so fail closed until a CLI-native enforcement layer exists.
92
+ throw new Error(`unsupported_coding_agent_driver_hooks:${command}-cli`);
93
+ }
94
+ const model = firstConfiguredEnvValue(env[modelEnvKey]);
95
+ const timeoutMs = configuredTimeoutMs(env);
96
+ const buildArgs = buildCliArgsWithConfiguredModel(command, model);
97
+ return createCliSubprocessCodingAgentDriver({
98
+ command,
99
+ spawn: options.spawn,
100
+ parentEnv: env,
101
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
102
+ ...(buildArgs !== undefined ? { buildArgs } : {}),
103
+ ...(options.knownSecrets !== undefined ? { knownSecrets: options.knownSecrets } : {}),
104
+ });
105
+ }
106
+ /** Resolve a concrete driver for `providerName`. Throws on unknown/unconfigured providers (fail-closed). */
107
+ export function createCodingAgentDriver(options) {
108
+ if (options.driver)
109
+ return options.driver;
110
+ const name = options.providerName.trim().toLowerCase();
111
+ const env = options.env ?? {};
112
+ if (!isConfiguredCodingAgentDriver(name, env)) {
113
+ throw new Error(`unconfigured_coding_agent_driver:${name}`);
114
+ }
115
+ switch (name) {
116
+ case "noop":
117
+ return createNoopCodingAgentDriver();
118
+ case "claude-cli":
119
+ return createCliProvider("claude", "MINER_CODING_AGENT_CLAUDE_MODEL", options, env);
120
+ case "codex-cli":
121
+ return createCliProvider("codex", "MINER_CODING_AGENT_CODEX_MODEL", options, env);
122
+ case "agent-sdk":
123
+ // No model/timeout config today — the SDK session uses the account default; hooks/query are optional.
124
+ return createAgentSdkCodingAgentDriver({
125
+ ...(options.query !== undefined ? { query: options.query } : {}),
126
+ ...(options.hooks !== undefined ? { hooks: options.hooks } : {}),
127
+ ...(options.listChangedFiles !== undefined ? { listChangedFiles: options.listChangedFiles } : {}),
128
+ });
129
+ /* v8 ignore next 2 -- isConfiguredCodingAgentDriver already rejects unknown names before this switch. */
130
+ default:
131
+ throw new Error(`unconfigured_coding_agent_driver:${name}`);
132
+ }
133
+ }
134
+ function resolveDriverForAttempt(options, mode) {
135
+ if (options.driver)
136
+ return options.driver;
137
+ // Dry-run/paused attempts never call `driver.run()` (see coding-agent-driver.md lifecycle). Constructing a
138
+ // CLI provider here would require spawn/query deps even though they would never be used — use the noop stub
139
+ // as a stand-in so shadow/paused attempts stay dependency-free (#4289 / gate fix for #4593).
140
+ if (!codingAgentModeExecutes(mode))
141
+ return createNoopCodingAgentDriver();
142
+ return createCodingAgentDriver({
143
+ providerName: options.providerName,
144
+ env: options.env,
145
+ spawn: options.spawn,
146
+ query: options.query,
147
+ hooks: options.hooks,
148
+ listChangedFiles: options.listChangedFiles,
149
+ knownSecrets: options.knownSecrets,
150
+ });
151
+ }
152
+ /** End-to-end entry: resolve mode from config, pick the driver, invoke under mode gating + attempt log, then
153
+ * (when `lintGuard` is supplied) run the changed files through the lint guard before the caller sees the result. */
154
+ export async function runCodingAgentAttempt(options) {
155
+ const mode = resolveCodingAgentModeFromConfig({
156
+ env: options.env,
157
+ agentPaused: options.agentPaused,
158
+ agentDryRun: options.agentDryRun,
159
+ });
160
+ const driver = resolveDriverForAttempt(options, mode);
161
+ const result = await invokeCodingAgentDriver(driver, mode, options.task, options.log);
162
+ if (!options.lintGuard)
163
+ return { mode, result };
164
+ return { mode, result: await guardCodingAgentDriverResult(result, options.lintGuard) };
165
+ }
166
+ /** Exported for parity tests — wraps a driver without changing its behavior (identity helper). */
167
+ export function createFakeCodingAgentDriverForFactory() {
168
+ return createFakeCodingAgentDriver();
169
+ }
@@ -0,0 +1,37 @@
1
+ import type { MinerKillSwitchScope } from "../governor/kill-switch.js";
2
+ import type { HandoffPacket } from "./iterate-policy.js";
3
+ import type { SelfReviewSlopBand } from "./self-review-adapter.js";
4
+ import { type SubmissionGateMode } from "./submission-gate.js";
5
+ export declare const DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS = 3;
6
+ export type HarnessSubmissionTriggerCandidate = {
7
+ /** Forwarded to `shouldSubmit`'s own kill-switch check (#2339) -- this function does not ALSO short-circuit
8
+ * on it separately (that would be a second, duplicated check, exactly what #2339's "single shared helper,
9
+ * not duplicated per call site" deliverable warns against); `shouldSubmit` is always still called (the
10
+ * circuit breaker above is the only thing that skips it), and its own kill-switch guard covers this. */
11
+ killSwitchScope: MinerKillSwitchScope;
12
+ handoffPacket: HandoffPacket;
13
+ slopThreshold: SelfReviewSlopBand;
14
+ mode: SubmissionGateMode;
15
+ /** Caller-computed count of CONSECUTIVE `allow: false` submission-gate decisions so far this session,
16
+ * ending with (and NOT including) this candidate's own about-to-be-computed decision. The caller owns this
17
+ * tally (mirrors #2338's caller-supplied `consecutiveDisengagements`); a `true` decision anywhere resets it
18
+ * to 0 for the caller's NEXT candidate. */
19
+ consecutiveGateBlocks: number;
20
+ maxConsecutiveGateBlocks?: number | undefined;
21
+ };
22
+ export type HarnessSubmissionTriggerDecision = {
23
+ allow: boolean;
24
+ reasons: string[];
25
+ /** True only when the SESSION-LEVEL circuit breaker (not a normal per-candidate block) is what stopped this
26
+ * decision -- the caller's own driving loop should treat this as "pause the run entirely pending human
27
+ * review," distinct from an ordinary `allow: false` a later, different candidate might still clear. */
28
+ circuitBreakerTripped: boolean;
29
+ };
30
+ /**
31
+ * THE final gate before a real call site may build the `open_pr` local-write spec from a passing
32
+ * `HandoffPacket`. Pure; identical inputs always yield the identical decision. Checks the session-level
33
+ * circuit breaker FIRST (never consults `shouldSubmit` once tripped), then re-checks `shouldSubmit`'s own
34
+ * predicted-gate-pass + slop-under-threshold signals against the handoff's own verdict -- defense in depth,
35
+ * not a blind trust of the fact that a handoff happened at all.
36
+ */
37
+ export declare function evaluateHarnessSubmissionTrigger(candidate: HarnessSubmissionTriggerCandidate): HarnessSubmissionTriggerDecision;
@@ -0,0 +1,46 @@
1
+ // Harness submission-gate wiring (#2337): connects the gated-submission decision function (`shouldSubmit`,
2
+ // submission-gate.ts, #2336) to the ACTUAL driving loop's own handoff signal -- iterate-loop.ts's (#2333)
3
+ // `HandoffPacket`, the exact object produced the moment a real run's self-review reaches a clean predicted-gate
4
+ // pass. This is the live actuation wiring itself: the trigger surface the safety-tier system reserves for
5
+ // maintainer review, since a bug here means an autonomous write happens when it should not have.
6
+ //
7
+ // WHAT THIS DOES NOT DO: build or invoke the actual `open_pr` local-write spec (`buildOpenPrSpec`,
8
+ // `src/mcp/local-write-tools.ts`) -- that lives in the private root `src/` tree, unreachable from this
9
+ // portable package for the same cross-package-boundary reason self-review-adapter.ts's slop injection exists
10
+ // (#2334's own module doc comment). This function's OUTPUT (`allow: true`) is the gate a real call site
11
+ // (root-side server/CLI integration, wired in a later issue) consults before it builds that spec itself --
12
+ // mirrors #2336's own "gated exclusively through this function" scoping.
13
+ //
14
+ // THE SESSION-LEVEL CIRCUIT BREAKER: distinct from `shouldSubmit`'s own per-candidate signal checks
15
+ // (predicted-gate pass, slop-under-threshold), this issue's own deliverable calls for "N consecutive
16
+ // submission-gate allow:false decisions in one session pauses the run entirely pending human review, never
17
+ // silently loops trying to force a pass." Checked FIRST, before ever consulting `shouldSubmit` -- once tripped,
18
+ // no candidate can un-trip it (that requires a human clearing the session's own consecutive-block tally),
19
+ // unlike a per-candidate block which a later, different candidate can clear on its own merits.
20
+ import { shouldSubmit } from "./submission-gate.js";
21
+ export const DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS = 3;
22
+ /**
23
+ * THE final gate before a real call site may build the `open_pr` local-write spec from a passing
24
+ * `HandoffPacket`. Pure; identical inputs always yield the identical decision. Checks the session-level
25
+ * circuit breaker FIRST (never consults `shouldSubmit` once tripped), then re-checks `shouldSubmit`'s own
26
+ * predicted-gate-pass + slop-under-threshold signals against the handoff's own verdict -- defense in depth,
27
+ * not a blind trust of the fact that a handoff happened at all.
28
+ */
29
+ export function evaluateHarnessSubmissionTrigger(candidate) {
30
+ const maxConsecutiveGateBlocks = candidate.maxConsecutiveGateBlocks ?? DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS;
31
+ if (candidate.consecutiveGateBlocks >= maxConsecutiveGateBlocks) {
32
+ return {
33
+ allow: false,
34
+ circuitBreakerTripped: true,
35
+ reasons: [`circuit_breaker_tripped_after_consecutive_blocks:${candidate.consecutiveGateBlocks}>=${maxConsecutiveGateBlocks}`],
36
+ };
37
+ }
38
+ const gateDecision = shouldSubmit({
39
+ killSwitchScope: candidate.killSwitchScope,
40
+ predictedGateVerdict: candidate.handoffPacket.selfReviewVerdict.predictedGateVerdict,
41
+ slopAssessment: candidate.handoffPacket.selfReviewVerdict.slopAssessment,
42
+ slopThreshold: candidate.slopThreshold,
43
+ mode: candidate.mode,
44
+ });
45
+ return { allow: gateDecision.allow, reasons: gateDecision.reasons, circuitBreakerTripped: false };
46
+ }
@@ -0,0 +1,97 @@
1
+ import type { CodingAgentDriver, CodingAgentDriverResult } from "./coding-agent-driver.js";
2
+ import { type CodingAgentExecutionMode } from "./coding-agent-mode.js";
3
+ import type { AttemptLogEvent } from "./attempt-log.js";
4
+ import { type SelfReviewAdapterDeps, type SelfReviewContext } from "./self-review-adapter.js";
5
+ import { type IterateLoopDecision, type HandoffPacket } from "./iterate-policy.js";
6
+ import { type AttemptBudget, type AttemptBudgetAxis, type AttemptMeterTotals } from "./attempt-metering.js";
7
+ /** Everything one call to {@link runIterateLoop} needs, aside from the injected {@link IterateLoopDeps}.
8
+ * Identity/context fields mirror self-review-adapter.ts's `AttemptDiffState`/`SelfReviewContext` exactly --
9
+ * the caller assembles these from whatever Phase 2 plan/acceptance-criteria packet exists; that packet's
10
+ * exact combined shape is explicitly out of scope for this issue. */
11
+ export type IterateLoopInput = {
12
+ attemptId: string;
13
+ workingDirectory: string;
14
+ acceptanceCriteriaPath: string;
15
+ instructions: string;
16
+ /** Resolved by the caller (e.g. the Governor chokepoint / action-mode resolution, #2340/#2342) -- this loop
17
+ * does not re-derive execution mode itself, only records whatever mode it is told. */
18
+ mode: CodingAgentExecutionMode;
19
+ /** Hard ceiling on iteration count, enforced every iteration via iterate-policy.ts. `<= 0` abandons before
20
+ * the first driver invocation. */
21
+ maxIterations: number;
22
+ /** Per-iteration turn budget, passed through to each `CodingAgentDriverTask`. */
23
+ maxTurnsPerIteration: number;
24
+ /** Optional cumulative budget ceiling(s), evaluated every iteration against the real running totals
25
+ * (attempt-metering.ts's `AttemptMeterTotals`, #5395). Omitted means no additional ceiling beyond what
26
+ * `maxIterations * maxTurnsPerIteration` already implies. A breach on any axis (turns/costUsd/wallClockMs
27
+ * -- tokens is never real-tracked today) is a HARD, unconditional stop: checked and abandoned on BEFORE
28
+ * self-review even runs, so a same-iteration pass can never bypass the ceiling (this is deliberately NOT
29
+ * routed through iterate-policy.ts's `costCeilingReached` field, whose own precedence checks a self-review
30
+ * pass first -- see the loop body's own comment at the check site for why that ordering doesn't fit a hard
31
+ * budget ceiling). */
32
+ budget?: AttemptBudget | undefined;
33
+ repoFullName: string;
34
+ contributorLogin: string;
35
+ title: string;
36
+ body?: string | undefined;
37
+ labels?: string[] | undefined;
38
+ linkedIssues?: number[] | undefined;
39
+ authorAssociation?: string | undefined;
40
+ /** Optional branch ref for the attempt's worktree, threaded through to a passing {@link HandoffPacket}
41
+ * unchanged -- this loop does not itself manage worktrees/branches (worktree-allocator.ts's job). */
42
+ branchRef?: string | undefined;
43
+ /** Repo-level self-review context (manifest, repo record, issues, pull requests, ...) -- passed through to
44
+ * `runSelfReview` unchanged every iteration. */
45
+ reviewContext: SelfReviewContext;
46
+ /** True when the target repo (or this contributor's history with it) has signaled it does not want
47
+ * automated contributions -- resolved by the caller (AI-policy-map / rejection-state-machine), consumed
48
+ * as-is. See iterate-policy.ts's own `IterationState.rejectionSignaled` doc comment. */
49
+ rejectionSignaled: boolean;
50
+ };
51
+ export type IterateLoopDeps = {
52
+ driver: CodingAgentDriver;
53
+ runSlopAssessment: SelfReviewAdapterDeps["runSlopAssessment"];
54
+ appendAttemptLogEvent: (event: AttemptLogEvent) => void;
55
+ /** Injected clock for real per-iteration wall-clock measurement (#5395), mirroring this package's own
56
+ * injected-dependency discipline elsewhere (never a hardcoded `Date.now()` a test can't control). Defaults
57
+ * to the real `Date.now` when omitted. */
58
+ nowMs?: (() => number) | undefined;
59
+ };
60
+ /** The terminal outcomes a full loop run can end in -- never `"continue"`, which is only ever a per-iteration,
61
+ * non-terminal signal. */
62
+ export type IterateLoopOutcome = "handoff" | "abandon";
63
+ export type IterateLoopIterationRecord = {
64
+ iterationNumber: number;
65
+ driverResult: CodingAgentDriverResult;
66
+ decision: IterateLoopDecision;
67
+ };
68
+ export type IterateLoopResult = {
69
+ outcome: IterateLoopOutcome;
70
+ finalDecision: IterateLoopDecision;
71
+ /** Count of iterations that actually invoked the driver -- `0` for the `maxIterations <= 0` immediate-abandon
72
+ * case, since the driver is never invoked there. */
73
+ iterationsUsed: number;
74
+ /** Cumulative `turnsUsed` summed across every iteration that ran. */
75
+ totalTurnsUsed: number;
76
+ /** Cumulative real dollar cost summed across every iteration that ran, from each iteration's
77
+ * `CodingAgentDriverResult.costUsd`. Only the `agent-sdk` provider reports this today (the CLI-subprocess
78
+ * providers report no cost signal) -- always `0` for a provider that never reports one, never fabricated. */
79
+ totalCostUsd: number;
80
+ /** The real accumulated {@link AttemptMeterTotals} across every iteration that ran (attempt-metering.ts,
81
+ * #5395) -- a superset of `totalTurnsUsed`/`totalCostUsd` above that also carries `wallClockMs` (real,
82
+ * measured around each driver invocation) and `tokens` (always 0 today -- no driver reports a real token
83
+ * count, an honest absence rather than a fabricated number). */
84
+ finalMeterTotals: AttemptMeterTotals;
85
+ /** The budget axes breached at the point this attempt abandoned, when `input.budget` was set and at least
86
+ * one axis was at/over its ceiling -- empty when no budget was configured or none breached. */
87
+ budgetBreaches: AttemptBudgetAxis[];
88
+ iterations: readonly IterateLoopIterationRecord[];
89
+ /** Populated only when `outcome === "handoff"`. */
90
+ handoffPacket?: HandoffPacket | undefined;
91
+ };
92
+ /**
93
+ * Thin wrapper over {@link runIterateLoopCore} that attaches the real accumulated
94
+ * {@link AttemptMeterTotals}/breached axes (#5395) once, at this single always-reached return point -- see
95
+ * {@link IterateLoopCoreResult}'s own doc comment for why the core itself doesn't carry these fields.
96
+ */
97
+ export declare function runIterateLoop(input: IterateLoopInput, deps: IterateLoopDeps): Promise<IterateLoopResult>;