fifony 0.1.47 → 0.1.48

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 (89) hide show
  1. package/README.md +78 -0
  2. package/app/dist/assets/CommandPalette-CZDG20HW.js +1 -0
  3. package/app/dist/assets/{KeyboardShortcutsHelp-CqEFfGcE.js → KeyboardShortcutsHelp-TYhQc4aA.js} +1 -1
  4. package/app/dist/assets/OnboardingWizard-CQ9YmVIT.js +1 -0
  5. package/app/dist/assets/agents.lazy-CgakDm_P.js +1 -0
  6. package/app/dist/assets/analytics.lazy-C0rw3sov.js +1 -0
  7. package/app/dist/assets/{createLucideIcon-luywpIq4.js → createLucideIcon-B3bah5lk.js} +1 -1
  8. package/app/dist/assets/hooks-CNPue7d7.js +1 -0
  9. package/app/dist/assets/index-B8XCmr0-.css +1 -0
  10. package/app/dist/assets/index-Dfn02uW3.js +47 -0
  11. package/app/dist/assets/index.lazy-JdqhBwPd.js +44 -0
  12. package/app/dist/assets/services-CHpVij2M.css +1 -0
  13. package/app/dist/assets/services.lazy-BShKUCOt.js +12 -0
  14. package/app/dist/assets/vendor-X6HTElZW.js +9 -0
  15. package/app/dist/assets/viz-Dsh_q2DK.js +4 -0
  16. package/app/dist/index.html +5 -4
  17. package/app/dist/service-worker.js +32 -1
  18. package/dist/agent/run-local.js +89 -76
  19. package/dist/{agent-DFSFG6DG.js → agent-DJ4SCNBZ.js} +22 -17
  20. package/dist/{analytics-broadcaster-O4AE3RUK.js → analytics-broadcaster-INNYWHDJ.js} +25 -20
  21. package/dist/approve-plan.command-WE2CO3H2.js +21 -0
  22. package/dist/{chunk-HOIOVUHI.js → chunk-5M7PBFMZ.js} +8 -6
  23. package/dist/chunk-7R7XFXJM.js +1247 -0
  24. package/dist/{chunk-2PRRKBG6.js → chunk-A4P2MYJF.js} +22 -9
  25. package/dist/chunk-AFOV3ZAF.js +722 -0
  26. package/dist/chunk-AFP36N23.js +134 -0
  27. package/dist/{chunk-AAZKYWOY.js → chunk-AFYKGVSP.js} +103 -8
  28. package/dist/chunk-APJOZXRP.js +737 -0
  29. package/dist/chunk-DLSPRIQL.js +241 -0
  30. package/dist/{chunk-5AMWD66T.js → chunk-EDIPHR5B.js} +6 -4
  31. package/dist/{chunk-K36BWMUV.js → chunk-JU3MF3MW.js} +2526 -736
  32. package/dist/{chunk-7TXZYZR5.js → chunk-N5HCNY4O.js} +7 -5
  33. package/dist/{chunk-JRLWLZOD.js → chunk-NKMZYPIS.js} +31 -23
  34. package/dist/{chunk-PI7Y77R3.js → chunk-OFIVTM2E.js} +17 -7
  35. package/dist/{chunk-QH6VCTET.js → chunk-RCSJFMQG.js} +909 -98
  36. package/dist/{chunk-AAVROEQC.js → chunk-UR7T7IA6.js} +253 -349
  37. package/dist/{chunk-QHISYRXJ.js → chunk-VOYLU3MI.js} +57 -3
  38. package/dist/{chunk-EBCSQFPR.js → chunk-W5IULOWV.js} +2 -3
  39. package/dist/chunk-X37RNTWU.js +193 -0
  40. package/dist/{chunk-PACI3T4I.js → chunk-XY2APMDE.js} +13 -5
  41. package/dist/chunk-Z6ZWNWWR.js +34 -0
  42. package/dist/cli.js +45 -17
  43. package/dist/constants-AAP7ZGCX.js +124 -0
  44. package/dist/create-issue.command-SX3AXXIC.js +29 -0
  45. package/dist/fsm-agent-JGV22WK4.js +59 -0
  46. package/dist/{fsm-issue-EHTSKMFN.js → fsm-issue-LHIJM5VB.js} +12 -8
  47. package/dist/{fsm-service-7O4AJG2R.js → fsm-service-GGDKUTWS.js} +13 -4
  48. package/dist/{helpers-ON2S7UEF.js → helpers-AENVYEZJ.js} +6 -2
  49. package/dist/{issue-log-broadcaster-FZGVEEIX.js → issue-log-broadcaster-QQWM7LOV.js} +29 -18
  50. package/dist/{issues-3YNNTB4U.js → issues-RXFKKSXB.js} +10 -7
  51. package/dist/{log-analyzer-EIX6R6PP.js → log-analyzer-4LNXQISY.js} +30 -20
  52. package/dist/{logger-IFLXTQPS.js → logger-4F6ATWNA.js} +2 -1
  53. package/dist/mcp/server.js +6 -2
  54. package/dist/merge-workspace.command-ZNGIZC4O.js +29 -0
  55. package/dist/{parallel-executor-DWESCNX3.js → parallel-executor-OL5CB33L.js} +78 -19
  56. package/dist/{pid-manager-UBWXVSMD.js → pid-manager-EDT4DHAU.js} +2 -1
  57. package/dist/queue-workers-NSKIIMQ2.js +43 -0
  58. package/dist/replan-issue.command-73PETERX.js +21 -0
  59. package/dist/retry-issue.command-DIDP4OCS.js +21 -0
  60. package/dist/reverse-proxy-server-QSS3H4UH.js +97 -0
  61. package/dist/scheduler-5YORYECF.js +37 -0
  62. package/dist/service-log-broadcaster-JIUP2L3D.js +21 -0
  63. package/dist/{settings-SOTIS6ZD.js → settings-ZNDXYL46.js} +34 -23
  64. package/dist/settings.resource-OKUHXICJ.js +35 -0
  65. package/dist/{store-S3NAYZ3S.js → store-P3ACO6YA.js} +22 -17
  66. package/dist/telemetry-KVUFHDQS.js +828 -0
  67. package/dist/template-variants-HEPLYKMP.js +24 -0
  68. package/dist/trace-bundle-IJOV7IWH.js +41 -0
  69. package/dist/{web-push-QCTLS7EJ.js → web-push-X2LLMQ4M.js} +2 -1
  70. package/dist/websocket-Q2TUCIC2.js +103 -0
  71. package/dist/{workspace-OS7GPMCN.js → workspace-TDX3NJCX.js} +10 -6
  72. package/package.json +12 -9
  73. package/app/dist/assets/CommandPalette-CL8p78lG.js +0 -1
  74. package/app/dist/assets/OnboardingWizard-BmI50ZUv.js +0 -1
  75. package/app/dist/assets/analytics.lazy-CXGjZabc.js +0 -1
  76. package/app/dist/assets/index-CEaccpYh.js +0 -96
  77. package/app/dist/assets/index-CzzWGzux.css +0 -1
  78. package/app/dist/assets/vendor-uqBx3VSC.js +0 -9
  79. package/dist/approve-plan.command-QGQZZXTQ.js +0 -17
  80. package/dist/chunk-N4KFNX2G.js +0 -370
  81. package/dist/chunk-VM5QAYP5.js +0 -404
  82. package/dist/create-issue.command-VAKYRECC.js +0 -24
  83. package/dist/merge-workspace.command-T2NIGR4M.js +0 -24
  84. package/dist/queue-workers-V57BYXAY.js +0 -38
  85. package/dist/replan-issue.command-2GQ3QXCR.js +0 -17
  86. package/dist/retry-issue.command-GJBUUYDJ.js +0 -17
  87. package/dist/scheduler-KYILMWLD.js +0 -32
  88. package/dist/settings.resource-JMD3JQOS.js +0 -30
  89. package/dist/websocket-T2Y3BY4B.js +0 -61
@@ -1,38 +1,58 @@
1
1
  import {
2
2
  markIssueDirty,
3
3
  normalizeAgentProvider
4
- } from "./chunk-PI7Y77R3.js";
4
+ } from "./chunk-OFIVTM2E.js";
5
+ import {
6
+ init_template_variants,
7
+ template_variants_exports
8
+ } from "./chunk-AFP36N23.js";
9
+ import {
10
+ finalizeAttemptManifest,
11
+ loadAttemptManifest,
12
+ readTraceContent,
13
+ traceDir
14
+ } from "./chunk-APJOZXRP.js";
5
15
  import {
6
16
  renderPrompt
7
- } from "./chunk-AAZKYWOY.js";
17
+ } from "./chunk-AFYKGVSP.js";
18
+ import {
19
+ appendFileTail,
20
+ idToSafePath,
21
+ init_helpers,
22
+ now
23
+ } from "./chunk-DLSPRIQL.js";
8
24
  import {
9
25
  SOURCE_MARKER,
10
26
  SOURCE_ROOT,
11
27
  TARGET_ROOT,
12
28
  WORKSPACE_ROOT,
13
- appendFileTail,
14
- idToSafePath,
15
- now
16
- } from "./chunk-VM5QAYP5.js";
29
+ init_constants
30
+ } from "./chunk-X37RNTWU.js";
17
31
  import {
18
32
  logger
19
33
  } from "./chunk-PXTIWKLQ.js";
34
+ import {
35
+ __toCommonJS
36
+ } from "./chunk-Z6ZWNWWR.js";
20
37
 
21
38
  // src/domains/workspace.ts
39
+ init_constants();
40
+ init_helpers();
22
41
  import {
23
- existsSync as existsSync5,
42
+ existsSync as existsSync8,
24
43
  mkdirSync as mkdirSync3,
25
- readdirSync as readdirSync2,
26
- readFileSync as readFileSync3,
44
+ readdirSync as readdirSync5,
45
+ readFileSync as readFileSync5,
27
46
  rmSync as rmSync2,
28
47
  statSync,
29
- writeFileSync as writeFileSync3
48
+ writeFileSync as writeFileSync5
30
49
  } from "fs";
31
50
  import { copyFile, mkdir, readdir, stat, writeFile } from "fs/promises";
32
- import { extname, join as join4, resolve } from "path";
51
+ import { extname, join as join7, resolve } from "path";
33
52
  import { execSync as execSync2 } from "child_process";
