@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,12 @@
1
+ /** Skip retrieval for a trivially-short query (e.g. a one-word scope string): not worth an embed +
2
+ * a vector query, and the matches would be noise. Single source of truth — `src/review/rag.ts`
3
+ * re-exports this so the retrieval guard and the query builder can never drift apart. (#cloud-opt) */
4
+ export declare const MIN_QUERY_CHARS = 40;
5
+ export type IssueRagQueryInput = {
6
+ title: string;
7
+ body?: string | undefined;
8
+ labels?: string[] | undefined;
9
+ };
10
+ export declare function buildIssueRagQuery(input: IssueRagQueryInput): {
11
+ queryText: string;
12
+ };
@@ -0,0 +1,35 @@
1
+ // Issue-centric RAG query composition (#2320), extracted from `src/review/issue-rag-wire.ts` (#4254) so the
2
+ // gittensory-miner analyze phase can build the identical retrieval query without importing the review stack.
3
+ // Pure, string-only: the miner has no PR diff yet, so retrieval is fed from the issue's title/body/labels
4
+ // while the RAG engine itself stays unchanged (retrieveContext remains Vectorize/D1-bound in `src/review/rag.ts`
5
+ // and is intentionally NOT part of this module).
6
+ /** Skip retrieval for a trivially-short query (e.g. a one-word scope string): not worth an embed +
7
+ * a vector query, and the matches would be noise. Single source of truth — `src/review/rag.ts`
8
+ * re-exports this so the retrieval guard and the query builder can never drift apart. (#cloud-opt) */
9
+ export const MIN_QUERY_CHARS = 40;
10
+ const MAX_ISSUE_BODY_CHARS = 4000;
11
+ const MAX_ISSUE_LABELS = 20;
12
+ function cleanLabels(labels) {
13
+ if (!labels)
14
+ return [];
15
+ return labels
16
+ .map((label) => label.trim())
17
+ .filter(Boolean)
18
+ .slice(0, MAX_ISSUE_LABELS);
19
+ }
20
+ export function buildIssueRagQuery(input) {
21
+ const sections = [];
22
+ const title = input.title.trim();
23
+ if (title)
24
+ sections.push(title);
25
+ const body = (input.body ?? "").trim().slice(0, MAX_ISSUE_BODY_CHARS);
26
+ if (body)
27
+ sections.push(body);
28
+ const labels = cleanLabels(input.labels);
29
+ if (labels.length > 0)
30
+ sections.push(`Labels: ${labels.join(", ")}`);
31
+ const queryText = sections.join("\n\n").trim();
32
+ if (queryText.length < MIN_QUERY_CHARS)
33
+ return { queryText: "" };
34
+ return { queryText };
35
+ }
@@ -0,0 +1,34 @@
1
+ export type LocalScorerChangedFile = {
2
+ path: string;
3
+ previousPath?: string | undefined;
4
+ additions?: number | undefined;
5
+ deletions?: number | undefined;
6
+ status?: "added" | "modified" | "deleted" | "renamed" | "copied" | "unknown" | undefined;
7
+ binary?: boolean | undefined;
8
+ };
9
+ export type LocalScorerValidation = {
10
+ command: string;
11
+ status: "passed" | "failed" | "not_run" | "skipped" | "focused" | "unknown";
12
+ summary?: string | undefined;
13
+ durationMs?: number | undefined;
14
+ exitCode?: number | undefined;
15
+ };
16
+ export type LocalScorerResult = {
17
+ mode: "metadata_only" | "external_command" | "gittensor_root";
18
+ activeModel?: string | undefined;
19
+ sourceTokenScore?: number | undefined;
20
+ totalTokenScore?: number | undefined;
21
+ sourceLines?: number | undefined;
22
+ testTokenScore?: number | undefined;
23
+ nonCodeTokenScore?: number | undefined;
24
+ warnings?: string[] | undefined;
25
+ };
26
+ /**
27
+ * Compute token scores from changed-file metadata + the local validation results. `isCodeFile` already excludes
28
+ * tests, so source / test / non-code are disjoint. Binary files carry no token value and are dropped. A failed
29
+ * validation does not change the scores (they describe the diff) but is surfaced as a warning. Pure.
30
+ */
31
+ export declare function computeLocalScorerTokens(input: {
32
+ changedFiles: LocalScorerChangedFile[];
33
+ validation?: LocalScorerValidation[] | undefined;
34
+ }): LocalScorerResult;
@@ -0,0 +1,36 @@
1
+ // #782 deterministic local scorer, extracted from src/signals/local-scorer.ts (#4253) so the published
2
+ // gittensory-mcp / gittensory-miner CLIs and the hosted Worker share one implementation. Replicates the
3
+ // gittensor-root token-scoring view from changed-file METADATA (paths + line counts) — never source content,
4
+ // so the no-upload boundary holds and it runs in every surface. It mirrors buildScorePreview's
5
+ // source/test/non-code classification, so feeding its output back in as `localScorer` (mode external_command)
6
+ // flips the preview off metadata-only with numbers it would otherwise have derived itself.
7
+ //
8
+ // The 3 dependent type shapes are narrowly duplicated here (the issue explicitly allows this) rather than
9
+ // moving the large, Node-coupled local-branch.ts; they are structurally identical to local-branch.ts's
10
+ // definitions. isCodeFile/isTestPath are the same portable classifiers local-branch.ts already delegates to.
11
+ import { isCodeFile, isTestPath } from "./signals/test-evidence.js";
12
+ const fileLines = (file) => Math.max(0, file.additions ?? 0) + Math.max(0, file.deletions ?? 0);
13
+ /**
14
+ * Compute token scores from changed-file metadata + the local validation results. `isCodeFile` already excludes
15
+ * tests, so source / test / non-code are disjoint. Binary files carry no token value and are dropped. A failed
16
+ * validation does not change the scores (they describe the diff) but is surfaced as a warning. Pure.
17
+ */
18
+ export function computeLocalScorerTokens(input) {
19
+ const files = input.changedFiles.filter((file) => !file.binary);
20
+ const testTokenScore = files.filter((file) => isTestPath(file.path)).reduce((sum, file) => sum + fileLines(file), 0);
21
+ const sourceTokenScore = files.filter((file) => isCodeFile(file.path)).reduce((sum, file) => sum + fileLines(file), 0);
22
+ const totalTokenScore = files.reduce((sum, file) => sum + fileLines(file), 0);
23
+ const nonCodeTokenScore = Math.max(0, totalTokenScore - sourceTokenScore - testTokenScore);
24
+ const failed = (input.validation ?? []).some((entry) => entry.status === "failed");
25
+ const warnings = failed ? ["Local validation reported failures — token scores describe the diff, not a passing build."] : [];
26
+ return {
27
+ mode: "external_command",
28
+ activeModel: "gittensory-deterministic",
29
+ sourceTokenScore,
30
+ totalTokenScore,
31
+ sourceLines: Math.max(1, sourceTokenScore || totalTokenScore || 1),
32
+ testTokenScore,
33
+ nonCodeTokenScore,
34
+ ...(warnings.length > 0 ? { warnings } : {}),
35
+ };
36
+ }
@@ -0,0 +1,9 @@
1
+ import type { MetadataCandidateIssue, MetadataRankContext } from "./opportunity-metadata.js";
2
+ import type { OpportunityRankInput } from "./opportunity-ranker.js";
3
+ /**
4
+ * Return the highest-scoring metadata candidate at or above `minScore`, or `null` when none qualify.
5
+ * Non-finite thresholds return `null`. Pure — delegates to {@link rankMetadataOpportunitiesAtOrAboveScore}.
6
+ */
7
+ export declare function bestMetadataOpportunityAtOrAboveScore<T extends MetadataCandidateIssue>(candidates: readonly T[], context: MetadataRankContext, minScore: number): (T & OpportunityRankInput & {
8
+ rankScore: number;
9
+ }) | null;
@@ -0,0 +1,9 @@
1
+ import { rankMetadataOpportunitiesAtOrAboveScore } from "./metadata-min-score.js";
2
+ /**
3
+ * Return the highest-scoring metadata candidate at or above `minScore`, or `null` when none qualify.
4
+ * Non-finite thresholds return `null`. Pure — delegates to {@link rankMetadataOpportunitiesAtOrAboveScore}.
5
+ */
6
+ export function bestMetadataOpportunityAtOrAboveScore(candidates, context, minScore) {
7
+ const survivors = rankMetadataOpportunitiesAtOrAboveScore(candidates, context, minScore);
8
+ return survivors[0] ?? null;
9
+ }
@@ -0,0 +1,9 @@
1
+ import { type MetadataCandidateIssue, type MetadataRankContext } from "./opportunity-metadata.js";
2
+ import type { OpportunityRankInput } from "./opportunity-ranker.js";
3
+ /**
4
+ * Return the highest-scoring metadata candidate, or `null` when none are targetable/ranked.
5
+ * Pure — delegates to {@link rankMetadataOpportunities} for scoring and tie-breaking.
6
+ */
7
+ export declare function bestMetadataOpportunity<T extends MetadataCandidateIssue>(candidates: readonly T[], context: MetadataRankContext): (T & OpportunityRankInput & {
8
+ rankScore: number;
9
+ }) | null;
@@ -0,0 +1,9 @@
1
+ import { rankMetadataOpportunities, } from "./opportunity-metadata.js";
2
+ /**
3
+ * Return the highest-scoring metadata candidate, or `null` when none are targetable/ranked.
4
+ * Pure — delegates to {@link rankMetadataOpportunities} for scoring and tie-breaking.
5
+ */
6
+ export function bestMetadataOpportunity(candidates, context) {
7
+ const ranked = rankMetadataOpportunities(candidates, context);
8
+ return ranked[0] ?? null;
9
+ }
@@ -0,0 +1,9 @@
1
+ import { type MetadataCandidateIssue, type MetadataRankContext } from "./opportunity-metadata.js";
2
+ import type { OpportunityRankInput } from "./opportunity-ranker.js";
3
+ /**
4
+ * Rank metadata candidates and keep only those whose rank score is at or above `minScore`.
5
+ * Non-finite thresholds return an empty list. Pure — delegates to {@link rankMetadataOpportunities}.
6
+ */
7
+ export declare function rankMetadataOpportunitiesAtOrAboveScore<T extends MetadataCandidateIssue>(candidates: readonly T[], context: MetadataRankContext, minScore: number): Array<T & OpportunityRankInput & {
8
+ rankScore: number;
9
+ }>;
@@ -0,0 +1,13 @@
1
+ import { rankMetadataOpportunities, } from "./opportunity-metadata.js";
2
+ /**
3
+ * Rank metadata candidates and keep only those whose rank score is at or above `minScore`.
4
+ * Non-finite thresholds return an empty list. Pure — delegates to {@link rankMetadataOpportunities}.
5
+ */
6
+ export function rankMetadataOpportunitiesAtOrAboveScore(candidates, context, minScore) {
7
+ if (!Number.isFinite(minScore))
8
+ return [];
9
+ if (candidates.length === 0)
10
+ return [];
11
+ const threshold = Math.min(1, Math.max(0, minScore));
12
+ return rankMetadataOpportunities(candidates, context).filter((entry) => entry.rankScore >= threshold);
13
+ }
@@ -0,0 +1,9 @@
1
+ import type { MetadataCandidateIssue, MetadataRankContext } from "./opportunity-metadata.js";
2
+ import type { OpportunityRankInput } from "./opportunity-ranker.js";
3
+ /**
4
+ * Rank metadata candidates, drop entries below `minScore`, and return the top `limit` survivors.
5
+ * Non-finite limits return an empty list. Pure — delegates to {@link rankMetadataOpportunitiesAtOrAboveScore}.
6
+ */
7
+ export declare function pickTopMetadataOpportunitiesAtOrAboveScore<T extends MetadataCandidateIssue>(candidates: readonly T[], context: MetadataRankContext, minScore: number, limit: number): Array<T & OpportunityRankInput & {
8
+ rankScore: number;
9
+ }>;
@@ -0,0 +1,13 @@
1
+ import { rankMetadataOpportunitiesAtOrAboveScore } from "./metadata-min-score.js";
2
+ /**
3
+ * Rank metadata candidates, drop entries below `minScore`, and return the top `limit` survivors.
4
+ * Non-finite limits return an empty list. Pure — delegates to {@link rankMetadataOpportunitiesAtOrAboveScore}.
5
+ */
6
+ export function pickTopMetadataOpportunitiesAtOrAboveScore(candidates, context, minScore, limit) {
7
+ if (!Number.isFinite(limit))
8
+ return [];
9
+ const safeLimit = Math.max(0, Math.trunc(limit));
10
+ if (safeLimit === 0)
11
+ return [];
12
+ return rankMetadataOpportunitiesAtOrAboveScore(candidates, context, minScore).slice(0, safeLimit);
13
+ }
@@ -0,0 +1,9 @@
1
+ import { type MetadataCandidateIssue, type MetadataRankContext } from "./opportunity-metadata.js";
2
+ import type { OpportunityRankInput } from "./opportunity-ranker.js";
3
+ /**
4
+ * Rank metadata candidates and return the top `limit` entries. Non-finite or negative limits return an empty list.
5
+ * Pure — delegates to {@link rankMetadataOpportunities} for target filtering, scoring, and tie-breaking.
6
+ */
7
+ export declare function pickTopMetadataOpportunities<T extends MetadataCandidateIssue>(candidates: readonly T[], context: MetadataRankContext, limit: number): Array<T & OpportunityRankInput & {
8
+ rankScore: number;
9
+ }>;
@@ -0,0 +1,13 @@
1
+ import { rankMetadataOpportunities, } from "./opportunity-metadata.js";
2
+ /**
3
+ * Rank metadata candidates and return the top `limit` entries. Non-finite or negative limits return an empty list.
4
+ * Pure — delegates to {@link rankMetadataOpportunities} for target filtering, scoring, and tie-breaking.
5
+ */
6
+ export function pickTopMetadataOpportunities(candidates, context, limit) {
7
+ if (!Number.isFinite(limit))
8
+ return [];
9
+ const safeLimit = Math.max(0, Math.trunc(limit));
10
+ if (safeLimit === 0 || candidates.length === 0)
11
+ return [];
12
+ return rankMetadataOpportunities(candidates, context).slice(0, safeLimit);
13
+ }
@@ -0,0 +1,43 @@
1
+ import { type PromptPacket } from "../prompt-packet.js";
2
+ import type { FeasibilityGateResult, FeasibilityVerdict } from "../feasibility.js";
3
+ /** Fixed on-disk filename for the per-attempt acceptance-criteria document written into the attempt worktree. */
4
+ export declare const ACCEPTANCE_CRITERIA_FILENAME = "acceptance-criteria.json";
5
+ /** Schema version of the serialized document; bump on any field-shape change. */
6
+ export declare const ACCEPTANCE_CRITERIA_VERSION = 1;
7
+ /** Inputs to the composer: the sanitized prompt packet and the feasibility verdict for this attempt. */
8
+ export type AcceptanceCriteriaInput = {
9
+ promptPacket: PromptPacket;
10
+ feasibility: FeasibilityGateResult;
11
+ };
12
+ /** The composed, immutable per-attempt success bar. All fields are read-only; the document is frozen once built. */
13
+ export type AcceptanceCriteria = {
14
+ readonly version: number;
15
+ readonly verdict: FeasibilityVerdict;
16
+ /** Whether this attempt is authorized to start (and therefore the file should be written): `verdict === "go"`. */
17
+ readonly writable: boolean;
18
+ readonly taskBrief: string;
19
+ readonly constraints: string;
20
+ readonly feasibilityNotes: string;
21
+ readonly retrievalContext: string;
22
+ readonly feasibilitySummary: string;
23
+ readonly avoidReasons: readonly string[];
24
+ readonly raiseReasons: readonly string[];
25
+ };
26
+ /**
27
+ * Only a `go` feasibility verdict authorizes the attempt to start, so only `go` gets an acceptance-criteria file
28
+ * written to the worktree. `raise`/`avoid` should be handled upstream (the attempt does not begin) — this predicate
29
+ * is the single source of truth for that gate.
30
+ */
31
+ export declare function shouldWriteAcceptanceCriteria(verdict: FeasibilityVerdict): boolean;
32
+ /**
33
+ * Pure builder: compose a {@link PromptPacket} and a {@link FeasibilityGateResult} into one immutable
34
+ * acceptance-criteria document. Text fields are re-sanitized with {@link sanitizePromptPacketField} (idempotent),
35
+ * and the returned document is deep-frozen so it cannot be mutated for the lifetime of the attempt.
36
+ */
37
+ export declare function buildAcceptanceCriteria(input: AcceptanceCriteriaInput): AcceptanceCriteria;
38
+ /**
39
+ * Deterministic canonical JSON serialization of a built document: fixed key order (independent of the input
40
+ * object's own key order) plus a trailing newline, so a checksum recorded alongside the file stays byte-stable
41
+ * across processes. `JSON.parse` round-trips it back to the same field values.
42
+ */
43
+ export declare function serializeAcceptanceCriteria(doc: AcceptanceCriteria): string;
@@ -0,0 +1,77 @@
1
+ import { sanitizePromptPacketField } from "../prompt-packet.js";
2
+ // Acceptance-criteria composer (#4271). Before a coding-agent driver (#4262's interface; #4266/#4267's
3
+ // implementations) starts editing, the miner pins down — immutably, so the agent cannot quietly redefine its own
4
+ // success bar mid-attempt — what "done" means for this attempt. This module is the pure composition step: it folds
5
+ // the two already-shipped Phase 2 primitives — the sanitized `PromptPacket` (prompt-packet.ts, the analyze→coding-
6
+ // agent "boundary membrane") and the `FeasibilityGateResult` go/raise/avoid verdict (feasibility.ts) — into one
7
+ // document. Producing the document is this module's job; actually writing it into the attempt's worktree is the
8
+ // worktree primitive's (#4269), and handing it to the driver is the driver interface's (#4262) — neither is here,
9
+ // so this stays pure and side-effect-free like the rest of gittensory-engine.
10
+ //
11
+ // DECISIONS this file makes (per the issue's open questions):
12
+ // - Serialization format: JSON, not markdown. The acceptance criteria are an immutable, checksum-verifiable success
13
+ // bar consumed by tooling (a self-review step must be able to prove the target did not move across iterations), so
14
+ // a deterministic, canonically-ordered JSON document beats prose. `serializeAcceptanceCriteria` emits stable-key-
15
+ // order JSON with a trailing newline so a checksum recorded alongside stays byte-stable.
16
+ // - Filename: a single fixed name, `ACCEPTANCE_CRITERIA_FILENAME`.
17
+ // - Immutability: the built document is deep-frozen (`Object.freeze`, arrays copied+frozen) so it cannot be mutated
18
+ // in-memory for the lifetime of the attempt; the byte-stable serialization is what a caller checksums on disk.
19
+ // - Written only on `go`: a `raise`/`avoid` verdict means the attempt should not start, so no criteria file is
20
+ // written. The builder still returns a document (with `writable: false`) so a caller can log *why* it was skipped;
21
+ // `shouldWriteAcceptanceCriteria` is the gate for the write itself.
22
+ //
23
+ // Redaction is delegated to `sanitizePromptPacketField` rather than re-implemented: this document is exactly as
24
+ // exposed to a prompt-injectable coding-agent session as the prompt packet, so it gets the same scrub (idempotent
25
+ // on already-sanitized packet text).
26
+ /** Fixed on-disk filename for the per-attempt acceptance-criteria document written into the attempt worktree. */
27
+ export const ACCEPTANCE_CRITERIA_FILENAME = "acceptance-criteria.json";
28
+ /** Schema version of the serialized document; bump on any field-shape change. */
29
+ export const ACCEPTANCE_CRITERIA_VERSION = 1;
30
+ /**
31
+ * Only a `go` feasibility verdict authorizes the attempt to start, so only `go` gets an acceptance-criteria file
32
+ * written to the worktree. `raise`/`avoid` should be handled upstream (the attempt does not begin) — this predicate
33
+ * is the single source of truth for that gate.
34
+ */
35
+ export function shouldWriteAcceptanceCriteria(verdict) {
36
+ return verdict === "go";
37
+ }
38
+ /**
39
+ * Pure builder: compose a {@link PromptPacket} and a {@link FeasibilityGateResult} into one immutable
40
+ * acceptance-criteria document. Text fields are re-sanitized with {@link sanitizePromptPacketField} (idempotent),
41
+ * and the returned document is deep-frozen so it cannot be mutated for the lifetime of the attempt.
42
+ */
43
+ export function buildAcceptanceCriteria(input) {
44
+ const { promptPacket, feasibility } = input;
45
+ return Object.freeze({
46
+ version: ACCEPTANCE_CRITERIA_VERSION,
47
+ verdict: feasibility.verdict,
48
+ writable: shouldWriteAcceptanceCriteria(feasibility.verdict),
49
+ taskBrief: sanitizePromptPacketField(promptPacket.taskBrief),
50
+ constraints: sanitizePromptPacketField(promptPacket.constraints),
51
+ feasibilityNotes: sanitizePromptPacketField(promptPacket.feasibilityNotes),
52
+ retrievalContext: sanitizePromptPacketField(promptPacket.retrievalContext),
53
+ feasibilitySummary: feasibility.summary,
54
+ avoidReasons: Object.freeze([...feasibility.avoidReasons]),
55
+ raiseReasons: Object.freeze([...feasibility.raiseReasons]),
56
+ });
57
+ }
58
+ /**
59
+ * Deterministic canonical JSON serialization of a built document: fixed key order (independent of the input
60
+ * object's own key order) plus a trailing newline, so a checksum recorded alongside the file stays byte-stable
61
+ * across processes. `JSON.parse` round-trips it back to the same field values.
62
+ */
63
+ export function serializeAcceptanceCriteria(doc) {
64
+ const ordered = {
65
+ version: doc.version,
66
+ verdict: doc.verdict,
67
+ writable: doc.writable,
68
+ taskBrief: doc.taskBrief,
69
+ constraints: doc.constraints,
70
+ feasibilityNotes: doc.feasibilityNotes,
71
+ retrievalContext: doc.retrievalContext,
72
+ feasibilitySummary: doc.feasibilitySummary,
73
+ avoidReasons: [...doc.avoidReasons],
74
+ raiseReasons: [...doc.raiseReasons],
75
+ };
76
+ return `${JSON.stringify(ordered, null, 2)}\n`;
77
+ }
@@ -0,0 +1,39 @@
1
+ import type { CodingAgentDriver } from "./coding-agent-driver.js";
2
+ /**
3
+ * Opaque hook registration forwarded verbatim to the SDK session (`Options['hooks']` — keyed by hook event name,
4
+ * e.g. `PreToolUse`). Typed loosely on purpose: the hook contract belongs to the SDK and to the policy module that
5
+ * registers the hooks, not to this driver.
6
+ */
7
+ export type AgentSdkHooks = Record<string, unknown>;
8
+ /** The exact option subset this driver puts on a `query()` session. */
9
+ export type AgentSdkQueryOptions = {
10
+ cwd: string;
11
+ maxTurns: number;
12
+ permissionMode: "acceptEdits";
13
+ hooks?: AgentSdkHooks | undefined;
14
+ };
15
+ /**
16
+ * Injected `query()`-shaped function — mirrors the injected-`SpawnFn` testability convention from #4262/#4266 so
17
+ * tests drive the driver with a fake async-iterable and CI never makes a real model call. Messages are consumed
18
+ * structurally (plain records), matching how the defensive fold below reads them.
19
+ */
20
+ export type AgentSdkQueryFn = (input: {
21
+ prompt: string;
22
+ options: AgentSdkQueryOptions;
23
+ }) => AsyncIterable<Record<string, unknown>>;
24
+ export type CreateAgentSdkDriverOptions = {
25
+ /** Injected `query()` loop; defaults to the real `@anthropic-ai/claude-agent-sdk` export. */
26
+ query?: AgentSdkQueryFn | undefined;
27
+ /** Forwarded verbatim to the SDK session — the #2343 `PreToolUse` interception point. */
28
+ hooks?: AgentSdkHooks | undefined;
29
+ /** Injected changed-file enumerator; defaults to git diff over the worktree. */
30
+ listChangedFiles?: ((cwd: string) => Promise<string[]>) | undefined;
31
+ };
32
+ /**
33
+ * A `CodingAgentDriver` that runs the attempt through an in-process Agent-SDK `query()` session in the task's
34
+ * working directory. Mirrors the CLI-subprocess driver's contract: structured failure results (never a throw),
35
+ * `changedFiles` reported only on success (the CLI driver cannot know them on failure, and #4296's parity suite
36
+ * holds both implementations to the same shape), and `task.instructions` forwarded verbatim as the prompt — the
37
+ * acceptance-criteria document already lives inside the worktree at `task.acceptanceCriteriaPath` (#4271).
38
+ */
39
+ export declare function createAgentSdkCodingAgentDriver(options?: CreateAgentSdkDriverOptions): CodingAgentDriver;
@@ -0,0 +1,162 @@
1
+ // Agent-SDK `CodingAgentDriver` (#4267): the second implementation of the #4262 seam, driving the coding agent
2
+ // in-process via `@anthropic-ai/claude-agent-sdk`'s `query()` async-iterable loop instead of shelling out to a CLI
3
+ // binary (#4266). The streamed SDK message/tool-use events are folded into the shared `CodingAgentDriverResult`
4
+ // right here — no SDK-specific event type leaks into the interface, so the iterate-loop orchestrator (#2333) can
5
+ // swap this driver for the CLI-subprocess one with no caller-side changes.
6
+ //
7
+ // The SDK session's hook surface is deliberately NOT encapsulated: callers pass `hooks` (e.g. a `PreToolUse`
8
+ // matcher, #2343's stated attachment point) and this driver forwards them verbatim onto the `query()` options, so
9
+ // house-rule enforcement can intercept every tool call before execution without this module knowing the rules.
10
+ import { execFile } from "node:child_process";
11
+ import { promisify } from "node:util";
12
+ import { redactSecrets } from "../subprocess-env.js";
13
+ const execFileAsync = promisify(execFile);
14
+ /** Ceiling for any redacted free text surfaced on the result (error detail, summary) — one named place. */
15
+ const MAX_REDACTED_TEXT_LENGTH = 500;
16
+ /* v8 ignore start -- real-SDK path: imports @anthropic-ai/claude-agent-sdk and spawns a live session; tests
17
+ inject a fake AgentSdkQueryFn instead (same convention as the CLI driver's injected SpawnFn). */
18
+ const defaultQuery = (input) => {
19
+ async function* stream() {
20
+ const sdk = (await import("@anthropic-ai/claude-agent-sdk"));
21
+ for await (const message of sdk.query({ prompt: input.prompt, options: input.options })) {
22
+ yield message;
23
+ }
24
+ }
25
+ return stream();
26
+ };
27
+ function asRecord(value) {
28
+ return typeof value === "object" && value !== null ? value : null;
29
+ }
30
+ async function listWorktreeChangedFiles(cwd) {
31
+ const [tracked, untracked] = await Promise.all([
32
+ execFileAsync("git", ["-C", cwd, "diff", "--name-only", "HEAD", "--"]),
33
+ execFileAsync("git", ["-C", cwd, "ls-files", "--others", "--exclude-standard"]),
34
+ ]);
35
+ return Array.from(new Set([tracked.stdout, untracked.stdout]
36
+ .join("\n")
37
+ .split(/\r?\n/)
38
+ .map((file) => file.trim())
39
+ .filter(Boolean)));
40
+ }
41
+ /** Fold one assistant message's content blocks into the transcript/changed-file accumulators. */
42
+ function foldAssistantMessage(message, transcript, changedFiles) {
43
+ const content = asRecord(message.message)?.content;
44
+ if (!Array.isArray(content))
45
+ return;
46
+ for (const rawBlock of content) {
47
+ const block = asRecord(rawBlock);
48
+ if (!block)
49
+ continue;
50
+ if (block.type === "text" && typeof block.text === "string") {
51
+ transcript.push(block.text);
52
+ }
53
+ else if (block.type === "tool_use" && typeof block.name === "string") {
54
+ const filePath = asRecord(block.input)?.file_path;
55
+ if (typeof filePath === "string")
56
+ changedFiles.add(filePath);
57
+ }
58
+ }
59
+ }
60
+ /**
61
+ * A `CodingAgentDriver` that runs the attempt through an in-process Agent-SDK `query()` session in the task's
62
+ * working directory. Mirrors the CLI-subprocess driver's contract: structured failure results (never a throw),
63
+ * `changedFiles` reported only on success (the CLI driver cannot know them on failure, and #4296's parity suite
64
+ * holds both implementations to the same shape), and `task.instructions` forwarded verbatim as the prompt — the
65
+ * acceptance-criteria document already lives inside the worktree at `task.acceptanceCriteriaPath` (#4271).
66
+ */
67
+ export function createAgentSdkCodingAgentDriver(options = {}) {
68
+ const query = options.query ?? defaultQuery;
69
+ const listChangedFiles = options.listChangedFiles ?? listWorktreeChangedFiles;
70
+ return {
71
+ async run(task) {
72
+ const transcriptParts = [];
73
+ const changedFiles = new Set();
74
+ let resultMessage = null;
75
+ try {
76
+ const stream = query({
77
+ prompt: task.instructions,
78
+ options: {
79
+ cwd: task.workingDirectory,
80
+ maxTurns: task.maxTurns,
81
+ // Same edit-permission scope as the CLI-subprocess driver (#4266): `--permission-mode acceptEdits`
82
+ // there, `acceptEdits` here — file edits run unattended inside the scoped worktree, nothing broader.
83
+ permissionMode: "acceptEdits",
84
+ hooks: options.hooks,
85
+ },
86
+ });
87
+ for await (const message of stream) {
88
+ if (message.type === "assistant") {
89
+ foldAssistantMessage(message, transcriptParts, changedFiles);
90
+ }
91
+ else if (message.type === "result") {
92
+ resultMessage = message;
93
+ }
94
+ }
95
+ }
96
+ catch (error) {
97
+ const detail = redactSecrets(error instanceof Error ? error.message : String(error)).slice(0, MAX_REDACTED_TEXT_LENGTH);
98
+ return {
99
+ ok: false,
100
+ changedFiles: [],
101
+ summary: "agent sdk session threw",
102
+ transcript: redactSecrets(transcriptParts.join("\n")),
103
+ error: `agent_sdk_thrown: ${detail}`,
104
+ };
105
+ }
106
+ const turnsUsed = typeof resultMessage?.num_turns === "number" ? resultMessage.num_turns : undefined;
107
+ // Real dollar cost: the SDK's own SDKResultSuccess/SDKResultError message types both declare
108
+ // `total_cost_usd: number` unconditionally -- present whenever a result message arrived at all, success
109
+ // or not (the session was billed either way), absent only when the stream produced no result message.
110
+ const costUsd = typeof resultMessage?.total_cost_usd === "number" ? resultMessage.total_cost_usd : undefined;
111
+ const resultText = typeof resultMessage?.result === "string" ? redactSecrets(resultMessage.result) : "";
112
+ const transcript = redactSecrets([...transcriptParts, ...(resultText ? [resultText] : [])].join("\n"));
113
+ // A stream that ends without a `result` frame is a protocol failure, not a silent success.
114
+ if (!resultMessage) {
115
+ return {
116
+ ok: false,
117
+ changedFiles: [],
118
+ summary: "agent sdk stream ended without a result message",
119
+ transcript,
120
+ error: "agent_sdk_no_result",
121
+ };
122
+ }
123
+ if (resultMessage.subtype !== "success" || resultMessage.is_error === true) {
124
+ const subtype = typeof resultMessage.subtype === "string" ? resultMessage.subtype : "unknown";
125
+ return {
126
+ ok: false,
127
+ changedFiles: [],
128
+ summary: "agent sdk session did not complete successfully",
129
+ transcript,
130
+ turnsUsed,
131
+ costUsd,
132
+ error: `agent_sdk_${subtype === "success" ? "errored" : subtype}`,
133
+ };
134
+ }
135
+ let worktreeChangedFiles;
136
+ try {
137
+ worktreeChangedFiles = await listChangedFiles(task.workingDirectory);
138
+ }
139
+ catch (error) {
140
+ const detail = redactSecrets(error instanceof Error ? error.message : String(error)).slice(0, MAX_REDACTED_TEXT_LENGTH);
141
+ return {
142
+ ok: false,
143
+ changedFiles: [],
144
+ summary: "agent sdk changed-file enumeration failed",
145
+ transcript,
146
+ turnsUsed,
147
+ costUsd,
148
+ error: `agent_sdk_changed_files_unavailable: ${detail}`,
149
+ };
150
+ }
151
+ const allChangedFiles = Array.from(new Set([...changedFiles, ...worktreeChangedFiles]));
152
+ return {
153
+ ok: true,
154
+ changedFiles: allChangedFiles,
155
+ summary: resultText.slice(0, MAX_REDACTED_TEXT_LENGTH) || `coding agent completed with ${allChangedFiles.length} changed file(s)`,
156
+ transcript,
157
+ turnsUsed,
158
+ costUsd,
159
+ };
160
+ },
161
+ };
162
+ }
@@ -0,0 +1,29 @@
1
+ import type { CodingAgentExecutionMode } from "./coding-agent-mode.js";
2
+ export declare const ATTEMPT_LOG_EVENT_TYPES: readonly ["attempt_started", "attempt_tool_edit", "attempt_shadow", "attempt_succeeded", "attempt_failed", "attempt_aborted"];
3
+ export type AttemptLogEventType = (typeof ATTEMPT_LOG_EVENT_TYPES)[number];
4
+ export type AttemptLogEvent = {
5
+ eventType: AttemptLogEventType;
6
+ attemptId: string;
7
+ actionClass: string;
8
+ mode: CodingAgentExecutionMode;
9
+ reason: string;
10
+ payload?: Record<string, unknown> | undefined;
11
+ };
12
+ export type NormalizedAttemptLogEvent = {
13
+ eventType: AttemptLogEventType;
14
+ attemptId: string;
15
+ actionClass: string;
16
+ mode: CodingAgentExecutionMode;
17
+ reason: string;
18
+ payloadJson: string;
19
+ };
20
+ /** Validate and normalize an attempt-log row before append. Fail-closed on unknown types/modes. */
21
+ export declare function normalizeAttemptLogEvent(input: unknown): NormalizedAttemptLogEvent;
22
+ /** Serialize normalized events as JSONL (one attempt's trace). Pure. */
23
+ export declare function formatAttemptLogJsonl(events: readonly NormalizedAttemptLogEvent[]): string;
24
+ /** In-memory appender for tests and local tooling — production persistence uses `gittensory-miner/lib/attempt-log.js`. */
25
+ export declare function createAttemptLogBuffer(): {
26
+ append: (event: AttemptLogEvent) => NormalizedAttemptLogEvent;
27
+ events: () => readonly NormalizedAttemptLogEvent[];
28
+ jsonl: () => string;
29
+ };