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