34
53
 
35
54
  // src/agents/command-executor.ts
55
+ init_helpers();
36
56
  import {
37
57
  appendFileSync,
38
58
  existsSync as existsSync3,
@@ -46,6 +66,7 @@ import { spawn } from "child_process";
46
66
  import { createConnection } from "net";
47
67
  import { createRequire } from "module";
48
68
  import { fileURLToPath } from "url";
69
+ init_constants();
49
70
 
50
71
  // src/agents/docker-runner.ts
51
72
  import { existsSync } from "fs";
@@ -356,7 +377,7 @@ async function runCommandWithTimeout(command, workspacePath, issue, config, prom
356
377
  if (!config.dockerExecution && DAEMON_SCRIPT) {
357
378
  const socketPath = join2(workspacePath, "agent.sock");
358
379
  if (existsSync3(socketPath)) {
359
- const { isDaemonAlive } = await import("./pid-manager-UBWXVSMD.js");
380
+ const { isDaemonAlive } = await import("./pid-manager-EDT4DHAU.js");
360
381
  if (isDaemonAlive(workspacePath)) {
361
382
  logger.info({ issueId: issue.id }, "[Agent] Live PTY daemon detected \u2014 reattaching to existing session");
362
383
  return attachToDaemon(socketPath, workspacePath, issue, config, started, outputFile, resultFile);
@@ -821,6 +842,10 @@ async function runHook(command, workspacePath, issue, hookName, extraEnv = {}) {
821
842
  }
822
843
  }
823
844
 
845
+ // src/agents/prompt-builder.ts
846
+ import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
847
+ import { join as join5, relative as relative2 } from "path";
848
+
824
849
  // src/agents/review-failure-history.ts
825
850
  function sortFailureHistory(history) {
826
851
  return [...history].sort((left, right) => {
@@ -944,49 +969,804 @@ function buildRecurringFailureContext(issue, scope) {
944
969
  return lines.join("\n");
945
970
  }
946
971
 
972
+ // src/domains/cross-attempt-analysis.ts
973
+ init_helpers();
974
+ import { existsSync as existsSync4, readFileSync as readFileSync2, readdirSync, writeFileSync as writeFileSync2 } from "fs";
975
+ import { join as join3 } from "path";
976
+ var CROSS_ATTEMPT_FILE = "cross-attempt.json";
977
+ function readJsonFile(filePath) {
978
+ if (!existsSync4(filePath)) return null;
979
+ try {
980
+ return JSON.parse(readFileSync2(filePath, "utf8"));
981
+ } catch (error) {
982
+ logger.debug({ err: String(error), filePath }, "[CrossAttempt] Failed to parse JSON file");
983
+ return null;
984
+ }
985
+ }
986
+ function diffStatSizeFor(traceDirectory) {
987
+ const filePath = join3(traceDirectory, "diff.stat");
988
+ if (!existsSync4(filePath)) return 0;
989
+ try {
990
+ return readFileSync2(filePath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean).length;
991
+ } catch {
992
+ return 0;
993
+ }
994
+ }
995
+ function changedFilesFor(traceDirectory) {
996
+ return readJsonFile(join3(traceDirectory, "changed-files.json")) ?? [];
997
+ }
998
+ function buildAttemptRecords(worktreePath, previousAttemptSummaries) {
999
+ const deduped = /* @__PURE__ */ new Map();
1000
+ for (const summary of previousAttemptSummaries ?? []) {
1001
+ deduped.set(`${summary.planVersion}:${summary.executeAttempt}`, summary);
1002
+ }
1003
+ return [...deduped.values()].sort(
1004
+ (left, right) => left.planVersion !== right.planVersion ? left.planVersion - right.planVersion : left.executeAttempt - right.executeAttempt
1005
+ ).map((summary) => {
1006
+ const directory = traceDir(worktreePath, summary.planVersion, summary.executeAttempt);
1007
+ const manifest = loadAttemptManifest(directory);
1008
+ return {
1009
+ planVersion: summary.planVersion,
1010
+ executeAttempt: summary.executeAttempt,
1011
+ summary,
1012
+ failureType: summary.insight?.errorType,
1013
+ changedFiles: changedFilesFor(directory),
1014
+ diffStatSize: diffStatSizeFor(directory),
1015
+ outcome: manifest?.outcome ?? "failure",
1016
+ nextIssueState: manifest?.nextIssueState
1017
+ };
1018
+ });
1019
+ }
1020
+ function persistCrossAttemptAnalysis(traceDirectory, analysis) {
1021
+ try {
1022
+ writeFileSync2(join3(traceDirectory, CROSS_ATTEMPT_FILE), JSON.stringify(analysis, null, 2), "utf8");
1023
+ } catch (error) {
1024
+ logger.warn({ err: String(error), traceDirectory }, "[CrossAttempt] Failed to write cross-attempt analysis");
1025
+ }
1026
+ }
1027
+ function loadCrossAttemptAnalysis(traceDirectory) {
1028
+ return readJsonFile(join3(traceDirectory, CROSS_ATTEMPT_FILE));
1029
+ }
1030
+ function formHypotheses(records) {
1031
+ if (records.length < 2) return [];
1032
+ const hypotheses = [];
1033
+ const fileAppearances = /* @__PURE__ */ new Map();
1034
+ for (const r of records) {
1035
+ for (const f of r.changedFiles) {
1036
+ const entry = fileAppearances.get(f) ?? { count: 0, errorTypes: /* @__PURE__ */ new Set(), attempts: [] };
1037
+ entry.count++;
1038
+ if (r.failureType) entry.errorTypes.add(r.failureType);
1039
+ entry.attempts.push(r.executeAttempt);
1040
+ fileAppearances.set(f, entry);
1041
+ }
1042
+ }
1043
+ for (const [file, data] of fileAppearances) {
1044
+ if (data.count >= 3 && data.errorTypes.size === 1) {
1045
+ const errorType = [...data.errorTypes][0];
1046
+ hypotheses.push({
1047
+ signal: `\`${file}\` modified in ${data.count} attempts, all with ${errorType} error`,
1048
+ hypothesis: `The current approach to \`${file}\` is fundamentally wrong for this error class`,
1049
+ evidence: [`Attempts ${data.attempts.join(", ")} all modified this file`, `Same error type (${errorType}) persists`],
1050
+ suggestion: `Try a completely different strategy for \`${file}\` \u2014 the repeated approach is not converging`
1051
+ });
1052
+ }
1053
+ }
1054
+ if (records.length >= 3) {
1055
+ const types = records.map((r) => r.failureType ?? "unknown");
1056
+ let oscillating = true;
1057
+ for (let i = 2; i < types.length; i++) {
1058
+ if (types[i] !== types[i - 2] || types[i] === types[i - 1]) {
1059
+ oscillating = false;
1060
+ break;
1061
+ }
1062
+ }
1063
+ if (oscillating && types.length >= 3) {
1064
+ hypotheses.push({
1065
+ signal: `Error types alternate: ${types.slice(-4).join(" \u2192 ")}`,
1066
+ hypothesis: "Fixing one error class introduces the other \u2014 the two are coupled",
1067
+ evidence: types.map((t, i) => `Attempt ${records[i].executeAttempt}: ${t}`),
1068
+ suggestion: "Address both error classes simultaneously in a single coherent change"
1069
+ });
1070
+ }
1071
+ }
1072
+ if (records.length >= 2) {
1073
+ const failedRecords = records.filter((r) => r.outcome === "failure");
1074
+ if (failedRecords.length >= 3) {
1075
+ const growing = failedRecords.every((r, i) => i === 0 || r.diffStatSize >= failedRecords[i - 1].diffStatSize);
1076
+ const firstSize = failedRecords[0].diffStatSize;
1077
+ const lastSize = failedRecords[failedRecords.length - 1].diffStatSize;
1078
+ if (growing && lastSize > firstSize * 1.5 && lastSize > 5) {
1079
+ hypotheses.push({
1080
+ signal: `Diff size grew from ${firstSize} to ${lastSize} lines across ${failedRecords.length} failed attempts`,
1081
+ hypothesis: "The approach is accumulating complexity without solving the root cause",
1082
+ evidence: failedRecords.map((r) => `Attempt ${r.executeAttempt}: ${r.diffStatSize} lines, outcome: ${r.outcome}`),
1083
+ suggestion: "Start with the minimal possible change \u2014 add one thing at a time and verify"
1084
+ });
1085
+ }
1086
+ }
1087
+ }
1088
+ const zeroDiffs = records.filter((r) => r.changedFiles.length === 0 && r.diffStatSize === 0);
1089
+ if (zeroDiffs.length > 0) {
1090
+ hypotheses.push({
1091
+ signal: `${zeroDiffs.length} attempt(s) produced no file changes`,
1092
+ hypothesis: "The agent got stuck in analysis without making concrete modifications",
1093
+ evidence: zeroDiffs.map((r) => `Attempt ${r.executeAttempt}: 0 files changed`),
1094
+ suggestion: "Make the smallest possible edit first, then iterate \u2014 avoid analysis-only turns"
1095
+ });
1096
+ }
1097
+ return hypotheses;
1098
+ }
1099
+ function detectStrategyPivot(records) {
1100
+ if (records.length < 3) return null;
1101
+ const recent = records.slice(-3);
1102
+ const types = recent.map((r) => r.failureType).filter(Boolean);
1103
+ if (types.length === 3 && types[0] === types[1] && types[1] === types[2]) {
1104
+ return {
1105
+ reason: `${types.length} consecutive ${types[0]} failures`,
1106
+ consecutiveRegressions: types.length,
1107
+ suggestedApproach: "The current implementation path is exhausted. Try a fundamentally different approach: different algorithm, different API surface, or different file structure."
1108
+ };
1109
+ }
1110
+ const suggestions = records.map((r) => r.summary?.insight?.suggestion).filter(Boolean);
1111
+ if (suggestions.length >= 2) {
1112
+ const last = suggestions[suggestions.length - 1];
1113
+ const secondLast = suggestions[suggestions.length - 2];
1114
+ if (last && secondLast && last === secondLast) {
1115
+ return {
1116
+ reason: "Same corrective suggestion repeated without progress",
1117
+ consecutiveRegressions: 2,
1118
+ suggestedApproach: "The previous suggestion was attempted but did not resolve the issue. Apply a structurally different fix rather than retrying the same correction."
1119
+ };
1120
+ }
1121
+ }
1122
+ return null;
1123
+ }
1124
+ function isolateConfounds(records) {
1125
+ if (records.length < 2) return [];
1126
+ const confounds = [];
1127
+ for (let i = 1; i < records.length; i++) {
1128
+ const prev = records[i - 1];
1129
+ const curr = records[i];
1130
+ if (prev.outcome !== "failure" || curr.outcome !== "failure") continue;
1131
+ const prevSet = new Set(prev.changedFiles);
1132
+ const currSet = new Set(curr.changedFiles);
1133
+ for (const f of prevSet) {
1134
+ if (!currSet.has(f)) confounds.push(`\`${f}\` changed in attempt ${prev.executeAttempt} but not ${curr.executeAttempt} \u2014 likely irrelevant to the failure`);
1135
+ }
1136
+ }
1137
+ return confounds.slice(0, 5);
1138
+ }
1139
+ function computeCrossAttemptAnalysis(worktreePath, currentPV, currentEA, previousAttemptSummaries) {
1140
+ const records = buildAttemptRecords(worktreePath, previousAttemptSummaries).filter((record) => record.planVersion < currentPV || record.executeAttempt < currentEA);
1141
+ const failureCounts = /* @__PURE__ */ new Map();
1142
+ for (const record of records) {
1143
+ if (!record.failureType || record.failureType === "unknown") continue;
1144
+ failureCounts.set(record.failureType, (failureCounts.get(record.failureType) ?? 0) + 1);
1145
+ }
1146
+ const repeatedFailureTypes = [...failureCounts.entries()].filter(([, count]) => count > 1).map(([failureType]) => failureType).sort((left, right) => left.localeCompare(right));
1147
+ const overlap = /* @__PURE__ */ new Set();
1148
+ for (let index = 1; index < records.length; index += 1) {
1149
+ const previous = new Set(records[index - 1].changedFiles);
1150
+ for (const filePath of records[index].changedFiles) {
1151
+ if (previous.has(filePath)) overlap.add(filePath);
1152
+ }
1153
+ }
1154
+ const outcomeTransitions = records.map((record) => ({
1155
+ attempt: record.executeAttempt,
1156
+ outcome: record.outcome,
1157
+ nextIssueState: record.nextIssueState
1158
+ }));
1159
+ const summary = [];
1160
+ if (records.length === 0) {
1161
+ summary.push("No previous attempt artifacts were available; retry should rely on summary-only context.");
1162
+ }
1163
+ if (repeatedFailureTypes.length > 0) {
1164
+ summary.push(`Repeated failure types: ${repeatedFailureTypes.join(", ")}.`);
1165
+ }
1166
+ if (overlap.size > 0) {
1167
+ summary.push(`Repeated file edits across adjacent attempts: ${[...overlap].slice(0, 8).join(", ")}.`);
1168
+ }
1169
+ if (records.length >= 2) {
1170
+ const previous = records[records.length - 2];
1171
+ const latest = records[records.length - 1];
1172
+ summary.push(
1173
+ `Recent outcome transition: a${previous.executeAttempt} ${previous.outcome}/${previous.nextIssueState ?? "unknown"} -> a${latest.executeAttempt} ${latest.outcome}/${latest.nextIssueState ?? "unknown"}.`
1174
+ );
1175
+ summary.push(
1176
+ `Diff footprint changed from ${previous.diffStatSize} diff.stat line(s) to ${latest.diffStatSize} diff.stat line(s).`
1177
+ );
1178
+ }
1179
+ if (summary.length === 0) {
1180
+ summary.push("No strong cross-attempt pattern detected.");
1181
+ }
1182
+ const hypotheses = formHypotheses(records);
1183
+ const strategyPivot = detectStrategyPivot(records);
1184
+ const confounds = isolateConfounds(records);
1185
+ return {
1186
+ generatedAt: now(),
1187
+ repeatedFailureTypes,
1188
+ changedFileOverlap: [...overlap].sort((left, right) => left.localeCompare(right)),
1189
+ outcomeTransitions,
1190
+ summary,
1191
+ hypotheses,
1192
+ strategyPivot,
1193
+ confounds
1194
+ };
1195
+ }
1196
+
1197
+ // src/domains/trace-retrieval.ts
1198
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1199
+ import { dirname, join as join4, relative } from "path";
1200
+ var TRACE_DIR_NAME = "traces";
1201
+ var TRACE_NAME_PATTERN = /^v(\d+)a(\d+)$/;
1202
+ var MAX_WORKSPACES = 48;
1203
+ var MAX_ATTEMPTS_PER_WORKSPACE = 4;
1204
+ var MAX_RESULTS = 3;
1205
+ var MIN_SCORE = 4;
1206
+ var STOP_WORDS = /* @__PURE__ */ new Set([
1207
+ "the",
1208
+ "and",
1209
+ "for",
1210
+ "with",
1211
+ "from",
1212
+ "that",
1213
+ "this",
1214
+ "into",
1215
+ "while",
1216
+ "when",
1217
+ "then",
1218
+ "failed",
1219
+ "failure",
1220
+ "error",
1221
+ "issue",
1222
+ "review",
1223
+ "execution",
1224
+ "attempt",
1225
+ "previous",
1226
+ "before",
1227
+ "after",
1228
+ "should",
1229
+ "would",
1230
+ "could",
1231
+ "must",
1232
+ "need",
1233
+ "file",
1234
+ "files"
1235
+ ]);
1236
+ function readJsonFile2(path) {
1237
+ try {
1238
+ return JSON.parse(readFileSync3(path, "utf8"));
1239
+ } catch {
1240
+ return null;
1241
+ }
1242
+ }
1243
+ function tokenize(text) {
1244
+ const matches = text.toLowerCase().match(/[a-z0-9_.-]{3,}/g) ?? [];
1245
+ return new Set(
1246
+ matches.filter(
1247
+ (token) => !STOP_WORDS.has(token) && !/^\d+$/.test(token)
1248
+ )
1249
+ );
1250
+ }
1251
+ function buildQuerySignal(issue) {
1252
+ const summaries = issue.previousAttemptSummaries ?? [];
1253
+ const latest = summaries.length > 0 ? summaries[summaries.length - 1] : null;
1254
+ const blockerIds = new Set(
1255
+ (issue.gradingReport?.criteria ?? []).filter((criterion) => criterion.result === "FAIL" && criterion.blocking).map((criterion) => criterion.id).filter((value) => typeof value === "string" && value.length > 0)
1256
+ );
1257
+ const files = new Set(
1258
+ latest?.insight?.filesInvolved?.filter((value) => typeof value === "string" && value.length > 0) ?? []
1259
+ );
1260
+ const errorType = (latest?.insight?.errorType ?? "").trim().toLowerCase();
1261
+ const errorText = [
1262
+ latest?.insight?.rootCause ?? "",
1263
+ latest?.error ?? "",
1264
+ latest?.outputTail ?? "",
1265
+ issue.lastError ?? ""
1266
+ ].join(" ");
1267
+ return {
1268
+ errorType,
1269
+ tokens: tokenize(errorText),
1270
+ files,
1271
+ blockerIds,
1272
+ harnessMode: issue.plan?.harnessMode ?? "standard",
1273
+ checkpointPolicy: issue.plan?.executionContract?.checkpointPolicy ?? "final_only",
1274
+ checkpointFailed: issue.checkpointStatus === "failed",
1275
+ contractBlocked: Boolean(issue.contractNegotiationStatus && issue.contractNegotiationStatus !== "approved"),
1276
+ contextResetCount: issue.contextResetCount ?? 0,
1277
+ nearRetryBudget: (issue.maxAttempts ?? 0) > 0 ? Math.max(0, (issue.maxAttempts ?? 0) - (issue.attempts ?? 0)) <= 1 : false,
1278
+ lastFailedPhase: (issue.lastFailedPhase ?? latest?.phase ?? "").trim().toLowerCase(),
1279
+ policyDecisionKinds: new Set(
1280
+ (issue.policyDecisions ?? []).map((decision) => decision.kind).filter((value) => typeof value === "string" && value.length > 0)
1281
+ )
1282
+ };
1283
+ }
1284
+ function parseTraceName(name) {
1285
+ const match = name.match(TRACE_NAME_PATTERN);
1286
+ if (!match) return null;
1287
+ return {
1288
+ planVersion: Number.parseInt(match[1] ?? "0", 10),
1289
+ executeAttempt: Number.parseInt(match[2] ?? "0", 10)
1290
+ };
1291
+ }
1292
+ function compareTraceNames(left, right) {
1293
+ const leftParsed = parseTraceName(left);
1294
+ const rightParsed = parseTraceName(right);
1295
+ if (!leftParsed || !rightParsed) return right.localeCompare(left);
1296
+ if (leftParsed.planVersion !== rightParsed.planVersion) {
1297
+ return rightParsed.planVersion - leftParsed.planVersion;
1298
+ }
1299
+ return rightParsed.executeAttempt - leftParsed.executeAttempt;
1300
+ }
1301
+ function readChangedFiles(tracePath) {
1302
+ const parsed = readJsonFile2(join4(tracePath, "changed-files.json"));
1303
+ return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string" && value.length > 0) : [];
1304
+ }
1305
+ function contextResetBucket(count) {
1306
+ if (count <= 0) return "none";
1307
+ if (count === 1) return "low";
1308
+ return "high";
1309
+ }
1310
+ function scoreCandidate(query, candidateText, changedFiles, blockerIds, rails) {
1311
+ let score = 0;
1312
+ const reasons = [];
1313
+ if (query.errorType && candidateText.includes(query.errorType)) {
1314
+ score += 5;
1315
+ reasons.push(`shared error signal \`${query.errorType}\``);
1316
+ }
1317
+ const candidateTokens = tokenize(candidateText);
1318
+ const sharedTokens = [...query.tokens].filter((token) => candidateTokens.has(token)).slice(0, 3);
1319
+ if (sharedTokens.length > 0) {
1320
+ score += sharedTokens.length;
1321
+ reasons.push(`shared terms ${sharedTokens.map((token) => `\`${token}\``).join(", ")}`);
1322
+ }
1323
+ const overlappingFiles = changedFiles.filter((file) => query.files.has(file)).slice(0, 2);
1324
+ if (overlappingFiles.length > 0) {
1325
+ score += overlappingFiles.length * 3;
1326
+ reasons.push(`shared files ${overlappingFiles.map((file) => `\`${file}\``).join(", ")}`);
1327
+ }
1328
+ const overlappingBlockers = blockerIds.filter((id) => query.blockerIds.has(id)).slice(0, 2);
1329
+ if (overlappingBlockers.length > 0) {
1330
+ score += overlappingBlockers.length * 4;
1331
+ reasons.push(`shared review blockers ${overlappingBlockers.map((id) => `\`${id}\``).join(", ")}`);
1332
+ }
1333
+ const harnessMode = rails?.harness?.mode?.trim().toLowerCase() ?? "";
1334
+ if (query.harnessMode && harnessMode === query.harnessMode) {
1335
+ score += 2;
1336
+ reasons.push(`same harness mode \`${query.harnessMode}\``);
1337
+ }
1338
+ const checkpointPolicy = rails?.harness?.checkpointPolicy?.trim().toLowerCase() ?? "";
1339
+ if (query.checkpointPolicy && checkpointPolicy === query.checkpointPolicy) {
1340
+ score += 2;
1341
+ reasons.push(`same checkpoint policy \`${query.checkpointPolicy}\``);
1342
+ }
1343
+ const candidateContextResetCount = rails?.runtimeRails?.contextResetCount ?? 0;
1344
+ if (query.contextResetCount > 0 && candidateContextResetCount > 0) {
1345
+ score += contextResetBucket(candidateContextResetCount) === contextResetBucket(query.contextResetCount) ? 2 : 1;
1346
+ reasons.push("similar context reset pressure");
1347
+ }
1348
+ const candidateRemaining = rails?.budget?.retryBudget?.remaining;
1349
+ if (query.nearRetryBudget && typeof candidateRemaining === "number" && candidateRemaining <= 1) {
1350
+ score += 2;
1351
+ reasons.push("similar retry budget pressure");
1352
+ }
1353
+ const candidateLastFailedPhase = rails?.runtimeRails?.lastFailedPhase?.trim().toLowerCase() ?? "";
1354
+ if (query.lastFailedPhase && candidateLastFailedPhase === query.lastFailedPhase) {
1355
+ score += 1;
1356
+ reasons.push(`same failed phase \`${query.lastFailedPhase}\``);
1357
+ }
1358
+ if (query.checkpointFailed && rails?.harness?.checkpointStatus === "failed") {
1359
+ score += 2;
1360
+ reasons.push("shared checkpoint failure state");
1361
+ }
1362
+ if (query.contractBlocked && rails?.harness?.contractNegotiationStatus && rails.harness.contractNegotiationStatus !== "approved") {
1363
+ score += 2;
1364
+ reasons.push("shared contract negotiation blocker");
1365
+ }
1366
+ const candidateDecisionKinds = new Set(
1367
+ (rails?.policyDecisions ?? []).map((decision) => decision.kind).filter((value) => typeof value === "string" && value.length > 0)
1368
+ );
1369
+ const overlappingDecisionKinds = [...query.policyDecisionKinds].filter((kind) => candidateDecisionKinds.has(kind)).slice(0, 2);
1370
+ if (overlappingDecisionKinds.length > 0) {
1371
+ score += overlappingDecisionKinds.length;
1372
+ reasons.push(`shared policy decisions ${overlappingDecisionKinds.map((kind) => `\`${kind}\``).join(", ")}`);
1373
+ }
1374
+ return { score, reasons };
1375
+ }
1376
+ function persistSimilarTraceSelection(traceDirectory, issue, hits) {
1377
+ if (!existsSync5(traceDirectory) || hits.length === 0) return null;
1378
+ const query = buildQuerySignal(issue);
1379
+ const artifact = {
1380
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1381
+ issue: {
1382
+ id: issue.id,
1383
+ identifier: issue.identifier,
1384
+ planVersion: issue.planVersion ?? 1,
1385
+ executeAttempt: issue.executeAttempt ?? 1
1386
+ },
1387
+ query: {
1388
+ errorType: query.errorType,
1389
+ files: [...query.files],
1390
+ blockerIds: [...query.blockerIds],
1391
+ harnessMode: query.harnessMode,
1392
+ checkpointPolicy: query.checkpointPolicy,
1393
+ checkpointFailed: query.checkpointFailed,
1394
+ contractBlocked: query.contractBlocked,
1395
+ contextResetCount: query.contextResetCount,
1396
+ nearRetryBudget: query.nearRetryBudget,
1397
+ lastFailedPhase: query.lastFailedPhase,
1398
+ policyDecisionKinds: [...query.policyDecisionKinds]
1399
+ },
1400
+ hits
1401
+ };
1402
+ const fileName = "similar-traces.json";
1403
+ try {
1404
+ writeFileSync3(join4(traceDirectory, fileName), `${JSON.stringify(artifact, null, 2)}
1405
+ `, "utf8");
1406
+ finalizeAttemptManifest(traceDirectory, {
1407
+ files: {
1408
+ similarTraces: fileName
1409
+ }
1410
+ });
1411
+ return fileName;
1412
+ } catch {
1413
+ return null;
1414
+ }
1415
+ }
1416
+ function extractLessonFromTrace(tracePath) {
1417
+ const manifest = loadAttemptManifest(tracePath);
1418
+ if (!manifest) return null;
1419
+ const outcome = manifest.outcome ?? "unknown";
1420
+ let handoffSummary = null;
1421
+ try {
1422
+ const handoffPath = join4(tracePath, "handoff.md");
1423
+ if (existsSync5(handoffPath)) {
1424
+ const raw = readFileSync3(handoffPath, "utf8");
1425
+ handoffSummary = raw.length > 500 ? raw.slice(0, 500) + "..." : raw;
1426
+ }
1427
+ } catch {
1428
+ }
1429
+ const checkpoint = readJsonFile2(join4(tracePath, "checkpoint.json"));
1430
+ const reviewBlockers = (checkpoint?.reviewBlockers ?? []).map((b) => b.id).filter((v) => typeof v === "string" && v.length > 0);
1431
+ let whatWorked = null;
1432
+ let whatFailed = null;
1433
+ if (outcome === "success") {
1434
+ const turnsDir = join4(tracePath, "turns");
1435
+ try {
1436
+ if (existsSync5(turnsDir)) {
1437
+ const directives = readdirSync2(turnsDir).filter((f) => f.endsWith(".directive.json")).sort();
1438
+ if (directives.length > 0) {
1439
+ const last = readJsonFile2(join4(turnsDir, directives[directives.length - 1]));
1440
+ whatWorked = last?.directiveSummary ?? last?.summary ?? null;
1441
+ }
1442
+ }
1443
+ } catch {
1444
+ }
1445
+ } else {
1446
+ whatFailed = manifest.error ?? null;
1447
+ if (!whatFailed && checkpoint?.remainingWork?.length) {
1448
+ whatFailed = `Remaining: ${checkpoint.remainingWork.slice(0, 3).join("; ")}`;
1449
+ }
1450
+ }
1451
+ return { outcome, handoffSummary, reviewBlockers, whatWorked, whatFailed };
1452
+ }
1453
+ function findSimilarIssueTraces(issue, workspacePath, options = {}) {
1454
+ const query = buildQuerySignal(issue);
1455
+ if (!query.errorType && query.tokens.size === 0 && query.files.size === 0 && query.blockerIds.size === 0) {
1456
+ return [];
1457
+ }
1458
+ const workspaceRoot = dirname(workspacePath);
1459
+ let workspaces = [];
1460
+ try {
1461
+ workspaces = readdirSync2(workspaceRoot).map((name) => join4(workspaceRoot, name)).filter((path) => path !== workspacePath && existsSync5(join4(path, TRACE_DIR_NAME))).sort((left, right) => right.localeCompare(left)).slice(0, MAX_WORKSPACES);
1462
+ } catch {
1463
+ return [];
1464
+ }
1465
+ const hits = [];
1466
+ for (const candidateWorkspace of workspaces) {
1467
+ const tracesRoot = join4(candidateWorkspace, TRACE_DIR_NAME);
1468
+ let traceNames = [];
1469
+ try {
1470
+ traceNames = readdirSync2(tracesRoot).filter((name) => TRACE_NAME_PATTERN.test(name)).sort(compareTraceNames).slice(0, MAX_ATTEMPTS_PER_WORKSPACE);
1471
+ } catch {
1472
+ continue;
1473
+ }
1474
+ for (const traceName of traceNames) {
1475
+ const tracePath = join4(tracesRoot, traceName);
1476
+ const manifest = loadAttemptManifest(tracePath);
1477
+ if (!manifest || manifest.issueId === issue.id) continue;
1478
+ const checkpoint = readJsonFile2(join4(tracePath, "checkpoint.json"));
1479
+ const rails = readJsonFile2(join4(tracePath, "rails.json"));
1480
+ const changedFiles = readChangedFiles(tracePath);
1481
+ const blockerIds = (checkpoint?.reviewBlockers ?? []).map((entry) => entry.id).filter((value) => typeof value === "string" && value.length > 0);
1482
+ const candidateText = [
1483
+ manifest.error ?? "",
1484
+ checkpoint?.lastTurn?.directiveSummary ?? "",
1485
+ checkpoint?.lastTurn?.outputPreview ?? "",
1486
+ ...checkpoint?.remainingWork ?? []
1487
+ ].join(" ").toLowerCase();
1488
+ const { score, reasons } = scoreCandidate(query, candidateText, changedFiles, blockerIds, rails);
1489
+ if (score < MIN_SCORE) continue;
1490
+ hits.push({
1491
+ issueId: manifest.issueId,
1492
+ issueIdentifier: manifest.issueIdentifier,
1493
+ score,
1494
+ reasons,
1495
+ files: {
1496
+ handoff: existsSync5(join4(tracePath, "handoff.md")) ? relative(workspacePath, join4(tracePath, "handoff.md")) : void 0,
1497
+ checkpoint: existsSync5(join4(tracePath, "checkpoint.json")) ? relative(workspacePath, join4(tracePath, "checkpoint.json")) : void 0,
1498
+ attempt: relative(workspacePath, join4(tracePath, "attempt.json")),
1499
+ diffPatch: existsSync5(join4(tracePath, "diff.patch")) ? relative(workspacePath, join4(tracePath, "diff.patch")) : void 0
1500
+ },
1501
+ lesson: extractLessonFromTrace(tracePath)
1502
+ });
1503
+ }
1504
+ }
1505
+ return hits.sort((left, right) => {
1506
+ if (left.score !== right.score) return right.score - left.score;
1507
+ return left.issueIdentifier.localeCompare(right.issueIdentifier);
1508
+ }).slice(0, options.maxResults ?? MAX_RESULTS);
1509
+ }
1510
+
947
1511
  // src/agents/prompt-builder.ts
948
- function buildRetryContext(issue) {
1512
+ function renderAttemptFull(s, index) {
1513
+ const lines = [];
1514
+ const phaseLabel = s.phase === "review" ? "review" : s.phase === "crash" ? "crash" : s.phase === "plan" ? "plan" : "execution";
1515
+ lines.push(`### Attempt ${index + 1} \u2014 ${phaseLabel} failure (plan v${s.planVersion}, exec #${s.executeAttempt})`);
1516
+ if (s.phase === "review") {
1517
+ lines.push("*The reviewer identified issues with the previous implementation. Focus on addressing the reviewer's feedback \u2014 do not redo work that was already approved.*");
1518
+ } else if (s.phase === "crash") {
1519
+ lines.push("*The agent process crashed or timed out. Simplify the approach \u2014 break the work into smaller steps.*");
1520
+ }
1521
+ if (s.insight) {
1522
+ lines.push(`**Failure type:** ${s.insight.errorType}`);
1523
+ lines.push(`**Root cause:** ${s.insight.rootCause}`);
1524
+ if (s.insight.failedCommand) lines.push(`**Failed command:** \`${s.insight.failedCommand}\``);
1525
+ if (s.insight.filesInvolved.length > 0) {
1526
+ lines.push(`**Files involved:** ${s.insight.filesInvolved.map((f) => `\`${f}\``).join(", ")}`);
1527
+ }
1528
+ lines.push(`**What to do differently:** ${s.insight.suggestion}`);
1529
+ } else {
1530
+ lines.push(`**Error:** ${s.error}`);
1531
+ }
1532
+ if (s.outputTail) {
1533
+ lines.push(`
1534
+ <details><summary>Output tail</summary>
1535
+
1536
+ \`\`\`
1537
+ ${s.outputTail}
1538
+ \`\`\`
1539
+ </details>`);
1540
+ }
1541
+ if (s.outputFile) {
1542
+ lines.push(`*Full output saved in: outputs/${s.outputFile}*`);
1543
+ }
1544
+ lines.push("");
1545
+ return lines.join("\n");
1546
+ }
1547
+ function renderAttemptCompressed(s, index) {
1548
+ const phaseLabel = s.phase === "review" ? "review" : s.phase === "crash" ? "crash" : s.phase === "plan" ? "plan" : "exec";
1549
+ const errorType = s.insight?.errorType ?? "unknown";
1550
+ const rootCause = s.insight?.rootCause ?? s.error?.slice(0, 120) ?? "no details";
1551
+ const suggestion = s.insight?.suggestion ?? "";
1552
+ return `- **Attempt ${index + 1}** (${phaseLabel}, v${s.planVersion}a${s.executeAttempt}): ${errorType} \u2014 ${rootCause}${suggestion ? ` \u2192 ${suggestion}` : ""}`;
1553
+ }
1554
+ var DEFAULT_RETRY_CONTEXT_MAX_CHARS = 1e4;
1555
+ var TRACE_REFERENCE_ATTEMPTS = 2;
1556
+ function computeRetryBudget(modelName) {
1557
+ const ctxWindow = resolveContextWindow(modelName);
1558
+ const totalChars = ctxWindow ? Math.min(6e4, Math.max(15e3, Math.round(ctxWindow * 0.04))) : DEFAULT_RETRY_CONTEXT_MAX_CHARS;
1559
+ return {
1560
+ totalChars,
1561
+ traceContentChars: Math.round(totalChars * 0.5),
1562
+ crossAttemptChars: Math.round(totalChars * 0.2),
1563
+ similarIssueChars: Math.round(totalChars * 0.15),
1564
+ gradingChars: Math.round(totalChars * 0.15)
1565
+ };
1566
+ }
1567
+ function hasTraceArtifacts(issue, worktreePath) {
1568
+ return (issue.previousAttemptSummaries ?? []).some(
1569
+ (summary) => existsSync6(traceDir(worktreePath, summary.planVersion, summary.executeAttempt))
1570
+ );
1571
+ }
1572
+ function renderTraceAttempt(worktreePath, summary, index, perAttemptBudget = 5e3) {
1573
+ const tracePath = traceDir(worktreePath, summary.planVersion, summary.executeAttempt);
1574
+ if (!existsSync6(tracePath)) {
1575
+ return renderAttemptFull(summary, index);
1576
+ }
1577
+ const content = readTraceContent(tracePath, perAttemptBudget);
1578
+ const lines = [renderAttemptFull(summary, index).trimEnd()];
1579
+ if (content.handoffMarkdown) {
1580
+ lines.push("\n**Handoff from previous attempt:**");
1581
+ lines.push(content.handoffMarkdown.trim());
1582
+ }
1583
+ if (content.checkpointSummary) {
1584
+ lines.push("\n**Checkpoint state:**");
1585
+ lines.push(content.checkpointSummary.trim());
1586
+ }
1587
+ if (content.lastDirectiveSummary) {
1588
+ lines.push("\n**Last agent directive:**");
1589
+ lines.push(content.lastDirectiveSummary.trim());
1590
+ }
1591
+ if (content.diffPatch) {
1592
+ lines.push("\n**Workspace changes (diff):**");
1593
+ lines.push("```diff");
1594
+ lines.push(content.diffPatch.trim());
1595
+ lines.push("```");
1596
+ }
1597
+ if (content.truncated) {
1598
+ const railsPath = relative2(worktreePath, join5(tracePath, "rails.json"));
1599
+ const manifestPath = relative2(worktreePath, join5(tracePath, "attempt.json"));
1600
+ lines.push(`
1601
+ *Additional trace artifacts (not inlined due to budget): \`${railsPath}\`, \`${manifestPath}\`*`);
1602
+ }
1603
+ lines.push("");
1604
+ return lines.join("\n");
1605
+ }
1606
+ function renderCrossAttemptContext(issue, worktreePath) {
1607
+ const currentTraceDir = traceDir(worktreePath, issue.planVersion ?? 1, issue.executeAttempt ?? 1);
1608
+ const analysis = loadCrossAttemptAnalysis(currentTraceDir);
1609
+ if (!analysis) return "";
1610
+ const lines = [
1611
+ "## Cross-Attempt Patterns\n",
1612
+ ...analysis.summary.map((entry) => `- ${entry}`)
1613
+ ];
1614
+ if (analysis.hypotheses && analysis.hypotheses.length > 0) {
1615
+ lines.push("\n### Causal Hypotheses\n");
1616
+ for (const h of analysis.hypotheses) {
1617
+ lines.push(`- **Signal:** ${h.signal}`);
1618
+ lines.push(` **Hypothesis:** ${h.hypothesis}`);
1619
+ lines.push(` **Try:** ${h.suggestion}`);
1620
+ }
1621
+ }
1622
+ if (analysis.strategyPivot) {
1623
+ lines.push(`
1624
+ ### Strategy Pivot Required
1625
+ `);
1626
+ lines.push(`**${analysis.strategyPivot.consecutiveRegressions} consecutive regressions.** ${analysis.strategyPivot.reason}.`);
1627
+ lines.push(`**Action:** ${analysis.strategyPivot.suggestedApproach}`);
1628
+ }
1629
+ if (analysis.confounds && analysis.confounds.length > 0) {
1630
+ lines.push(`
1631
+ ### Likely Irrelevant Changes
1632
+ `);
1633
+ for (const c of analysis.confounds) {
1634
+ lines.push(`- ${c}`);
1635
+ }
1636
+ }
1637
+ lines.push("");
1638
+ return lines.join("\n");
1639
+ }
1640
+ function renderSimilarIssueTraceContext(_worktreePath, _currentTraceDir, hits) {
1641
+ if (hits.length === 0) return "";
1642
+ const lines = [
1643
+ "## Similar Prior Failures Across Issues\n",
1644
+ "These are lessons extracted from other issues with overlapping failure signals. Apply them only if the match is genuinely relevant.\n"
1645
+ ];
1646
+ for (const hit of hits) {
1647
+ lines.push(`### ${hit.issueIdentifier} (score ${hit.score}): ${hit.reasons.join("; ")}`);
1648
+ if (hit.lesson) {
1649
+ lines.push(`**Outcome:** ${hit.lesson.outcome}`);
1650
+ if (hit.lesson.whatFailed) {
1651
+ lines.push(`**What failed:** ${hit.lesson.whatFailed}`);
1652
+ }
1653
+ if (hit.lesson.whatWorked) {
1654
+ lines.push(`**What worked:** ${hit.lesson.whatWorked}`);
1655
+ }
1656
+ if (hit.lesson.reviewBlockers.length > 0) {
1657
+ lines.push(`**Review blockers:** ${hit.lesson.reviewBlockers.join(", ")}`);
1658
+ }
1659
+ if (hit.lesson.handoffSummary) {
1660
+ lines.push(`**Handoff excerpt:**`);
1661
+ lines.push(hit.lesson.handoffSummary.trim());
1662
+ }
1663
+ } else {
1664
+ if (hit.files.handoff) lines.push(` - \`${hit.files.handoff}\``);
1665
+ lines.push(` - \`${hit.files.attempt}\``);
1666
+ }
1667
+ lines.push("");
1668
+ }
1669
+ return lines.join("\n");
1670
+ }
1671
+ function buildRetryContext(issue, worktreePath, options = {}) {
949
1672
  const summaries = issue.previousAttemptSummaries;
950
1673
  const recurringFailureContext = buildRecurringFailureContext(issue);
951
1674
  if ((!summaries || summaries.length === 0) && !recurringFailureContext) return "";
1675
+ let variantParams = { inlineTraceContent: true, hypothesisGeneration: true, lessonExtraction: true, budgetMultiplier: 1 };
1676
+ if (options.variantId) {
1677
+ try {
1678
+ const { getVariant } = (init_template_variants(), __toCommonJS(template_variants_exports));
1679
+ const variant = getVariant(options.variantId);
1680
+ if (variant?.parameters) {
1681
+ variantParams = { ...variantParams, ...variant.parameters };
1682
+ }
1683
+ } catch {
1684
+ }
1685
+ }
1686
+ const budget = options.budget ?? computeRetryBudget(options.modelName);
1687
+ if (variantParams.budgetMultiplier !== 1) {
1688
+ budget.totalChars = Math.round(budget.totalChars * variantParams.budgetMultiplier);
1689
+ budget.traceContentChars = Math.round(budget.traceContentChars * variantParams.budgetMultiplier);
1690
+ budget.crossAttemptChars = Math.round(budget.crossAttemptChars * variantParams.budgetMultiplier);
1691
+ budget.similarIssueChars = Math.round(budget.similarIssueChars * variantParams.budgetMultiplier);
1692
+ budget.gradingChars = Math.round(budget.gradingChars * variantParams.budgetMultiplier);
1693
+ }
952
1694
  const lines = [];
1695
+ const maxChars = budget.totalChars;
1696
+ const canUseTraces = variantParams.inlineTraceContent && Boolean(worktreePath && hasTraceArtifacts(issue, worktreePath));
1697
+ const currentTraceDir = worktreePath ? traceDir(worktreePath, issue.planVersion ?? 1, issue.executeAttempt ?? 1) : "";
1698
+ const crossAttemptContext = worktreePath && variantParams.hypothesisGeneration ? renderCrossAttemptContext(issue, worktreePath) : "";
1699
+ const similarIssueTraceHits = worktreePath && variantParams.lessonExtraction ? findSimilarIssueTraces(issue, worktreePath, { maxResults: 2 }) : [];
1700
+ if (worktreePath && currentTraceDir && similarIssueTraceHits.length > 0) {
1701
+ persistSimilarTraceSelection(currentTraceDir, issue, similarIssueTraceHits);
1702
+ }
1703
+ const similarIssueTraceContext = worktreePath ? renderSimilarIssueTraceContext(worktreePath, currentTraceDir, similarIssueTraceHits) : "";
953
1704
  if (summaries && summaries.length > 0) {
954
1705
  lines.push("## Previous Attempts\n");
955
1706
  lines.push("The following previous attempts FAILED. Do NOT repeat the same approach. Try a fundamentally different strategy.\n");
1707
+ if (canUseTraces) {
1708
+ lines.push("**This context includes inline trace content from prior attempts** \u2014 handoffs, diffs, and checkpoint data are embedded below. Use this evidence to understand exactly what was tried and why it failed.\n");
1709
+ } else {
1710
+ lines.push("**This context is self-contained** \u2014 all evidence you need is below. Do not assume prior knowledge. Read the specific errors, file paths, and suggestions carefully before starting.\n");
1711
+ }
956
1712
  }
957
- for (let i = 0; i < (summaries?.length ?? 0); i++) {
958
- const s = summaries[i];
959
- const phaseLabel = s.phase === "review" ? "review" : s.phase === "crash" ? "crash" : s.phase === "plan" ? "plan" : "execution";
960
- lines.push(`### Attempt ${i + 1} \u2014 ${phaseLabel} failure (plan v${s.planVersion}, exec #${s.executeAttempt})`);
961
- if (s.phase === "review") {
962
- lines.push("*The reviewer identified issues with the previous implementation. Focus on addressing the reviewer's feedback \u2014 do not redo work that was already approved.*");
963
- } else if (s.phase === "crash") {
964
- lines.push("*The agent process crashed or timed out. Simplify the approach \u2014 break the work into smaller steps.*");
965
- }
966
- if (s.insight) {
967
- lines.push(`**Failure type:** ${s.insight.errorType}`);
968
- lines.push(`**Root cause:** ${s.insight.rootCause}`);
969
- if (s.insight.failedCommand) lines.push(`**Failed command:** \`${s.insight.failedCommand}\``);
970
- if (s.insight.filesInvolved.length > 0) {
971
- lines.push(`**Files involved:** ${s.insight.filesInvolved.map((f) => `\`${f}\``).join(", ")}`);
1713
+ if (crossAttemptContext) {
1714
+ lines.push(crossAttemptContext);
1715
+ }
1716
+ if (similarIssueTraceContext) {
1717
+ lines.push(similarIssueTraceContext);
1718
+ }
1719
+ if (canUseTraces && summaries && summaries.length > 0) {
1720
+ const recentAttempts = summaries.slice(-TRACE_REFERENCE_ATTEMPTS);
1721
+ const perAttemptBudget = Math.floor(budget.traceContentChars / Math.max(1, recentAttempts.length));
1722
+ lines.push("### Most Relevant Prior Attempts\n");
1723
+ for (let i = 0; i < recentAttempts.length; i++) {
1724
+ const absoluteIndex = summaries.length - recentAttempts.length + i;
1725
+ lines.push(renderTraceAttempt(worktreePath, recentAttempts[i], absoluteIndex, perAttemptBudget));
1726
+ }
1727
+ } else if (summaries && summaries.length >= 5) {
1728
+ const olderAttempts = summaries.slice(0, -2);
1729
+ const recentAttempts = summaries.slice(-2);
1730
+ const clusters = /* @__PURE__ */ new Map();
1731
+ for (const s of olderAttempts) {
1732
+ const key = s.insight?.errorType ?? "unknown";
1733
+ if (!clusters.has(key)) clusters.set(key, []);
1734
+ clusters.get(key).push(s);
1735
+ }
1736
+ lines.push(`### Failure Pattern Summary (${olderAttempts.length} earlier attempts)
1737
+ `);
1738
+ lines.push("These error types have been encountered \u2014 avoid all of them:\n");
1739
+ for (const [errorType, attempts] of clusters) {
1740
+ const representative = attempts[attempts.length - 1];
1741
+ const suggestion = representative.insight?.suggestion ?? "";
1742
+ lines.push(`- **${errorType}** (${attempts.length}\xD7): ${representative.insight?.rootCause ?? representative.error?.slice(0, 120) ?? "unknown"}${suggestion ? ` \u2192 *${suggestion}*` : ""}`);
1743
+ const allFiles = [...new Set(attempts.flatMap((a) => a.insight?.filesInvolved ?? []))];
1744
+ if (allFiles.length > 0) {
1745
+ lines.push(` Files involved: ${allFiles.slice(0, 5).map((f) => `\`${f}\``).join(", ")}${allFiles.length > 5 ? ` (+${allFiles.length - 5} more)` : ""}`);
972
1746
  }
973
- lines.push(`**What to do differently:** ${s.insight.suggestion}`);
974
- } else {
975
- lines.push(`**Error:** ${s.error}`);
976
1747
  }
977
- if (s.outputTail) {
978
- lines.push(`
979
- <details><summary>Output tail</summary>
980
-
981
- \`\`\`
982
- ${s.outputTail}
983
- \`\`\`
984
- </details>`);
1748
+ lines.push("");
1749
+ lines.push("### Recent Attempts (detailed)\n");
1750
+ for (let i = 0; i < recentAttempts.length; i++) {
1751
+ lines.push(renderAttemptFull(recentAttempts[i], olderAttempts.length + i));
985
1752
  }
986
- if (s.outputFile) {
987
- lines.push(`*Full output saved in: outputs/${s.outputFile}*`);
1753
+ } else if (summaries && summaries.length >= 3) {
1754
+ const olderAttempts = summaries.slice(0, -2);
1755
+ const recentAttempts = summaries.slice(-2);
1756
+ lines.push(`### Earlier Attempts (compressed, ${olderAttempts.length} total)
1757
+ `);
1758
+ for (let i = 0; i < olderAttempts.length; i++) {
1759
+ lines.push(renderAttemptCompressed(olderAttempts[i], i));
988
1760
  }
989
1761
  lines.push("");
1762
+ lines.push("### Recent Attempts (detailed)\n");
1763
+ for (let i = 0; i < recentAttempts.length; i++) {
1764
+ lines.push(renderAttemptFull(recentAttempts[i], olderAttempts.length + i));
1765
+ }
1766
+ } else {
1767
+ for (let i = 0; i < (summaries?.length ?? 0); i++) {
1768
+ lines.push(renderAttemptFull(summaries[i], i));
1769
+ }
990
1770
  }
991
1771
  if (issue.lastFailedPhase === "review" && issue.gradingReport) {
992
1772
  const failedCriteria = issue.gradingReport.criteria.filter(
@@ -994,18 +1774,45 @@ ${s.outputTail}
994
1774
  );
995
1775
  if (failedCriteria.length > 0) {
996
1776
  lines.push("## Previous Review Grade: FAIL\n");
997
- lines.push("The automated reviewer graded your last submission and found these specific failures:");
1777
+ lines.push("The automated reviewer graded your last submission and found these **specific failures with concrete evidence**. Each item below tells you exactly what was wrong and where. Fix the root cause for each \u2014 don't just make the symptom go away:");
998
1778
  for (const c of failedCriteria) {
999
- lines.push(`- **${c.id}** [${c.category}] FAILED: ${c.description} \u2014 ${c.evidence}`);
1779
+ lines.push(`- **${c.id}** [${c.category}] FAILED: ${c.description}`);
1780
+ lines.push(` Evidence: ${c.evidence}`);
1000
1781
  }
1001
- lines.push("\nYou MUST address ALL of these before submitting. The reviewer will check each one again.\n");
1782
+ lines.push("\nYou MUST address ALL of these before submitting. The reviewer will check each one again with the same criteria.\n");
1002
1783
  }
1003
1784
  }
1004
1785
  if (recurringFailureContext) {
1005
1786
  lines.push(recurringFailureContext);
1006
1787
  }
1007
1788
  const full = lines.join("\n");
1008
- return full.length > 8e3 ? full.slice(0, 8e3) + "\n[...truncated]" : full;
1789
+ const text = full.length > maxChars ? full.slice(0, maxChars) + "\n[...truncated]" : full;
1790
+ const analysis = worktreePath ? loadCrossAttemptAnalysis(traceDir(worktreePath, issue.planVersion ?? 1, issue.executeAttempt ?? 1)) : null;
1791
+ const metrics = {
1792
+ retryContextChars: text.length,
1793
+ traceContentChars: canUseTraces ? Math.min(text.length, budget.traceContentChars) : 0,
1794
+ crossAttemptChars: crossAttemptContext.length,
1795
+ similarIssueChars: similarIssueTraceContext.length,
1796
+ gradingChars: text.length - crossAttemptContext.length - similarIssueTraceContext.length,
1797
+ budgetTotalChars: budget.totalChars,
1798
+ budgetUtilizationPct: budget.totalChars > 0 ? Math.round(text.length / budget.totalChars * 100) : 0,
1799
+ modelName: options.modelName ?? null
1800
+ };
1801
+ buildRetryContext.lastMetrics = metrics;
1802
+ buildRetryContext.lastAnalysis = analysis;
1803
+ return text;
1804
+ }
1805
+ function getLastRetryContextMetrics() {
1806
+ const metrics = buildRetryContext.lastMetrics;
1807
+ const analysis = buildRetryContext.lastAnalysis;
1808
+ if (!metrics) return null;
1809
+ return {
1810
+ metrics,
1811
+ hypothesesGenerated: analysis?.hypotheses?.length ?? 0,
1812
+ strategyPivotTriggered: Boolean(analysis?.strategyPivot),
1813
+ similarIssuesUsed: 0
1814
+ // populated by caller if needed
1815
+ };
1009
1816
  }
1010
1817
  async function buildPrompt(issue, _workflowDefinition) {
1011
1818
  const rendered = await renderPrompt("workflow-default", { issue, attempt: issue.attempts || 0 });
@@ -1091,14 +1898,15 @@ async function buildProviderBasePrompt(provider, issue, basePrompt, workspacePat
1091
1898
  }
1092
1899
 
1093
1900
  // src/agents/memory-engine.ts
1901
+ init_helpers();
1094
1902
  import {
1095
- existsSync as existsSync4,
1903
+ existsSync as existsSync7,
1096
1904
  mkdirSync as mkdirSync2,
1097
- readdirSync,
1098
- readFileSync as readFileSync2,
1099
- writeFileSync as writeFileSync2
1905
+ readdirSync as readdirSync4,
1906
+ readFileSync as readFileSync4,
1907
+ writeFileSync as writeFileSync4
1100
1908
  } from "fs";
1101
- import { join as join3 } from "path";
1909
+ import { join as join6 } from "path";
1102
1910
  var MEMORY_DIRNAME = "memory";
1103
1911
  var WORKFLOW_FILE = "WORKFLOW.md";
1104
1912
  var MEMORY_FILE = "MEMORY.md";
@@ -1107,19 +1915,19 @@ function resolveTodayDate(value = now()) {
1107
1915
  return value.slice(0, 10);
1108
1916
  }
1109
1917
  function resolvePaths(workspacePath, date = resolveTodayDate()) {
1110
- const memoryDir = join3(workspacePath, MEMORY_DIRNAME);
1918
+ const memoryDir = join6(workspacePath, MEMORY_DIRNAME);
1111
1919
  return {
1112
1920
  root: workspacePath,
1113
1921
  memoryDir,
1114
- workflowFile: join3(workspacePath, WORKFLOW_FILE),
1115
- memoryFile: join3(workspacePath, MEMORY_FILE),
1116
- heartbeatFile: join3(workspacePath, HEARTBEAT_FILE),
1117
- dailyFile: join3(memoryDir, `${date}.md`)
1922
+ workflowFile: join6(workspacePath, WORKFLOW_FILE),
1923
+ memoryFile: join6(workspacePath, MEMORY_FILE),
1924
+ heartbeatFile: join6(workspacePath, HEARTBEAT_FILE),
1925
+ dailyFile: join6(memoryDir, `${date}.md`)
1118
1926
  };
1119
1927
  }
1120
1928
  function readText(filePath) {
1121
1929
  try {
1122
- return existsSync4(filePath) ? readFileSync2(filePath, "utf8") : "";
1930
+ return existsSync7(filePath) ? readFileSync4(filePath, "utf8") : "";
1123
1931
  } catch {
1124
1932
  return "";
1125
1933
  }
@@ -1127,12 +1935,12 @@ function readText(filePath) {
1127
1935
  function writeIfChanged(filePath, next) {
1128
1936
  const current = readText(filePath);
1129
1937
  if (current === next) return false;
1130
- writeFileSync2(filePath, next, "utf8");
1938
+ writeFileSync4(filePath, next, "utf8");
1131
1939
  return true;
1132
1940
  }
1133
1941
  function ensureFile(filePath, initial) {
1134
- if (existsSync4(filePath)) return false;
1135
- writeFileSync2(filePath, initial, "utf8");
1942
+ if (existsSync7(filePath)) return false;
1943
+ writeFileSync4(filePath, initial, "utf8");
1136
1944
  return true;
1137
1945
  }
1138
1946
  function renderWorkflowDocument(issue) {
@@ -1316,12 +2124,12 @@ function appendUniqueEntry(filePath, entry) {
1316
2124
  const current = readText(filePath);
1317
2125
  if (current.includes(marker)) return false;
1318
2126
  const prefix = current && !current.endsWith("\n") ? "\n\n" : current ? "\n" : "";
1319
- writeFileSync2(filePath, `${current}${prefix}${renderEntry(entry)}`, "utf8");
2127
+ writeFileSync4(filePath, `${current}${prefix}${renderEntry(entry)}`, "utf8");
1320
2128
  return true;
1321
2129
  }
1322
2130
  function listRecentDailyFiles(memoryDir) {
1323
- if (!existsSync4(memoryDir)) return [];
1324
- return readdirSync(memoryDir).filter((entry) => entry.endsWith(".md")).sort((left, right) => right.localeCompare(left)).slice(0, 3).map((entry) => join3(memoryDir, entry));
2131
+ if (!existsSync7(memoryDir)) return [];
2132
+ return readdirSync4(memoryDir).filter((entry) => entry.endsWith(".md")).sort((left, right) => right.localeCompare(left)).slice(0, 3).map((entry) => join6(memoryDir, entry));
1325
2133
  }
1326
2134
  function ensureWorkspaceMemoryFiles(issue, workspacePath) {
1327
2135
  const paths = resolvePaths(workspacePath);
@@ -1342,8 +2150,8 @@ function flushWorkspaceMemory(issue, workspacePath, reason) {
1342
2150
  let promotedEntries = 0;
1343
2151
  if (writeIfChanged(paths.workflowFile, renderWorkflowDocument(issue))) changedFiles.push(paths.workflowFile);
1344
2152
  if (writeIfChanged(paths.heartbeatFile, renderHeartbeatDocument(issue))) changedFiles.push(paths.heartbeatFile);
1345
- if (!existsSync4(paths.memoryFile)) {
1346
- writeFileSync2(paths.memoryFile, renderMemoryHeader(issue), "utf8");
2153
+ if (!existsSync7(paths.memoryFile)) {
2154
+ writeFileSync4(paths.memoryFile, renderMemoryHeader(issue), "utf8");
1347
2155
  changedFiles.push(paths.memoryFile);
1348
2156
  }
1349
2157
  for (const entry of buildDurableEntries(issue)) {
@@ -1443,11 +2251,11 @@ function shouldSkipPath(relativePath) {
1443
2251
  return false;
1444
2252
  }
1445
2253
  function bootstrapSource() {
1446
- if (existsSync5(SOURCE_MARKER)) return;
2254
+ if (existsSync8(SOURCE_MARKER)) return;
1447
2255
  logger.info("Creating local source snapshot for Fifony (local-only runtime)...");
1448
2256
  const copyRecursive = (source, target, rel = "") => {
1449
2257
  mkdirSync3(target, { recursive: true });
1450
- const items = readdirSync2(source, { withFileTypes: true });
2258
+ const items = readdirSync5(source, { withFileTypes: true });
1451
2259
  for (const item of items) {
1452
2260
  const nextRel = rel ? `${rel}/${item.name}` : item.name;
1453
2261
  if (shouldSkipPath(nextRel)) continue;
@@ -1461,8 +2269,8 @@ function bootstrapSource() {
1461
2269
  if (item.isSymbolicLink() || itemStat.isSymbolicLink()) continue;
1462
2270
  if (itemStat.isFile() || itemStat.isFIFO()) {
1463
2271
  try {
1464
- const file = readFileSync3(sourcePath);
1465
- writeFileSync3(targetPath, file);
2272
+ const file = readFileSync5(sourcePath);
2273
+ writeFileSync5(targetPath, file);
1466
2274
  } catch (error) {
1467
2275
  if (error.code === "ENOENT") {
1468
2276
  logger.debug(`Skipped missing source file: ${sourcePath}`);
@@ -1475,7 +2283,7 @@ function bootstrapSource() {
1475
2283
  };
1476
2284
  mkdirSync3(SOURCE_ROOT, { recursive: true });
1477
2285
  copyRecursive(TARGET_ROOT, SOURCE_ROOT);
1478
- writeFileSync3(SOURCE_MARKER, `${now()}
2286
+ writeFileSync5(SOURCE_MARKER, `${now()}
1479
2287
  `, "utf8");
1480
2288
  }
1481
2289
  var sourceReadyPromise = null;
@@ -1488,7 +2296,7 @@ async function ensureSourceReady(onProgress) {
1488
2296
  onProgress?.("ready");
1489
2297
  return;
1490
2298
  }
1491
- if (existsSync5(SOURCE_MARKER)) {
2299
+ if (existsSync8(SOURCE_MARKER)) {
1492
2300
  onProgress?.("ready");
1493
2301
  return;
1494
2302
  }
@@ -1628,9 +2436,9 @@ var CLI_CONFIG_DIRS = [".claude", ".codex", ".gemini"];
1628
2436
  var CLI_CONFIG_FILES = ["CLAUDE.md"];
1629
2437
  function copyCliConfigDirs(sourceRoot, worktreePath) {
1630
2438
  for (const dir of CLI_CONFIG_DIRS) {
1631
- const src = join4(sourceRoot, dir);
1632
- const dst = join4(worktreePath, dir);
1633
- if (existsSync5(src) && statSync(src).isDirectory() && !existsSync5(dst)) {
2439
+ const src = join7(sourceRoot, dir);
2440
+ const dst = join7(worktreePath, dir);
2441
+ if (existsSync8(src) && statSync(src).isDirectory() && !existsSync8(dst)) {
1634
2442
  try {
1635
2443
  execSync2(`cp -R "${src}" "${dst}"`, { stdio: "pipe", timeout: 1e4 });
1636
2444
  logger.debug({ dir, worktreePath }, "[Workspace] Copied CLI config dir to worktree");
@@ -1640,9 +2448,9 @@ function copyCliConfigDirs(sourceRoot, worktreePath) {
1640
2448
  }
1641
2449
  }
1642
2450
  for (const file of CLI_CONFIG_FILES) {
1643
- const src = join4(sourceRoot, file);
1644
- const dst = join4(worktreePath, file);
1645
- if (existsSync5(src) && !existsSync5(dst)) {
2451
+ const src = join7(sourceRoot, file);
2452
+ const dst = join7(worktreePath, file);
2453
+ if (existsSync8(src) && !existsSync8(dst)) {
1646
2454
  try {
1647
2455
  execSync2(`cp "${src}" "${dst}"`, { stdio: "pipe", timeout: 5e3 });
1648
2456
  logger.debug({ file, worktreePath }, "[Workspace] Copied CLI config file to worktree");
@@ -1661,16 +2469,16 @@ function isGitWorkingTree(dir) {
1661
2469
  }
1662
2470
  }
1663
2471
  function resolveTestWorkspacePath(issue) {
1664
- const workspaceRoot = issue.workspacePath ?? join4(WORKSPACE_ROOT, idToSafePath(issue.id));
1665
- return join4(workspaceRoot, "test-worktree");
2472
+ const workspaceRoot = issue.workspacePath ?? join7(WORKSPACE_ROOT, idToSafePath(issue.id));
2473
+ return join7(workspaceRoot, "test-worktree");
1666
2474
  }
1667
2475
  function createTestWorkspace(issue) {
1668
2476
  ensureGitRepoReadyForWorktrees(TARGET_ROOT, "create isolated test workspaces");
1669
2477
  assertIssueHasGitWorktree(issue, "create a test workspace");
1670
- const workspaceRoot = issue.workspacePath ?? join4(WORKSPACE_ROOT, idToSafePath(issue.id));
2478
+ const workspaceRoot = issue.workspacePath ?? join7(WORKSPACE_ROOT, idToSafePath(issue.id));
1671
2479
  const testWorkspacePath = issue.testWorkspacePath ?? resolveTestWorkspacePath(issue);
1672
2480
  mkdirSync3(workspaceRoot, { recursive: true });
1673
- if (existsSync5(testWorkspacePath)) {
2481
+ if (existsSync8(testWorkspacePath)) {
1674
2482
  if (isGitWorkingTree(testWorkspacePath)) {
1675
2483
  issue.testWorkspacePath = testWorkspacePath;
1676
2484
  issue.testApplied = true;
@@ -1727,7 +2535,7 @@ async function createGitWorktree(issue, worktreePath, baseBranch) {
1727
2535
  execSync2("git worktree prune", { cwd: TARGET_ROOT, stdio: "pipe" });
1728
2536
  } catch {
1729
2537
  }
1730
- if (existsSync5(worktreePath)) {
2538
+ if (existsSync8(worktreePath)) {
1731
2539
  try {
1732
2540
  execSync2(`git worktree remove --force "${worktreePath}"`, { cwd: TARGET_ROOT, stdio: "pipe" });
1733
2541
  } catch {
@@ -1763,11 +2571,11 @@ async function createGitWorktree(issue, worktreePath, baseBranch) {
1763
2571
  stdio: "pipe"
1764
2572
  });
1765
2573
  try {
1766
- const gitFileContent = readFileSync3(join4(worktreePath, ".git"), "utf8").trim();
2574
+ const gitFileContent = readFileSync5(join7(worktreePath, ".git"), "utf8").trim();
1767
2575
  const gitDirRel = gitFileContent.replace("gitdir: ", "").trim();
1768
2576
  const gitDirPath = resolve(worktreePath, gitDirRel);
1769
- mkdirSync3(join4(gitDirPath, "info"), { recursive: true });
1770
- writeFileSync3(join4(gitDirPath, "info", "exclude"), "fifony-*\n.fifony-*\nfifony_*\n", "utf8");
2577
+ mkdirSync3(join7(gitDirPath, "info"), { recursive: true });
2578
+ writeFileSync5(join7(gitDirPath, "info", "exclude"), "fifony-*\n.fifony-*\nfifony_*\n", "utf8");
1771
2579
  } catch (err) {
1772
2580
  logger.warn({ err: String(err) }, "[Agent] Failed to write worktree excludes");
1773
2581
  }
@@ -1780,9 +2588,9 @@ async function createGitWorktree(issue, worktreePath, baseBranch) {
1780
2588
  }
1781
2589
  async function prepareWorkspace(issue, state, defaultBranch) {
1782
2590
  const safeId = idToSafePath(issue.id);
1783
- const workspaceRoot = join4(WORKSPACE_ROOT, safeId);
1784
- const worktreePath = join4(workspaceRoot, "worktree");
1785
- const createdNow = !existsSync5(worktreePath);
2591
+ const workspaceRoot = join7(WORKSPACE_ROOT, safeId);
2592
+ const worktreePath = join7(workspaceRoot, "worktree");
2593
+ const createdNow = !existsSync8(worktreePath);
1786
2594
  if (createdNow) {
1787
2595
  mkdirSync3(workspaceRoot, { recursive: true });
1788
2596
  logger.debug({ issueId: issue.id, identifier: issue.identifier, workspacePath: workspaceRoot }, "[Agent] Creating workspace");
@@ -1797,9 +2605,9 @@ async function prepareWorkspace(issue, state, defaultBranch) {
1797
2605
  } else {
1798
2606
  logger.debug({ issueId: issue.id, workspacePath: workspaceRoot }, "[Agent] Reusing existing workspace");
1799
2607
  }
1800
- const metaPath = join4(workspaceRoot, "issue.json");
2608
+ const metaPath = join7(workspaceRoot, "issue.json");
1801
2609
  const promptText = await buildPrompt(issue, null);
1802
- const promptFile = join4(workspaceRoot, "prompt.md");
2610
+ const promptFile = join7(workspaceRoot, "prompt.md");
1803
2611
  ensureWorkspaceMemoryFiles(issue, workspaceRoot);
1804
2612
  if (createdNow) {
1805
2613
  recordWorkspaceMemoryEvent(issue, workspaceRoot, {
@@ -1816,8 +2624,8 @@ async function prepareWorkspace(issue, state, defaultBranch) {
1816
2624
  tags: ["workspace", "bootstrap"]
1817
2625
  });
1818
2626
  }
1819
- writeFileSync3(metaPath, JSON.stringify({ ...issue, runtimeSource: SOURCE_ROOT, bootstrapAt: now() }, null, 2), "utf8");
1820
- writeFileSync3(promptFile, `${promptText}
2627
+ writeFileSync5(metaPath, JSON.stringify({ ...issue, runtimeSource: SOURCE_ROOT, bootstrapAt: now() }, null, 2), "utf8");
2628
+ writeFileSync5(promptFile, `${promptText}
1821
2629
  `, "utf8");
1822
2630
  issue.workspacePath = workspaceRoot;
1823
2631
  issue.worktreePath = worktreePath;
@@ -1826,8 +2634,8 @@ async function prepareWorkspace(issue, state, defaultBranch) {
1826
2634
  }
1827
2635
  async function cleanWorkspace(issueId, issue, state) {
1828
2636
  const safeId = idToSafePath(issueId);
1829
- const workspacePath = issue?.workspacePath ?? join4(WORKSPACE_ROOT, safeId);
1830
- if (!existsSync5(workspacePath)) return;
2637
+ const workspacePath = issue?.workspacePath ?? join7(WORKSPACE_ROOT, safeId);
2638
+ if (!existsSync8(workspacePath)) return;
1831
2639
  if (state.config.beforeRemoveHook) {
1832
2640
  try {
1833
2641
  const dummyIssue = issue ?? { id: issueId, identifier: issueId };
@@ -1913,7 +2721,7 @@ function parseDiffStats(issue, raw) {
1913
2721
  }
1914
2722
  async function syncIssueDiffStatsToStore(issue) {
1915
2723
  if (!issue?.id) return;
1916
- const { getIssueStateResource } = await import("./store-S3NAYZ3S.js");
2724
+ const { getIssueStateResource } = await import("./store-P3ACO6YA.js");
1917
2725
  const issueResource = getIssueStateResource();
1918
2726
  if (!issueResource) return;
1919
2727
  const toNumber = (value) => {
@@ -2152,11 +2960,11 @@ function hydrateIssuePathsFromWorkspace(issue) {
2152
2960
  return inferredPaths;
2153
2961
  }
2154
2962
  function writeVersionedArtifacts(workspacePath, prefix, planVersion, attempt, sources) {
2155
- const { writeFileSync: _wfs, readFileSync: _rfs, existsSync: _es } = { writeFileSync: writeFileSync3, readFileSync: readFileSync3, existsSync: existsSync5 };
2963
+ const { writeFileSync: _wfs, readFileSync: _rfs, existsSync: _es } = { writeFileSync: writeFileSync5, readFileSync: readFileSync5, existsSync: existsSync8 };
2156
2964
  for (const { srcFile, destSuffix } of sources) {
2157
- const src = join4(workspacePath, srcFile);
2965
+ const src = join7(workspacePath, srcFile);
2158
2966
  if (_es(src)) {
2159
- _wfs(join4(workspacePath, `${prefix}.v${planVersion}a${attempt}.${destSuffix}`), _rfs(src, "utf8"), "utf8");
2967
+ _wfs(join7(workspacePath, `${prefix}.v${planVersion}a${attempt}.${destSuffix}`), _rfs(src, "utf8"), "utf8");
2160
2968
  }
2161
2969
  }
2162
2970
  }
@@ -2170,7 +2978,10 @@ export {
2170
2978
  runHook,
2171
2979
  recordReviewFailures,
2172
2980
  findRecurringBlockingFailures,
2981
+ persistCrossAttemptAnalysis,
2982
+ computeCrossAttemptAnalysis,
2173
2983
  buildRetryContext,
2984
+ getLastRetryContextMetrics,
2174
2985
  buildPrompt,
2175
2986
  resolveContextWindow,
2176
2987
  buildTurnPrompt,
@@ -2202,4 +3013,4 @@ export {
2202
3013
  hydrateIssuePathsFromWorkspace,
2203
3014
  writeVersionedArtifacts
2204
3015
  };
2205
- //# sourceMappingURL=chunk-QH6VCTET.js.map
3016
+ //# sourceMappingURL=chunk-RCSJFMQG.js.map