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.
- package/README.md +78 -0
- package/app/dist/assets/CommandPalette-CZDG20HW.js +1 -0
- package/app/dist/assets/{KeyboardShortcutsHelp-CqEFfGcE.js → KeyboardShortcutsHelp-TYhQc4aA.js} +1 -1
- package/app/dist/assets/OnboardingWizard-CQ9YmVIT.js +1 -0
- package/app/dist/assets/agents.lazy-CgakDm_P.js +1 -0
- package/app/dist/assets/analytics.lazy-C0rw3sov.js +1 -0
- package/app/dist/assets/{createLucideIcon-luywpIq4.js → createLucideIcon-B3bah5lk.js} +1 -1
- package/app/dist/assets/hooks-CNPue7d7.js +1 -0
- package/app/dist/assets/index-B8XCmr0-.css +1 -0
- package/app/dist/assets/index-Dfn02uW3.js +47 -0
- package/app/dist/assets/index.lazy-JdqhBwPd.js +44 -0
- package/app/dist/assets/services-CHpVij2M.css +1 -0
- package/app/dist/assets/services.lazy-BShKUCOt.js +12 -0
- package/app/dist/assets/vendor-X6HTElZW.js +9 -0
- package/app/dist/assets/viz-Dsh_q2DK.js +4 -0
- package/app/dist/index.html +5 -4
- package/app/dist/service-worker.js +32 -1
- package/dist/agent/run-local.js +89 -76
- package/dist/{agent-DFSFG6DG.js → agent-DJ4SCNBZ.js} +22 -17
- package/dist/{analytics-broadcaster-O4AE3RUK.js → analytics-broadcaster-INNYWHDJ.js} +25 -20
- package/dist/approve-plan.command-WE2CO3H2.js +21 -0
- package/dist/{chunk-HOIOVUHI.js → chunk-5M7PBFMZ.js} +8 -6
- package/dist/chunk-7R7XFXJM.js +1247 -0
- package/dist/{chunk-2PRRKBG6.js → chunk-A4P2MYJF.js} +22 -9
- package/dist/chunk-AFOV3ZAF.js +722 -0
- package/dist/chunk-AFP36N23.js +134 -0
- package/dist/{chunk-AAZKYWOY.js → chunk-AFYKGVSP.js} +103 -8
- package/dist/chunk-APJOZXRP.js +737 -0
- package/dist/chunk-DLSPRIQL.js +241 -0
- package/dist/{chunk-5AMWD66T.js → chunk-EDIPHR5B.js} +6 -4
- package/dist/{chunk-K36BWMUV.js → chunk-JU3MF3MW.js} +2526 -736
- package/dist/{chunk-7TXZYZR5.js → chunk-N5HCNY4O.js} +7 -5
- package/dist/{chunk-JRLWLZOD.js → chunk-NKMZYPIS.js} +31 -23
- package/dist/{chunk-PI7Y77R3.js → chunk-OFIVTM2E.js} +17 -7
- package/dist/{chunk-QH6VCTET.js → chunk-RCSJFMQG.js} +909 -98
- package/dist/{chunk-AAVROEQC.js → chunk-UR7T7IA6.js} +253 -349
- package/dist/{chunk-QHISYRXJ.js → chunk-VOYLU3MI.js} +57 -3
- package/dist/{chunk-EBCSQFPR.js → chunk-W5IULOWV.js} +2 -3
- package/dist/chunk-X37RNTWU.js +193 -0
- package/dist/{chunk-PACI3T4I.js → chunk-XY2APMDE.js} +13 -5
- package/dist/chunk-Z6ZWNWWR.js +34 -0
- package/dist/cli.js +45 -17
- package/dist/constants-AAP7ZGCX.js +124 -0
- package/dist/create-issue.command-SX3AXXIC.js +29 -0
- package/dist/fsm-agent-JGV22WK4.js +59 -0
- package/dist/{fsm-issue-EHTSKMFN.js → fsm-issue-LHIJM5VB.js} +12 -8
- package/dist/{fsm-service-7O4AJG2R.js → fsm-service-GGDKUTWS.js} +13 -4
- package/dist/{helpers-ON2S7UEF.js → helpers-AENVYEZJ.js} +6 -2
- package/dist/{issue-log-broadcaster-FZGVEEIX.js → issue-log-broadcaster-QQWM7LOV.js} +29 -18
- package/dist/{issues-3YNNTB4U.js → issues-RXFKKSXB.js} +10 -7
- package/dist/{log-analyzer-EIX6R6PP.js → log-analyzer-4LNXQISY.js} +30 -20
- package/dist/{logger-IFLXTQPS.js → logger-4F6ATWNA.js} +2 -1
- package/dist/mcp/server.js +6 -2
- package/dist/merge-workspace.command-ZNGIZC4O.js +29 -0
- package/dist/{parallel-executor-DWESCNX3.js → parallel-executor-OL5CB33L.js} +78 -19
- package/dist/{pid-manager-UBWXVSMD.js → pid-manager-EDT4DHAU.js} +2 -1
- package/dist/queue-workers-NSKIIMQ2.js +43 -0
- package/dist/replan-issue.command-73PETERX.js +21 -0
- package/dist/retry-issue.command-DIDP4OCS.js +21 -0
- package/dist/reverse-proxy-server-QSS3H4UH.js +97 -0
- package/dist/scheduler-5YORYECF.js +37 -0
- package/dist/service-log-broadcaster-JIUP2L3D.js +21 -0
- package/dist/{settings-SOTIS6ZD.js → settings-ZNDXYL46.js} +34 -23
- package/dist/settings.resource-OKUHXICJ.js +35 -0
- package/dist/{store-S3NAYZ3S.js → store-P3ACO6YA.js} +22 -17
- package/dist/telemetry-KVUFHDQS.js +828 -0
- package/dist/template-variants-HEPLYKMP.js +24 -0
- package/dist/trace-bundle-IJOV7IWH.js +41 -0
- package/dist/{web-push-QCTLS7EJ.js → web-push-X2LLMQ4M.js} +2 -1
- package/dist/websocket-Q2TUCIC2.js +103 -0
- package/dist/{workspace-OS7GPMCN.js → workspace-TDX3NJCX.js} +10 -6
- package/package.json +12 -9
- package/app/dist/assets/CommandPalette-CL8p78lG.js +0 -1
- package/app/dist/assets/OnboardingWizard-BmI50ZUv.js +0 -1
- package/app/dist/assets/analytics.lazy-CXGjZabc.js +0 -1
- package/app/dist/assets/index-CEaccpYh.js +0 -96
- package/app/dist/assets/index-CzzWGzux.css +0 -1
- package/app/dist/assets/vendor-uqBx3VSC.js +0 -9
- package/dist/approve-plan.command-QGQZZXTQ.js +0 -17
- package/dist/chunk-N4KFNX2G.js +0 -370
- package/dist/chunk-VM5QAYP5.js +0 -404
- package/dist/create-issue.command-VAKYRECC.js +0 -24
- package/dist/merge-workspace.command-T2NIGR4M.js +0 -24
- package/dist/queue-workers-V57BYXAY.js +0 -38
- package/dist/replan-issue.command-2GQ3QXCR.js +0 -17
- package/dist/retry-issue.command-GJBUUYDJ.js +0 -17
- package/dist/scheduler-KYILMWLD.js +0 -32
- package/dist/settings.resource-JMD3JQOS.js +0 -30
- package/dist/websocket-T2Y3BY4B.js +0 -61
|
@@ -1,38 +1,58 @@
|
|
|
1
1
|
import {
|
|
2
2
|
markIssueDirty,
|
|
3
3
|
normalizeAgentProvider
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
|
42
|
+
existsSync as existsSync8,
|
|
24
43
|
mkdirSync as mkdirSync3,
|
|
25
|
-
readdirSync as
|
|
26
|
-
readFileSync as
|
|
44
|
+
readdirSync as readdirSync5,
|
|
45
|
+
readFileSync as readFileSync5,
|
|
27
46
|
rmSync as rmSync2,
|
|
28
47
|
statSync,
|
|
29
|
-
writeFileSync as
|
|
48
|
+
writeFileSync as writeFileSync5
|
|
30
49
|
} from "fs";
|
|
31
50
|
import { copyFile, mkdir, readdir, stat, writeFile } from "fs/promises";
|
|
32
|
-
import { extname, join as
|
|
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-
|
|
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
|
|
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
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
978
|
-
|
|
979
|
-
<
|
|
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
|
-
|
|
987
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
|
1903
|
+
existsSync as existsSync7,
|
|
1096
1904
|
mkdirSync as mkdirSync2,
|
|
1097
|
-
readdirSync,
|
|
1098
|
-
readFileSync as
|
|
1099
|
-
writeFileSync as
|
|
1905
|
+
readdirSync as readdirSync4,
|
|
1906
|
+
readFileSync as readFileSync4,
|
|
1907
|
+
writeFileSync as writeFileSync4
|
|
1100
1908
|
} from "fs";
|
|
1101
|
-
import { join as
|
|
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 =
|
|
1918
|
+
const memoryDir = join6(workspacePath, MEMORY_DIRNAME);
|
|
1111
1919
|
return {
|
|
1112
1920
|
root: workspacePath,
|
|
1113
1921
|
memoryDir,
|
|
1114
|
-
workflowFile:
|
|
1115
|
-
memoryFile:
|
|
1116
|
-
heartbeatFile:
|
|
1117
|
-
dailyFile:
|
|
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
|
|
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
|
-
|
|
1938
|
+
writeFileSync4(filePath, next, "utf8");
|
|
1131
1939
|
return true;
|
|
1132
1940
|
}
|
|
1133
1941
|
function ensureFile(filePath, initial) {
|
|
1134
|
-
if (
|
|
1135
|
-
|
|
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
|
-
|
|
2127
|
+
writeFileSync4(filePath, `${current}${prefix}${renderEntry(entry)}`, "utf8");
|
|
1320
2128
|
return true;
|
|
1321
2129
|
}
|
|
1322
2130
|
function listRecentDailyFiles(memoryDir) {
|
|
1323
|
-
if (!
|
|
1324
|
-
return
|
|
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 (!
|
|
1346
|
-
|
|
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 (
|
|
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 =
|
|
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 =
|
|
1465
|
-
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
1632
|
-
const dst =
|
|
1633
|
-
if (
|
|
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 =
|
|
1644
|
-
const dst =
|
|
1645
|
-
if (
|
|
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 ??
|
|
1665
|
-
return
|
|
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 ??
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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(
|
|
1770
|
-
|
|
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 =
|
|
1784
|
-
const worktreePath =
|
|
1785
|
-
const createdNow = !
|
|
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 =
|
|
2608
|
+
const metaPath = join7(workspaceRoot, "issue.json");
|
|
1801
2609
|
const promptText = await buildPrompt(issue, null);
|
|
1802
|
-
const promptFile =
|
|
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
|
-
|
|
1820
|
-
|
|
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 ??
|
|
1830
|
-
if (!
|
|
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-
|
|
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:
|
|
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 =
|
|
2965
|
+
const src = join7(workspacePath, srcFile);
|
|
2158
2966
|
if (_es(src)) {
|
|
2159
|
-
_wfs(
|
|
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-
|
|
3016
|
+
//# sourceMappingURL=chunk-RCSJFMQG.js.map
|