pi-smart-compact 8.0.2 → 8.0.3
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/CHANGELOG.md +15 -0
- package/README.md +16 -9
- package/dist/app/run-context.d.ts +2 -0
- package/dist/app/run-context.d.ts.map +1 -1
- package/dist/app/run-smart-compact.d.ts +2 -0
- package/dist/app/run-smart-compact.d.ts.map +1 -1
- package/dist/app/steps/metrics.d.ts.map +1 -1
- package/dist/app/steps/state.d.ts.map +1 -1
- package/dist/app/steps/tier.d.ts.map +1 -1
- package/dist/app/steps/verify.d.ts +2 -2
- package/dist/app/steps/verify.d.ts.map +1 -1
- package/dist/app/steps/window.d.ts.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/domain/summary-parse.d.ts +2 -0
- package/dist/domain/summary-parse.d.ts.map +1 -1
- package/dist/domain/telemetry.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +248 -112
- package/dist/infra/context-graph.d.ts.map +1 -1
- package/dist/phases/synthesize.d.ts.map +1 -1
- package/dist/phases/verify.d.ts +14 -0
- package/dist/phases/verify.d.ts.map +1 -1
- package/dist/provider-eval.js +1 -1
- package/dist/provider-scenario-eval.js +8 -4
- package/dist/telemetry-report.js +2 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/extraction.d.ts.map +1 -1
- package/dist/utils/state.d.ts.map +1 -1
- package/docs/MIGRATING_TO_V8.md +4 -3
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -130,7 +130,7 @@ var init_mode_policy = __esm(() => {
|
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
// src/constants.ts
|
|
133
|
-
var VERSION = "8.0.
|
|
133
|
+
var VERSION = "8.0.3", CHARS_PER_TOKEN = 3.8, COMPACT_SYSTEM_PREFIX, PROFILES, DEFAULT_CONFIG, NO_OP_RE, SHIFT_RE, CHOICE_RE, SINGLE_PASS_PREFIX, SINGLE_PASS_SUFFIX = `
|
|
134
134
|
{PREV_CONTEXT}
|
|
135
135
|
|
|
136
136
|
{EXTRACTION_CONTEXT}
|
|
@@ -1314,6 +1314,12 @@ function isBenignSearchResult(tc, result) {
|
|
|
1314
1314
|
const exit = result.match(/Command exited with code (\d+)\s*$/i)?.[1];
|
|
1315
1315
|
return exit === undefined || exit === "1";
|
|
1316
1316
|
}
|
|
1317
|
+
function hasCommandFailureSignal(text) {
|
|
1318
|
+
if (/Command exited with code [1-9]\d*\s*$/i.test(text))
|
|
1319
|
+
return true;
|
|
1320
|
+
const firstLine = text.split(/\r?\n/).find((line) => line.trim())?.trim() ?? "";
|
|
1321
|
+
return LIKELY_ERROR_RE.test(firstLine) || /^(?:npm\s+error|fatal:|traceback\b)/i.test(firstLine);
|
|
1322
|
+
}
|
|
1317
1323
|
function catalogErrors(msgs, _tcIdx) {
|
|
1318
1324
|
const tcIdx = _tcIdx ?? buildToolCallIndex(msgs);
|
|
1319
1325
|
const errors = [];
|
|
@@ -1330,7 +1336,7 @@ function catalogErrors(msgs, _tcIdx) {
|
|
|
1330
1336
|
continue;
|
|
1331
1337
|
}
|
|
1332
1338
|
if (tc && classifyToolOperation(tc.arguments, tc.name) === "execute") {
|
|
1333
|
-
if (
|
|
1339
|
+
if (hasCommandFailureSignal(text) && text.length < ERROR_SCAN_MAX_LEN) {
|
|
1334
1340
|
errors.push({ index: i, tool: tc.name, message: text.slice(0, TRUNC.MESSAGE), retryAttempted: false, resolved: false });
|
|
1335
1341
|
}
|
|
1336
1342
|
}
|
|
@@ -3148,6 +3154,8 @@ function classifyTelemetryFailure(error, timedOut = false) {
|
|
|
3148
3154
|
const text = (fields.name + " " + fields.code + " " + fields.message).toLowerCase();
|
|
3149
3155
|
if (timedOut || /timeout|timed out|watchdog|deadline/.test(text))
|
|
3150
3156
|
return "timeout";
|
|
3157
|
+
if (fields.name.toLowerCase() === "verificationgateerror")
|
|
3158
|
+
return "verification";
|
|
3151
3159
|
if (/budgetexceeded|token budget|call budget|latency budget/.test(text))
|
|
3152
3160
|
return "budget";
|
|
3153
3161
|
if (fields.status === 429 || /rate.?limit|too many requests|quota/.test(text))
|
|
@@ -3160,6 +3168,8 @@ function classifyTelemetryFailure(error, timedOut = false) {
|
|
|
3160
3168
|
return "cancelled";
|
|
3161
3169
|
if (/native compaction|persist|write|rename|filesystem|sqlite|database/.test(text))
|
|
3162
3170
|
return "persistence";
|
|
3171
|
+
if (/verificationgateerror|verification gate|verification.*(?:gap|summary)/.test(text))
|
|
3172
|
+
return "verification";
|
|
3163
3173
|
if (/invalid|validation|schema|malformed|required/.test(text))
|
|
3164
3174
|
return "validation";
|
|
3165
3175
|
if (fields.status != null && fields.status >= 500 || /provider|api error|stream|network|fetch failed|socket/.test(text))
|
|
@@ -3292,6 +3302,7 @@ var init_telemetry = __esm(() => {
|
|
|
3292
3302
|
"provider",
|
|
3293
3303
|
"persistence",
|
|
3294
3304
|
"validation",
|
|
3305
|
+
"verification",
|
|
3295
3306
|
"internal"
|
|
3296
3307
|
]);
|
|
3297
3308
|
});
|
|
@@ -3614,6 +3625,7 @@ function buildDashboardInsights(entries, damageEntries = [], options = {}) {
|
|
|
3614
3625
|
"provider",
|
|
3615
3626
|
"persistence",
|
|
3616
3627
|
"validation",
|
|
3628
|
+
"verification",
|
|
3617
3629
|
"internal"
|
|
3618
3630
|
]);
|
|
3619
3631
|
for (const entry of entries) {
|
|
@@ -3704,6 +3716,9 @@ var init_summary_schema = __esm(() => {
|
|
|
3704
3716
|
});
|
|
3705
3717
|
|
|
3706
3718
|
// src/domain/summary-parse.ts
|
|
3719
|
+
function summaryEvidenceLine(value, maxLength) {
|
|
3720
|
+
return value.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, " ").replace(/\s+/g, " ").trim().replace(/^(?:(?:#{1,6}|[-*+]|>)\s+)+/, "").slice(0, maxLength).trim();
|
|
3721
|
+
}
|
|
3707
3722
|
function mergeBodies(first, second) {
|
|
3708
3723
|
const seen = new Set;
|
|
3709
3724
|
return [first, second].filter(Boolean).flatMap((body) => body.split(`
|
|
@@ -4003,7 +4018,7 @@ function renderContinuityCapsule(state, maxChars = TRUNC.CONTINUITY_CAPSULE, exi
|
|
|
4003
4018
|
const haystack = normalizeFactKey(existing);
|
|
4004
4019
|
const lines = ["## Continuity Ledger"];
|
|
4005
4020
|
const add = (label, value) => {
|
|
4006
|
-
const text = value.
|
|
4021
|
+
const text = summaryEvidenceLine(value, TRUNC.MESSAGE);
|
|
4007
4022
|
if (!text || haystack.includes(normalizeFactKey(text)))
|
|
4008
4023
|
return;
|
|
4009
4024
|
const line = "- " + label + ": " + text;
|
|
@@ -4031,8 +4046,9 @@ function injectOpenLoopsSection(summary, openLoops) {
|
|
|
4031
4046
|
return summary;
|
|
4032
4047
|
const body = openLoops.map((l) => {
|
|
4033
4048
|
const prio = l.priority === "critical" || l.priority === "high" ? "[" + l.priority + "] " : "";
|
|
4034
|
-
const files = l.files.
|
|
4035
|
-
|
|
4049
|
+
const files = l.files.map((file) => summaryEvidenceLine(file, TRUNC.MESSAGE)).filter(Boolean);
|
|
4050
|
+
const suffix = files.length ? " \u2014 " + files.join(", ") : "";
|
|
4051
|
+
return "- " + prio + summaryEvidenceLine(l.summary, TRUNC.OPEN_LOOP_SUMMARY) + suffix;
|
|
4036
4052
|
}).join(`
|
|
4037
4053
|
`);
|
|
4038
4054
|
const parsed = parseSummary(summary);
|
|
@@ -4081,32 +4097,33 @@ function hasDeltaChanges(delta) {
|
|
|
4081
4097
|
}
|
|
4082
4098
|
function formatDeltaSection(delta) {
|
|
4083
4099
|
const lines = ["## Changes Since Last Compaction", ""];
|
|
4100
|
+
const safe = (value, max) => summaryEvidenceLine(value, max);
|
|
4084
4101
|
if (delta.goalChanged) {
|
|
4085
|
-
lines.push("- **Goal shifted**: " + (delta.previousGoal ?? "?") + " \u2192 see current goal above");
|
|
4102
|
+
lines.push("- **Goal shifted**: " + safe(delta.previousGoal ?? "?", TRUNC.MESSAGE) + " \u2192 see current goal above");
|
|
4086
4103
|
}
|
|
4087
4104
|
if (delta.resolvedLoops.length) {
|
|
4088
|
-
lines.push("- **Resolved loops**: " + delta.resolvedLoops.map((s) => "~~" + s
|
|
4105
|
+
lines.push("- **Resolved loops**: " + delta.resolvedLoops.map((s) => "~~" + safe(s, TRUNC.DECISION_DETAIL) + "~~").join(", "));
|
|
4089
4106
|
}
|
|
4090
4107
|
if (delta.persistentLoops.length) {
|
|
4091
|
-
lines.push("- **Still open**: " + delta.persistentLoops.map((s) => s
|
|
4108
|
+
lines.push("- **Still open**: " + delta.persistentLoops.map((s) => safe(s, TRUNC.DECISION_DETAIL)).join("; "));
|
|
4092
4109
|
}
|
|
4093
4110
|
if (delta.newLoops.length) {
|
|
4094
|
-
lines.push("- **New loops**: " + delta.newLoops.map((s) => s
|
|
4111
|
+
lines.push("- **New loops**: " + delta.newLoops.map((s) => safe(s, TRUNC.DECISION_DETAIL)).join("; "));
|
|
4095
4112
|
}
|
|
4096
4113
|
if (delta.newDecisions.length) {
|
|
4097
|
-
lines.push("- **New decisions**: " + delta.newDecisions.map((s) => s
|
|
4114
|
+
lines.push("- **New decisions**: " + delta.newDecisions.map((s) => safe(s, TRUNC.SNIPPET)).join("; "));
|
|
4098
4115
|
}
|
|
4099
4116
|
if (delta.removedDecisions.length) {
|
|
4100
|
-
lines.push("- **Removed decisions**: " + delta.removedDecisions.map((s) => "~~" + s
|
|
4117
|
+
lines.push("- **Removed decisions**: " + delta.removedDecisions.map((s) => "~~" + safe(s, TRUNC.SNIPPET) + "~~").join("; "));
|
|
4101
4118
|
}
|
|
4102
4119
|
if (delta.resolvedErrors.length) {
|
|
4103
|
-
lines.push("- **Resolved errors**: " + delta.resolvedErrors.map((s) => s
|
|
4120
|
+
lines.push("- **Resolved errors**: " + delta.resolvedErrors.map((s) => safe(s, TRUNC.DECISION_DETAIL)).join("; "));
|
|
4104
4121
|
}
|
|
4105
4122
|
if (delta.newErrors.length) {
|
|
4106
|
-
lines.push("- **New errors**: " + delta.newErrors.map((s) => s
|
|
4123
|
+
lines.push("- **New errors**: " + delta.newErrors.map((s) => safe(s, TRUNC.DECISION_DETAIL)).join("; "));
|
|
4107
4124
|
}
|
|
4108
4125
|
if (delta.newModifiedFiles.length) {
|
|
4109
|
-
lines.push("- **New files touched**: " + delta.newModifiedFiles.join(", "));
|
|
4126
|
+
lines.push("- **New files touched**: " + delta.newModifiedFiles.map((file) => safe(file, TRUNC.MESSAGE)).join(", "));
|
|
4110
4127
|
}
|
|
4111
4128
|
lines.push("");
|
|
4112
4129
|
return lines.join(`
|
|
@@ -4128,7 +4145,7 @@ function ensurePinnedPaths(summary, pinned) {
|
|
|
4128
4145
|
if (!pinned.length)
|
|
4129
4146
|
return summary;
|
|
4130
4147
|
const lower = summary.toLowerCase();
|
|
4131
|
-
const missing = pinned.
|
|
4148
|
+
const missing = pinned.map((path7) => summaryEvidenceLine(path7, TRUNC.MESSAGE)).filter((path7) => path7 && !lower.includes(path7.toLowerCase()));
|
|
4132
4149
|
if (!missing.length)
|
|
4133
4150
|
return summary;
|
|
4134
4151
|
const parsed = parseSummary(summary);
|
|
@@ -5725,33 +5742,42 @@ async function prepareRun(rc) {
|
|
|
5725
5742
|
// src/app/steps/window.ts
|
|
5726
5743
|
init_mode_policy();
|
|
5727
5744
|
init_helpers();
|
|
5745
|
+
init_constants();
|
|
5728
5746
|
function resolveCompactionWindow(rc) {
|
|
5729
5747
|
const usage = rc.ctx.getContextUsage();
|
|
5730
5748
|
const totalTokens = usage?.tokens ?? 0;
|
|
5731
5749
|
const manager = rc.ctx.sessionManager;
|
|
5732
5750
|
const branch = typeof manager.buildContextEntries === "function" ? manager.buildContextEntries() : manager.getBranch();
|
|
5733
5751
|
const msgs = branch.filter((e) => e.type === "message" && e.message != null);
|
|
5734
|
-
if (msgs.length < 3)
|
|
5752
|
+
if (msgs.length < 3) {
|
|
5753
|
+
if (rc.flags.force)
|
|
5754
|
+
rc.notify("Manual compaction skipped: fewer than 3 active messages are available.", "warning");
|
|
5735
5755
|
return null;
|
|
5756
|
+
}
|
|
5736
5757
|
const adaptiveKeepTokens = rc.ctx.model ? Math.min(rc.profileCfg.keepRecentTokens * 2, Math.max(rc.profileCfg.keepRecentTokens, rc.ctx.model.contextWindow * 0.04)) : rc.profileCfg.keepRecentTokens;
|
|
5737
5758
|
const mode = rc.mode ?? (rc.profile ? modeFromLegacyProfile(rc.profile) : "balanced");
|
|
5738
5759
|
const targetPercent = MODE_POLICIES[mode].targetContextPercent;
|
|
5739
5760
|
const messageTokens = msgs.map((entry) => rc.estimator.message(entry.message));
|
|
5740
5761
|
const allMessageTokens = messageTokens.reduce((sum, tokens) => sum + tokens, 0);
|
|
5741
|
-
const
|
|
5762
|
+
const overflowedContext = !!rc.flags.overflowRecovery || !!rc.ctx.model && totalTokens > rc.ctx.model.contextWindow;
|
|
5763
|
+
const messageScale = totalTokens > 0 && allMessageTokens > 0 && (allMessageTokens > totalTokens || overflowedContext) ? totalTokens / allMessageTokens : 1;
|
|
5764
|
+
const fixedContextTokens = overflowedContext ? 0 : Math.max(0, totalTokens - allMessageTokens);
|
|
5742
5765
|
const targetRetainedTokens = rc.ctx.model ? Math.max(0, rc.ctx.model.contextWindow * targetPercent / 100 - fixedContextTokens - rc.profileCfg.summaryBudgetTokens) : adaptiveKeepTokens;
|
|
5743
|
-
const retentionBudget = Math.max(adaptiveKeepTokens, targetRetainedTokens);
|
|
5766
|
+
const retentionBudget = rc.flags.force ? adaptiveKeepTokens : Math.max(adaptiveKeepTokens, targetRetainedTokens);
|
|
5767
|
+
const rawAdaptiveKeepTokens = adaptiveKeepTokens / messageScale;
|
|
5768
|
+
const rawRetentionBudget = retentionBudget / messageScale;
|
|
5744
5769
|
let accTokens = 0;
|
|
5745
5770
|
let keepFrom = msgs.length - 1;
|
|
5746
5771
|
for (let i = msgs.length - 1;i >= 0; i--) {
|
|
5747
5772
|
const next = messageTokens[i];
|
|
5748
|
-
if (accTokens >=
|
|
5773
|
+
if (accTokens >= rawAdaptiveKeepTokens && accTokens + next > rawRetentionBudget) {
|
|
5749
5774
|
keepFrom = i + 1;
|
|
5750
5775
|
break;
|
|
5751
5776
|
}
|
|
5752
5777
|
accTokens += next;
|
|
5753
5778
|
keepFrom = i;
|
|
5754
5779
|
}
|
|
5780
|
+
const plannedKeepFrom = keepFrom;
|
|
5755
5781
|
const recentUsers = msgs.map((entry, index) => ({ index, role: entry.message?.role })).filter((entry) => entry.role === "user");
|
|
5756
5782
|
if (recentUsers.length) {
|
|
5757
5783
|
const protectedUser = recentUsers[recentUsers.length >= 2 ? recentUsers.length - 2 : recentUsers.length - 1];
|
|
@@ -5759,19 +5785,42 @@ function resolveCompactionWindow(rc) {
|
|
|
5759
5785
|
}
|
|
5760
5786
|
keepFrom = smartKeepBoundary(msgs, keepFrom, branch);
|
|
5761
5787
|
keepFrom = guardToolCallBoundary(msgs, keepFrom);
|
|
5762
|
-
const
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5788
|
+
const recount = (from) => ({
|
|
5789
|
+
compact: Math.round(messageTokens.slice(0, from).reduce((sum, tokens) => sum + tokens, 0) * messageScale),
|
|
5790
|
+
retained: Math.round(messageTokens.slice(from).reduce((sum, tokens) => sum + tokens, 0) * messageScale)
|
|
5791
|
+
});
|
|
5792
|
+
let counts = recount(keepFrom);
|
|
5793
|
+
const softProtectionBudget = rc.flags.force ? Math.max(retentionBudget, adaptiveKeepTokens * 2) : retentionBudget;
|
|
5794
|
+
const protectionCeiling = fixedContextTokens + softProtectionBudget + rc.profileCfg.summaryBudgetTokens;
|
|
5795
|
+
if (overflowedContext && keepFrom < plannedKeepFrom && fixedContextTokens + counts.retained + rc.profileCfg.summaryBudgetTokens > protectionCeiling) {
|
|
5796
|
+
keepFrom = guardToolCallBoundary(msgs, plannedKeepFrom);
|
|
5797
|
+
counts = recount(keepFrom);
|
|
5798
|
+
rc.notify("Context exceeds the active model window. EESV will summarize through soft recent-turn/checkpoint protections while preserving complete tool-call pairs; native fallback would resend the oversized context.", "warning");
|
|
5799
|
+
}
|
|
5800
|
+
const compactTokens = counts.compact;
|
|
5801
|
+
accTokens = counts.retained;
|
|
5767
5802
|
if (msgs[keepFrom]?.message?.role === "toolResult") {
|
|
5803
|
+
if (rc.flags.force)
|
|
5804
|
+
rc.notify("Manual compaction skipped: no safe boundary can separate the retained tool result from its tool call.", "warning");
|
|
5768
5805
|
return null;
|
|
5769
5806
|
}
|
|
5770
5807
|
const toCompact = msgs.slice(0, keepFrom);
|
|
5771
|
-
if (!toCompact.length)
|
|
5808
|
+
if (!toCompact.length) {
|
|
5809
|
+
if (rc.flags.force) {
|
|
5810
|
+
rc.notify("Manual compaction skipped: no eligible prefix remains after protecting recent user turns and tool-call boundaries. It may have been run again too soon.", "warning");
|
|
5811
|
+
}
|
|
5772
5812
|
return null;
|
|
5813
|
+
}
|
|
5773
5814
|
const contextPercent = rc.ctx.model && totalTokens ? totalTokens / rc.ctx.model.contextWindow * 100 : 0;
|
|
5774
|
-
if (
|
|
5815
|
+
if (rc.flags.force) {
|
|
5816
|
+
const lowYield = compactTokens <= rc.profileCfg.summaryBudgetTokens + MIN_TOKEN_THRESHOLD || totalTokens > 0 && compactTokens / totalTokens < 0.1;
|
|
5817
|
+
if (lowYield) {
|
|
5818
|
+
rc.notify("Low-yield manual compaction: only " + compactTokens.toLocaleString() + "t are eligible after preserving " + accTokens.toLocaleString() + "t of recent context. Continuing because you requested it; repeated compaction may lose nuance.", "warning");
|
|
5819
|
+
} else if (rc.config.minContextPercent > 0 && contextPercent < rc.config.minContextPercent) {
|
|
5820
|
+
rc.notify("Manual compaction override at " + Math.round(contextPercent) + "% (" + totalTokens.toLocaleString() + "t): compacting about " + compactTokens.toLocaleString() + "t while preserving " + accTokens.toLocaleString() + "t of recent context. Early compaction is lossy; verification remains fail-closed.", "warning");
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
if (!rc.flags.force && !overflowedContext && rc.ctx.model && totalTokens > 0 && rc.config.minContextPercent > 0) {
|
|
5775
5824
|
const projectedTokens = fixedContextTokens + accTokens + rc.profileCfg.summaryBudgetTokens;
|
|
5776
5825
|
const targetTokens = rc.ctx.model.contextWindow * targetPercent / 100;
|
|
5777
5826
|
if (projectedTokens > targetTokens) {
|
|
@@ -6032,7 +6081,7 @@ init_constants();
|
|
|
6032
6081
|
init_helpers();
|
|
6033
6082
|
function selectTier(rc) {
|
|
6034
6083
|
const toolPercent = computeToolCharPercentage(rc.branch);
|
|
6035
|
-
const tier = rc.flags.force ? rc.contextPercent >= 80 ? "full" : "light" : selectCompactionTier(rc.contextPercent, toolPercent, rc.totalTokens, MIN_TOKEN_THRESHOLD, rc.config.minContextPercent);
|
|
6084
|
+
const tier = rc.flags.overflowRecovery ? "full" : rc.flags.force ? rc.contextPercent >= 80 ? "full" : "light" : selectCompactionTier(rc.contextPercent, toolPercent, rc.totalTokens, MIN_TOKEN_THRESHOLD, rc.config.minContextPercent);
|
|
6036
6085
|
if (tier === "none") {
|
|
6037
6086
|
if (!rc.flags.autoTriggered) {
|
|
6038
6087
|
rc.ctx.ui.notify("Context OK (" + Math.round(rc.contextPercent) + "%). pi-toolkit manages context well.", "info");
|
|
@@ -6853,6 +6902,7 @@ function getCachedBatchClone(value) {
|
|
|
6853
6902
|
}
|
|
6854
6903
|
|
|
6855
6904
|
// src/phases/synthesize.ts
|
|
6905
|
+
init_summary_parse();
|
|
6856
6906
|
function boundedToolArgs(value, depth = 0) {
|
|
6857
6907
|
if (typeof value === "string")
|
|
6858
6908
|
return value.length > TRUNC.DETAIL ? value.slice(0, TRUNC.DETAIL) + "\u2026" : value;
|
|
@@ -7099,21 +7149,29 @@ async function assembleLLM(summaries, extraction, report, model, auth, budget, p
|
|
|
7099
7149
|
`).trim();
|
|
7100
7150
|
}
|
|
7101
7151
|
function assembleFallback(summaries, extraction) {
|
|
7102
|
-
const
|
|
7103
|
-
const
|
|
7104
|
-
const
|
|
7105
|
-
const
|
|
7152
|
+
const safe = (value, max = TRUNC.PREVIEW_MID) => summaryEvidenceLine(value, max);
|
|
7153
|
+
const detModified = extraction.modifiedFiles.map((f) => safe(f.path)).filter(Boolean);
|
|
7154
|
+
const detRead = extraction.readFiles.map((file) => safe(file)).filter(Boolean);
|
|
7155
|
+
const unresolved = extraction.errors.filter((error) => !error.resolved).map((error) => safe(error.message, TRUNC.PREVIEW)).filter(Boolean);
|
|
7156
|
+
const constraints = extraction.constraints.map((item) => "- [" + item.category + "] " + safe(item.text)).filter((line) => !line.endsWith("] "));
|
|
7157
|
+
const decisions = extraction.decisions.map((item) => {
|
|
7158
|
+
const summary = safe(item.summary, TRUNC.DECISION_SUMMARY);
|
|
7159
|
+
const response = item.userResponse ? safe(item.userResponse, TRUNC.USER_RESPONSE) : "";
|
|
7160
|
+
return summary ? "- **" + summary + "**" + (response ? " \u2192 " + response : "") : "";
|
|
7161
|
+
}).filter(Boolean);
|
|
7162
|
+
const inProgress = summaries.filter((item) => item.priority === "critical" || item.priority === "high").map((item) => safe(item.summary, TRUNC.PREVIEW)).filter(Boolean).map((item) => "- [ ] " + item);
|
|
7106
7163
|
if (!inProgress.length) {
|
|
7107
|
-
inProgress.push(...extraction.lastUserMessages.slice(-3).map((message) => "- [ ] " + message
|
|
7164
|
+
inProgress.push(...extraction.lastUserMessages.slice(-3).map((message) => safe(message, TRUNC.PREVIEW)).filter(Boolean).map((message) => "- [ ] " + message));
|
|
7108
7165
|
}
|
|
7109
7166
|
inProgress.push(...detModified.map((file) => "- [ ] Continue work in " + file));
|
|
7110
|
-
const next = extraction.lastUserMessages.at(-1)
|
|
7167
|
+
const next = safe(extraction.lastUserMessages.at(-1) ?? extraction.timeline.at(-1)?.summary ?? "", TRUNC.PREVIEW) || "Continue from the latest preserved context.";
|
|
7168
|
+
const goal = safe(extraction.mainGoal ?? "", TRUNC.DETAIL) || "Continue the current task.";
|
|
7111
7169
|
return [
|
|
7112
7170
|
"## Goal",
|
|
7113
|
-
|
|
7171
|
+
goal,
|
|
7114
7172
|
"",
|
|
7115
7173
|
"## Constraints & Preferences",
|
|
7116
|
-
...
|
|
7174
|
+
...constraints.length ? constraints : ["- None recorded."],
|
|
7117
7175
|
"",
|
|
7118
7176
|
"## Progress",
|
|
7119
7177
|
"### Done",
|
|
@@ -7121,25 +7179,28 @@ function assembleFallback(summaries, extraction) {
|
|
|
7121
7179
|
"### In Progress",
|
|
7122
7180
|
...inProgress.length ? inProgress : ["- Continue current work."],
|
|
7123
7181
|
"### Blocked",
|
|
7124
|
-
...unresolved.length ? unresolved.map((error) => "- " + error
|
|
7182
|
+
...unresolved.length ? unresolved.map((error) => "- " + error) : ["- None recorded."],
|
|
7125
7183
|
"",
|
|
7126
7184
|
"## Key Decisions",
|
|
7127
|
-
...
|
|
7185
|
+
...decisions.length ? decisions : ["- None recorded."],
|
|
7128
7186
|
"",
|
|
7129
7187
|
"## Files Modified",
|
|
7130
|
-
...detModified.length ? detModified.map((
|
|
7188
|
+
...detModified.length ? detModified.map((file) => "- " + file) : ["- None recorded."],
|
|
7131
7189
|
"",
|
|
7132
7190
|
"## Files Read",
|
|
7133
|
-
...detRead.length ? detRead.map((
|
|
7191
|
+
...detRead.length ? detRead.map((file) => "- " + file) : ["- None recorded."],
|
|
7134
7192
|
"",
|
|
7135
7193
|
"## Next Steps",
|
|
7136
7194
|
"1. " + next,
|
|
7137
7195
|
"",
|
|
7138
7196
|
"## Critical Context",
|
|
7139
|
-
...unresolved.length ? unresolved.map((
|
|
7197
|
+
...unresolved.length ? unresolved.map((error) => "- Unresolved error: " + safe(error, TRUNC.TOPIC_LABEL)) : ["- None recorded."],
|
|
7140
7198
|
"",
|
|
7141
7199
|
"## Topics Covered",
|
|
7142
|
-
...summaries.length ? summaries.map((
|
|
7200
|
+
...summaries.length ? summaries.map((item) => {
|
|
7201
|
+
const topic = safe(item.topic, TRUNC.TOPIC_LABEL) || "Segment";
|
|
7202
|
+
return "- **" + topic + "** [" + item.priority + "]: " + safe(item.summary);
|
|
7203
|
+
}) : extraction.topics.map((topic, index) => "- Topic " + (index + 1) + ": " + safe(topic.primaryFile ?? topic.type))
|
|
7143
7204
|
].join(`
|
|
7144
7205
|
`);
|
|
7145
7206
|
}
|
|
@@ -7148,8 +7209,8 @@ function failedChunkSummary(ch) {
|
|
|
7148
7209
|
topic: ch.topic,
|
|
7149
7210
|
startIndex: ch.startIndex,
|
|
7150
7211
|
endIndex: ch.endIndex,
|
|
7151
|
-
summary: "[Failed] " + ch.messages.map((m) => extractText(m.content)).join(`
|
|
7152
|
-
`)
|
|
7212
|
+
summary: "[Failed] " + summaryEvidenceLine(ch.messages.map((m) => extractText(m.content)).join(`
|
|
7213
|
+
`), TRUNC.DETAIL),
|
|
7153
7214
|
keyDecisions: [],
|
|
7154
7215
|
filesModified: [],
|
|
7155
7216
|
filesRead: [],
|
|
@@ -7530,6 +7591,21 @@ function verificationFailureMessage(result) {
|
|
|
7530
7591
|
const findings = result.gaps.slice(0, 3).map((gap) => formatVerificationGap(gap).replace(/\s+/g, " ").slice(0, 160)).join("; ");
|
|
7531
7592
|
return "Verification gate rejected summary (" + result.score + "/100, " + result.gaps.length + " unresolved gap(s))" + (findings ? ": " + findings : "");
|
|
7532
7593
|
}
|
|
7594
|
+
|
|
7595
|
+
class VerificationGateError extends Error {
|
|
7596
|
+
score;
|
|
7597
|
+
initialScore;
|
|
7598
|
+
gapKinds;
|
|
7599
|
+
gapCount;
|
|
7600
|
+
constructor(result, initialScore) {
|
|
7601
|
+
super(verificationFailureMessage(result) ?? "Verification gate rejected summary");
|
|
7602
|
+
this.name = "VerificationGateError";
|
|
7603
|
+
this.score = result.score;
|
|
7604
|
+
this.initialScore = initialScore;
|
|
7605
|
+
this.gapKinds = Array.from(new Set(result.gaps.map((gap) => gap.kind)));
|
|
7606
|
+
this.gapCount = result.gaps.length;
|
|
7607
|
+
}
|
|
7608
|
+
}
|
|
7533
7609
|
var NEGATION_MARKERS = new Set([
|
|
7534
7610
|
"no",
|
|
7535
7611
|
"not",
|
|
@@ -7600,14 +7676,15 @@ function semanticTokens(text) {
|
|
|
7600
7676
|
return (text.normalize("NFKC").match(/[\p{L}\p{N}_-]+/gu) ?? []).map(stemToken).filter((token) => token.length > 2);
|
|
7601
7677
|
}
|
|
7602
7678
|
function evidenceFragments(text) {
|
|
7603
|
-
|
|
7679
|
+
const fragments = text.split(/\r?\n/).flatMap((line) => [line, ...line.split(/[.;]/)]).map((part) => part.replace(/^\s*[-*\d.)]+\s*/, "").trim()).filter(Boolean);
|
|
7680
|
+
return Array.from(new Set(fragments));
|
|
7604
7681
|
}
|
|
7605
7682
|
function hasNearbyMarker(tokens, anchor, markers) {
|
|
7606
7683
|
return tokens.some((token, index) => token === anchor && tokens.slice(Math.max(0, index - 2), index + 3).some((near) => markers.has(near)));
|
|
7607
7684
|
}
|
|
7608
7685
|
function semanticShape(source) {
|
|
7609
7686
|
const sourceTokens = semanticTokens(source);
|
|
7610
|
-
const concepts = Array.from(new Set(sourceTokens.filter((token) => !SEMANTIC_STOP.has(token) && !NEGATION_MARKERS.has(token) && !CONDITION_MARKERS.has(token))));
|
|
7687
|
+
const concepts = Array.from(new Set(sourceTokens.filter((token) => !/^\d+$/.test(token) && !SEMANTIC_STOP.has(token) && !NEGATION_MARKERS.has(token) && !CONDITION_MARKERS.has(token))));
|
|
7611
7688
|
const negative = sourceTokens.some((token) => NEGATION_MARKERS.has(token));
|
|
7612
7689
|
const conditional = sourceTokens.some((token) => CONDITION_MARKERS.has(token));
|
|
7613
7690
|
const anchor = concepts.find((concept) => hasNearbyMarker(sourceTokens, concept, NEGATION_MARKERS)) ?? concepts[0] ?? "";
|
|
@@ -7637,12 +7714,15 @@ function hasSemanticContradiction(source, target) {
|
|
|
7637
7714
|
const { sourceTokens, concepts, anchor, negative, conditional } = semanticShape(source);
|
|
7638
7715
|
if (!anchor)
|
|
7639
7716
|
return false;
|
|
7717
|
+
const required = Math.min(concepts.length, Math.max(1, Math.ceil(concepts.length * 0.6)));
|
|
7640
7718
|
return evidenceFragments(target).some((fragment) => {
|
|
7641
7719
|
const tokens = semanticTokens(fragment);
|
|
7642
7720
|
if (!tokens.includes(anchor))
|
|
7643
7721
|
return false;
|
|
7722
|
+
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
7723
|
+
if (overlap < required)
|
|
7724
|
+
return false;
|
|
7644
7725
|
if (negative && !hasNearbyMarker(tokens, anchor, NEGATION_MARKERS)) {
|
|
7645
|
-
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
7646
7726
|
const validConditional = sourceTokens.includes("without") && tokens.some((token) => CONDITION_MARKERS.has(token)) && overlap >= Math.min(2, concepts.length);
|
|
7647
7727
|
return !validConditional;
|
|
7648
7728
|
}
|
|
@@ -7656,6 +7736,28 @@ function isDeterministicallyPatchable(gap) {
|
|
|
7656
7736
|
return gap.detail.startsWith("blocked-none:");
|
|
7657
7737
|
return true;
|
|
7658
7738
|
}
|
|
7739
|
+
function repairSummaryDeterministically(summary, result, extraction, continuity = null, maxRounds = 3) {
|
|
7740
|
+
const patched = [];
|
|
7741
|
+
const seen = new Set;
|
|
7742
|
+
for (let round = 0;round < maxRounds; round++) {
|
|
7743
|
+
const patchable = result.gaps.filter(isDeterministicallyPatchable);
|
|
7744
|
+
if (!patchable.length)
|
|
7745
|
+
break;
|
|
7746
|
+
const next = patchDeterministic(summary, patchable, extraction, continuity);
|
|
7747
|
+
if (next === summary)
|
|
7748
|
+
break;
|
|
7749
|
+
for (const gap of patchable) {
|
|
7750
|
+
const key = formatVerificationGap(gap);
|
|
7751
|
+
if (!seen.has(key)) {
|
|
7752
|
+
seen.add(key);
|
|
7753
|
+
patched.push(gap);
|
|
7754
|
+
}
|
|
7755
|
+
}
|
|
7756
|
+
summary = next;
|
|
7757
|
+
result = verifySummary(summary, extraction, continuity);
|
|
7758
|
+
}
|
|
7759
|
+
return { summary, result, patched };
|
|
7760
|
+
}
|
|
7659
7761
|
function verifySummary(summary, extraction, continuity = null) {
|
|
7660
7762
|
const parsed = parseSummary(summary);
|
|
7661
7763
|
const gaps = [];
|
|
@@ -7806,15 +7908,15 @@ function verifySummary(summary, extraction, continuity = null) {
|
|
|
7806
7908
|
}
|
|
7807
7909
|
function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
7808
7910
|
let canonical = parseSummary(summary);
|
|
7809
|
-
const
|
|
7911
|
+
const safe = (value, max = TRUNC.MESSAGE) => summaryEvidenceLine(value, max);
|
|
7810
7912
|
const unresolvedMessages = Array.from(new Set([
|
|
7811
7913
|
...extraction.errors.filter((error) => !error.resolved).map((error) => error.message),
|
|
7812
7914
|
...(continuity?.unresolvedErrors ?? []).map((error) => error.message)
|
|
7813
7915
|
]));
|
|
7814
7916
|
const unresolvedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved");
|
|
7815
7917
|
const blockedItems = [
|
|
7816
|
-
...unresolvedMessages.map((message) => "- " + message
|
|
7817
|
-
...unresolvedLoops.map((loop) => "- " +
|
|
7918
|
+
...unresolvedMessages.map((message) => safe(message)).filter(Boolean).map((message) => "- " + message),
|
|
7919
|
+
...unresolvedLoops.map((loop) => safe(loop.summary)).filter(Boolean).map((summary2) => "- " + summary2)
|
|
7818
7920
|
];
|
|
7819
7921
|
const patchBlockedNone = () => {
|
|
7820
7922
|
const progress = findSection(canonical, "progress");
|
|
@@ -7828,7 +7930,7 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
7828
7930
|
switch (gap.kind) {
|
|
7829
7931
|
case "missing-section": {
|
|
7830
7932
|
if (gap.section === "goal") {
|
|
7831
|
-
canonical = upsertSection(canonical, "goal", extraction.mainGoal ?? "Continue the current coding task.");
|
|
7933
|
+
canonical = upsertSection(canonical, "goal", safe(extraction.mainGoal ?? "", TRUNC.DETAIL) || "Continue the current coding task.");
|
|
7832
7934
|
} else if (gap.section === "progress") {
|
|
7833
7935
|
canonical = upsertSection(canonical, "progress", `### Done
|
|
7834
7936
|
- No explicit completion recorded.
|
|
@@ -7838,36 +7940,36 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
7838
7940
|
` + (blockedItems.join(`
|
|
7839
7941
|
`) || "- None recorded."));
|
|
7840
7942
|
} else if (gap.section === "critical-context") {
|
|
7841
|
-
const critical = unresolvedMessages.map((message) => "- Unresolved error: " + message
|
|
7943
|
+
const critical = unresolvedMessages.map((message) => safe(message)).filter(Boolean).map((message) => "- Unresolved error: " + message);
|
|
7842
7944
|
canonical = upsertSection(canonical, "critical-context", critical.join(`
|
|
7843
7945
|
`) || "- None recorded.");
|
|
7844
7946
|
}
|
|
7845
7947
|
break;
|
|
7846
7948
|
}
|
|
7847
7949
|
case "missing-file":
|
|
7848
|
-
canonical = appendToSection(canonical, "files-modified", "- " + gap.path);
|
|
7950
|
+
canonical = appendToSection(canonical, "files-modified", "- " + safe(gap.path));
|
|
7849
7951
|
break;
|
|
7850
7952
|
case "missing-error": {
|
|
7851
7953
|
const existing = findSection(canonical, "critical-context")?.body.toLowerCase() ?? "";
|
|
7852
|
-
const message = gap.message
|
|
7954
|
+
const message = safe(gap.message);
|
|
7853
7955
|
if (!existing.includes(message.toLowerCase())) {
|
|
7854
7956
|
canonical = appendToSection(canonical, "critical-context", "- Unresolved error: " + message);
|
|
7855
7957
|
}
|
|
7856
7958
|
break;
|
|
7857
7959
|
}
|
|
7858
7960
|
case "missing-constraint":
|
|
7859
|
-
canonical = appendToSection(canonical, "constraints", "- " + gap.text
|
|
7961
|
+
canonical = appendToSection(canonical, "constraints", "- " + safe(gap.text, TRUNC.CONSTRAINT_TEXT));
|
|
7860
7962
|
break;
|
|
7861
7963
|
case "missing-decision":
|
|
7862
|
-
canonical = appendToSection(canonical, "decisions", "- **" + gap.summary
|
|
7964
|
+
canonical = appendToSection(canonical, "decisions", "- **" + safe(gap.summary, TRUNC.DECISION_SUMMARY) + "**");
|
|
7863
7965
|
break;
|
|
7864
7966
|
case "missing-goal":
|
|
7865
|
-
canonical = upsertSection(canonical, "goal", gap.goal);
|
|
7967
|
+
canonical = upsertSection(canonical, "goal", safe(gap.goal, TRUNC.DETAIL) || "Continue the current task.");
|
|
7866
7968
|
break;
|
|
7867
7969
|
case "missing-open-loops": {
|
|
7868
|
-
const current = extraction.errors.filter((error) => !error.resolved).map((error) => "- [high] Resolve " +
|
|
7869
|
-
const carriedErrors = (continuity?.unresolvedErrors ?? []).map((error) => "- [high] Resolve " +
|
|
7870
|
-
const carriedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved").map((loop) => "- [" +
|
|
7970
|
+
const current = extraction.errors.filter((error) => !error.resolved).map((error) => safe(error.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
|
|
7971
|
+
const carriedErrors = (continuity?.unresolvedErrors ?? []).map((error) => safe(error.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
|
|
7972
|
+
const carriedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved").map((loop) => ({ priority: loop.priority, summary: safe(loop.summary, TRUNC.SNIPPET) })).filter((item) => item.summary).map((item) => "- [" + item.priority + "] " + item.summary);
|
|
7871
7973
|
const body = Array.from(new Set([...current, ...carriedErrors, ...carriedLoops])).slice(0, gap.unresolvedCount).join(`
|
|
7872
7974
|
`);
|
|
7873
7975
|
canonical = upsertSection(canonical, "open-loops", body || "- Review unresolved errors.", "next-steps");
|
|
@@ -7875,7 +7977,6 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
7875
7977
|
}
|
|
7876
7978
|
case "fabricated-file": {
|
|
7877
7979
|
const normalizedRef = gap.ref.replace(/\\/g, "/").toLowerCase();
|
|
7878
|
-
let removed = false;
|
|
7879
7980
|
canonical = {
|
|
7880
7981
|
sections: canonical.sections.map((section) => ({
|
|
7881
7982
|
...section,
|
|
@@ -7884,42 +7985,32 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
7884
7985
|
if (!/^\s*[-*]\s+/.test(line))
|
|
7885
7986
|
return true;
|
|
7886
7987
|
const matches = extractFileRefs(line).some((ref) => ref.replace(/\\/g, "/").toLowerCase() === normalizedRef);
|
|
7887
|
-
if (matches)
|
|
7888
|
-
removed = true;
|
|
7889
7988
|
return !matches;
|
|
7890
7989
|
}).join(`
|
|
7891
7990
|
`).trim()
|
|
7892
7991
|
}))
|
|
7893
7992
|
};
|
|
7894
|
-
if (!removed)
|
|
7895
|
-
verificationNotes.push(formatVerificationGap(gap));
|
|
7896
7993
|
break;
|
|
7897
7994
|
}
|
|
7898
7995
|
case "inconsistency":
|
|
7899
7996
|
if (gap.detail.startsWith("blocked-none:"))
|
|
7900
7997
|
patchBlockedNone();
|
|
7901
|
-
else
|
|
7902
|
-
verificationNotes.push(formatVerificationGap(gap));
|
|
7903
7998
|
break;
|
|
7904
7999
|
}
|
|
7905
8000
|
}
|
|
7906
|
-
if (verificationNotes.length > 0) {
|
|
7907
|
-
canonical = upsertSection(canonical, "verification-note", verificationNotes.map((note) => "- " + note).join(`
|
|
7908
|
-
`));
|
|
7909
|
-
}
|
|
7910
8001
|
return renderSummary(canonical, { canonicalHeadings: true });
|
|
7911
8002
|
}
|
|
7912
8003
|
async function patchSummary(summary, gaps, model, auth, signal, services) {
|
|
7913
|
-
const patchPrompt = `
|
|
8004
|
+
const patchPrompt = `Correct every verification finding below WITHOUT restructuring the summary. Add missing evidence, remove fabricated references, and rewrite contradictory claims so they preserve the source constraint/decision polarity. Do not add a Verification Note.
|
|
7914
8005
|
|
|
7915
|
-
|
|
8006
|
+
Findings:
|
|
7916
8007
|
` + gaps.map((gap, index) => index + 1 + ". " + formatVerificationGap(gap)).join(`
|
|
7917
8008
|
`) + `
|
|
7918
8009
|
|
|
7919
8010
|
Current summary:
|
|
7920
8011
|
` + summary + `
|
|
7921
8012
|
|
|
7922
|
-
Return the COMPLETE
|
|
8013
|
+
Return the COMPLETE corrected summary in the same format.`;
|
|
7923
8014
|
try {
|
|
7924
8015
|
const maxTokens = Math.min(8192, getProviderCaps(model.provider).maxOutputTokens);
|
|
7925
8016
|
const response = await trackedComplete("patch", model, {
|
|
@@ -7939,10 +8030,6 @@ Return the COMPLETE updated summary with missing items integrated. Keep the same
|
|
|
7939
8030
|
init_overlays();
|
|
7940
8031
|
init_logger();
|
|
7941
8032
|
init_mode_policy();
|
|
7942
|
-
function informationTokenCount(summary) {
|
|
7943
|
-
const ignored = new Set(["none", "recorded", "continue", "current", "work", "explicit", "completion"]);
|
|
7944
|
-
return new Set((summary.normalize("NFKC").toLowerCase().match(/[\p{L}\p{N}_-]{4,}/gu) ?? []).filter((token) => !ignored.has(token))).size;
|
|
7945
|
-
}
|
|
7946
8033
|
async function verifyAndPatch(rc) {
|
|
7947
8034
|
const extraction = rc.extraction;
|
|
7948
8035
|
let summary = rc.finalSummary;
|
|
@@ -7959,17 +8046,20 @@ async function verifyAndPatch(rc) {
|
|
|
7959
8046
|
}
|
|
7960
8047
|
let verification = verifySummary(summary, extraction, rc.previousState);
|
|
7961
8048
|
const initialScore = verification.score;
|
|
7962
|
-
const deterministicPatched =
|
|
8049
|
+
const deterministicPatched = [];
|
|
7963
8050
|
let llmPatched = false;
|
|
7964
8051
|
let qualityFloorUsed = false;
|
|
7965
8052
|
rc.vlog("Verification score=" + verification.score + " ok=" + verification.ok + " gaps=" + verification.gaps.length);
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
8053
|
+
const initialPatchable = verification.gaps.filter(isDeterministicallyPatchable);
|
|
8054
|
+
if (initialPatchable.length > 0) {
|
|
8055
|
+
rc.notify("Phase 4 Verify: " + initialPatchable.length + " deterministic gap(s), score=" + verification.score + ", applying repair", "warning");
|
|
8056
|
+
const repaired = repairSummaryDeterministically(summary, verification, extraction, rc.previousState);
|
|
8057
|
+
summary = repaired.summary;
|
|
8058
|
+
verification = repaired.result;
|
|
8059
|
+
deterministicPatched.push(...repaired.patched);
|
|
7970
8060
|
}
|
|
7971
8061
|
const mode = rc.mode ?? (rc.profile ? modeFromLegacyProfile(rc.profile) : "balanced");
|
|
7972
|
-
if (MODE_POLICIES[mode].allowLlmPatch && !verification.ok
|
|
8062
|
+
if (MODE_POLICIES[mode].allowLlmPatch && !verification.ok) {
|
|
7973
8063
|
rc.notify("Phase 4 Verify: deterministic repair insufficient (score=" + verification.score + "), requesting LLM patch", "warning");
|
|
7974
8064
|
const beforePatch = summary;
|
|
7975
8065
|
try {
|
|
@@ -7981,34 +8071,30 @@ async function verifyAndPatch(rc) {
|
|
|
7981
8071
|
if (summary !== beforePatch) {
|
|
7982
8072
|
llmPatched = true;
|
|
7983
8073
|
verification = verifySummary(summary, extraction, rc.previousState);
|
|
8074
|
+
const repaired = repairSummaryDeterministically(summary, verification, extraction, rc.previousState);
|
|
8075
|
+
summary = repaired.summary;
|
|
8076
|
+
verification = repaired.result;
|
|
8077
|
+
deterministicPatched.push(...repaired.patched);
|
|
7984
8078
|
}
|
|
7985
8079
|
}
|
|
7986
8080
|
if (!verification.ok) {
|
|
7987
8081
|
let deterministic = assembleFallback(rc.summaries, extraction);
|
|
7988
8082
|
let deterministicVerification = verifySummary(deterministic, extraction, rc.previousState);
|
|
7989
|
-
const
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
}
|
|
7994
|
-
const gain = deterministicVerification.score - verification.score;
|
|
7995
|
-
const currentInformation = Math.max(1, informationTokenCount(summary));
|
|
7996
|
-
const fallbackCoverage = informationTokenCount(deterministic) / currentInformation;
|
|
7997
|
-
const semanticSafetyFailure = verification.gaps.some((gap) => gap.kind === "inconsistency" && gap.detail.startsWith("semantic-contradiction:"));
|
|
7998
|
-
const catastrophic = verification.score < 50;
|
|
7999
|
-
const materiallyBetter = gain >= 15 && fallbackCoverage >= 0.65;
|
|
8000
|
-
if (deterministicVerification.ok && (semanticSafetyFailure || catastrophic || materiallyBetter)) {
|
|
8083
|
+
const repaired = repairSummaryDeterministically(deterministic, deterministicVerification, extraction, rc.previousState);
|
|
8084
|
+
deterministic = repaired.summary;
|
|
8085
|
+
deterministicVerification = repaired.result;
|
|
8086
|
+
if (deterministicVerification.ok) {
|
|
8001
8087
|
summary = deterministic;
|
|
8002
8088
|
verification = deterministicVerification;
|
|
8003
|
-
deterministicPatched.push(...
|
|
8089
|
+
deterministicPatched.push(...repaired.patched);
|
|
8004
8090
|
qualityFloorUsed = true;
|
|
8005
|
-
rc.notify("Quality floor replaced unsafe or
|
|
8091
|
+
rc.notify("Quality floor replaced unsafe or unverifiable model output", "warning");
|
|
8006
8092
|
}
|
|
8007
8093
|
}
|
|
8008
8094
|
const failure = verificationFailureMessage(verification);
|
|
8009
8095
|
if (failure) {
|
|
8010
8096
|
rc.notify(failure + " \u2014 current conversation left unchanged", "error");
|
|
8011
|
-
throw new
|
|
8097
|
+
throw new VerificationGateError(verification, initialScore);
|
|
8012
8098
|
}
|
|
8013
8099
|
const out = rc;
|
|
8014
8100
|
out.finalSummary = summary;
|
|
@@ -8083,24 +8169,23 @@ function buildState(rc) {
|
|
|
8083
8169
|
summary = rc.services.scrubber.scrubText(summary).value;
|
|
8084
8170
|
compactionState = rc.services.scrubber.scrubValue(compactionState).value;
|
|
8085
8171
|
let postVerification = verifySummary(summary, extraction, compactionState);
|
|
8086
|
-
const
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
}
|
|
8172
|
+
const postInitialScore = postVerification.score;
|
|
8173
|
+
const postRepair = repairSummaryDeterministically(summary, postVerification, extraction, compactionState);
|
|
8174
|
+
summary = postRepair.summary;
|
|
8175
|
+
postVerification = postRepair.result;
|
|
8091
8176
|
rc.verified = postVerification.ok;
|
|
8092
8177
|
rc.verificationScore = postVerification.score;
|
|
8093
8178
|
rc.verificationGaps = postVerification.gaps.map(formatVerificationGap);
|
|
8094
8179
|
rc.verificationProvenance = {
|
|
8095
8180
|
...rc.verificationProvenance,
|
|
8096
|
-
deterministicPatched: [...rc.verificationProvenance.deterministicPatched, ...
|
|
8181
|
+
deterministicPatched: [...rc.verificationProvenance.deterministicPatched, ...postRepair.patched],
|
|
8097
8182
|
finalScore: postVerification.score,
|
|
8098
8183
|
remainingGaps: postVerification.gaps
|
|
8099
8184
|
};
|
|
8100
8185
|
const failure = verificationFailureMessage(postVerification);
|
|
8101
8186
|
if (failure) {
|
|
8102
8187
|
rc.notify(failure + " after continuity injection \u2014 current conversation left unchanged", "error");
|
|
8103
|
-
throw new
|
|
8188
|
+
throw new VerificationGateError(postVerification, postInitialScore);
|
|
8104
8189
|
}
|
|
8105
8190
|
const detModified = extraction.modifiedFiles.map((f) => f.path);
|
|
8106
8191
|
const detRead = extraction.readFiles;
|
|
@@ -8164,11 +8249,37 @@ var MAX_MANUAL_NODES = 500;
|
|
|
8164
8249
|
var MAX_SESSION_NODES = 256;
|
|
8165
8250
|
var MAX_QUERY_CANDIDATES = 80;
|
|
8166
8251
|
var NINETY_DAYS_MS = 90 * 24 * 60 * 60 * 1000;
|
|
8252
|
+
function nodeSqliteAdapter(db) {
|
|
8253
|
+
return {
|
|
8254
|
+
exec: (sql) => db.exec(sql),
|
|
8255
|
+
query: (sql) => db.prepare(sql),
|
|
8256
|
+
transaction: (fn) => (...args) => {
|
|
8257
|
+
db.exec("BEGIN IMMEDIATE");
|
|
8258
|
+
try {
|
|
8259
|
+
const result = fn(...args);
|
|
8260
|
+
db.exec("COMMIT");
|
|
8261
|
+
return result;
|
|
8262
|
+
} catch (error) {
|
|
8263
|
+
try {
|
|
8264
|
+
db.exec("ROLLBACK");
|
|
8265
|
+
} catch {}
|
|
8266
|
+
throw error;
|
|
8267
|
+
}
|
|
8268
|
+
},
|
|
8269
|
+
close: () => db.close()
|
|
8270
|
+
};
|
|
8271
|
+
}
|
|
8167
8272
|
function openDatabase() {
|
|
8168
8273
|
const fp = contextGraphFile();
|
|
8169
8274
|
fs7.mkdirSync(path10.dirname(fp), { recursive: true });
|
|
8170
|
-
|
|
8171
|
-
|
|
8275
|
+
let db;
|
|
8276
|
+
if ("bun" in process.versions) {
|
|
8277
|
+
const { Database } = require2("bun:sqlite");
|
|
8278
|
+
db = new Database(fp);
|
|
8279
|
+
} else {
|
|
8280
|
+
const { DatabaseSync } = require2("node:sqlite");
|
|
8281
|
+
db = nodeSqliteAdapter(new DatabaseSync(fp));
|
|
8282
|
+
}
|
|
8172
8283
|
try {
|
|
8173
8284
|
fs7.chmodSync(fp, 384);
|
|
8174
8285
|
} catch {}
|
|
@@ -8835,6 +8946,22 @@ function recordSuccessMetrics(rc, status) {
|
|
|
8835
8946
|
function recordFailureMetrics(rc, err, fields) {
|
|
8836
8947
|
const releaseChannel = rc.config?.telemetryChannel ?? loadConfig().telemetryChannel;
|
|
8837
8948
|
const failureKind = classifyTelemetryFailure(err, rc.cancellation.timedOut);
|
|
8949
|
+
const gate = err && typeof err === "object" ? err : null;
|
|
8950
|
+
const knownGapKinds = new Set([
|
|
8951
|
+
"missing-section",
|
|
8952
|
+
"missing-file",
|
|
8953
|
+
"missing-error",
|
|
8954
|
+
"missing-constraint",
|
|
8955
|
+
"missing-decision",
|
|
8956
|
+
"missing-goal",
|
|
8957
|
+
"fabricated-file",
|
|
8958
|
+
"inconsistency",
|
|
8959
|
+
"missing-open-loops"
|
|
8960
|
+
]);
|
|
8961
|
+
const gapKinds = Array.isArray(gate?.gapKinds) ? gate.gapKinds.filter((kind) => typeof kind === "string" && knownGapKinds.has(kind)) : undefined;
|
|
8962
|
+
const verificationScore = typeof gate?.score === "number" && Number.isFinite(gate.score) ? gate.score : undefined;
|
|
8963
|
+
const initialVerificationScore = typeof gate?.initialScore === "number" && Number.isFinite(gate.initialScore) ? gate.initialScore : undefined;
|
|
8964
|
+
const verificationGaps = typeof gate?.gapCount === "number" && Number.isInteger(gate.gapCount) && gate.gapCount >= 0 ? gate.gapCount : undefined;
|
|
8838
8965
|
appendMetricsLog(fields.sessionId ?? "unknown", {
|
|
8839
8966
|
runId: rc.runId,
|
|
8840
8967
|
metricsSchemaVersion: 2,
|
|
@@ -8854,6 +8981,11 @@ function recordFailureMetrics(rc, err, fields) {
|
|
|
8854
8981
|
runType: runType(rc),
|
|
8855
8982
|
status: rc.cancellation.timedOut ? "timeout" : "error",
|
|
8856
8983
|
fallbackReason: "failure:" + failureKind,
|
|
8984
|
+
verificationScore,
|
|
8985
|
+
initialVerificationScore,
|
|
8986
|
+
verificationGaps,
|
|
8987
|
+
remainingVerificationGaps: verificationGaps,
|
|
8988
|
+
verificationGapKinds: gapKinds,
|
|
8857
8989
|
phaseTimings: rc.phaseTimings,
|
|
8858
8990
|
durationMs: Date.now() - rc.pipelineStart
|
|
8859
8991
|
}, rc.services);
|
|
@@ -8989,7 +9121,8 @@ function makeBase(opts) {
|
|
|
8989
9121
|
dryRun: !!opts.dryRun,
|
|
8990
9122
|
autoTriggered: !!opts.autoTriggered,
|
|
8991
9123
|
skipCompact: !!opts.skipCompact,
|
|
8992
|
-
force: !!opts.force
|
|
9124
|
+
force: !!opts.force,
|
|
9125
|
+
overflowRecovery: !!opts.overflowRecovery
|
|
8993
9126
|
},
|
|
8994
9127
|
userNote: opts.userNote,
|
|
8995
9128
|
focus: opts.focus,
|
|
@@ -9015,8 +9148,11 @@ async function runSmartCompact(opts) {
|
|
|
9015
9148
|
return;
|
|
9016
9149
|
}
|
|
9017
9150
|
const runSessionId = resolveSessionId(opts.ctx);
|
|
9018
|
-
if (!acquireRunLock(opts.isRunning, runSessionId))
|
|
9151
|
+
if (!acquireRunLock(opts.isRunning, runSessionId)) {
|
|
9152
|
+
if (!opts.autoTriggered)
|
|
9153
|
+
opts.ctx.ui.notify("Smart compact is already running for this session.", "warning");
|
|
9019
9154
|
return;
|
|
9155
|
+
}
|
|
9020
9156
|
const base = makeBase(opts);
|
|
9021
9157
|
const abortFromHost = () => {
|
|
9022
9158
|
base.cancellation.timedOut = true;
|
|
@@ -9786,8 +9922,7 @@ Paths: ` + relatedPaths.join(", ") : ""));
|
|
|
9786
9922
|
const totalTokens = usage?.tokens ?? 0;
|
|
9787
9923
|
const pct = ctx.model && totalTokens ? Math.round(totalTokens / ctx.model.contextWindow * 100) : 0;
|
|
9788
9924
|
if (!totalTokens || totalTokens < MIN_TOKEN_THRESHOLD) {
|
|
9789
|
-
ctx.ui.notify("Context
|
|
9790
|
-
return;
|
|
9925
|
+
ctx.ui.notify("Context usage is low or unknown (" + totalTokens.toLocaleString() + "t). Manual compaction may save little and can lose nuance; continuing because you requested it.", "warning");
|
|
9791
9926
|
}
|
|
9792
9927
|
const cur = ctx.model;
|
|
9793
9928
|
const avail = ctx.modelRegistry.getAvailable();
|
|
@@ -9856,7 +9991,7 @@ Paths: ` + relatedPaths.join(", ") : ""));
|
|
|
9856
9991
|
if (!totalTokens || totalTokens < MIN_TOKEN_THRESHOLD)
|
|
9857
9992
|
return;
|
|
9858
9993
|
const pct = ctx.model && totalTokens ? totalTokens / ctx.model.contextWindow * 100 : 0;
|
|
9859
|
-
if (pct < config.minContextPercent)
|
|
9994
|
+
if (event.reason !== "overflow" && pct < config.minContextPercent)
|
|
9860
9995
|
return;
|
|
9861
9996
|
const cur = ctx.model;
|
|
9862
9997
|
if (!cur)
|
|
@@ -9885,6 +10020,7 @@ Paths: ` + relatedPaths.join(", ") : ""));
|
|
|
9885
10020
|
isRunning,
|
|
9886
10021
|
onNativeApplyError,
|
|
9887
10022
|
autoTriggered: true,
|
|
10023
|
+
overflowRecovery: event.reason === "overflow",
|
|
9888
10024
|
timeoutMs: effectiveTimeoutMs,
|
|
9889
10025
|
cancellationOut
|
|
9890
10026
|
});
|