pi-smart-compact 9.2.1 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -2
- package/CHANGELOG.md +95 -0
- package/dist/app/steps/extract.d.ts.map +1 -1
- package/dist/app/steps/synthesize.d.ts.map +1 -1
- package/dist/app/steps/verify.d.ts.map +1 -1
- package/dist/constants.d.ts +1 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/domain/scrub.d.ts.map +1 -1
- package/dist/domain/summary-parse.d.ts +5 -0
- package/dist/domain/summary-parse.d.ts.map +1 -1
- package/dist/index.js +715 -152
- package/dist/infra/llm-client.d.ts +1 -1
- package/dist/infra/llm-client.d.ts.map +1 -1
- package/dist/phases/synthesize.d.ts +3 -5
- package/dist/phases/synthesize.d.ts.map +1 -1
- package/dist/phases/verify.d.ts +2 -1
- package/dist/phases/verify.d.ts.map +1 -1
- package/dist/provider-eval.js +392 -58
- package/dist/provider-scenario-eval.js +800 -249
- package/dist/telemetry-report.js +364 -30
- package/dist/utils/extraction.d.ts.map +1 -1
- package/dist/utils/file-needles.d.ts.map +1 -1
- package/dist/utils/file-ref-detect.d.ts.map +1 -1
- package/dist/utils/state.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
7
7
|
import { Type as Type2 } from "typebox";
|
|
8
8
|
|
|
9
9
|
// src/constants.ts
|
|
10
|
-
var VERSION = "9.
|
|
10
|
+
var VERSION = "9.3.0";
|
|
11
11
|
var CHARS_PER_TOKEN = 3.8;
|
|
12
12
|
var MIN_COMPACTION_SAVING_RATIO = 0.1;
|
|
13
13
|
var ESTIMATOR_ROUNDING_TOLERANCE_TOKENS = 1;
|
|
@@ -1131,10 +1131,22 @@ function buildUniquePathNeedles(filePath, allPaths) {
|
|
|
1131
1131
|
});
|
|
1132
1132
|
}
|
|
1133
1133
|
function isKnownPathReference(ref, knownPaths) {
|
|
1134
|
-
const normalizedRef = normalizePath(ref);
|
|
1134
|
+
const normalizedRef = normalizePath(ref).replace(/^\/+/, "");
|
|
1135
|
+
if (!normalizedRef)
|
|
1136
|
+
return false;
|
|
1137
|
+
const pathShaped = normalizedRef.includes("/");
|
|
1135
1138
|
return knownPaths.some((path3) => {
|
|
1136
|
-
const normalizedPath = normalizePath(path3);
|
|
1137
|
-
|
|
1139
|
+
const normalizedPath = normalizePath(path3).replace(/^\/+/, "");
|
|
1140
|
+
if (normalizedPath === normalizedRef || normalizedPath.endsWith("/" + normalizedRef))
|
|
1141
|
+
return true;
|
|
1142
|
+
if (normalizedPath.endsWith(normalizedRef)) {
|
|
1143
|
+
const boundary = normalizedPath[normalizedPath.length - normalizedRef.length - 1];
|
|
1144
|
+
if (boundary && !/[\w./-]/.test(boundary))
|
|
1145
|
+
return true;
|
|
1146
|
+
}
|
|
1147
|
+
if (!pathShaped)
|
|
1148
|
+
return false;
|
|
1149
|
+
return normalizedPath.split("/").some((_, index, parts) => parts.slice(index).join("/").startsWith(normalizedRef + "/"));
|
|
1138
1150
|
});
|
|
1139
1151
|
}
|
|
1140
1152
|
|
|
@@ -1407,10 +1419,20 @@ function isLikelyFileRef(candidate) {
|
|
|
1407
1419
|
return CODE_EXT_RE.test(candidate);
|
|
1408
1420
|
}
|
|
1409
1421
|
function extractFileRefs(summary) {
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1422
|
+
const matcher = new RegExp(FILE_REF_CANDIDATE_RE.source, FILE_REF_CANDIDATE_RE.flags);
|
|
1423
|
+
const refs = [];
|
|
1424
|
+
for (const match of summary.matchAll(matcher)) {
|
|
1425
|
+
if (/[\\/]/.test(summary[(match.index ?? 0) + match[0].length] ?? ""))
|
|
1426
|
+
continue;
|
|
1427
|
+
if (isLikelyFileRef(match[0]))
|
|
1428
|
+
refs.push(match[0]);
|
|
1429
|
+
}
|
|
1430
|
+
return refs;
|
|
1412
1431
|
}
|
|
1413
1432
|
|
|
1433
|
+
// src/domain/summary-parse.ts
|
|
1434
|
+
import { createHash } from "crypto";
|
|
1435
|
+
|
|
1414
1436
|
// src/domain/summary-schema.ts
|
|
1415
1437
|
function classifyHeading(raw) {
|
|
1416
1438
|
const text = raw.replace(/^#+\s*/, "").replace(/[:\s]+$/, "").trim().toLowerCase();
|
|
@@ -1483,6 +1505,58 @@ var HEADING_RE = /^(#{1,3})\s+(.+?)\s*$/;
|
|
|
1483
1505
|
function summaryEvidenceLine(value, maxLength) {
|
|
1484
1506
|
return value.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, " ").replace(/\s+/g, " ").trim().replace(/^(?:(?:#{1,6}|[-*+]|>)\s+)+/, "").slice(0, maxLength).trim();
|
|
1485
1507
|
}
|
|
1508
|
+
function summaryPathLine(value) {
|
|
1509
|
+
return JSON.stringify(value);
|
|
1510
|
+
}
|
|
1511
|
+
function compactPathLine(value, maxLength, digest) {
|
|
1512
|
+
const minimal = JSON.stringify("#" + digest);
|
|
1513
|
+
if (minimal.length >= maxLength)
|
|
1514
|
+
return minimal;
|
|
1515
|
+
const chars = Array.from(value.replace(/\\/g, "/"));
|
|
1516
|
+
let low = 0;
|
|
1517
|
+
let high = chars.length;
|
|
1518
|
+
let best = minimal;
|
|
1519
|
+
while (low <= high) {
|
|
1520
|
+
const length = Math.floor((low + high) / 2);
|
|
1521
|
+
const candidate = JSON.stringify("\u2026/" + chars.slice(-length).join("") + "#" + digest);
|
|
1522
|
+
if (candidate.length <= maxLength) {
|
|
1523
|
+
best = candidate;
|
|
1524
|
+
low = length + 1;
|
|
1525
|
+
} else {
|
|
1526
|
+
high = length - 1;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return best;
|
|
1530
|
+
}
|
|
1531
|
+
function buildSummaryPathEvidence(paths, budgetTokens = PROFILES.balanced.summaryBudgetTokens) {
|
|
1532
|
+
const unique = Array.from(new Set(paths.filter(Boolean)));
|
|
1533
|
+
if (!unique.length)
|
|
1534
|
+
return new Map;
|
|
1535
|
+
const full = unique.map((path3) => [path3, summaryPathLine(path3)]);
|
|
1536
|
+
const minimumPerLine = JSON.stringify("#" + "x".repeat(12)).length + 3;
|
|
1537
|
+
const budgetChars = Math.max(unique.length * minimumPerLine, Math.min(20000, Math.max(4000, Math.floor(budgetTokens * 2))));
|
|
1538
|
+
if (full.reduce((total, [, line]) => total + line.length + 3, 0) <= budgetChars) {
|
|
1539
|
+
return new Map(full);
|
|
1540
|
+
}
|
|
1541
|
+
const digests = new Map;
|
|
1542
|
+
const owners = new Map;
|
|
1543
|
+
for (const path3 of unique) {
|
|
1544
|
+
const fullDigest = createHash("sha256").update(path3).digest("base64url");
|
|
1545
|
+
let digest = fullDigest.slice(0, 12);
|
|
1546
|
+
const owner = owners.get(digest);
|
|
1547
|
+
if (owner && owner !== path3) {
|
|
1548
|
+
digest = fullDigest;
|
|
1549
|
+
digests.set(owner, createHash("sha256").update(owner).digest("base64url"));
|
|
1550
|
+
}
|
|
1551
|
+
owners.set(digest, path3);
|
|
1552
|
+
digests.set(path3, digest);
|
|
1553
|
+
}
|
|
1554
|
+
const perPath = Math.max(JSON.stringify("#" + "x".repeat(12)).length, Math.floor((budgetChars - unique.length * 3) / unique.length));
|
|
1555
|
+
return new Map(unique.map((path3) => [
|
|
1556
|
+
path3,
|
|
1557
|
+
compactPathLine(path3, perPath, digests.get(path3) ?? "")
|
|
1558
|
+
]));
|
|
1559
|
+
}
|
|
1486
1560
|
function mergeBodies(first, second) {
|
|
1487
1561
|
const seen = new Set;
|
|
1488
1562
|
return [first, second].filter(Boolean).flatMap((body) => body.split(`
|
|
@@ -1507,7 +1581,11 @@ function parseSummary(markdown) {
|
|
|
1507
1581
|
if (existing)
|
|
1508
1582
|
existing.body = mergeBodies(existing.body, body);
|
|
1509
1583
|
else
|
|
1510
|
-
sections.push({
|
|
1584
|
+
sections.push({
|
|
1585
|
+
kind: currentKind,
|
|
1586
|
+
heading: currentHeading.trim(),
|
|
1587
|
+
body
|
|
1588
|
+
});
|
|
1511
1589
|
};
|
|
1512
1590
|
for (const line of lines) {
|
|
1513
1591
|
const fenceMatch = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
|
|
@@ -1674,7 +1752,14 @@ function extractMediaAttachments(msgs) {
|
|
|
1674
1752
|
const source = typeof rec.url === "string" ? "url" : typeof rec.path === "string" ? "path" : typeof rec.data === "string" || typeof rec.base64 === "string" ? "inline" : undefined;
|
|
1675
1753
|
const kind = mediaKind(type, mimeType);
|
|
1676
1754
|
if (kind !== "unknown" || source || mimeType || name) {
|
|
1677
|
-
out.push({
|
|
1755
|
+
out.push({
|
|
1756
|
+
index: i,
|
|
1757
|
+
kind,
|
|
1758
|
+
mimeType,
|
|
1759
|
+
name,
|
|
1760
|
+
sizeBytes: typeof sizeBytes === "number" ? sizeBytes : undefined,
|
|
1761
|
+
source
|
|
1762
|
+
});
|
|
1678
1763
|
}
|
|
1679
1764
|
}
|
|
1680
1765
|
}
|
|
@@ -1698,7 +1783,11 @@ function buildToolCallIndex(msgs) {
|
|
|
1698
1783
|
for (let t = 0;t < nested.length; t++) {
|
|
1699
1784
|
const tool = nested[t];
|
|
1700
1785
|
const id = nestedToolCallId(b.id, i, t, tool.id);
|
|
1701
|
-
idx.set(id, {
|
|
1786
|
+
idx.set(id, {
|
|
1787
|
+
name: tool.name,
|
|
1788
|
+
arguments: tool.arguments,
|
|
1789
|
+
msgIndex: i
|
|
1790
|
+
});
|
|
1702
1791
|
}
|
|
1703
1792
|
}
|
|
1704
1793
|
}
|
|
@@ -1729,7 +1818,10 @@ function trackFileOps(msgs, _tcIdx) {
|
|
|
1729
1818
|
const shell = extractShellFileOperations(tc.arguments);
|
|
1730
1819
|
for (const file of shell.modified) {
|
|
1731
1820
|
const existing = modMap.get(file);
|
|
1732
|
-
modMap.set(file, {
|
|
1821
|
+
modMap.set(file, {
|
|
1822
|
+
toolCalls: (existing?.toolCalls ?? 0) + 1,
|
|
1823
|
+
lastIdx: i
|
|
1824
|
+
});
|
|
1733
1825
|
deletedAt.delete(file);
|
|
1734
1826
|
}
|
|
1735
1827
|
for (const file of shell.deleted) {
|
|
@@ -1746,7 +1838,10 @@ function trackFileOps(msgs, _tcIdx) {
|
|
|
1746
1838
|
const resultText = extractText(m.content);
|
|
1747
1839
|
if (isTruncated(resultText) || !NO_OP_RE.test(resultText)) {
|
|
1748
1840
|
const existing = modMap.get(filePath);
|
|
1749
|
-
modMap.set(filePath, {
|
|
1841
|
+
modMap.set(filePath, {
|
|
1842
|
+
toolCalls: (existing?.toolCalls ?? 0) + 1,
|
|
1843
|
+
lastIdx: i
|
|
1844
|
+
});
|
|
1750
1845
|
deletedAt.delete(filePath);
|
|
1751
1846
|
}
|
|
1752
1847
|
} else if (operation === "delete") {
|
|
@@ -1759,7 +1854,11 @@ function trackFileOps(msgs, _tcIdx) {
|
|
|
1759
1854
|
}
|
|
1760
1855
|
}
|
|
1761
1856
|
return {
|
|
1762
|
-
modified: [...modMap.entries()].map(([p, d]) => ({
|
|
1857
|
+
modified: [...modMap.entries()].map(([p, d]) => ({
|
|
1858
|
+
path: p,
|
|
1859
|
+
toolCalls: d.toolCalls,
|
|
1860
|
+
lastModifiedIndex: d.lastIdx
|
|
1861
|
+
})),
|
|
1763
1862
|
read: [...readAt.entries()].sort((a, b) => a[1] - b[1]).map(([file]) => file),
|
|
1764
1863
|
deleted: [...deletedAt.entries()].sort((a, b) => a[1] - b[1]).map(([file]) => file),
|
|
1765
1864
|
referenced: [...referencedAt.entries()].sort((a, b) => a[1] - b[1]).map(([file]) => file)
|
|
@@ -1851,10 +1950,10 @@ function catalogErrors(msgs, _tcIdx) {
|
|
|
1851
1950
|
const result = msgs[k];
|
|
1852
1951
|
if (result?.role !== "toolResult" || result.isError)
|
|
1853
1952
|
continue;
|
|
1854
|
-
const resolved = retryTool.id
|
|
1953
|
+
const resolved = retryTool.id == null ? (() => {
|
|
1855
1954
|
const resultCall = tcIdx.get(result.toolCallId ?? "");
|
|
1856
1955
|
return Boolean(resultCall && sameToolOperation(retryTool, resultCall));
|
|
1857
|
-
})();
|
|
1956
|
+
})() : result.toolCallId === retryTool.id;
|
|
1858
1957
|
if (resolved) {
|
|
1859
1958
|
err.resolved = true;
|
|
1860
1959
|
break;
|
|
@@ -1877,7 +1976,12 @@ function extractDecisions(msgs, _tcIdx) {
|
|
|
1877
1976
|
continue;
|
|
1878
1977
|
for (let i = tc.msgIndex + 1;i < Math.min(msgs.length, tc.msgIndex + 4); i++) {
|
|
1879
1978
|
if (msgs[i]?.role === "toolResult" && msgs[i]?.toolCallId === id) {
|
|
1880
|
-
decisions.push({
|
|
1979
|
+
decisions.push({
|
|
1980
|
+
index: tc.msgIndex,
|
|
1981
|
+
type: "explicit",
|
|
1982
|
+
summary: question.slice(0, TRUNC.DECISION_SUMMARY),
|
|
1983
|
+
userResponse: extractText(msgs[i].content).slice(0, TRUNC.USER_RESPONSE)
|
|
1984
|
+
});
|
|
1881
1985
|
break;
|
|
1882
1986
|
}
|
|
1883
1987
|
}
|
|
@@ -1887,18 +1991,46 @@ function extractDecisions(msgs, _tcIdx) {
|
|
|
1887
1991
|
continue;
|
|
1888
1992
|
const txt = extractText(msgs[i].content);
|
|
1889
1993
|
if (CHOICE_RE.test(txt)) {
|
|
1890
|
-
decisions.push({
|
|
1994
|
+
decisions.push({
|
|
1995
|
+
index: i,
|
|
1996
|
+
type: "implicit",
|
|
1997
|
+
summary: txt.slice(0, TRUNC.DECISION_SUMMARY)
|
|
1998
|
+
});
|
|
1891
1999
|
}
|
|
1892
2000
|
}
|
|
1893
2001
|
return decisions;
|
|
1894
2002
|
}
|
|
1895
2003
|
var CONSTRAINT_PATTERNS = [
|
|
1896
|
-
{
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
{
|
|
2004
|
+
{
|
|
2005
|
+
re: /\b(?:must|need|require|has to|important)\b.*\b(?:be|use|have|include|support)\b/i,
|
|
2006
|
+
cat: "requirement",
|
|
2007
|
+
conf: TUNING.CONFIDENCE_HIGH
|
|
2008
|
+
},
|
|
2009
|
+
{
|
|
2010
|
+
re: /\b(?:don't|never|avoid|shouldn't|must not|do not|no\s+(?:need|want))\b/i,
|
|
2011
|
+
cat: "prohibition",
|
|
2012
|
+
conf: TUNING.CONFIDENCE_MEDIUM
|
|
2013
|
+
},
|
|
2014
|
+
{
|
|
2015
|
+
re: /\b(?:prefer|like|want|would rather|should)\b.*\b(?:use|be|have|with)\b/i,
|
|
2016
|
+
cat: "preference",
|
|
2017
|
+
conf: TUNING.CONFIDENCE_LOW
|
|
2018
|
+
},
|
|
2019
|
+
{
|
|
2020
|
+
re: /(?<![A-Za-z0-9_])(?:yapma|kullanma|sak\u0131n|sak\u0131nha|asla(?:\s+(?:kullanma|yapma|getirme))?|bunu yapma)(?![A-Za-z0-9_])/iu,
|
|
2021
|
+
cat: "prohibition",
|
|
2022
|
+
conf: TUNING.CONFIDENCE_MEDIUM
|
|
2023
|
+
},
|
|
2024
|
+
{
|
|
2025
|
+
re: /(?<![A-Za-z0-9_])(?:kritik|kritikal|\u00F6nemli|onemli|\u015Fart|sart|zorunlu|\u015Fart ko\u015Ful|\u00F6nemli \u015Fart|kesinlikle|kesinlikle \u015Fart|b\u00F6yle olsun|b\u00F6yle yap\u0131n|\u015F\u00F6yle olsun|\u015F\u00F6yle yap\u0131n)(?![A-Za-z0-9_])/iu,
|
|
2026
|
+
cat: "requirement",
|
|
2027
|
+
conf: TUNING.CONFIDENCE_MEDIUM
|
|
2028
|
+
},
|
|
2029
|
+
{
|
|
2030
|
+
re: /(?<![A-Za-z0-9_])(?:tercih|isterim|olsun|kullanal\u0131m|yapal\u0131m|istiyorum)(?![A-Za-z0-9_])/iu,
|
|
2031
|
+
cat: "preference",
|
|
2032
|
+
conf: TUNING.CONFIDENCE_LOW
|
|
2033
|
+
}
|
|
1902
2034
|
];
|
|
1903
2035
|
function isDiagnosticConstraintText(text) {
|
|
1904
2036
|
const candidate = text.replace(/^\s*[-*]\s+/, "").trim();
|
|
@@ -1923,7 +2055,12 @@ function mineConstraints(msgs) {
|
|
|
1923
2055
|
const normalized = candidate.toLowerCase().replace(/\s+/g, " ");
|
|
1924
2056
|
if (!seen.has(normalized)) {
|
|
1925
2057
|
seen.add(normalized);
|
|
1926
|
-
constraints.push({
|
|
2058
|
+
constraints.push({
|
|
2059
|
+
index: i,
|
|
2060
|
+
text: candidate.slice(0, TRUNC.CONSTRAINT_TEXT),
|
|
2061
|
+
category: cat,
|
|
2062
|
+
confidence: conf
|
|
2063
|
+
});
|
|
1927
2064
|
}
|
|
1928
2065
|
break;
|
|
1929
2066
|
}
|
|
@@ -1932,6 +2069,12 @@ function mineConstraints(msgs) {
|
|
|
1932
2069
|
return constraints;
|
|
1933
2070
|
}
|
|
1934
2071
|
function segmentTopicsHeuristic(msgs, pc, maxSegs = 20, _tcIdx) {
|
|
2072
|
+
const shiftBasename = (filePath) => {
|
|
2073
|
+
if (!filePath)
|
|
2074
|
+
return null;
|
|
2075
|
+
const base = path3.basename(filePath);
|
|
2076
|
+
return GENERIC_BASENAMES.has(base.toLowerCase()) ? null : base;
|
|
2077
|
+
};
|
|
1935
2078
|
const topics = [];
|
|
1936
2079
|
let startIdx = 0, tokenAcc = 0, lastFile = null, errAcc = 0;
|
|
1937
2080
|
let currentType = "exploration";
|
|
@@ -1943,7 +2086,7 @@ function segmentTopicsHeuristic(msgs, pc, maxSegs = 20, _tcIdx) {
|
|
|
1943
2086
|
const messageTokens = estimateTokens(text);
|
|
1944
2087
|
const tools = message.role === "assistant" ? (Array.isArray(message.content) ? message.content : []).flatMap(flattenToolCallBlock) : [];
|
|
1945
2088
|
const nextFile = tools.map((tool) => extractToolPath(tool.arguments)).find((value) => Boolean(value));
|
|
1946
|
-
const nextBasename =
|
|
2089
|
+
const nextBasename = shiftBasename(nextFile);
|
|
1947
2090
|
const closesActiveTool = message.role === "toolResult" && (tcIdx.get(message.toolCallId ?? "")?.msgIndex ?? -1) >= startIdx;
|
|
1948
2091
|
const fileShift = Boolean(lastFile && nextBasename && nextBasename !== lastFile);
|
|
1949
2092
|
const userShift = message.role === "user" && SHIFT_RE.test(text);
|
|
@@ -1968,7 +2111,7 @@ function segmentTopicsHeuristic(msgs, pc, maxSegs = 20, _tcIdx) {
|
|
|
1968
2111
|
for (const tool of tools) {
|
|
1969
2112
|
const filePath = extractToolPath(tool.arguments);
|
|
1970
2113
|
if (filePath) {
|
|
1971
|
-
lastFile =
|
|
2114
|
+
lastFile = shiftBasename(filePath) ?? lastFile;
|
|
1972
2115
|
currentPrimaryFile = filePath;
|
|
1973
2116
|
}
|
|
1974
2117
|
const operation = classifyToolOperation(tool.arguments, tool.name);
|
|
@@ -1991,7 +2134,13 @@ function segmentTopicsHeuristic(msgs, pc, maxSegs = 20, _tcIdx) {
|
|
|
1991
2134
|
}
|
|
1992
2135
|
}
|
|
1993
2136
|
if (startIdx < msgs.length) {
|
|
1994
|
-
topics.push({
|
|
2137
|
+
topics.push({
|
|
2138
|
+
startIndex: startIdx,
|
|
2139
|
+
endIndex: msgs.length - 1,
|
|
2140
|
+
primaryFile: currentPrimaryFile,
|
|
2141
|
+
type: currentType,
|
|
2142
|
+
errorDensity: errAcc
|
|
2143
|
+
});
|
|
1995
2144
|
}
|
|
1996
2145
|
return topics;
|
|
1997
2146
|
}
|
|
@@ -2003,10 +2152,18 @@ function buildTimeline(msgs, errors) {
|
|
|
2003
2152
|
if (m.role === "user") {
|
|
2004
2153
|
const txt = extractText(m.content);
|
|
2005
2154
|
if (!txt.startsWith("/"))
|
|
2006
|
-
timeline.push({
|
|
2155
|
+
timeline.push({
|
|
2156
|
+
index: i,
|
|
2157
|
+
event: "user_request",
|
|
2158
|
+
summary: txt.slice(0, TRUNC.TIMELINE_EVENT)
|
|
2159
|
+
});
|
|
2007
2160
|
}
|
|
2008
2161
|
if (errorIndices.has(i))
|
|
2009
|
-
timeline.push({
|
|
2162
|
+
timeline.push({
|
|
2163
|
+
index: i,
|
|
2164
|
+
event: "error",
|
|
2165
|
+
summary: errors.find((e) => e.index === i)?.message.slice(0, TRUNC.TIMELINE_ERROR) ?? "error"
|
|
2166
|
+
});
|
|
2010
2167
|
}
|
|
2011
2168
|
return timeline.length > 30 ? [
|
|
2012
2169
|
...timeline.filter((t) => t.event === "user_request").slice(-TRUNC.TIMELINE_DISPLAY),
|
|
@@ -2080,6 +2237,13 @@ function extractOpenLoops(msgs, extraction) {
|
|
|
2080
2237
|
sourceIndex: err.index
|
|
2081
2238
|
});
|
|
2082
2239
|
}
|
|
2240
|
+
const isNearbyDuplicate = (existing, text, index) => {
|
|
2241
|
+
if (Math.abs((existing.sourceIndex ?? 0) - index) >= 5)
|
|
2242
|
+
return false;
|
|
2243
|
+
const existingKey = normalizeFactKey(existing.summary);
|
|
2244
|
+
const textKey = normalizeFactKey(text);
|
|
2245
|
+
return existingKey === textKey || existingKey.includes(textKey) || textKey.includes(existingKey);
|
|
2246
|
+
};
|
|
2083
2247
|
const FOLLOWUP_RE = /(?:next\s+(?:step|thing)|todo|action item|follow\s*up|still (?:need|have) to|gotta|yapalim|yapal\u0131m|yapmamiz|yapmam\u0131z|gerekiyor|eklenecek|d\u00FCzeltilecek|duzeltilecek|bitmedi|kaldi|kald\u0131)/iu;
|
|
2084
2248
|
for (let idx = 0;idx < msgs.length; idx++) {
|
|
2085
2249
|
const msg = msgs[idx];
|
|
@@ -2089,7 +2253,7 @@ function extractOpenLoops(msgs, extraction) {
|
|
|
2089
2253
|
if (txt.length < 10 || txt.startsWith("/"))
|
|
2090
2254
|
continue;
|
|
2091
2255
|
if (FOLLOWUP_RE.test(txt)) {
|
|
2092
|
-
const isDup = loops.some((l) =>
|
|
2256
|
+
const isDup = loops.some((l) => isNearbyDuplicate(l, txt, idx));
|
|
2093
2257
|
if (!isDup) {
|
|
2094
2258
|
loops.push({
|
|
2095
2259
|
id: ID_PREFIX.OPEN_LOOP + ++loopId,
|
|
@@ -2112,7 +2276,7 @@ function extractOpenLoops(msgs, extraction) {
|
|
|
2112
2276
|
if (txt.length < 10 || txt.startsWith("/"))
|
|
2113
2277
|
continue;
|
|
2114
2278
|
if (BLOCKED_RE.test(txt)) {
|
|
2115
|
-
const isDup = loops.some((l) =>
|
|
2279
|
+
const isDup = loops.some((l) => isNearbyDuplicate(l, txt, idx));
|
|
2116
2280
|
if (!isDup) {
|
|
2117
2281
|
loops.push({
|
|
2118
2282
|
id: ID_PREFIX.OPEN_LOOP + ++loopId,
|
|
@@ -2126,20 +2290,6 @@ function extractOpenLoops(msgs, extraction) {
|
|
|
2126
2290
|
}
|
|
2127
2291
|
}
|
|
2128
2292
|
}
|
|
2129
|
-
for (const err of extraction.errors.filter((e) => e.retryAttempted && !e.resolved)) {
|
|
2130
|
-
const exists = loops.some((l) => l.type === "bugfix" && l.sourceIndex === err.index);
|
|
2131
|
-
if (!exists) {
|
|
2132
|
-
loops.push({
|
|
2133
|
-
id: ID_PREFIX.OPEN_LOOP + ++loopId,
|
|
2134
|
-
type: "retry",
|
|
2135
|
-
priority: "high",
|
|
2136
|
-
status: "open",
|
|
2137
|
-
summary: "Retried but unresolved: " + err.message.slice(0, TRUNC.SNIPPET),
|
|
2138
|
-
files: [],
|
|
2139
|
-
sourceIndex: err.index
|
|
2140
|
-
});
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
2293
|
for (const loop of loops) {
|
|
2144
2294
|
if (loop.type !== "follow-up" || loop.sourceIndex == null)
|
|
2145
2295
|
continue;
|
|
@@ -2149,8 +2299,12 @@ function extractOpenLoops(msgs, extraction) {
|
|
|
2149
2299
|
const end = Math.min(msgs.length, loop.sourceIndex + 50);
|
|
2150
2300
|
for (let index = loop.sourceIndex + 1;index < end; index++) {
|
|
2151
2301
|
const message = msgs[index];
|
|
2152
|
-
if (message?.role === "user")
|
|
2302
|
+
if (message?.role === "user") {
|
|
2303
|
+
const turn = extractText(message.content);
|
|
2304
|
+
if (turn.length < 10 || ACK_ONLY_RE.test(turn))
|
|
2305
|
+
continue;
|
|
2153
2306
|
break;
|
|
2307
|
+
}
|
|
2154
2308
|
if (message?.role !== "assistant")
|
|
2155
2309
|
continue;
|
|
2156
2310
|
const response = extractText(message.content);
|
|
@@ -3065,7 +3219,10 @@ function withCodexWireLimit(model, opts) {
|
|
|
3065
3219
|
onPayload: async (payload, requestModel) => {
|
|
3066
3220
|
const transformed = await previous?.(payload, requestModel);
|
|
3067
3221
|
const body = transformed ?? payload;
|
|
3068
|
-
return body && typeof body === "object" ? {
|
|
3222
|
+
return body && typeof body === "object" ? {
|
|
3223
|
+
...body,
|
|
3224
|
+
max_output_tokens: opts.maxTokens
|
|
3225
|
+
} : body;
|
|
3069
3226
|
}
|
|
3070
3227
|
};
|
|
3071
3228
|
}
|
|
@@ -3086,11 +3243,12 @@ function assertSuccessful(message) {
|
|
|
3086
3243
|
}
|
|
3087
3244
|
return message;
|
|
3088
3245
|
}
|
|
3089
|
-
async function withProviderDeadline(opts, invoke) {
|
|
3246
|
+
async function withProviderDeadline(opts, invoke, modelId) {
|
|
3090
3247
|
if (opts.signal?.aborted)
|
|
3091
3248
|
throw new Error("LLM request aborted before dispatch");
|
|
3092
3249
|
const controller = new AbortController;
|
|
3093
|
-
const
|
|
3250
|
+
const multiplier = modelId && !((opts.codexWatchdogMs ?? 0) > 0) ? getProviderCaps(modelId).timeoutMultiplier : 1;
|
|
3251
|
+
const watchdogMs = Math.round(resolveCodexWatchdogMs(opts.maxTokens, opts.codexWatchdogMs) * multiplier);
|
|
3094
3252
|
const abort = Promise.withResolvers();
|
|
3095
3253
|
const abortFromCaller = () => {
|
|
3096
3254
|
controller.abort(opts.signal?.reason);
|
|
@@ -3163,7 +3321,7 @@ var rawLlmClient = {
|
|
|
3163
3321
|
return completeChatGptCodex(model, body, bounded);
|
|
3164
3322
|
const response = bounded.reasoning === undefined ? await (await resolveComplete())(model, body, bounded) : await (await resolveCompleteSimple())(model, body, bounded);
|
|
3165
3323
|
return assertSuccessful(response);
|
|
3166
|
-
});
|
|
3324
|
+
}, model.id);
|
|
3167
3325
|
}
|
|
3168
3326
|
};
|
|
3169
3327
|
var defaultLlmClient = rawLlmClient;
|
|
@@ -3177,7 +3335,10 @@ import crypto4 from "crypto";
|
|
|
3177
3335
|
|
|
3178
3336
|
// src/domain/scrub.ts
|
|
3179
3337
|
var SECRET_PATTERNS = [
|
|
3180
|
-
{
|
|
3338
|
+
{
|
|
3339
|
+
kind: "private-key",
|
|
3340
|
+
regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
|
|
3341
|
+
},
|
|
3181
3342
|
{ kind: "aws-access-key", regex: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
3182
3343
|
{ kind: "google-api-key", regex: /\bAIza[0-9A-Za-z_-]{30,}\b/g },
|
|
3183
3344
|
{ kind: "stripe-key", regex: /\b[rs]k_(?:live|test)_[0-9A-Za-z]{16,}\b/g },
|
|
@@ -3186,8 +3347,15 @@ var SECRET_PATTERNS = [
|
|
|
3186
3347
|
{ kind: "github-token", regex: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g },
|
|
3187
3348
|
{ kind: "api-key", regex: /\bsk-(?:ant-)?[A-Za-z0-9_-]{20,}\b/g },
|
|
3188
3349
|
{ kind: "slack-token", regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
3189
|
-
{
|
|
3190
|
-
|
|
3350
|
+
{
|
|
3351
|
+
kind: "jwt",
|
|
3352
|
+
regex: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g
|
|
3353
|
+
},
|
|
3354
|
+
{
|
|
3355
|
+
kind: "bearer-token",
|
|
3356
|
+
regex: /\bBearer\s+[A-Za-z0-9._~+/-]{12,}=*/gi,
|
|
3357
|
+
replacement: () => "Bearer [REDACTED:bearer-token]"
|
|
3358
|
+
},
|
|
3191
3359
|
{
|
|
3192
3360
|
kind: "connection-password",
|
|
3193
3361
|
regex: /\b([a-z][a-z0-9+.-]*:\/\/[^:\s/@]+:)[^@\s/]+(@)/gi,
|
|
@@ -3196,12 +3364,35 @@ var SECRET_PATTERNS = [
|
|
|
3196
3364
|
{
|
|
3197
3365
|
kind: "credential",
|
|
3198
3366
|
regex: /\b((?:[A-Za-z0-9]+[_-])*(?:api[_-]?key|access[_-]?token|auth[_-]?token|token|password|passwd|secret(?:[_-]?(?:access)?[_-]?key)?|client[_-]?secret)(?:[_-][A-Za-z0-9]+)*)\s*([:=])\s*["']?([^\s"']{16,})["']?/gi,
|
|
3199
|
-
replacement: (name, separator) => name + separator + "[REDACTED:credential]"
|
|
3367
|
+
replacement: (name, separator, value, match) => /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value) ? match : name + separator + "[REDACTED:credential]"
|
|
3200
3368
|
}
|
|
3201
3369
|
];
|
|
3370
|
+
function passesLuhn(candidate) {
|
|
3371
|
+
const digits = candidate.replace(/\D/g, "");
|
|
3372
|
+
if (digits.length < 13 || digits.length > 19)
|
|
3373
|
+
return false;
|
|
3374
|
+
if (/^(\d)\1+$/.test(digits))
|
|
3375
|
+
return false;
|
|
3376
|
+
let sum = 0, double = false;
|
|
3377
|
+
for (let i = digits.length - 1;i >= 0; i--) {
|
|
3378
|
+
let d = digits.charCodeAt(i) - 48;
|
|
3379
|
+
if (double) {
|
|
3380
|
+
d *= 2;
|
|
3381
|
+
if (d > 9)
|
|
3382
|
+
d -= 9;
|
|
3383
|
+
}
|
|
3384
|
+
sum += d;
|
|
3385
|
+
double = !double;
|
|
3386
|
+
}
|
|
3387
|
+
return sum % 10 === 0;
|
|
3388
|
+
}
|
|
3202
3389
|
var PII_PATTERNS = [
|
|
3203
3390
|
{ kind: "email", regex: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi },
|
|
3204
|
-
{
|
|
3391
|
+
{
|
|
3392
|
+
kind: "payment-card",
|
|
3393
|
+
regex: /\b(?:\d[ -]*?){13,19}\b/g,
|
|
3394
|
+
replacement: (candidate) => passesLuhn(candidate) ? "[REDACTED:payment-card]" : candidate
|
|
3395
|
+
},
|
|
3205
3396
|
{ kind: "phone", regex: /(?<![\w.])(?:\+?\d[\d ()-]{8,}\d)(?![\w.])/g }
|
|
3206
3397
|
];
|
|
3207
3398
|
function redact(text, patterns) {
|
|
@@ -3209,15 +3400,22 @@ function redact(text, patterns) {
|
|
|
3209
3400
|
let value = text;
|
|
3210
3401
|
for (const pattern of patterns) {
|
|
3211
3402
|
value = value.replace(pattern.regex, (...args) => {
|
|
3212
|
-
|
|
3403
|
+
const match = String(args[0]);
|
|
3404
|
+
let replacement = "[REDACTED:" + pattern.kind + "]";
|
|
3213
3405
|
if (pattern.replacement) {
|
|
3214
3406
|
const groups = args.slice(1, -2).map(String);
|
|
3215
|
-
|
|
3407
|
+
replacement = pattern.replacement(...groups, match);
|
|
3216
3408
|
}
|
|
3217
|
-
|
|
3409
|
+
if (replacement === match)
|
|
3410
|
+
return match;
|
|
3411
|
+
counts.set(pattern.kind, (counts.get(pattern.kind) ?? 0) + 1);
|
|
3412
|
+
return replacement;
|
|
3218
3413
|
});
|
|
3219
3414
|
}
|
|
3220
|
-
return {
|
|
3415
|
+
return {
|
|
3416
|
+
value,
|
|
3417
|
+
findings: [...counts].map(([kind, count]) => ({ kind, count }))
|
|
3418
|
+
};
|
|
3221
3419
|
}
|
|
3222
3420
|
function mergeFindings(target, findings) {
|
|
3223
3421
|
for (const finding of findings)
|
|
@@ -3247,7 +3445,6 @@ var SECRET_KEY_NAMES = {
|
|
|
3247
3445
|
set_cookie: true,
|
|
3248
3446
|
otp: true,
|
|
3249
3447
|
one_time_password: true,
|
|
3250
|
-
pin: true,
|
|
3251
3448
|
passcode: true
|
|
3252
3449
|
};
|
|
3253
3450
|
function normalizeObjectKey(key) {
|
|
@@ -3313,7 +3510,7 @@ class SecretScrubber {
|
|
|
3313
3510
|
const output = {};
|
|
3314
3511
|
seen.set(value2, output);
|
|
3315
3512
|
for (const [key, item] of Object.entries(value2)) {
|
|
3316
|
-
const carriesSecret = typeof item === "string"
|
|
3513
|
+
const carriesSecret = typeof item === "string" && item.length >= 8;
|
|
3317
3514
|
if (this.secretsEnabled && isSecretBearingKey(key) && carriesSecret) {
|
|
3318
3515
|
output[key] = "[REDACTED:credential]";
|
|
3319
3516
|
recordCredential();
|
|
@@ -3324,7 +3521,10 @@ class SecretScrubber {
|
|
|
3324
3521
|
return output;
|
|
3325
3522
|
};
|
|
3326
3523
|
const value = visit(input);
|
|
3327
|
-
return {
|
|
3524
|
+
return {
|
|
3525
|
+
value,
|
|
3526
|
+
findings: [...findings].map(([kind, count]) => ({ kind, count }))
|
|
3527
|
+
};
|
|
3328
3528
|
}
|
|
3329
3529
|
count() {
|
|
3330
3530
|
return this.total;
|
|
@@ -4772,7 +4972,7 @@ function writeMetricsDashboard(entries = readMetricsLog(200), damageEntries = re
|
|
|
4772
4972
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4773
4973
|
|
|
4774
4974
|
// src/app/session-run-lock.ts
|
|
4775
|
-
import { createHash, randomUUID } from "crypto";
|
|
4975
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
4776
4976
|
import fs5 from "fs";
|
|
4777
4977
|
import path6 from "path";
|
|
4778
4978
|
function processAlive(pid) {
|
|
@@ -4898,7 +5098,7 @@ function createSessionRunLock(maxConcurrent = 2, options = {}) {
|
|
|
4898
5098
|
return true;
|
|
4899
5099
|
}
|
|
4900
5100
|
try {
|
|
4901
|
-
const sessionHash =
|
|
5101
|
+
const sessionHash = createHash2("sha256").update(sessionId).digest("hex").slice(0, 24);
|
|
4902
5102
|
const session = acquireFileLease(path6.join(leaseDir, "session-" + sessionHash + ".lock"), staleMs);
|
|
4903
5103
|
if (!session)
|
|
4904
5104
|
return false;
|
|
@@ -5937,7 +6137,15 @@ function sanitizeCompactionStateEvidence(state) {
|
|
|
5937
6137
|
const criticalContext = state.criticalContext.filter((item) => !isNoise(item));
|
|
5938
6138
|
if (goal === state.goal && constraints.length === state.constraints.length && unresolvedErrors.length === state.unresolvedErrors.length && resolvedErrors.length === state.resolvedErrors.length && openLoops.length === state.openLoops.length && criticalContext.length === state.criticalContext.length)
|
|
5939
6139
|
return state;
|
|
5940
|
-
return {
|
|
6140
|
+
return {
|
|
6141
|
+
...state,
|
|
6142
|
+
goal,
|
|
6143
|
+
constraints,
|
|
6144
|
+
unresolvedErrors,
|
|
6145
|
+
resolvedErrors,
|
|
6146
|
+
openLoops,
|
|
6147
|
+
criticalContext
|
|
6148
|
+
};
|
|
5941
6149
|
}
|
|
5942
6150
|
function freshState(fp, data) {
|
|
5943
6151
|
if (!data)
|
|
@@ -6040,7 +6248,12 @@ function applyLoopOverrides(loops, overrides) {
|
|
|
6040
6248
|
function upsertLoopOverride(overrides, loop, patch) {
|
|
6041
6249
|
const summaryKey = normalizeFactKey(loop.summary);
|
|
6042
6250
|
const index = overrides.findIndex((override) => override.summaryKey === summaryKey);
|
|
6043
|
-
const next = {
|
|
6251
|
+
const next = {
|
|
6252
|
+
...index >= 0 ? overrides[index] : { id: loop.id, summaryKey },
|
|
6253
|
+
...patch,
|
|
6254
|
+
id: loop.id,
|
|
6255
|
+
summaryKey
|
|
6256
|
+
};
|
|
6044
6257
|
if (index < 0)
|
|
6045
6258
|
return [...overrides, next];
|
|
6046
6259
|
const copy = overrides.slice();
|
|
@@ -6065,7 +6278,10 @@ function buildCompactionState(extraction, openLoops, report, nextActions, critic
|
|
|
6065
6278
|
let decisionId = 0;
|
|
6066
6279
|
let constraintId = 0;
|
|
6067
6280
|
let errorId = 0;
|
|
6068
|
-
const fileNeedles = extraction.modifiedFiles.map((f) => ({
|
|
6281
|
+
const fileNeedles = extraction.modifiedFiles.map((f) => ({
|
|
6282
|
+
path: f.path,
|
|
6283
|
+
needles: buildPathNeedles(f.path)
|
|
6284
|
+
}));
|
|
6069
6285
|
return {
|
|
6070
6286
|
goal: extraction.mainGoal,
|
|
6071
6287
|
goalKey: extraction.mainGoal ? normalizeFactKey(extraction.mainGoal) : undefined,
|
|
@@ -6123,9 +6339,17 @@ function mergeBy(current, previous, key, limit) {
|
|
|
6123
6339
|
return true;
|
|
6124
6340
|
}).slice(0, limit);
|
|
6125
6341
|
}
|
|
6126
|
-
var LOOP_PRIORITY = {
|
|
6342
|
+
var LOOP_PRIORITY = {
|
|
6343
|
+
critical: 0,
|
|
6344
|
+
high: 1,
|
|
6345
|
+
normal: 2,
|
|
6346
|
+
low: 3
|
|
6347
|
+
};
|
|
6127
6348
|
function mergeOpenLoops(current, previous) {
|
|
6128
|
-
return mergeBy(current, previous, (item) => normalizeFactKey(item.summary), Number.MAX_SAFE_INTEGER).map((item, order) => ({ item, order })).sort((a, b) => Number(a.item.status === "resolved") - Number(b.item.status === "resolved") || LOOP_PRIORITY[a.item.priority] - LOOP_PRIORITY[b.item.priority] || a.order - b.order).slice(0, MAX_STATE_OPEN_LOOPS).map(({ item }, index) => ({
|
|
6349
|
+
return mergeBy(current, previous, (item) => normalizeFactKey(item.summary), Number.MAX_SAFE_INTEGER).map((item, order) => ({ item, order })).sort((a, b) => Number(a.item.status === "resolved") - Number(b.item.status === "resolved") || LOOP_PRIORITY[a.item.priority] - LOOP_PRIORITY[b.item.priority] || a.order - b.order).slice(0, MAX_STATE_OPEN_LOOPS).map(({ item }, index) => ({
|
|
6350
|
+
...item,
|
|
6351
|
+
id: ID_PREFIX.OPEN_LOOP + (index + 1)
|
|
6352
|
+
}));
|
|
6129
6353
|
}
|
|
6130
6354
|
function mergeCompactionStates(previous, current) {
|
|
6131
6355
|
if (!previous) {
|
|
@@ -6138,13 +6362,27 @@ function mergeCompactionStates(previous, current) {
|
|
|
6138
6362
|
const currentPresent = new Set([...activeCurrent.modifiedFiles, ...activeCurrent.readFiles].map(normalizeFactKey));
|
|
6139
6363
|
const currentDeleted = new Set(activeCurrent.deletedFiles.map(normalizeFactKey));
|
|
6140
6364
|
const resolvedKeys = new Set(activeCurrent.resolvedErrors.map((error2) => normalizeFactKey(error2.message)));
|
|
6365
|
+
const isResolvedLoop = (loop) => {
|
|
6366
|
+
if (loop.type !== "bugfix" || loop.status !== "open")
|
|
6367
|
+
return false;
|
|
6368
|
+
const summaryKey = normalizeFactKey(loop.summary);
|
|
6369
|
+
if (summaryKey.length < 16)
|
|
6370
|
+
return false;
|
|
6371
|
+
for (const key of resolvedKeys) {
|
|
6372
|
+
if (key === summaryKey || key.length > summaryKey.length && key.startsWith(summaryKey))
|
|
6373
|
+
return true;
|
|
6374
|
+
}
|
|
6375
|
+
return false;
|
|
6376
|
+
};
|
|
6141
6377
|
const decisions = mergeBy(activeCurrent.decisions, activePrevious.decisions, (item) => normalizeFactKey(item.summary), 30).map((item, index) => ({ ...item, id: ID_PREFIX.DECISION + (index + 1) }));
|
|
6142
6378
|
const constraints = mergeBy(activeCurrent.constraints, activePrevious.constraints, (item) => normalizeFactKey(item.text), 30).map((item, index) => ({ ...item, id: "constraint-" + (index + 1) }));
|
|
6143
6379
|
const unresolvedErrors = mergeBy(activeCurrent.unresolvedErrors, activePrevious.unresolvedErrors.filter((error2) => !resolvedKeys.has(normalizeFactKey(error2.message))), (item) => normalizeFactKey(item.message), 15).map((item, index) => ({ ...item, id: ID_PREFIX.ERROR + (index + 1) }));
|
|
6144
|
-
const openLoops = mergeOpenLoops(activeCurrent.openLoops, activePrevious.openLoops);
|
|
6380
|
+
const openLoops = mergeOpenLoops(activeCurrent.openLoops, activePrevious.openLoops.map((loop) => isResolvedLoop(loop) ? { ...loop, status: "resolved" } : loop));
|
|
6145
6381
|
const currentGoalKey = activeCurrent.goalKey ?? (activeCurrent.goal ? normalizeFactKey(activeCurrent.goal) : "");
|
|
6146
6382
|
const previousGoalKey = activePrevious.goalKey ?? (activePrevious.goal ? normalizeFactKey(activePrevious.goal) : "");
|
|
6147
|
-
const
|
|
6383
|
+
const PREV_GOAL_PREFIX = "Previous goal: ";
|
|
6384
|
+
const durablePreviousContext = activePrevious.criticalContext.filter((line) => !line.startsWith(PREV_GOAL_PREFIX));
|
|
6385
|
+
const oldGoal = previousGoalKey && currentGoalKey && previousGoalKey !== currentGoalKey ? [PREV_GOAL_PREFIX + activePrevious.goal] : [];
|
|
6148
6386
|
return applyContinuityOverrides({
|
|
6149
6387
|
...activeCurrent,
|
|
6150
6388
|
goal: activeCurrent.goal ?? activePrevious.goal,
|
|
@@ -6161,7 +6399,7 @@ function mergeCompactionStates(previous, current) {
|
|
|
6161
6399
|
factOverrides,
|
|
6162
6400
|
topics: mergeBy(activeCurrent.topics, activePrevious.topics, (item) => normalizeFactKey(item.title), 30),
|
|
6163
6401
|
nextActions: mergeBy(activeCurrent.nextActions, activePrevious.nextActions, normalizeFactKey, 15),
|
|
6164
|
-
criticalContext: mergeBy([...oldGoal, ...activeCurrent.criticalContext],
|
|
6402
|
+
criticalContext: mergeBy([...oldGoal, ...activeCurrent.criticalContext], durablePreviousContext, normalizeFactKey, 20),
|
|
6165
6403
|
updatedAt: Date.now()
|
|
6166
6404
|
}, factOverrides);
|
|
6167
6405
|
}
|
|
@@ -7555,7 +7793,11 @@ function extractWithCache(rc) {
|
|
|
7555
7793
|
return convText;
|
|
7556
7794
|
const safeMessages = scrubLlmMessages(selectedMessages, rc.services.scrubber);
|
|
7557
7795
|
const backupText = serializeConversation(asSerializableMessages(safeMessages));
|
|
7558
|
-
|
|
7796
|
+
const scrubbed = rc.services.scrubber.scrubText(backupText);
|
|
7797
|
+
if (scrubbed.findings.length > 0) {
|
|
7798
|
+
rc.notify("Backup written with redactions (" + scrubbed.findings.map((f) => f.count + "x " + f.kind).join(", ") + ") \u2014 restore will lack that data", "info");
|
|
7799
|
+
}
|
|
7800
|
+
return scrubbed.value;
|
|
7559
7801
|
};
|
|
7560
7802
|
preparedBackup = prepareConversationBackup(materializeBackup, rc.sessionId, {
|
|
7561
7803
|
branchLeafId: branchEntryIds(rc.branch).at(-1),
|
|
@@ -7583,7 +7825,7 @@ function extractWithCache(rc) {
|
|
|
7583
7825
|
cacheUsable = branchPrefixMatch && prunedPrefixMatch && boundedCacheShape && cachedExt.messageCount === keptCount && cachedExt.messageCount <= rc.llmMessages.length;
|
|
7584
7826
|
cacheExact = cacheUsable && cachedExt.messageCount === rc.llmMessages.length;
|
|
7585
7827
|
if (!cacheUsable) {
|
|
7586
|
-
missReason =
|
|
7828
|
+
missReason = branchPrefixMatch ? prunedPrefixMatch ? boundedCacheShape ? cachedExt.messageCount === keptCount ? "cache-domain-ahead" : "cache-shape-mismatch" : "cache-evidence-unbounded" : "pruned-prefix-changed" : "entry-prefix-mismatch";
|
|
7587
7829
|
}
|
|
7588
7830
|
} else {
|
|
7589
7831
|
missReason = "legacy-no-kept-entryids";
|
|
@@ -8149,13 +8391,13 @@ Output ONLY JSON: {"mainGoal":"...","sessionType":"implementation|review|debuggi
|
|
|
8149
8391
|
}
|
|
8150
8392
|
|
|
8151
8393
|
// src/infra/synthesis-cache.ts
|
|
8152
|
-
import { createHash as
|
|
8394
|
+
import { createHash as createHash3 } from "crypto";
|
|
8153
8395
|
var cache = new Map;
|
|
8154
8396
|
var batchCache = new Map;
|
|
8155
8397
|
var TTL_MS = 10 * 60000;
|
|
8156
8398
|
var MAX_ENTRIES = 16;
|
|
8157
8399
|
function fingerprint(value) {
|
|
8158
|
-
return
|
|
8400
|
+
return createHash3("sha256").update(value ?? "").digest("hex");
|
|
8159
8401
|
}
|
|
8160
8402
|
function cloneExplorationReport(report) {
|
|
8161
8403
|
if (!report)
|
|
@@ -8219,7 +8461,7 @@ function synthesisCacheKey(rc) {
|
|
|
8219
8461
|
userNote: rc.userNote,
|
|
8220
8462
|
zeroCall: rc.config.zeroCallEnabled !== false
|
|
8221
8463
|
});
|
|
8222
|
-
return
|
|
8464
|
+
return createHash3("sha256").update(payload).digest("hex");
|
|
8223
8465
|
}
|
|
8224
8466
|
function getCachedSynthesis(key, now = Date.now()) {
|
|
8225
8467
|
const entry = cache.get(key);
|
|
@@ -8244,7 +8486,7 @@ function setCachedSynthesis(key, value, now = Date.now()) {
|
|
|
8244
8486
|
cache.set(key, { value: cloneSynthesis(value), createdAt: now });
|
|
8245
8487
|
}
|
|
8246
8488
|
function batchCacheKey(value) {
|
|
8247
|
-
return
|
|
8489
|
+
return createHash3("sha256").update(VERSION + `
|
|
8248
8490
|
` + JSON.stringify(value)).digest("hex");
|
|
8249
8491
|
}
|
|
8250
8492
|
function getCachedBatch(key, now = Date.now()) {
|
|
@@ -8309,9 +8551,12 @@ function fitChunkBudget(messages, maxTokens, estimator) {
|
|
|
8309
8551
|
const head = Math.max(8, Math.floor(target * 0.6));
|
|
8310
8552
|
const tail = Math.max(4, target - head);
|
|
8311
8553
|
changed = true;
|
|
8312
|
-
return {
|
|
8554
|
+
return {
|
|
8555
|
+
...message,
|
|
8556
|
+
content: text.slice(0, head) + `
|
|
8313
8557
|
[\u2026tool evidence bounded for synthesis\u2026]
|
|
8314
|
-
` + text.slice(-tail)
|
|
8558
|
+
` + text.slice(-tail)
|
|
8559
|
+
};
|
|
8315
8560
|
});
|
|
8316
8561
|
if (!changed)
|
|
8317
8562
|
break;
|
|
@@ -8326,12 +8571,14 @@ function fitChunkBudget(messages, maxTokens, estimator) {
|
|
|
8326
8571
|
if (chars <= 0)
|
|
8327
8572
|
return [{ role: "user", content: marker }];
|
|
8328
8573
|
const head = Math.ceil(chars * 0.6);
|
|
8329
|
-
return [
|
|
8330
|
-
|
|
8331
|
-
|
|
8574
|
+
return [
|
|
8575
|
+
{
|
|
8576
|
+
role: "user",
|
|
8577
|
+
content: rendered.slice(0, head) + `
|
|
8332
8578
|
` + marker + `
|
|
8333
8579
|
` + rendered.slice(-(chars - head))
|
|
8334
|
-
|
|
8580
|
+
}
|
|
8581
|
+
];
|
|
8335
8582
|
};
|
|
8336
8583
|
let best = candidate(0);
|
|
8337
8584
|
if (estimateChunkTokens(best, estimator) > maxTokens) {
|
|
@@ -8410,7 +8657,10 @@ function splitOversizedChunk(ch, maxTokens, estimator) {
|
|
|
8410
8657
|
});
|
|
8411
8658
|
start = end;
|
|
8412
8659
|
}
|
|
8413
|
-
return parts.map((part, index) => ({
|
|
8660
|
+
return parts.map((part, index) => ({
|
|
8661
|
+
...part,
|
|
8662
|
+
topic: ch.topic + " (part " + (index + 1) + "/" + parts.length + ")"
|
|
8663
|
+
}));
|
|
8414
8664
|
}
|
|
8415
8665
|
function chunkLlmMessages(msgs, boundaries, pc, estimator = makeTokenEstimator(), focus) {
|
|
8416
8666
|
if (!msgs.length)
|
|
@@ -8506,10 +8756,23 @@ Preserve extra detail about: ` + focus + `
|
|
|
8506
8756
|
const resp = await trackedComplete("single-pass", model, {
|
|
8507
8757
|
systemPrompt: COMPACT_SYSTEM_PREFIX,
|
|
8508
8758
|
messages: [
|
|
8509
|
-
{
|
|
8510
|
-
|
|
8759
|
+
{
|
|
8760
|
+
role: "user",
|
|
8761
|
+
content: [{ type: "text", text: adaptedPrefix }],
|
|
8762
|
+
timestamp: Date.now()
|
|
8763
|
+
},
|
|
8764
|
+
{
|
|
8765
|
+
role: "user",
|
|
8766
|
+
content: [{ type: "text", text: dynamicSuffix }],
|
|
8767
|
+
timestamp: Date.now()
|
|
8768
|
+
}
|
|
8511
8769
|
]
|
|
8512
|
-
}, {
|
|
8770
|
+
}, {
|
|
8771
|
+
apiKey: auth.apiKey,
|
|
8772
|
+
headers: auth.headers,
|
|
8773
|
+
maxTokens: Math.min(budgetTokens, getProviderCaps(model.provider).maxOutputTokens),
|
|
8774
|
+
signal
|
|
8775
|
+
}, services);
|
|
8513
8776
|
const summary = resp.content.filter((c) => c.type === "text").map((c) => c.text).join(`
|
|
8514
8777
|
`).trim();
|
|
8515
8778
|
if (!summary.startsWith("##"))
|
|
@@ -8517,7 +8780,10 @@ Preserve extra detail about: ` + focus + `
|
|
|
8517
8780
|
return { summary, llmCalls: 1 };
|
|
8518
8781
|
}
|
|
8519
8782
|
async function summarizeBatch(batch, extraction, model, auth, signal, services, maxOutputTokens, cacheScope) {
|
|
8520
|
-
const range = {
|
|
8783
|
+
const range = {
|
|
8784
|
+
start: batch[0].startIndex,
|
|
8785
|
+
end: batch[batch.length - 1].endIndex
|
|
8786
|
+
};
|
|
8521
8787
|
const extractionCtx = buildExtractionContext(extraction, range);
|
|
8522
8788
|
const activeDecisions = extraction.decisions.filter((d) => d.index < range.start).map((d) => "- " + d.summary.slice(0, TRUNC.OPEN_LOOP_SUMMARY) + (d.userResponse ? " \u2192 " + d.userResponse.slice(0, TRUNC.DECISION_DETAIL) : ""));
|
|
8523
8789
|
const decisionCtx = activeDecisions.length ? `
|
|
@@ -8548,8 +8814,16 @@ async function summarizeBatch(batch, extraction, model, auth, signal, services,
|
|
|
8548
8814
|
const resp = await trackedComplete("batch", model, {
|
|
8549
8815
|
systemPrompt: COMPACT_SYSTEM_PREFIX,
|
|
8550
8816
|
messages: [
|
|
8551
|
-
{
|
|
8552
|
-
|
|
8817
|
+
{
|
|
8818
|
+
role: "user",
|
|
8819
|
+
content: [{ type: "text", text: promptPrefix }],
|
|
8820
|
+
timestamp: Date.now()
|
|
8821
|
+
},
|
|
8822
|
+
{
|
|
8823
|
+
role: "user",
|
|
8824
|
+
content: [{ type: "text", text: dynamicSuffix }],
|
|
8825
|
+
timestamp: Date.now()
|
|
8826
|
+
}
|
|
8553
8827
|
]
|
|
8554
8828
|
}, {
|
|
8555
8829
|
apiKey: auth.apiKey,
|
|
@@ -8600,29 +8874,56 @@ async function summarizeBatch(batch, extraction, model, auth, signal, services,
|
|
|
8600
8874
|
setCachedBatch(cacheKey, result);
|
|
8601
8875
|
return result;
|
|
8602
8876
|
}
|
|
8603
|
-
|
|
8877
|
+
function deterministicFileEvidence(extraction, budgetTokens, continuity = null) {
|
|
8878
|
+
const modifiedPaths = extraction.modifiedFiles.map((file) => file.path);
|
|
8879
|
+
const readPaths = extraction.readFiles;
|
|
8880
|
+
const deletedPaths = Array.from(new Set([...extraction.deletedFiles, ...continuity?.deletedFiles ?? []]));
|
|
8881
|
+
const evidence = buildSummaryPathEvidence([...modifiedPaths, ...readPaths, ...deletedPaths], budgetTokens);
|
|
8882
|
+
return {
|
|
8883
|
+
modified: modifiedPaths.map((path12) => evidence.get(path12)).filter((path12) => Boolean(path12)),
|
|
8884
|
+
read: readPaths.map((path12) => evidence.get(path12)).filter((path12) => Boolean(path12)),
|
|
8885
|
+
deleted: deletedPaths.map((path12) => evidence.get(path12)).filter((path12) => Boolean(path12))
|
|
8886
|
+
};
|
|
8887
|
+
}
|
|
8888
|
+
async function assembleLLM(summaries, extraction, report, model, auth, budget, prevContext, signal, services, focus, continuity = null) {
|
|
8604
8889
|
const pp = preProcessSummaries(summaries, budget, focus);
|
|
8605
|
-
const
|
|
8606
|
-
const
|
|
8607
|
-
const
|
|
8890
|
+
const files = deterministicFileEvidence(extraction, budget, continuity);
|
|
8891
|
+
const detModified = files.modified;
|
|
8892
|
+
const detRead = files.read;
|
|
8893
|
+
const detDeleted = files.deleted;
|
|
8608
8894
|
const explorationCtx = report ? buildExplorationContext(report) : "";
|
|
8609
8895
|
const dynamicSuffix = ASSEMBLY_PROMPT_SUFFIX.replace("{DECISIONS}", pp.decisions.join("; ") || "None").replace("{MODIFIED}", detModified.join(", ") || "None").replace("{READ}", detRead.join(", ") || "None").replace("{DELETED}", detDeleted.join(", ") || "None").replace("{EXPLORATION_CONTEXT}", explorationCtx).replace("{PREV_CONTEXT}", prevContext).replace("{SUMMARIES}", pp.text);
|
|
8610
8896
|
const resp = await trackedComplete("assemble", model, {
|
|
8611
8897
|
systemPrompt: COMPACT_SYSTEM_PREFIX,
|
|
8612
8898
|
messages: [
|
|
8613
|
-
{
|
|
8614
|
-
|
|
8899
|
+
{
|
|
8900
|
+
role: "user",
|
|
8901
|
+
content: [{ type: "text", text: ASSEMBLY_PROMPT_PREFIX }],
|
|
8902
|
+
timestamp: Date.now()
|
|
8903
|
+
},
|
|
8904
|
+
{
|
|
8905
|
+
role: "user",
|
|
8906
|
+
content: [{ type: "text", text: dynamicSuffix }],
|
|
8907
|
+
timestamp: Date.now()
|
|
8908
|
+
}
|
|
8615
8909
|
]
|
|
8616
|
-
}, {
|
|
8910
|
+
}, {
|
|
8911
|
+
apiKey: auth.apiKey,
|
|
8912
|
+
headers: auth.headers,
|
|
8913
|
+
maxTokens: Math.min(budget, getProviderCaps(model.provider).maxOutputTokens),
|
|
8914
|
+
signal
|
|
8915
|
+
}, services);
|
|
8617
8916
|
return resp.content.filter((c) => c.type === "text").map((c) => c.text).join(`
|
|
8618
8917
|
`).trim();
|
|
8619
8918
|
}
|
|
8620
|
-
function assembleFallback(summaries, extraction, steering = {}) {
|
|
8919
|
+
function assembleFallback(summaries, extraction, steering = {}, budgetTokens = 6000, continuity = null) {
|
|
8621
8920
|
const safe = (value, max = TRUNC.PREVIEW_MID) => summaryEvidenceLine(value, max);
|
|
8622
|
-
const
|
|
8623
|
-
const
|
|
8624
|
-
const
|
|
8921
|
+
const files = deterministicFileEvidence(extraction, budgetTokens, continuity);
|
|
8922
|
+
const detModified = files.modified;
|
|
8923
|
+
const detRead = files.read;
|
|
8924
|
+
const detDeleted = files.deleted;
|
|
8625
8925
|
const unresolved = extraction.errors.filter((error2) => !error2.resolved).map((error2) => safe(error2.message, TRUNC.PREVIEW)).filter(Boolean);
|
|
8926
|
+
const resolved = extraction.errors.filter((error2) => error2.resolved).slice(-5).map((error2) => safe(error2.message, TRUNC.PREVIEW)).filter(Boolean);
|
|
8626
8927
|
const constraints = extraction.constraints.map((item) => "- [" + item.category + "] " + safe(item.text)).filter((line) => !line.endsWith("] "));
|
|
8627
8928
|
if (steering.focus?.trim())
|
|
8628
8929
|
constraints.push("- [focus] Preserve detail about: " + safe(steering.focus, TRUNC.CONSTRAINT_TEXT));
|
|
@@ -8637,12 +8938,13 @@ function assembleFallback(summaries, extraction, steering = {}) {
|
|
|
8637
8938
|
if (!inProgress.length) {
|
|
8638
8939
|
inProgress.push(...extraction.lastUserMessages.slice(-3).map((message) => safe(message, TRUNC.PREVIEW)).filter(Boolean).map((message) => "- [ ] " + message));
|
|
8639
8940
|
}
|
|
8640
|
-
inProgress.push(...detModified.map((file) => "- [ ] Continue work in " + file));
|
|
8941
|
+
inProgress.push(...detModified.slice(-5).map((file) => "- [ ] Continue work in " + file));
|
|
8641
8942
|
const next = safe(extraction.lastUserMessages.at(-1) ?? extraction.timeline.at(-1)?.summary ?? "", TRUNC.PREVIEW) || "Continue from the latest preserved context.";
|
|
8642
8943
|
const goal = safe(extraction.mainGoal ?? "", TRUNC.DETAIL) || "Continue the current task.";
|
|
8643
8944
|
const overflow = Object.entries(extraction.evidenceOverflow ?? {}).filter(([, count]) => typeof count === "number" && count > 0).map(([kind, count]) => "- Safety bound omitted " + count + " older " + kind + " item(s) from the human summary.");
|
|
8644
8945
|
const critical = [
|
|
8645
8946
|
...unresolved.map((error2) => "- Unresolved error: " + safe(error2, TRUNC.TOPIC_LABEL)),
|
|
8947
|
+
...resolved.map((error2) => "- Resolved error: " + safe(error2, TRUNC.TOPIC_LABEL)),
|
|
8646
8948
|
...overflow
|
|
8647
8949
|
];
|
|
8648
8950
|
return [
|
|
@@ -8787,7 +9089,7 @@ async function summarizeConversation(rc) {
|
|
|
8787
9089
|
phaseName: "Synthesize",
|
|
8788
9090
|
detail: "Building a deterministic continuation summary \xB7 no LLM call"
|
|
8789
9091
|
});
|
|
8790
|
-
const finalSummary2 = assembleFallback([], extraction, { focus: rc.focus, note: rc.userNote });
|
|
9092
|
+
const finalSummary2 = assembleFallback([], extraction, { focus: rc.focus, note: rc.userNote }, pc.summaryBudgetTokens, rc.previousState);
|
|
8791
9093
|
setCachedSynthesis(cacheKey, {
|
|
8792
9094
|
finalSummary: finalSummary2,
|
|
8793
9095
|
method: "heuristic",
|
|
@@ -8818,7 +9120,7 @@ async function summarizeConversation(rc) {
|
|
|
8818
9120
|
rc.vlog("Tier=" + rc.tier + " | convTokens=" + rc.convTokens + " | singlePassMax=" + singlePassMaxTokens);
|
|
8819
9121
|
let finalSummary;
|
|
8820
9122
|
let method;
|
|
8821
|
-
|
|
9123
|
+
const summaries = [];
|
|
8822
9124
|
let explorationReport = null;
|
|
8823
9125
|
let explorationRounds = 0;
|
|
8824
9126
|
let chunkCount = 0;
|
|
@@ -8839,7 +9141,7 @@ async function summarizeConversation(rc) {
|
|
|
8839
9141
|
phaseName: "Synthesize",
|
|
8840
9142
|
detail: "Summary route unavailable \xB7 building a deterministic summary"
|
|
8841
9143
|
});
|
|
8842
|
-
finalSummary = assembleFallback([], extraction, { focus: rc.focus, note: rc.userNote });
|
|
9144
|
+
finalSummary = assembleFallback([], extraction, { focus: rc.focus, note: rc.userNote }, pc.summaryBudgetTokens, rc.previousState);
|
|
8843
9145
|
method = "heuristic";
|
|
8844
9146
|
} else if (rc.convTokens < singlePassMaxTokens) {
|
|
8845
9147
|
showProgressOverlay(rc.ctx, {
|
|
@@ -8859,7 +9161,7 @@ async function summarizeConversation(rc) {
|
|
|
8859
9161
|
generationFallbacks.push("single-pass generation failed");
|
|
8860
9162
|
debugError("Single-pass synthesis used deterministic fallback", err);
|
|
8861
9163
|
rc.notify("Single-pass generation stopped \xB7 using deterministic fallback", "info");
|
|
8862
|
-
finalSummary = assembleFallback([], extraction, { focus: rc.focus, note: rc.userNote });
|
|
9164
|
+
finalSummary = assembleFallback([], extraction, { focus: rc.focus, note: rc.userNote }, pc.summaryBudgetTokens, rc.previousState);
|
|
8863
9165
|
method = "heuristic";
|
|
8864
9166
|
}
|
|
8865
9167
|
} else {
|
|
@@ -8876,7 +9178,10 @@ async function summarizeConversation(rc) {
|
|
|
8876
9178
|
});
|
|
8877
9179
|
try {
|
|
8878
9180
|
const segAuth = await resolveStageAuth(rc, "explore");
|
|
8879
|
-
const expResult = await exploreConversation(rc.llmMessages, extraction, rc.segModel, segAuth, rc.prevContext || undefined, [
|
|
9181
|
+
const expResult = await exploreConversation(rc.llmMessages, extraction, rc.segModel, segAuth, rc.prevContext || undefined, [
|
|
9182
|
+
rc.userNote,
|
|
9183
|
+
rc.config.focusWeighting && rc.focus ? "Focus extra preservation on: " + rc.focus : undefined
|
|
9184
|
+
].filter(Boolean).join(`
|
|
8880
9185
|
`) || undefined, rc.cancellation.signal, MAX_EXPLORATION_ROUNDS, rc.notify, rc.services);
|
|
8881
9186
|
explorationReport = expResult.report;
|
|
8882
9187
|
explorationRounds = expResult.rounds;
|
|
@@ -9050,7 +9355,7 @@ async function summarizeConversation(rc) {
|
|
|
9050
9355
|
totalBatches: batches.length
|
|
9051
9356
|
});
|
|
9052
9357
|
try {
|
|
9053
|
-
const r = await assembleLLM(summaries, extraction, explorationReport, rc.summaryModel, summaryAuth, pc.summaryBudgetTokens, rc.prevContext, rc.cancellation.signal, rc.services, rc.config.focusWeighting ? rc.focus : undefined);
|
|
9358
|
+
const r = await assembleLLM(summaries, extraction, explorationReport, rc.summaryModel, summaryAuth, pc.summaryBudgetTokens, rc.prevContext, rc.cancellation.signal, rc.services, rc.config.focusWeighting ? rc.focus : undefined, rc.previousState);
|
|
9054
9359
|
if (r?.startsWith("##"))
|
|
9055
9360
|
finalSummary = r;
|
|
9056
9361
|
else
|
|
@@ -9059,7 +9364,7 @@ async function summarizeConversation(rc) {
|
|
|
9059
9364
|
cacheable = false;
|
|
9060
9365
|
generationFallbacks.push("assembly generation failed");
|
|
9061
9366
|
debugError("Assembly used deterministic fallback", err);
|
|
9062
|
-
finalSummary = assembleFallback(summaries, extraction, { focus: rc.focus, note: rc.userNote });
|
|
9367
|
+
finalSummary = assembleFallback(summaries, extraction, { focus: rc.focus, note: rc.userNote }, pc.summaryBudgetTokens, rc.previousState);
|
|
9063
9368
|
}
|
|
9064
9369
|
method = "eesv";
|
|
9065
9370
|
}
|
|
@@ -9092,6 +9397,76 @@ async function summarizeConversation(rc) {
|
|
|
9092
9397
|
// src/phases/verify.ts
|
|
9093
9398
|
var HIGH_RISK_OUTCOME_RE = /(?:\ball\s+tests?\s+(?:pass|passed|passing)\b|\btests?\s+(?:pass|passed|passing)\b|\b(?:build|deployment|migration)\s+(?:completed|succeeded|passed|successful)\b|\b(?:deployed|published|released)\b|\b(?:bug|issue|error)\s+(?:fixed|resolved)\b|\bno\s+(?:errors?|failures?)\b|\bcompleted successfully\b|\btestler?\s+(?:ge\u00E7ti|ba\u015Far\u0131l\u0131)\b|\bba\u015Far\u0131yla\s+(?:tamamland\u0131|da\u011F\u0131t\u0131ld\u0131|yay\u0131nland\u0131)\b|\b(?:deploy edildi|yay\u0131nland\u0131|hata yok)\b)/iu;
|
|
9094
9399
|
var NEGATED_OUTCOME_RE = /\b(?:not|never|pending|failed|failing|unresolved|hen\u00FCz|de\u011Fil|ba\u015Far\u0131s\u0131z)\b/iu;
|
|
9400
|
+
var NONE_BLOCKER_VALUE_RE = /^(?:none|no blockers?|yok)\s*(?:recorded|known)?[.!]?$/i;
|
|
9401
|
+
var BULLET_NONE_BLOCKER_RE = /^(?:[-*+]|\d+[.)])\s+(?:none|no blockers?|yok)\s*(?:recorded|known)?[.!]?$/i;
|
|
9402
|
+
var PATH_PLACEHOLDER_RE = /^(?:none|none recorded|no blockers?|yok)[.!]?$/i;
|
|
9403
|
+
function noneBlockerLineIndexes(lines) {
|
|
9404
|
+
const indexes = new Set;
|
|
9405
|
+
const nonEmpty = lines.map((line, index) => ({ index, text: line.trim() })).filter((item) => item.text);
|
|
9406
|
+
for (const item of nonEmpty) {
|
|
9407
|
+
if (BULLET_NONE_BLOCKER_RE.test(item.text))
|
|
9408
|
+
indexes.add(item.index);
|
|
9409
|
+
}
|
|
9410
|
+
if (nonEmpty.length === 1 && NONE_BLOCKER_VALUE_RE.test(nonEmpty[0].text)) {
|
|
9411
|
+
indexes.add(nonEmpty[0].index);
|
|
9412
|
+
}
|
|
9413
|
+
return indexes;
|
|
9414
|
+
}
|
|
9415
|
+
function collectListedPaths(body, expectedPaths) {
|
|
9416
|
+
const values = new Set;
|
|
9417
|
+
const encodedValues = new Set;
|
|
9418
|
+
for (const line of body.split(`
|
|
9419
|
+
`)) {
|
|
9420
|
+
const raw = line.replace(/^\s*(?:[-*+]|\d+[.)])\s+/, "").trim();
|
|
9421
|
+
if (!raw)
|
|
9422
|
+
continue;
|
|
9423
|
+
if (raw.startsWith('"')) {
|
|
9424
|
+
try {
|
|
9425
|
+
const decoded = JSON.parse(raw);
|
|
9426
|
+
if (typeof decoded === "string") {
|
|
9427
|
+
values.add(decoded);
|
|
9428
|
+
encodedValues.add(decoded);
|
|
9429
|
+
continue;
|
|
9430
|
+
}
|
|
9431
|
+
} catch {}
|
|
9432
|
+
}
|
|
9433
|
+
values.add(raw);
|
|
9434
|
+
if (expectedPaths.has(raw))
|
|
9435
|
+
continue;
|
|
9436
|
+
const unwrapped = raw.startsWith("`") && raw.endsWith("`") ? raw.slice(1, -1) : raw;
|
|
9437
|
+
const unchecked = unwrapped.replace(/^\[[ x]\]\s+/i, "");
|
|
9438
|
+
if (expectedPaths.has(unchecked))
|
|
9439
|
+
values.add(unchecked);
|
|
9440
|
+
}
|
|
9441
|
+
return {
|
|
9442
|
+
values,
|
|
9443
|
+
encodedValues,
|
|
9444
|
+
normalizedValues: new Set(Array.from(values, normalizePath))
|
|
9445
|
+
};
|
|
9446
|
+
}
|
|
9447
|
+
function decodePathDisplay(display) {
|
|
9448
|
+
try {
|
|
9449
|
+
const decoded = JSON.parse(display);
|
|
9450
|
+
return typeof decoded === "string" ? decoded : display;
|
|
9451
|
+
} catch {
|
|
9452
|
+
return display;
|
|
9453
|
+
}
|
|
9454
|
+
}
|
|
9455
|
+
function hasListedPath(listed, file, display, normalizedOwners) {
|
|
9456
|
+
const decodedDisplay = decodePathDisplay(display);
|
|
9457
|
+
if (listed.encodedValues.has(decodedDisplay))
|
|
9458
|
+
return true;
|
|
9459
|
+
if (PATH_PLACEHOLDER_RE.test(file))
|
|
9460
|
+
return false;
|
|
9461
|
+
if (listed.values.has(file))
|
|
9462
|
+
return true;
|
|
9463
|
+
for (const candidate of [file, decodedDisplay]) {
|
|
9464
|
+
const normalized = normalizePath(candidate);
|
|
9465
|
+
if (normalizedOwners.get(normalized) === 1 && listed.normalizedValues.has(normalized))
|
|
9466
|
+
return true;
|
|
9467
|
+
}
|
|
9468
|
+
return false;
|
|
9469
|
+
}
|
|
9095
9470
|
function outcomeClaims(summary) {
|
|
9096
9471
|
return Array.from(new Set(summary.split(/\r?\n/).map((line) => line.replace(/^\s*(?:[-*+]|\d+[.)])\s+/, "").replace(/^\[[ x]\]\s+/i, "").trim()).filter((line) => line.length > 0 && !line.startsWith("#")).filter((line) => HIGH_RISK_OUTCOME_RE.test(line)).filter((line) => /\bno\s+(?:errors?|failures?)\b/i.test(line) || !NEGATED_OUTCOME_RE.test(line)))).slice(0, 12);
|
|
9097
9472
|
}
|
|
@@ -9110,6 +9485,28 @@ function classifyOutcomeClaim(claim) {
|
|
|
9110
9485
|
return "generic";
|
|
9111
9486
|
}
|
|
9112
9487
|
var successfulToolEvidenceCache = new WeakMap;
|
|
9488
|
+
var sourceTextCache = new WeakMap;
|
|
9489
|
+
function sourceSupportsFileReference(ref, messages) {
|
|
9490
|
+
let texts = sourceTextCache.get(messages);
|
|
9491
|
+
if (!texts) {
|
|
9492
|
+
texts = messages.map((message) => extractText(message.content).replace(/\\/g, "/").toLowerCase());
|
|
9493
|
+
sourceTextCache.set(messages, texts);
|
|
9494
|
+
}
|
|
9495
|
+
const needle = ref.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
|
|
9496
|
+
if (!needle)
|
|
9497
|
+
return false;
|
|
9498
|
+
for (const text of texts) {
|
|
9499
|
+
let index = text.indexOf(needle);
|
|
9500
|
+
while (index >= 0) {
|
|
9501
|
+
const before = text[index - 1] ?? "";
|
|
9502
|
+
const after = text[index + needle.length] ?? "";
|
|
9503
|
+
if ((!before || !/[\w.-]/.test(before)) && (!after || !/[\w.-]/.test(after)))
|
|
9504
|
+
return true;
|
|
9505
|
+
index = text.indexOf(needle, index + 1);
|
|
9506
|
+
}
|
|
9507
|
+
}
|
|
9508
|
+
return false;
|
|
9509
|
+
}
|
|
9113
9510
|
function successfulToolEvidence(messages) {
|
|
9114
9511
|
const cached = successfulToolEvidenceCache.get(messages);
|
|
9115
9512
|
if (cached)
|
|
@@ -9293,8 +9690,72 @@ var SEMANTIC_STOP = new Set([
|
|
|
9293
9690
|
"de\u011Fil",
|
|
9294
9691
|
"olmadan"
|
|
9295
9692
|
]);
|
|
9693
|
+
var TR_SUFFIXES = [
|
|
9694
|
+
"lar\u0131",
|
|
9695
|
+
"leri",
|
|
9696
|
+
"\u0131n\u0131n",
|
|
9697
|
+
"inin",
|
|
9698
|
+
"unun",
|
|
9699
|
+
"\xFCn\xFCn",
|
|
9700
|
+
"\u0131nda",
|
|
9701
|
+
"inde",
|
|
9702
|
+
"unda",
|
|
9703
|
+
"\xFCnde",
|
|
9704
|
+
"m\u0131\u015F",
|
|
9705
|
+
"mi\u015F",
|
|
9706
|
+
"mu\u015F",
|
|
9707
|
+
"m\xFC\u015F",
|
|
9708
|
+
"lar",
|
|
9709
|
+
"ler",
|
|
9710
|
+
"\u0131n\u0131",
|
|
9711
|
+
"ini",
|
|
9712
|
+
"unu",
|
|
9713
|
+
"\xFCn\xFC",
|
|
9714
|
+
"\u0131na",
|
|
9715
|
+
"ine",
|
|
9716
|
+
"una",
|
|
9717
|
+
"\xFCne",
|
|
9718
|
+
"dan",
|
|
9719
|
+
"den",
|
|
9720
|
+
"tan",
|
|
9721
|
+
"ten",
|
|
9722
|
+
"d\u0131r",
|
|
9723
|
+
"dir",
|
|
9724
|
+
"dur",
|
|
9725
|
+
"d\xFCr",
|
|
9726
|
+
"t\u0131r",
|
|
9727
|
+
"tir",
|
|
9728
|
+
"tur",
|
|
9729
|
+
"t\xFCr",
|
|
9730
|
+
"yor",
|
|
9731
|
+
"mak",
|
|
9732
|
+
"mek",
|
|
9733
|
+
"da",
|
|
9734
|
+
"de",
|
|
9735
|
+
"ta",
|
|
9736
|
+
"te",
|
|
9737
|
+
"d\u0131",
|
|
9738
|
+
"di",
|
|
9739
|
+
"du",
|
|
9740
|
+
"d\xFC",
|
|
9741
|
+
"t\u0131",
|
|
9742
|
+
"ti",
|
|
9743
|
+
"tu",
|
|
9744
|
+
"t\xFC",
|
|
9745
|
+
"\u0131n",
|
|
9746
|
+
"in",
|
|
9747
|
+
"un",
|
|
9748
|
+
"\xFCn",
|
|
9749
|
+
"sa",
|
|
9750
|
+
"se"
|
|
9751
|
+
];
|
|
9296
9752
|
function stemToken(token) {
|
|
9297
9753
|
const lower = token.toLocaleLowerCase();
|
|
9754
|
+
for (const suffix of TR_SUFFIXES) {
|
|
9755
|
+
if (lower.length >= 4 + suffix.length && lower.endsWith(suffix)) {
|
|
9756
|
+
return lower.slice(0, -suffix.length);
|
|
9757
|
+
}
|
|
9758
|
+
}
|
|
9298
9759
|
if (lower.length > 6 && lower.endsWith("ing"))
|
|
9299
9760
|
return lower.slice(0, -3);
|
|
9300
9761
|
if (lower.length > 5 && lower.endsWith("ed"))
|
|
@@ -9409,7 +9870,7 @@ function repairSummaryDeterministically(summary, result, extraction, continuity
|
|
|
9409
9870
|
const patchable = result.gaps.filter(isDeterministicallyPatchable);
|
|
9410
9871
|
if (!patchable.length)
|
|
9411
9872
|
break;
|
|
9412
|
-
const next = patchDeterministic(summary, patchable, extraction, continuity);
|
|
9873
|
+
const next = patchDeterministic(summary, patchable, extraction, continuity, evidence);
|
|
9413
9874
|
if (next === summary)
|
|
9414
9875
|
break;
|
|
9415
9876
|
for (const gap of patchable) {
|
|
@@ -9442,11 +9903,15 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
9442
9903
|
};
|
|
9443
9904
|
const unresolvedEvidence = uniqueByText([
|
|
9444
9905
|
...extraction.errors.filter((error2) => !error2.resolved).map((error2) => ({ message: error2.message })),
|
|
9445
|
-
...(continuity?.unresolvedErrors ?? []).map((error2) => ({
|
|
9906
|
+
...(continuity?.unresolvedErrors ?? []).map((error2) => ({
|
|
9907
|
+
message: error2.message
|
|
9908
|
+
}))
|
|
9446
9909
|
], (item) => item.message);
|
|
9447
9910
|
const resolvedEvidence = uniqueByText([
|
|
9448
9911
|
...extraction.errors.filter((error2) => error2.resolved).map((error2) => ({ message: error2.message })),
|
|
9449
|
-
...(continuity?.resolvedErrors ?? []).map((error2) => ({
|
|
9912
|
+
...(continuity?.resolvedErrors ?? []).map((error2) => ({
|
|
9913
|
+
message: error2.message
|
|
9914
|
+
}))
|
|
9450
9915
|
], (item) => item.message).slice(-5);
|
|
9451
9916
|
const steeringConstraints = [
|
|
9452
9917
|
evidence.steering?.focus ? { text: "Preserve detail about: " + evidence.steering.focus } : null,
|
|
@@ -9473,40 +9938,64 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
9473
9938
|
score -= req.penalty;
|
|
9474
9939
|
}
|
|
9475
9940
|
}
|
|
9476
|
-
const listedPaths = (kind) => new Set((findSection(parsed, kind)?.body ?? "").split(`
|
|
9477
|
-
`).map((line) => line.replace(/^\s*(?:[-*+]|\d+[.)])\s+/, "").replace(/^\[[ x]\]\s+/i, "").trim()).map((line) => line.startsWith("`") && line.endsWith("`") ? line.slice(1, -1) : line).filter((line) => line.length > 0 && !/^none(?: recorded)?[.!]?$/i.test(line)).map(normalizePath));
|
|
9478
9941
|
const modifiedPaths = extraction.modifiedFiles.map((file) => file.path);
|
|
9942
|
+
const readPaths = extraction.readFiles;
|
|
9943
|
+
const deletedEvidence = Array.from(new Set([...extraction.deletedFiles, ...continuity?.deletedFiles ?? []]));
|
|
9944
|
+
const requiredPaths = [...modifiedPaths, ...readPaths, ...deletedEvidence];
|
|
9945
|
+
const expectedPathSet = new Set(requiredPaths);
|
|
9946
|
+
const pathEvidence = buildSummaryPathEvidence(requiredPaths, evidence.summaryBudgetTokens);
|
|
9947
|
+
const normalizedOwnerSets = new Map;
|
|
9948
|
+
for (const file of requiredPaths) {
|
|
9949
|
+
const display = pathEvidence.get(file);
|
|
9950
|
+
for (const candidate of [
|
|
9951
|
+
file,
|
|
9952
|
+
...display ? [decodePathDisplay(display)] : []
|
|
9953
|
+
]) {
|
|
9954
|
+
const normalized = normalizePath(candidate);
|
|
9955
|
+
const owners = normalizedOwnerSets.get(normalized) ?? new Set;
|
|
9956
|
+
owners.add(file);
|
|
9957
|
+
normalizedOwnerSets.set(normalized, owners);
|
|
9958
|
+
}
|
|
9959
|
+
}
|
|
9960
|
+
const normalizedOwners = new Map(Array.from(normalizedOwnerSets, ([path12, owners]) => [path12, owners.size]));
|
|
9961
|
+
const listedPaths = (kind) => collectListedPaths(findSection(parsed, kind)?.body ?? "", expectedPathSet);
|
|
9479
9962
|
const modifiedListed = listedPaths("files-modified");
|
|
9480
9963
|
const readListed = listedPaths("files-read");
|
|
9481
9964
|
const deletedListed = listedPaths("files-deleted");
|
|
9482
9965
|
for (const file of modifiedPaths) {
|
|
9483
|
-
|
|
9966
|
+
const display = pathEvidence.get(file);
|
|
9967
|
+
if (display && !hasListedPath(modifiedListed, file, display, normalizedOwners)) {
|
|
9484
9968
|
gaps.push({ kind: "missing-file", path: file });
|
|
9969
|
+
}
|
|
9485
9970
|
}
|
|
9486
|
-
for (const file of
|
|
9487
|
-
|
|
9971
|
+
for (const file of readPaths) {
|
|
9972
|
+
const display = pathEvidence.get(file);
|
|
9973
|
+
if (display && !hasListedPath(readListed, file, display, normalizedOwners)) {
|
|
9488
9974
|
gaps.push({ kind: "missing-read-file", path: file });
|
|
9975
|
+
}
|
|
9489
9976
|
}
|
|
9490
|
-
const deletedEvidence = Array.from(new Set([
|
|
9491
|
-
...extraction.deletedFiles,
|
|
9492
|
-
...continuity?.deletedFiles ?? []
|
|
9493
|
-
]));
|
|
9494
9977
|
for (const file of deletedEvidence) {
|
|
9495
|
-
|
|
9978
|
+
const display = pathEvidence.get(file);
|
|
9979
|
+
if (display && !hasListedPath(deletedListed, file, display, normalizedOwners)) {
|
|
9496
9980
|
gaps.push({ kind: "missing-deleted-file", path: file });
|
|
9981
|
+
}
|
|
9497
9982
|
}
|
|
9498
9983
|
score -= gaps.filter((gap) => gap.kind === "missing-file" || gap.kind === "missing-read-file" || gap.kind === "missing-deleted-file").length * 5;
|
|
9499
9984
|
for (const error2 of unresolvedEvidence) {
|
|
9500
|
-
const snippet = summaryEvidenceLine(error2.message, TRUNC.ERROR_SNIPPET).toLowerCase();
|
|
9985
|
+
const snippet = summaryEvidenceLine(error2.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
|
|
9501
9986
|
if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
|
|
9502
9987
|
gaps.push({ kind: "missing-error", message: error2.message });
|
|
9503
9988
|
score -= 5;
|
|
9504
9989
|
}
|
|
9505
9990
|
}
|
|
9506
9991
|
for (const error2 of resolvedEvidence) {
|
|
9507
|
-
const snippet = summaryEvidenceLine(error2.message, TRUNC.ERROR_SNIPPET).toLowerCase();
|
|
9992
|
+
const snippet = summaryEvidenceLine(error2.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
|
|
9508
9993
|
if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
|
|
9509
|
-
gaps.push({
|
|
9994
|
+
gaps.push({
|
|
9995
|
+
kind: "missing-error",
|
|
9996
|
+
message: error2.message,
|
|
9997
|
+
resolved: true
|
|
9998
|
+
});
|
|
9510
9999
|
score -= 2;
|
|
9511
10000
|
}
|
|
9512
10001
|
}
|
|
@@ -9521,7 +10010,10 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
9521
10010
|
score -= 8;
|
|
9522
10011
|
}
|
|
9523
10012
|
if (hasSemanticContradiction(constraint.text, constraintTarget)) {
|
|
9524
|
-
gaps.push({
|
|
10013
|
+
gaps.push({
|
|
10014
|
+
kind: "inconsistency",
|
|
10015
|
+
detail: "semantic-contradiction: constraint contradicts " + constraint.text.slice(0, TRUNC.SNIPPET)
|
|
10016
|
+
});
|
|
9525
10017
|
score -= 20;
|
|
9526
10018
|
}
|
|
9527
10019
|
}
|
|
@@ -9532,32 +10024,52 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
9532
10024
|
score -= 12;
|
|
9533
10025
|
}
|
|
9534
10026
|
if (hasSemanticContradiction(goalEvidence, goalTarget)) {
|
|
9535
|
-
gaps.push({
|
|
10027
|
+
gaps.push({
|
|
10028
|
+
kind: "inconsistency",
|
|
10029
|
+
detail: "semantic-contradiction: goal polarity or condition changed"
|
|
10030
|
+
});
|
|
9536
10031
|
score -= 20;
|
|
9537
10032
|
}
|
|
9538
10033
|
}
|
|
9539
|
-
const
|
|
10034
|
+
const groundedEvidence = [
|
|
9540
10035
|
...unresolvedEvidence.map((item) => item.message),
|
|
10036
|
+
...resolvedEvidence.map((item) => item.message),
|
|
9541
10037
|
...constraintEvidence.map((item) => item.text),
|
|
9542
10038
|
...decisionEvidence.map((item) => item.summary),
|
|
9543
10039
|
...goalEvidence ? [goalEvidence] : [],
|
|
10040
|
+
...extraction.lastUserMessages,
|
|
10041
|
+
...extraction.timeline.map((item) => item.summary),
|
|
10042
|
+
...extraction.topics.map((item) => item.primaryFile ?? ""),
|
|
9544
10043
|
...continuity?.openLoops.map((item) => item.summary) ?? [],
|
|
9545
10044
|
...continuity?.criticalContext ?? []
|
|
9546
|
-
]
|
|
9547
|
-
const
|
|
10045
|
+
];
|
|
10046
|
+
const groundedEvidenceFiles = groundedEvidence.flatMap((value) => [
|
|
10047
|
+
value,
|
|
10048
|
+
summaryEvidenceLine(value, TRUNC.ERROR_SNIPPET),
|
|
10049
|
+
summaryEvidenceLine(value, TRUNC.TOPIC_LABEL),
|
|
10050
|
+
summaryEvidenceLine(value, TRUNC.PREVIEW),
|
|
10051
|
+
summaryEvidenceLine(value, TRUNC.MESSAGE)
|
|
10052
|
+
]).flatMap(extractFileRefs);
|
|
10053
|
+
const renderedPathEvidence = Array.from(pathEvidence.values()).flatMap((line) => [
|
|
10054
|
+
decodePathDisplay(line),
|
|
10055
|
+
line.startsWith('"') && line.endsWith('"') ? line.slice(1, -1) : line
|
|
10056
|
+
]);
|
|
10057
|
+
const rawKnownFiles = Array.from(new Set([
|
|
9548
10058
|
...modifiedPaths,
|
|
9549
|
-
...
|
|
9550
|
-
...
|
|
10059
|
+
...readPaths,
|
|
10060
|
+
...deletedEvidence,
|
|
9551
10061
|
...extraction.referencedFiles ?? [],
|
|
9552
10062
|
...groundedEvidenceFiles,
|
|
10063
|
+
...renderedPathEvidence,
|
|
10064
|
+
...renderedPathEvidence.flatMap(extractFileRefs),
|
|
9553
10065
|
...continuity?.modifiedFiles ?? [],
|
|
9554
10066
|
...continuity?.readFiles ?? [],
|
|
9555
|
-
...continuity?.deletedFiles ?? [],
|
|
9556
10067
|
...(continuity?.unresolvedErrors ?? []).flatMap((error2) => error2.files),
|
|
9557
10068
|
...(continuity?.openLoops ?? []).flatMap((loop) => loop.files)
|
|
9558
10069
|
]));
|
|
9559
10070
|
for (const ref of new Set(extractFileRefs(summary))) {
|
|
9560
|
-
|
|
10071
|
+
const grounded = isKnownPathReference(ref, rawKnownFiles) || Boolean(evidence.sourceMessages && sourceSupportsFileReference(ref, evidence.sourceMessages));
|
|
10072
|
+
if (!grounded) {
|
|
9561
10073
|
gaps.push({ kind: "fabricated-file", ref });
|
|
9562
10074
|
score -= 4;
|
|
9563
10075
|
}
|
|
@@ -9566,8 +10078,12 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
9566
10078
|
if (progressSection) {
|
|
9567
10079
|
const doneSection = progressSection.body.match(/###\s*Done[\s\S]*?(?=###|$)/i)?.[0] ?? "";
|
|
9568
10080
|
const blockedSection = progressSection.body.match(/###\s*Blocked[\s\S]*?(?=###|$)/i)?.[0] ?? "";
|
|
9569
|
-
|
|
9570
|
-
|
|
10081
|
+
const blockedLines = blockedSection.split(/\r?\n/).slice(1);
|
|
10082
|
+
if (unresolvedEvidence.length > 0 && noneBlockerLineIndexes(blockedLines).size > 0) {
|
|
10083
|
+
gaps.push({
|
|
10084
|
+
kind: "inconsistency",
|
|
10085
|
+
detail: "blocked-none: Blocked says none despite unresolved errors"
|
|
10086
|
+
});
|
|
9571
10087
|
score -= 12;
|
|
9572
10088
|
}
|
|
9573
10089
|
const doneRefs = new Set(extractFileRefs(doneSection).map(normalizePath));
|
|
@@ -9581,7 +10097,10 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
9581
10097
|
return uniqueNeedles.some((needle) => errorRefs.includes(normalizePath(needle)));
|
|
9582
10098
|
});
|
|
9583
10099
|
if (unresolved) {
|
|
9584
|
-
gaps.push({
|
|
10100
|
+
gaps.push({
|
|
10101
|
+
kind: "inconsistency",
|
|
10102
|
+
detail: file.path + " marked Done but has unresolved error"
|
|
10103
|
+
});
|
|
9585
10104
|
score -= 5;
|
|
9586
10105
|
}
|
|
9587
10106
|
}
|
|
@@ -9593,7 +10112,10 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
9593
10112
|
score -= 8;
|
|
9594
10113
|
}
|
|
9595
10114
|
if (hasSemanticContradiction(decision.summary, decisionBody)) {
|
|
9596
|
-
gaps.push({
|
|
10115
|
+
gaps.push({
|
|
10116
|
+
kind: "inconsistency",
|
|
10117
|
+
detail: "semantic-contradiction: decision contradicts " + decision.summary.slice(0, TRUNC.SNIPPET)
|
|
10118
|
+
});
|
|
9597
10119
|
score -= 20;
|
|
9598
10120
|
}
|
|
9599
10121
|
}
|
|
@@ -9614,8 +10136,17 @@ function verifySummary(summary, extraction, continuity = null, evidence = {}) {
|
|
|
9614
10136
|
const finalScore = Math.max(0, score);
|
|
9615
10137
|
return { ok: gaps.length === 0 && finalScore >= 85, gaps, score: finalScore };
|
|
9616
10138
|
}
|
|
9617
|
-
function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
10139
|
+
function patchDeterministic(summary, gaps, extraction, continuity = null, evidence = {}) {
|
|
9618
10140
|
let canonical = parseSummary(summary);
|
|
10141
|
+
const modifiedPaths = extraction.modifiedFiles.map((file) => file.path);
|
|
10142
|
+
const readPaths = extraction.readFiles;
|
|
10143
|
+
const deletedPaths = Array.from(new Set([...extraction.deletedFiles, ...continuity?.deletedFiles ?? []]));
|
|
10144
|
+
const pathEvidence = buildSummaryPathEvidence([...modifiedPaths, ...readPaths, ...deletedPaths], evidence.summaryBudgetTokens);
|
|
10145
|
+
const replaceFileSection = (kind, paths) => {
|
|
10146
|
+
const body = paths.map((path12) => "- " + (pathEvidence.get(path12) ?? JSON.stringify(path12))).join(`
|
|
10147
|
+
`);
|
|
10148
|
+
canonical = upsertSection(canonical, kind, body || "- None recorded.");
|
|
10149
|
+
};
|
|
9619
10150
|
const safe = (value, max = TRUNC.MESSAGE) => summaryEvidenceLine(value, max);
|
|
9620
10151
|
const unresolvedMessages = Array.from(new Set([
|
|
9621
10152
|
...extraction.errors.filter((error2) => !error2.resolved).map((error2) => error2.message),
|
|
@@ -9630,9 +10161,24 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
9630
10161
|
const progress = findSection(canonical, "progress");
|
|
9631
10162
|
if (!progress || !blockedItems.length)
|
|
9632
10163
|
return;
|
|
9633
|
-
const
|
|
10164
|
+
const lines = progress.body.split(/\r?\n/);
|
|
10165
|
+
const start = lines.findIndex((line) => /^###\s*Blocked\s*$/i.test(line.trim()));
|
|
10166
|
+
if (start < 0)
|
|
10167
|
+
return;
|
|
10168
|
+
let end = lines.findIndex((line, index) => index > start && /^###\s+/.test(line.trim()));
|
|
10169
|
+
if (end < 0)
|
|
10170
|
+
end = lines.length;
|
|
10171
|
+
const existing = lines.slice(start + 1, end);
|
|
10172
|
+
const noneIndexes = noneBlockerLineIndexes(existing);
|
|
10173
|
+
if (!noneIndexes.size)
|
|
10174
|
+
return;
|
|
10175
|
+
const replacement = Array.from(new Set([
|
|
10176
|
+
...blockedItems,
|
|
10177
|
+
...existing.filter((line, index) => line.trim() && !noneIndexes.has(index))
|
|
10178
|
+
]));
|
|
10179
|
+
lines.splice(start + 1, end - start - 1, ...replacement);
|
|
10180
|
+
canonical = upsertSection(canonical, "progress", lines.join(`
|
|
9634
10181
|
`));
|
|
9635
|
-
canonical = upsertSection(canonical, "progress", body);
|
|
9636
10182
|
};
|
|
9637
10183
|
for (const gap of gaps) {
|
|
9638
10184
|
switch (gap.kind) {
|
|
@@ -9648,20 +10194,23 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
9648
10194
|
` + (blockedItems.join(`
|
|
9649
10195
|
`) || "- None recorded."));
|
|
9650
10196
|
} else if (gap.section === "critical-context") {
|
|
9651
|
-
const critical = unresolvedMessages.
|
|
10197
|
+
const critical = unresolvedMessages.flatMap((message) => {
|
|
10198
|
+
const text = safe(message);
|
|
10199
|
+
return text ? ["- Unresolved error: " + text] : [];
|
|
10200
|
+
});
|
|
9652
10201
|
canonical = upsertSection(canonical, "critical-context", critical.join(`
|
|
9653
10202
|
`) || "- None recorded.");
|
|
9654
10203
|
}
|
|
9655
10204
|
break;
|
|
9656
10205
|
}
|
|
9657
10206
|
case "missing-file":
|
|
9658
|
-
|
|
10207
|
+
replaceFileSection("files-modified", modifiedPaths);
|
|
9659
10208
|
break;
|
|
9660
10209
|
case "missing-read-file":
|
|
9661
|
-
|
|
10210
|
+
replaceFileSection("files-read", readPaths);
|
|
9662
10211
|
break;
|
|
9663
10212
|
case "missing-deleted-file":
|
|
9664
|
-
|
|
10213
|
+
replaceFileSection("files-deleted", deletedPaths);
|
|
9665
10214
|
break;
|
|
9666
10215
|
case "missing-error": {
|
|
9667
10216
|
const existing = findSection(canonical, "critical-context")?.body.toLowerCase() ?? "";
|
|
@@ -9683,7 +10232,10 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
9683
10232
|
case "missing-open-loops": {
|
|
9684
10233
|
const current = extraction.errors.filter((error2) => !error2.resolved).map((error2) => safe(error2.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
|
|
9685
10234
|
const carriedErrors = (continuity?.unresolvedErrors ?? []).map((error2) => safe(error2.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
|
|
9686
|
-
const carriedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved").map((loop) => ({
|
|
10235
|
+
const carriedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved").map((loop) => ({
|
|
10236
|
+
priority: loop.priority,
|
|
10237
|
+
summary: safe(loop.summary, TRUNC.SNIPPET)
|
|
10238
|
+
})).filter((item) => item.summary).map((item) => "- [" + item.priority + "] " + item.summary);
|
|
9687
10239
|
const body = Array.from(new Set([...current, ...carriedErrors, ...carriedLoops])).slice(0, gap.unresolvedCount).join(`
|
|
9688
10240
|
`);
|
|
9689
10241
|
canonical = upsertSection(canonical, "open-loops", body || "- Review unresolved errors.", "next-steps");
|
|
@@ -9754,7 +10306,13 @@ Return the COMPLETE corrected summary in the same format.`;
|
|
|
9754
10306
|
const maxTokens = Math.min(8192, getProviderCaps(model.provider).maxOutputTokens);
|
|
9755
10307
|
const response = await trackedComplete("patch", model, {
|
|
9756
10308
|
systemPrompt: COMPACT_SYSTEM_PREFIX,
|
|
9757
|
-
messages: [
|
|
10309
|
+
messages: [
|
|
10310
|
+
{
|
|
10311
|
+
role: "user",
|
|
10312
|
+
content: [{ type: "text", text: patchPrompt }],
|
|
10313
|
+
timestamp: Date.now()
|
|
10314
|
+
}
|
|
10315
|
+
]
|
|
9758
10316
|
}, { apiKey: auth.apiKey, headers: auth.headers, maxTokens, signal }, services);
|
|
9759
10317
|
const patched = response.content.filter((content) => content.type === "text").map((content) => content.text).join(`
|
|
9760
10318
|
`).trim();
|
|
@@ -9762,7 +10320,10 @@ Return the COMPLETE corrected summary in the same format.`;
|
|
|
9762
10320
|
return summary;
|
|
9763
10321
|
const originalSections = parseSummary(summary).sections;
|
|
9764
10322
|
const patchedSections = parseSummary(patched).sections;
|
|
9765
|
-
const patchedBodies = new Map(patchedSections.map((section) => [
|
|
10323
|
+
const patchedBodies = new Map(patchedSections.map((section) => [
|
|
10324
|
+
sectionIdentity(section),
|
|
10325
|
+
section.body.trim()
|
|
10326
|
+
]));
|
|
9766
10327
|
const preserved = originalSections.every((section) => !section.body.trim() || Boolean(patchedBodies.get(sectionIdentity(section))));
|
|
9767
10328
|
return preserved ? patched : summary;
|
|
9768
10329
|
} catch (error2) {
|
|
@@ -9775,9 +10336,11 @@ Return the COMPLETE corrected summary in the same format.`;
|
|
|
9775
10336
|
async function verifyAndPatch(rc) {
|
|
9776
10337
|
const extraction = rc.extraction;
|
|
9777
10338
|
let summary = rc.finalSummary;
|
|
10339
|
+
const summaryBudgetTokens = rc.profileCfg?.summaryBudgetTokens ?? 6000;
|
|
9778
10340
|
const evidence = {
|
|
9779
10341
|
sourceMessages: rc.llmMessages,
|
|
9780
|
-
steering: { focus: rc.focus, note: rc.userNote }
|
|
10342
|
+
steering: { focus: rc.focus, note: rc.userNote },
|
|
10343
|
+
summaryBudgetTokens
|
|
9781
10344
|
};
|
|
9782
10345
|
showProgressOverlay(rc.ctx, {
|
|
9783
10346
|
phase: 4,
|
|
@@ -9840,7 +10403,7 @@ async function verifyAndPatch(rc) {
|
|
|
9840
10403
|
detail: "Trying the deterministic safety summary",
|
|
9841
10404
|
explorationRounds: rc.explorationRounds
|
|
9842
10405
|
});
|
|
9843
|
-
let deterministic = assembleFallback([], extraction, evidence.steering);
|
|
10406
|
+
let deterministic = assembleFallback([], extraction, evidence.steering, summaryBudgetTokens, rc.previousState);
|
|
9844
10407
|
let deterministicVerification = verifySummary(deterministic, extraction, rc.previousState, evidence);
|
|
9845
10408
|
const repaired = repairSummaryDeterministically(deterministic, deterministicVerification, extraction, rc.previousState, evidence);
|
|
9846
10409
|
deterministic = repaired.summary;
|
|
@@ -10062,7 +10625,7 @@ function buildState(rc) {
|
|
|
10062
10625
|
}
|
|
10063
10626
|
|
|
10064
10627
|
// src/infra/context-graph.ts
|
|
10065
|
-
import { createHash as
|
|
10628
|
+
import { createHash as createHash4 } from "crypto";
|
|
10066
10629
|
import fs9 from "fs";
|
|
10067
10630
|
import path13 from "path";
|
|
10068
10631
|
import { createRequire } from "module";
|
|
@@ -10193,7 +10756,7 @@ function openDatabase() {
|
|
|
10193
10756
|
return db;
|
|
10194
10757
|
}
|
|
10195
10758
|
function stableId(...parts) {
|
|
10196
|
-
return "cg-" +
|
|
10759
|
+
return "cg-" + createHash4("sha256").update(parts.join("\x00")).digest("hex").slice(0, 24);
|
|
10197
10760
|
}
|
|
10198
10761
|
function factKey(text) {
|
|
10199
10762
|
return normalizeFactKey(text) || text.trim().toLowerCase();
|