pi-smart-compact 8.0.1 → 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 +22 -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 +261 -114
- 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 +12 -5
- 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 +7 -5
- 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(`
|
|
@@ -3824,9 +3839,17 @@ import fs5 from "fs";
|
|
|
3824
3839
|
function getStatePath(projectId, state) {
|
|
3825
3840
|
return state?.scope?.sessionId ? scopedCompactionStateFile(projectId, state.scope.sessionId) : compactionStateFile(projectId);
|
|
3826
3841
|
}
|
|
3842
|
+
function isLegacySearchOutput(text) {
|
|
3843
|
+
const firstLine = text.trim().split(/\r?\n/, 1)[0] ?? "";
|
|
3844
|
+
return /^[^\s:][^:]*:\d+(?::\d+)?:/.test(firstLine);
|
|
3845
|
+
}
|
|
3827
3846
|
function sanitizeCompactionStateEvidence(state) {
|
|
3828
3847
|
const constraints = state.constraints.filter((item) => !isDiagnosticConstraintText(item.text));
|
|
3829
|
-
|
|
3848
|
+
const unresolvedErrors = state.unresolvedErrors.filter((item) => !isLegacySearchOutput(item.message));
|
|
3849
|
+
const openLoops = state.openLoops.filter((item) => !isLegacySearchOutput(item.summary));
|
|
3850
|
+
if (constraints.length === state.constraints.length && unresolvedErrors.length === state.unresolvedErrors.length && openLoops.length === state.openLoops.length)
|
|
3851
|
+
return state;
|
|
3852
|
+
return { ...state, constraints, unresolvedErrors, openLoops };
|
|
3830
3853
|
}
|
|
3831
3854
|
function freshState(fp, data) {
|
|
3832
3855
|
if (!data)
|
|
@@ -3995,7 +4018,7 @@ function renderContinuityCapsule(state, maxChars = TRUNC.CONTINUITY_CAPSULE, exi
|
|
|
3995
4018
|
const haystack = normalizeFactKey(existing);
|
|
3996
4019
|
const lines = ["## Continuity Ledger"];
|
|
3997
4020
|
const add = (label, value) => {
|
|
3998
|
-
const text = value.
|
|
4021
|
+
const text = summaryEvidenceLine(value, TRUNC.MESSAGE);
|
|
3999
4022
|
if (!text || haystack.includes(normalizeFactKey(text)))
|
|
4000
4023
|
return;
|
|
4001
4024
|
const line = "- " + label + ": " + text;
|
|
@@ -4023,8 +4046,9 @@ function injectOpenLoopsSection(summary, openLoops) {
|
|
|
4023
4046
|
return summary;
|
|
4024
4047
|
const body = openLoops.map((l) => {
|
|
4025
4048
|
const prio = l.priority === "critical" || l.priority === "high" ? "[" + l.priority + "] " : "";
|
|
4026
|
-
const files = l.files.
|
|
4027
|
-
|
|
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;
|
|
4028
4052
|
}).join(`
|
|
4029
4053
|
`);
|
|
4030
4054
|
const parsed = parseSummary(summary);
|
|
@@ -4073,32 +4097,33 @@ function hasDeltaChanges(delta) {
|
|
|
4073
4097
|
}
|
|
4074
4098
|
function formatDeltaSection(delta) {
|
|
4075
4099
|
const lines = ["## Changes Since Last Compaction", ""];
|
|
4100
|
+
const safe = (value, max) => summaryEvidenceLine(value, max);
|
|
4076
4101
|
if (delta.goalChanged) {
|
|
4077
|
-
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");
|
|
4078
4103
|
}
|
|
4079
4104
|
if (delta.resolvedLoops.length) {
|
|
4080
|
-
lines.push("- **Resolved loops**: " + delta.resolvedLoops.map((s) => "~~" + s
|
|
4105
|
+
lines.push("- **Resolved loops**: " + delta.resolvedLoops.map((s) => "~~" + safe(s, TRUNC.DECISION_DETAIL) + "~~").join(", "));
|
|
4081
4106
|
}
|
|
4082
4107
|
if (delta.persistentLoops.length) {
|
|
4083
|
-
lines.push("- **Still open**: " + delta.persistentLoops.map((s) => s
|
|
4108
|
+
lines.push("- **Still open**: " + delta.persistentLoops.map((s) => safe(s, TRUNC.DECISION_DETAIL)).join("; "));
|
|
4084
4109
|
}
|
|
4085
4110
|
if (delta.newLoops.length) {
|
|
4086
|
-
lines.push("- **New loops**: " + delta.newLoops.map((s) => s
|
|
4111
|
+
lines.push("- **New loops**: " + delta.newLoops.map((s) => safe(s, TRUNC.DECISION_DETAIL)).join("; "));
|
|
4087
4112
|
}
|
|
4088
4113
|
if (delta.newDecisions.length) {
|
|
4089
|
-
lines.push("- **New decisions**: " + delta.newDecisions.map((s) => s
|
|
4114
|
+
lines.push("- **New decisions**: " + delta.newDecisions.map((s) => safe(s, TRUNC.SNIPPET)).join("; "));
|
|
4090
4115
|
}
|
|
4091
4116
|
if (delta.removedDecisions.length) {
|
|
4092
|
-
lines.push("- **Removed decisions**: " + delta.removedDecisions.map((s) => "~~" + s
|
|
4117
|
+
lines.push("- **Removed decisions**: " + delta.removedDecisions.map((s) => "~~" + safe(s, TRUNC.SNIPPET) + "~~").join("; "));
|
|
4093
4118
|
}
|
|
4094
4119
|
if (delta.resolvedErrors.length) {
|
|
4095
|
-
lines.push("- **Resolved errors**: " + delta.resolvedErrors.map((s) => s
|
|
4120
|
+
lines.push("- **Resolved errors**: " + delta.resolvedErrors.map((s) => safe(s, TRUNC.DECISION_DETAIL)).join("; "));
|
|
4096
4121
|
}
|
|
4097
4122
|
if (delta.newErrors.length) {
|
|
4098
|
-
lines.push("- **New errors**: " + delta.newErrors.map((s) => s
|
|
4123
|
+
lines.push("- **New errors**: " + delta.newErrors.map((s) => safe(s, TRUNC.DECISION_DETAIL)).join("; "));
|
|
4099
4124
|
}
|
|
4100
4125
|
if (delta.newModifiedFiles.length) {
|
|
4101
|
-
lines.push("- **New files touched**: " + delta.newModifiedFiles.join(", "));
|
|
4126
|
+
lines.push("- **New files touched**: " + delta.newModifiedFiles.map((file) => safe(file, TRUNC.MESSAGE)).join(", "));
|
|
4102
4127
|
}
|
|
4103
4128
|
lines.push("");
|
|
4104
4129
|
return lines.join(`
|
|
@@ -4120,7 +4145,7 @@ function ensurePinnedPaths(summary, pinned) {
|
|
|
4120
4145
|
if (!pinned.length)
|
|
4121
4146
|
return summary;
|
|
4122
4147
|
const lower = summary.toLowerCase();
|
|
4123
|
-
const missing = pinned.
|
|
4148
|
+
const missing = pinned.map((path7) => summaryEvidenceLine(path7, TRUNC.MESSAGE)).filter((path7) => path7 && !lower.includes(path7.toLowerCase()));
|
|
4124
4149
|
if (!missing.length)
|
|
4125
4150
|
return summary;
|
|
4126
4151
|
const parsed = parseSummary(summary);
|
|
@@ -5717,33 +5742,42 @@ async function prepareRun(rc) {
|
|
|
5717
5742
|
// src/app/steps/window.ts
|
|
5718
5743
|
init_mode_policy();
|
|
5719
5744
|
init_helpers();
|
|
5745
|
+
init_constants();
|
|
5720
5746
|
function resolveCompactionWindow(rc) {
|
|
5721
5747
|
const usage = rc.ctx.getContextUsage();
|
|
5722
5748
|
const totalTokens = usage?.tokens ?? 0;
|
|
5723
5749
|
const manager = rc.ctx.sessionManager;
|
|
5724
5750
|
const branch = typeof manager.buildContextEntries === "function" ? manager.buildContextEntries() : manager.getBranch();
|
|
5725
5751
|
const msgs = branch.filter((e) => e.type === "message" && e.message != null);
|
|
5726
|
-
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");
|
|
5727
5755
|
return null;
|
|
5756
|
+
}
|
|
5728
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;
|
|
5729
5758
|
const mode = rc.mode ?? (rc.profile ? modeFromLegacyProfile(rc.profile) : "balanced");
|
|
5730
5759
|
const targetPercent = MODE_POLICIES[mode].targetContextPercent;
|
|
5731
5760
|
const messageTokens = msgs.map((entry) => rc.estimator.message(entry.message));
|
|
5732
5761
|
const allMessageTokens = messageTokens.reduce((sum, tokens) => sum + tokens, 0);
|
|
5733
|
-
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);
|
|
5734
5765
|
const targetRetainedTokens = rc.ctx.model ? Math.max(0, rc.ctx.model.contextWindow * targetPercent / 100 - fixedContextTokens - rc.profileCfg.summaryBudgetTokens) : adaptiveKeepTokens;
|
|
5735
|
-
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;
|
|
5736
5769
|
let accTokens = 0;
|
|
5737
5770
|
let keepFrom = msgs.length - 1;
|
|
5738
5771
|
for (let i = msgs.length - 1;i >= 0; i--) {
|
|
5739
5772
|
const next = messageTokens[i];
|
|
5740
|
-
if (accTokens >=
|
|
5773
|
+
if (accTokens >= rawAdaptiveKeepTokens && accTokens + next > rawRetentionBudget) {
|
|
5741
5774
|
keepFrom = i + 1;
|
|
5742
5775
|
break;
|
|
5743
5776
|
}
|
|
5744
5777
|
accTokens += next;
|
|
5745
5778
|
keepFrom = i;
|
|
5746
5779
|
}
|
|
5780
|
+
const plannedKeepFrom = keepFrom;
|
|
5747
5781
|
const recentUsers = msgs.map((entry, index) => ({ index, role: entry.message?.role })).filter((entry) => entry.role === "user");
|
|
5748
5782
|
if (recentUsers.length) {
|
|
5749
5783
|
const protectedUser = recentUsers[recentUsers.length >= 2 ? recentUsers.length - 2 : recentUsers.length - 1];
|
|
@@ -5751,19 +5785,42 @@ function resolveCompactionWindow(rc) {
|
|
|
5751
5785
|
}
|
|
5752
5786
|
keepFrom = smartKeepBoundary(msgs, keepFrom, branch);
|
|
5753
5787
|
keepFrom = guardToolCallBoundary(msgs, keepFrom);
|
|
5754
|
-
const
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
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;
|
|
5759
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");
|
|
5760
5805
|
return null;
|
|
5761
5806
|
}
|
|
5762
5807
|
const toCompact = msgs.slice(0, keepFrom);
|
|
5763
|
-
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
|
+
}
|
|
5764
5812
|
return null;
|
|
5813
|
+
}
|
|
5765
5814
|
const contextPercent = rc.ctx.model && totalTokens ? totalTokens / rc.ctx.model.contextWindow * 100 : 0;
|
|
5766
|
-
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) {
|
|
5767
5824
|
const projectedTokens = fixedContextTokens + accTokens + rc.profileCfg.summaryBudgetTokens;
|
|
5768
5825
|
const targetTokens = rc.ctx.model.contextWindow * targetPercent / 100;
|
|
5769
5826
|
if (projectedTokens > targetTokens) {
|
|
@@ -6024,7 +6081,7 @@ init_constants();
|
|
|
6024
6081
|
init_helpers();
|
|
6025
6082
|
function selectTier(rc) {
|
|
6026
6083
|
const toolPercent = computeToolCharPercentage(rc.branch);
|
|
6027
|
-
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);
|
|
6028
6085
|
if (tier === "none") {
|
|
6029
6086
|
if (!rc.flags.autoTriggered) {
|
|
6030
6087
|
rc.ctx.ui.notify("Context OK (" + Math.round(rc.contextPercent) + "%). pi-toolkit manages context well.", "info");
|
|
@@ -6845,6 +6902,7 @@ function getCachedBatchClone(value) {
|
|
|
6845
6902
|
}
|
|
6846
6903
|
|
|
6847
6904
|
// src/phases/synthesize.ts
|
|
6905
|
+
init_summary_parse();
|
|
6848
6906
|
function boundedToolArgs(value, depth = 0) {
|
|
6849
6907
|
if (typeof value === "string")
|
|
6850
6908
|
return value.length > TRUNC.DETAIL ? value.slice(0, TRUNC.DETAIL) + "\u2026" : value;
|
|
@@ -7091,21 +7149,29 @@ async function assembleLLM(summaries, extraction, report, model, auth, budget, p
|
|
|
7091
7149
|
`).trim();
|
|
7092
7150
|
}
|
|
7093
7151
|
function assembleFallback(summaries, extraction) {
|
|
7094
|
-
const
|
|
7095
|
-
const
|
|
7096
|
-
const
|
|
7097
|
-
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);
|
|
7098
7163
|
if (!inProgress.length) {
|
|
7099
|
-
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));
|
|
7100
7165
|
}
|
|
7101
7166
|
inProgress.push(...detModified.map((file) => "- [ ] Continue work in " + file));
|
|
7102
|
-
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.";
|
|
7103
7169
|
return [
|
|
7104
7170
|
"## Goal",
|
|
7105
|
-
|
|
7171
|
+
goal,
|
|
7106
7172
|
"",
|
|
7107
7173
|
"## Constraints & Preferences",
|
|
7108
|
-
...
|
|
7174
|
+
...constraints.length ? constraints : ["- None recorded."],
|
|
7109
7175
|
"",
|
|
7110
7176
|
"## Progress",
|
|
7111
7177
|
"### Done",
|
|
@@ -7113,25 +7179,28 @@ function assembleFallback(summaries, extraction) {
|
|
|
7113
7179
|
"### In Progress",
|
|
7114
7180
|
...inProgress.length ? inProgress : ["- Continue current work."],
|
|
7115
7181
|
"### Blocked",
|
|
7116
|
-
...unresolved.length ? unresolved.map((error) => "- " + error
|
|
7182
|
+
...unresolved.length ? unresolved.map((error) => "- " + error) : ["- None recorded."],
|
|
7117
7183
|
"",
|
|
7118
7184
|
"## Key Decisions",
|
|
7119
|
-
...
|
|
7185
|
+
...decisions.length ? decisions : ["- None recorded."],
|
|
7120
7186
|
"",
|
|
7121
7187
|
"## Files Modified",
|
|
7122
|
-
...detModified.length ? detModified.map((
|
|
7188
|
+
...detModified.length ? detModified.map((file) => "- " + file) : ["- None recorded."],
|
|
7123
7189
|
"",
|
|
7124
7190
|
"## Files Read",
|
|
7125
|
-
...detRead.length ? detRead.map((
|
|
7191
|
+
...detRead.length ? detRead.map((file) => "- " + file) : ["- None recorded."],
|
|
7126
7192
|
"",
|
|
7127
7193
|
"## Next Steps",
|
|
7128
7194
|
"1. " + next,
|
|
7129
7195
|
"",
|
|
7130
7196
|
"## Critical Context",
|
|
7131
|
-
...unresolved.length ? unresolved.map((
|
|
7197
|
+
...unresolved.length ? unresolved.map((error) => "- Unresolved error: " + safe(error, TRUNC.TOPIC_LABEL)) : ["- None recorded."],
|
|
7132
7198
|
"",
|
|
7133
7199
|
"## Topics Covered",
|
|
7134
|
-
...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))
|
|
7135
7204
|
].join(`
|
|
7136
7205
|
`);
|
|
7137
7206
|
}
|
|
@@ -7140,8 +7209,8 @@ function failedChunkSummary(ch) {
|
|
|
7140
7209
|
topic: ch.topic,
|
|
7141
7210
|
startIndex: ch.startIndex,
|
|
7142
7211
|
endIndex: ch.endIndex,
|
|
7143
|
-
summary: "[Failed] " + ch.messages.map((m) => extractText(m.content)).join(`
|
|
7144
|
-
`)
|
|
7212
|
+
summary: "[Failed] " + summaryEvidenceLine(ch.messages.map((m) => extractText(m.content)).join(`
|
|
7213
|
+
`), TRUNC.DETAIL),
|
|
7145
7214
|
keyDecisions: [],
|
|
7146
7215
|
filesModified: [],
|
|
7147
7216
|
filesRead: [],
|
|
@@ -7522,6 +7591,21 @@ function verificationFailureMessage(result) {
|
|
|
7522
7591
|
const findings = result.gaps.slice(0, 3).map((gap) => formatVerificationGap(gap).replace(/\s+/g, " ").slice(0, 160)).join("; ");
|
|
7523
7592
|
return "Verification gate rejected summary (" + result.score + "/100, " + result.gaps.length + " unresolved gap(s))" + (findings ? ": " + findings : "");
|
|
7524
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
|
+
}
|
|
7525
7609
|
var NEGATION_MARKERS = new Set([
|
|
7526
7610
|
"no",
|
|
7527
7611
|
"not",
|
|
@@ -7592,14 +7676,15 @@ function semanticTokens(text) {
|
|
|
7592
7676
|
return (text.normalize("NFKC").match(/[\p{L}\p{N}_-]+/gu) ?? []).map(stemToken).filter((token) => token.length > 2);
|
|
7593
7677
|
}
|
|
7594
7678
|
function evidenceFragments(text) {
|
|
7595
|
-
|
|
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));
|
|
7596
7681
|
}
|
|
7597
7682
|
function hasNearbyMarker(tokens, anchor, markers) {
|
|
7598
7683
|
return tokens.some((token, index) => token === anchor && tokens.slice(Math.max(0, index - 2), index + 3).some((near) => markers.has(near)));
|
|
7599
7684
|
}
|
|
7600
7685
|
function semanticShape(source) {
|
|
7601
7686
|
const sourceTokens = semanticTokens(source);
|
|
7602
|
-
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))));
|
|
7603
7688
|
const negative = sourceTokens.some((token) => NEGATION_MARKERS.has(token));
|
|
7604
7689
|
const conditional = sourceTokens.some((token) => CONDITION_MARKERS.has(token));
|
|
7605
7690
|
const anchor = concepts.find((concept) => hasNearbyMarker(sourceTokens, concept, NEGATION_MARKERS)) ?? concepts[0] ?? "";
|
|
@@ -7629,12 +7714,15 @@ function hasSemanticContradiction(source, target) {
|
|
|
7629
7714
|
const { sourceTokens, concepts, anchor, negative, conditional } = semanticShape(source);
|
|
7630
7715
|
if (!anchor)
|
|
7631
7716
|
return false;
|
|
7717
|
+
const required = Math.min(concepts.length, Math.max(1, Math.ceil(concepts.length * 0.6)));
|
|
7632
7718
|
return evidenceFragments(target).some((fragment) => {
|
|
7633
7719
|
const tokens = semanticTokens(fragment);
|
|
7634
7720
|
if (!tokens.includes(anchor))
|
|
7635
7721
|
return false;
|
|
7722
|
+
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
7723
|
+
if (overlap < required)
|
|
7724
|
+
return false;
|
|
7636
7725
|
if (negative && !hasNearbyMarker(tokens, anchor, NEGATION_MARKERS)) {
|
|
7637
|
-
const overlap = concepts.filter((concept) => tokens.includes(concept)).length;
|
|
7638
7726
|
const validConditional = sourceTokens.includes("without") && tokens.some((token) => CONDITION_MARKERS.has(token)) && overlap >= Math.min(2, concepts.length);
|
|
7639
7727
|
return !validConditional;
|
|
7640
7728
|
}
|
|
@@ -7648,6 +7736,28 @@ function isDeterministicallyPatchable(gap) {
|
|
|
7648
7736
|
return gap.detail.startsWith("blocked-none:");
|
|
7649
7737
|
return true;
|
|
7650
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
|
+
}
|
|
7651
7761
|
function verifySummary(summary, extraction, continuity = null) {
|
|
7652
7762
|
const parsed = parseSummary(summary);
|
|
7653
7763
|
const gaps = [];
|
|
@@ -7767,7 +7877,10 @@ function verifySummary(summary, extraction, continuity = null) {
|
|
|
7767
7877
|
const basename = file.path.split("/").pop() ?? "";
|
|
7768
7878
|
if (!doneSection.toLowerCase().includes(basename.toLowerCase()))
|
|
7769
7879
|
continue;
|
|
7770
|
-
const unresolved = unresolvedEvidence.find((error) =>
|
|
7880
|
+
const unresolved = unresolvedEvidence.find((error) => {
|
|
7881
|
+
const firstLine = error.message.split(/\r?\n/, 1)[0] ?? "";
|
|
7882
|
+
return extractFileRefs(firstLine).some((ref) => isKnownPathReference(ref, [file.path]));
|
|
7883
|
+
});
|
|
7771
7884
|
if (unresolved) {
|
|
7772
7885
|
gaps.push({ kind: "inconsistency", detail: basename + " marked Done but has unresolved error" });
|
|
7773
7886
|
score -= 5;
|
|
@@ -7795,15 +7908,15 @@ function verifySummary(summary, extraction, continuity = null) {
|
|
|
7795
7908
|
}
|
|
7796
7909
|
function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
7797
7910
|
let canonical = parseSummary(summary);
|
|
7798
|
-
const
|
|
7911
|
+
const safe = (value, max = TRUNC.MESSAGE) => summaryEvidenceLine(value, max);
|
|
7799
7912
|
const unresolvedMessages = Array.from(new Set([
|
|
7800
7913
|
...extraction.errors.filter((error) => !error.resolved).map((error) => error.message),
|
|
7801
7914
|
...(continuity?.unresolvedErrors ?? []).map((error) => error.message)
|
|
7802
7915
|
]));
|
|
7803
7916
|
const unresolvedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved");
|
|
7804
7917
|
const blockedItems = [
|
|
7805
|
-
...unresolvedMessages.map((message) => "- " + message
|
|
7806
|
-
...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)
|
|
7807
7920
|
];
|
|
7808
7921
|
const patchBlockedNone = () => {
|
|
7809
7922
|
const progress = findSection(canonical, "progress");
|
|
@@ -7817,7 +7930,7 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
7817
7930
|
switch (gap.kind) {
|
|
7818
7931
|
case "missing-section": {
|
|
7819
7932
|
if (gap.section === "goal") {
|
|
7820
|
-
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.");
|
|
7821
7934
|
} else if (gap.section === "progress") {
|
|
7822
7935
|
canonical = upsertSection(canonical, "progress", `### Done
|
|
7823
7936
|
- No explicit completion recorded.
|
|
@@ -7827,36 +7940,36 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
7827
7940
|
` + (blockedItems.join(`
|
|
7828
7941
|
`) || "- None recorded."));
|
|
7829
7942
|
} else if (gap.section === "critical-context") {
|
|
7830
|
-
const critical = unresolvedMessages.map((message) => "- Unresolved error: " + message
|
|
7943
|
+
const critical = unresolvedMessages.map((message) => safe(message)).filter(Boolean).map((message) => "- Unresolved error: " + message);
|
|
7831
7944
|
canonical = upsertSection(canonical, "critical-context", critical.join(`
|
|
7832
7945
|
`) || "- None recorded.");
|
|
7833
7946
|
}
|
|
7834
7947
|
break;
|
|
7835
7948
|
}
|
|
7836
7949
|
case "missing-file":
|
|
7837
|
-
canonical = appendToSection(canonical, "files-modified", "- " + gap.path);
|
|
7950
|
+
canonical = appendToSection(canonical, "files-modified", "- " + safe(gap.path));
|
|
7838
7951
|
break;
|
|
7839
7952
|
case "missing-error": {
|
|
7840
7953
|
const existing = findSection(canonical, "critical-context")?.body.toLowerCase() ?? "";
|
|
7841
|
-
const message = gap.message
|
|
7954
|
+
const message = safe(gap.message);
|
|
7842
7955
|
if (!existing.includes(message.toLowerCase())) {
|
|
7843
7956
|
canonical = appendToSection(canonical, "critical-context", "- Unresolved error: " + message);
|
|
7844
7957
|
}
|
|
7845
7958
|
break;
|
|
7846
7959
|
}
|
|
7847
7960
|
case "missing-constraint":
|
|
7848
|
-
canonical = appendToSection(canonical, "constraints", "- " + gap.text
|
|
7961
|
+
canonical = appendToSection(canonical, "constraints", "- " + safe(gap.text, TRUNC.CONSTRAINT_TEXT));
|
|
7849
7962
|
break;
|
|
7850
7963
|
case "missing-decision":
|
|
7851
|
-
canonical = appendToSection(canonical, "decisions", "- **" + gap.summary
|
|
7964
|
+
canonical = appendToSection(canonical, "decisions", "- **" + safe(gap.summary, TRUNC.DECISION_SUMMARY) + "**");
|
|
7852
7965
|
break;
|
|
7853
7966
|
case "missing-goal":
|
|
7854
|
-
canonical = upsertSection(canonical, "goal", gap.goal);
|
|
7967
|
+
canonical = upsertSection(canonical, "goal", safe(gap.goal, TRUNC.DETAIL) || "Continue the current task.");
|
|
7855
7968
|
break;
|
|
7856
7969
|
case "missing-open-loops": {
|
|
7857
|
-
const current = extraction.errors.filter((error) => !error.resolved).map((error) => "- [high] Resolve " +
|
|
7858
|
-
const carriedErrors = (continuity?.unresolvedErrors ?? []).map((error) => "- [high] Resolve " +
|
|
7859
|
-
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);
|
|
7860
7973
|
const body = Array.from(new Set([...current, ...carriedErrors, ...carriedLoops])).slice(0, gap.unresolvedCount).join(`
|
|
7861
7974
|
`);
|
|
7862
7975
|
canonical = upsertSection(canonical, "open-loops", body || "- Review unresolved errors.", "next-steps");
|
|
@@ -7864,7 +7977,6 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
7864
7977
|
}
|
|
7865
7978
|
case "fabricated-file": {
|
|
7866
7979
|
const normalizedRef = gap.ref.replace(/\\/g, "/").toLowerCase();
|
|
7867
|
-
let removed = false;
|
|
7868
7980
|
canonical = {
|
|
7869
7981
|
sections: canonical.sections.map((section) => ({
|
|
7870
7982
|
...section,
|
|
@@ -7873,42 +7985,32 @@ function patchDeterministic(summary, gaps, extraction, continuity = null) {
|
|
|
7873
7985
|
if (!/^\s*[-*]\s+/.test(line))
|
|
7874
7986
|
return true;
|
|
7875
7987
|
const matches = extractFileRefs(line).some((ref) => ref.replace(/\\/g, "/").toLowerCase() === normalizedRef);
|
|
7876
|
-
if (matches)
|
|
7877
|
-
removed = true;
|
|
7878
7988
|
return !matches;
|
|
7879
7989
|
}).join(`
|
|
7880
7990
|
`).trim()
|
|
7881
7991
|
}))
|
|
7882
7992
|
};
|
|
7883
|
-
if (!removed)
|
|
7884
|
-
verificationNotes.push(formatVerificationGap(gap));
|
|
7885
7993
|
break;
|
|
7886
7994
|
}
|
|
7887
7995
|
case "inconsistency":
|
|
7888
7996
|
if (gap.detail.startsWith("blocked-none:"))
|
|
7889
7997
|
patchBlockedNone();
|
|
7890
|
-
else
|
|
7891
|
-
verificationNotes.push(formatVerificationGap(gap));
|
|
7892
7998
|
break;
|
|
7893
7999
|
}
|
|
7894
8000
|
}
|
|
7895
|
-
if (verificationNotes.length > 0) {
|
|
7896
|
-
canonical = upsertSection(canonical, "verification-note", verificationNotes.map((note) => "- " + note).join(`
|
|
7897
|
-
`));
|
|
7898
|
-
}
|
|
7899
8001
|
return renderSummary(canonical, { canonicalHeadings: true });
|
|
7900
8002
|
}
|
|
7901
8003
|
async function patchSummary(summary, gaps, model, auth, signal, services) {
|
|
7902
|
-
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.
|
|
7903
8005
|
|
|
7904
|
-
|
|
8006
|
+
Findings:
|
|
7905
8007
|
` + gaps.map((gap, index) => index + 1 + ". " + formatVerificationGap(gap)).join(`
|
|
7906
8008
|
`) + `
|
|
7907
8009
|
|
|
7908
8010
|
Current summary:
|
|
7909
8011
|
` + summary + `
|
|
7910
8012
|
|
|
7911
|
-
Return the COMPLETE
|
|
8013
|
+
Return the COMPLETE corrected summary in the same format.`;
|
|
7912
8014
|
try {
|
|
7913
8015
|
const maxTokens = Math.min(8192, getProviderCaps(model.provider).maxOutputTokens);
|
|
7914
8016
|
const response = await trackedComplete("patch", model, {
|
|
@@ -7928,10 +8030,6 @@ Return the COMPLETE updated summary with missing items integrated. Keep the same
|
|
|
7928
8030
|
init_overlays();
|
|
7929
8031
|
init_logger();
|
|
7930
8032
|
init_mode_policy();
|
|
7931
|
-
function informationTokenCount(summary) {
|
|
7932
|
-
const ignored = new Set(["none", "recorded", "continue", "current", "work", "explicit", "completion"]);
|
|
7933
|
-
return new Set((summary.normalize("NFKC").toLowerCase().match(/[\p{L}\p{N}_-]{4,}/gu) ?? []).filter((token) => !ignored.has(token))).size;
|
|
7934
|
-
}
|
|
7935
8033
|
async function verifyAndPatch(rc) {
|
|
7936
8034
|
const extraction = rc.extraction;
|
|
7937
8035
|
let summary = rc.finalSummary;
|
|
@@ -7948,17 +8046,20 @@ async function verifyAndPatch(rc) {
|
|
|
7948
8046
|
}
|
|
7949
8047
|
let verification = verifySummary(summary, extraction, rc.previousState);
|
|
7950
8048
|
const initialScore = verification.score;
|
|
7951
|
-
const deterministicPatched =
|
|
8049
|
+
const deterministicPatched = [];
|
|
7952
8050
|
let llmPatched = false;
|
|
7953
8051
|
let qualityFloorUsed = false;
|
|
7954
8052
|
rc.vlog("Verification score=" + verification.score + " ok=" + verification.ok + " gaps=" + verification.gaps.length);
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
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);
|
|
7959
8060
|
}
|
|
7960
8061
|
const mode = rc.mode ?? (rc.profile ? modeFromLegacyProfile(rc.profile) : "balanced");
|
|
7961
|
-
if (MODE_POLICIES[mode].allowLlmPatch && !verification.ok
|
|
8062
|
+
if (MODE_POLICIES[mode].allowLlmPatch && !verification.ok) {
|
|
7962
8063
|
rc.notify("Phase 4 Verify: deterministic repair insufficient (score=" + verification.score + "), requesting LLM patch", "warning");
|
|
7963
8064
|
const beforePatch = summary;
|
|
7964
8065
|
try {
|
|
@@ -7970,34 +8071,30 @@ async function verifyAndPatch(rc) {
|
|
|
7970
8071
|
if (summary !== beforePatch) {
|
|
7971
8072
|
llmPatched = true;
|
|
7972
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);
|
|
7973
8078
|
}
|
|
7974
8079
|
}
|
|
7975
8080
|
if (!verification.ok) {
|
|
7976
8081
|
let deterministic = assembleFallback(rc.summaries, extraction);
|
|
7977
8082
|
let deterministicVerification = verifySummary(deterministic, extraction, rc.previousState);
|
|
7978
|
-
const
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
}
|
|
7983
|
-
const gain = deterministicVerification.score - verification.score;
|
|
7984
|
-
const currentInformation = Math.max(1, informationTokenCount(summary));
|
|
7985
|
-
const fallbackCoverage = informationTokenCount(deterministic) / currentInformation;
|
|
7986
|
-
const semanticSafetyFailure = verification.gaps.some((gap) => gap.kind === "inconsistency" && gap.detail.startsWith("semantic-contradiction:"));
|
|
7987
|
-
const catastrophic = verification.score < 50;
|
|
7988
|
-
const materiallyBetter = gain >= 15 && fallbackCoverage >= 0.65;
|
|
7989
|
-
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) {
|
|
7990
8087
|
summary = deterministic;
|
|
7991
8088
|
verification = deterministicVerification;
|
|
7992
|
-
deterministicPatched.push(...
|
|
8089
|
+
deterministicPatched.push(...repaired.patched);
|
|
7993
8090
|
qualityFloorUsed = true;
|
|
7994
|
-
rc.notify("Quality floor replaced unsafe or
|
|
8091
|
+
rc.notify("Quality floor replaced unsafe or unverifiable model output", "warning");
|
|
7995
8092
|
}
|
|
7996
8093
|
}
|
|
7997
8094
|
const failure = verificationFailureMessage(verification);
|
|
7998
8095
|
if (failure) {
|
|
7999
8096
|
rc.notify(failure + " \u2014 current conversation left unchanged", "error");
|
|
8000
|
-
throw new
|
|
8097
|
+
throw new VerificationGateError(verification, initialScore);
|
|
8001
8098
|
}
|
|
8002
8099
|
const out = rc;
|
|
8003
8100
|
out.finalSummary = summary;
|
|
@@ -8072,24 +8169,23 @@ function buildState(rc) {
|
|
|
8072
8169
|
summary = rc.services.scrubber.scrubText(summary).value;
|
|
8073
8170
|
compactionState = rc.services.scrubber.scrubValue(compactionState).value;
|
|
8074
8171
|
let postVerification = verifySummary(summary, extraction, compactionState);
|
|
8075
|
-
const
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
}
|
|
8172
|
+
const postInitialScore = postVerification.score;
|
|
8173
|
+
const postRepair = repairSummaryDeterministically(summary, postVerification, extraction, compactionState);
|
|
8174
|
+
summary = postRepair.summary;
|
|
8175
|
+
postVerification = postRepair.result;
|
|
8080
8176
|
rc.verified = postVerification.ok;
|
|
8081
8177
|
rc.verificationScore = postVerification.score;
|
|
8082
8178
|
rc.verificationGaps = postVerification.gaps.map(formatVerificationGap);
|
|
8083
8179
|
rc.verificationProvenance = {
|
|
8084
8180
|
...rc.verificationProvenance,
|
|
8085
|
-
deterministicPatched: [...rc.verificationProvenance.deterministicPatched, ...
|
|
8181
|
+
deterministicPatched: [...rc.verificationProvenance.deterministicPatched, ...postRepair.patched],
|
|
8086
8182
|
finalScore: postVerification.score,
|
|
8087
8183
|
remainingGaps: postVerification.gaps
|
|
8088
8184
|
};
|
|
8089
8185
|
const failure = verificationFailureMessage(postVerification);
|
|
8090
8186
|
if (failure) {
|
|
8091
8187
|
rc.notify(failure + " after continuity injection \u2014 current conversation left unchanged", "error");
|
|
8092
|
-
throw new
|
|
8188
|
+
throw new VerificationGateError(postVerification, postInitialScore);
|
|
8093
8189
|
}
|
|
8094
8190
|
const detModified = extraction.modifiedFiles.map((f) => f.path);
|
|
8095
8191
|
const detRead = extraction.readFiles;
|
|
@@ -8153,11 +8249,37 @@ var MAX_MANUAL_NODES = 500;
|
|
|
8153
8249
|
var MAX_SESSION_NODES = 256;
|
|
8154
8250
|
var MAX_QUERY_CANDIDATES = 80;
|
|
8155
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
|
+
}
|
|
8156
8272
|
function openDatabase() {
|
|
8157
8273
|
const fp = contextGraphFile();
|
|
8158
8274
|
fs7.mkdirSync(path10.dirname(fp), { recursive: true });
|
|
8159
|
-
|
|
8160
|
-
|
|
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
|
+
}
|
|
8161
8283
|
try {
|
|
8162
8284
|
fs7.chmodSync(fp, 384);
|
|
8163
8285
|
} catch {}
|
|
@@ -8824,6 +8946,22 @@ function recordSuccessMetrics(rc, status) {
|
|
|
8824
8946
|
function recordFailureMetrics(rc, err, fields) {
|
|
8825
8947
|
const releaseChannel = rc.config?.telemetryChannel ?? loadConfig().telemetryChannel;
|
|
8826
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;
|
|
8827
8965
|
appendMetricsLog(fields.sessionId ?? "unknown", {
|
|
8828
8966
|
runId: rc.runId,
|
|
8829
8967
|
metricsSchemaVersion: 2,
|
|
@@ -8843,6 +8981,11 @@ function recordFailureMetrics(rc, err, fields) {
|
|
|
8843
8981
|
runType: runType(rc),
|
|
8844
8982
|
status: rc.cancellation.timedOut ? "timeout" : "error",
|
|
8845
8983
|
fallbackReason: "failure:" + failureKind,
|
|
8984
|
+
verificationScore,
|
|
8985
|
+
initialVerificationScore,
|
|
8986
|
+
verificationGaps,
|
|
8987
|
+
remainingVerificationGaps: verificationGaps,
|
|
8988
|
+
verificationGapKinds: gapKinds,
|
|
8846
8989
|
phaseTimings: rc.phaseTimings,
|
|
8847
8990
|
durationMs: Date.now() - rc.pipelineStart
|
|
8848
8991
|
}, rc.services);
|
|
@@ -8978,7 +9121,8 @@ function makeBase(opts) {
|
|
|
8978
9121
|
dryRun: !!opts.dryRun,
|
|
8979
9122
|
autoTriggered: !!opts.autoTriggered,
|
|
8980
9123
|
skipCompact: !!opts.skipCompact,
|
|
8981
|
-
force: !!opts.force
|
|
9124
|
+
force: !!opts.force,
|
|
9125
|
+
overflowRecovery: !!opts.overflowRecovery
|
|
8982
9126
|
},
|
|
8983
9127
|
userNote: opts.userNote,
|
|
8984
9128
|
focus: opts.focus,
|
|
@@ -9004,8 +9148,11 @@ async function runSmartCompact(opts) {
|
|
|
9004
9148
|
return;
|
|
9005
9149
|
}
|
|
9006
9150
|
const runSessionId = resolveSessionId(opts.ctx);
|
|
9007
|
-
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");
|
|
9008
9154
|
return;
|
|
9155
|
+
}
|
|
9009
9156
|
const base = makeBase(opts);
|
|
9010
9157
|
const abortFromHost = () => {
|
|
9011
9158
|
base.cancellation.timedOut = true;
|
|
@@ -9775,8 +9922,7 @@ Paths: ` + relatedPaths.join(", ") : ""));
|
|
|
9775
9922
|
const totalTokens = usage?.tokens ?? 0;
|
|
9776
9923
|
const pct = ctx.model && totalTokens ? Math.round(totalTokens / ctx.model.contextWindow * 100) : 0;
|
|
9777
9924
|
if (!totalTokens || totalTokens < MIN_TOKEN_THRESHOLD) {
|
|
9778
|
-
ctx.ui.notify("Context
|
|
9779
|
-
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");
|
|
9780
9926
|
}
|
|
9781
9927
|
const cur = ctx.model;
|
|
9782
9928
|
const avail = ctx.modelRegistry.getAvailable();
|
|
@@ -9845,7 +9991,7 @@ Paths: ` + relatedPaths.join(", ") : ""));
|
|
|
9845
9991
|
if (!totalTokens || totalTokens < MIN_TOKEN_THRESHOLD)
|
|
9846
9992
|
return;
|
|
9847
9993
|
const pct = ctx.model && totalTokens ? totalTokens / ctx.model.contextWindow * 100 : 0;
|
|
9848
|
-
if (pct < config.minContextPercent)
|
|
9994
|
+
if (event.reason !== "overflow" && pct < config.minContextPercent)
|
|
9849
9995
|
return;
|
|
9850
9996
|
const cur = ctx.model;
|
|
9851
9997
|
if (!cur)
|
|
@@ -9874,6 +10020,7 @@ Paths: ` + relatedPaths.join(", ") : ""));
|
|
|
9874
10020
|
isRunning,
|
|
9875
10021
|
onNativeApplyError,
|
|
9876
10022
|
autoTriggered: true,
|
|
10023
|
+
overflowRecovery: event.reason === "overflow",
|
|
9877
10024
|
timeoutMs: effectiveTimeoutMs,
|
|
9878
10025
|
cancellationOut
|
|
9879
10026
|
});
|