@echomem/mcp 1.4.7 → 1.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -9
- package/assets/canonical-scorer/README.md +18 -0
- package/assets/canonical-scorer/analyze-10-problems.mjs +857 -0
- package/assets/canonical-scorer/build-session-waste-dashboard.mjs +1628 -0
- package/assets/canonical-scorer/golden_anchors.mjs +83 -0
- package/assets/canonical-scorer/optimizable_detail.mjs +633 -0
- package/assets/hud/claude.svg +1 -0
- package/assets/hud/codex.svg +1 -0
- package/assets/hud/session-viewer.html +35 -0
- package/dist/city/chaos-to-clarity-pencil.html +582 -0
- package/dist/city/echo-ai-city-only.html +1126 -109
- package/dist/city/echo-ai-city-only.template.html +1126 -109
- package/dist/city/echo-face-cutout.png +0 -0
- package/dist/city/pencil-pie-generator.html +883 -0
- package/dist/city/pencil-webgl-landscape.html +1239 -0
- package/dist/city/spatial-fan-story.html +479 -0
- package/dist/codex-session-files.js +283 -0
- package/dist/codex-sync.js +7 -2
- package/dist/context-analysis/canonical-golden.js +47 -0
- package/dist/context-analysis/claude-native-canonical.js +1193 -0
- package/dist/context-analysis/vendored-canonical.js +793 -0
- package/dist/context-analysis/workspace-report.js +1838 -0
- package/dist/context-metrics/calculate.js +56 -0
- package/dist/context-metrics/model-limits.js +26 -0
- package/dist/context-metrics/types.js +1 -0
- package/dist/forensics-10-problems.js +7 -6
- package/dist/forensics.js +863 -132
- package/dist/hud/adapters.js +8 -4
- package/dist/hud/autostart.js +66 -0
- package/dist/hud/cli.js +31 -0
- package/dist/hud/electron-main.js +182 -19
- package/dist/hud/metric.js +13 -4
- package/dist/hud/monitor.js +171 -84
- package/dist/hud/preload.cjs +3 -0
- package/dist/hud/server.js +321 -4
- package/dist/hud/web.js +880 -270
- package/dist/index.js +122 -24
- package/dist/local-data-paths.js +87 -0
- package/dist/migrate.js +55 -29
- package/dist/report.js +101 -40
- package/dist/setup-page.js +4257 -245
- package/dist/setup-preview.js +245 -0
- package/dist/setup.js +786 -75
- package/dist/v1-contract.js +20 -2
- package/package.json +6 -4
- package/templates/echomem-recall.md +2 -2
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// OPTIMIZABLE DETAIL — per turn, what inside the window is removable and WHY, anchored to each commit message.
|
|
3
|
+
// Partition of every turn's real C_t into KEEP (floor) + 3 cleanable buckets (mapped to EchoMem's redundancy types):
|
|
4
|
+
// KEEP overhead = system/tools/turn_ctx + serialization (irreducible)
|
|
5
|
+
// KEEP product = current/near-current committed file context + the user spec + active same-turn discovery
|
|
6
|
+
// OPT dup = duplicate/stale rereads + superseded write versions → "Stale Reread" (hold 1, not N)
|
|
7
|
+
// OPT refind = carried search/git/status/build/reasoning/self-talk after the source turn → "Repo re-discovery / tool-instead-of-recall / self-explanation"
|
|
8
|
+
// OPT dead = reads unrelated to any commit (tier-3) + piled-up screenshots → "Irrelevant injection / expired view"
|
|
9
|
+
// optimizable = dup + refind + dead. Each turn sums to real C_t. Commit message of the episode is the anchor.
|
|
10
|
+
// Important interpretation: "OPT/waste" does NOT mean the work was useless when it happened.
|
|
11
|
+
// Early orientation turns legitimately need searches, git checks, memory lookup, route reads,
|
|
12
|
+
// and wrapper-file reads to understand the app. This strict Kobe-style score asks a narrower
|
|
13
|
+
// question: which tokens should keep riding in the context window after they served that purpose?
|
|
14
|
+
// In other words, OPT is "non-final / compressible / droppable later" context. For product
|
|
15
|
+
// judgment, separate temporary discovery/support cost from truly avoidable waste.
|
|
16
|
+
// Same-turn discovery/support gets a grace turn: it becomes waste only when carried
|
|
17
|
+
// forward, or when superseded by a later same-turn duplicate.
|
|
18
|
+
// Usage: node optimizable_detail.mjs WebPageReactVersion 70
|
|
19
|
+
|
|
20
|
+
import fs from 'fs';
|
|
21
|
+
import os from 'os';
|
|
22
|
+
import readline from 'readline';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import { chooseGroundTruthAnchors } from './golden_anchors.mjs';
|
|
25
|
+
|
|
26
|
+
const HOME = os.homedir();
|
|
27
|
+
const PACKAGE_ROOT = path.resolve(process.cwd(), 'ErikMachine-Context_Golden_Standard');
|
|
28
|
+
const ROOT = process.env.GOLDEN_ROOT || path.join(PACKAGE_ROOT, 'data');
|
|
29
|
+
const ECHO = process.env.GOLDEN_ECHO || path.join(PACKAGE_ROOT, 'inputs', 'kobe-compatible');
|
|
30
|
+
const args = parseArgs(process.argv.slice(2));
|
|
31
|
+
const argTarget = args._[0] || 'WebPageReactVersion';
|
|
32
|
+
const argTurns = +(args._[1]) || 0;
|
|
33
|
+
const scoringMode = normalizeScoringMode(args['scoring-mode'] || args.scorer || args.mode || 'local-live');
|
|
34
|
+
const isEpisodeOutcome = scoringMode === 'episode-outcome';
|
|
35
|
+
const debugItems = Boolean(args['debug-items'] || args.debugItems);
|
|
36
|
+
const directSessionPath = resolveDirectSession(argTarget);
|
|
37
|
+
const proj = directSessionPath ? sessionIdFromFile(directSessionPath) : argTarget;
|
|
38
|
+
const wantTurns = argTurns || (directSessionPath ? await countUserTurns(directSessionPath) : 70);
|
|
39
|
+
const READ_VERBS = new Set(['cat', 'head', 'tail', 'sed', 'nl', 'less', 'more', 'bat', 'strings', 'view']);
|
|
40
|
+
const SEARCH_VERBS = new Set(['rg', 'grep', 'ag', 'ack', 'find', 'fd', 'fgrep', 'egrep']);
|
|
41
|
+
const R_CODE = 3.3, R_TEXT = 4.0, IMG_TOK = 4000, TRUNC_CAP = 12000, SER_FRAC = 0.03;
|
|
42
|
+
const reTok = /Original token count:\s*(\d+)/;
|
|
43
|
+
const baseName = (p) => p ? String(p).split('/').pop().toLowerCase() : null;
|
|
44
|
+
const isImageOut = (out) => Array.isArray(out) ? out.some((b) => b?.type === 'input_image' || b?.image_url) : false;
|
|
45
|
+
function countImagesDeep(value) {
|
|
46
|
+
if (!value) return 0;
|
|
47
|
+
if (typeof value === 'string') return (value.match(/"type"\s*:\s*"input_image"/g) || []).length + (value.match(/data:image\//g) || []).length;
|
|
48
|
+
if (Array.isArray(value)) return value.reduce((a, x) => a + countImagesDeep(x), 0);
|
|
49
|
+
if (typeof value === 'object') return (value.type === 'input_image' ? 1 : 0) + Object.values(value).reduce((a, x) => a + countImagesDeep(x), 0);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
function countUserImages(payload) {
|
|
53
|
+
return (payload.images?.length || 0) + (payload.local_images?.length || 0) + countImagesDeep(payload.message || '');
|
|
54
|
+
}
|
|
55
|
+
function messageText(payload) {
|
|
56
|
+
return typeof payload.message === 'string' ? payload.message : JSON.stringify(payload.message || '');
|
|
57
|
+
}
|
|
58
|
+
function walk(dir, out) { let e; try { e = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } for (const x of e) { const p = path.join(dir, x.name); if (x.isDirectory()) walk(p, out); else if (x.name.endsWith('.jsonl')) out.set(x.name, p); } }
|
|
59
|
+
function resolveDirectSession(value) {
|
|
60
|
+
if (!value) return null;
|
|
61
|
+
if (String(value).endsWith('.jsonl') && fs.existsSync(value)) return path.resolve(value);
|
|
62
|
+
if (!/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i.test(String(value))) return null;
|
|
63
|
+
const idx = new Map();
|
|
64
|
+
walk(path.join(HOME, '.codex', 'sessions'), idx);
|
|
65
|
+
const matches = [...idx.values()].filter((fp) => fp.includes(value));
|
|
66
|
+
if (!matches.length) return null;
|
|
67
|
+
return matches.sort((a, b) => a.length - b.length)[0];
|
|
68
|
+
}
|
|
69
|
+
async function countUserTurns(file) {
|
|
70
|
+
let n = 0;
|
|
71
|
+
const lines = readline.createInterface({ input: fs.createReadStream(file), crlfDelay: Infinity });
|
|
72
|
+
for await (const line of lines) {
|
|
73
|
+
if (!line.trim()) continue;
|
|
74
|
+
let r; try { r = JSON.parse(line); } catch { continue; }
|
|
75
|
+
if ((r.payload || {}).type === 'user_message') n++;
|
|
76
|
+
}
|
|
77
|
+
return n;
|
|
78
|
+
}
|
|
79
|
+
function createProviderRequestTracker() { return { cumulativeInputTokens: 0, sawCumulativeInput: false, legacyFingerprint: null }; }
|
|
80
|
+
function acceptProviderRequest(info, tracker) {
|
|
81
|
+
if (!info || typeof info !== 'object') return null;
|
|
82
|
+
const last = info.last_token_usage;
|
|
83
|
+
const cumulativeInput = cumulativeInputTokens(info.total_token_usage);
|
|
84
|
+
if (cumulativeInput != null) {
|
|
85
|
+
tracker.sawCumulativeInput = true;
|
|
86
|
+
// Output and total can grow across snapshots of one in-flight provider request. Only a strict
|
|
87
|
+
// increase in cumulative input starts the next request; the session-global ledger also prevents
|
|
88
|
+
// a repeated snapshot from moving into the following human turn.
|
|
89
|
+
if (cumulativeInput <= tracker.cumulativeInputTokens) return null;
|
|
90
|
+
tracker.cumulativeInputTokens = cumulativeInput;
|
|
91
|
+
if (!last || typeof last !== 'object') return null;
|
|
92
|
+
const request = normalizeProviderUsage(last);
|
|
93
|
+
tracker.legacyFingerprint = providerUsageFingerprint(request);
|
|
94
|
+
return request;
|
|
95
|
+
}
|
|
96
|
+
// Compatibility for older JSONL fixtures without total_token_usage. The session-global
|
|
97
|
+
// fingerprint still de-duplicates repeated snapshots across human-turn boundaries.
|
|
98
|
+
if (tracker.sawCumulativeInput || !last || typeof last !== 'object') return null;
|
|
99
|
+
const request = normalizeProviderUsage(last);
|
|
100
|
+
const fingerprint = providerUsageFingerprint(request);
|
|
101
|
+
if (fingerprint === tracker.legacyFingerprint) return null;
|
|
102
|
+
tracker.legacyFingerprint = fingerprint;
|
|
103
|
+
return request;
|
|
104
|
+
}
|
|
105
|
+
function cumulativeInputTokens(usage) {
|
|
106
|
+
if (!usage || typeof usage !== 'object') return null;
|
|
107
|
+
const input = Number(usage.input_tokens);
|
|
108
|
+
return Number.isFinite(input) && input > 0 ? input : null;
|
|
109
|
+
}
|
|
110
|
+
function normalizeProviderUsage(usage) {
|
|
111
|
+
return {
|
|
112
|
+
inputTokens: Number(usage.input_tokens || 0),
|
|
113
|
+
cachedInputTokens: Number(usage.cached_input_tokens || 0),
|
|
114
|
+
outputTokens: Number(usage.output_tokens || 0),
|
|
115
|
+
reasoningOutputTokens: Number(usage.reasoning_output_tokens || 0),
|
|
116
|
+
totalTokens: Number(usage.total_tokens || 0),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function providerUsageFingerprint(usage) {
|
|
120
|
+
return [usage.inputTokens, usage.cachedInputTokens, usage.outputTokens, usage.reasoningOutputTokens, usage.totalTokens].join(':');
|
|
121
|
+
}
|
|
122
|
+
function readFileArg(cmd) { if (typeof cmd !== 'string') return null; const seg = cmd.split('|')[0].split('>')[0].trim(); const t = seg.split(/\s+/); const v = (t[0] || '').split('/').pop(); if (!READ_VERBS.has(v)) return null; for (let i = t.length - 1; i >= 1; i--) { let x = t[i].replace(/^['"]|['"]$/g, ''); if (x && !x.startsWith('-') && (x.includes('/') || x.includes('.'))) return x; } return null; }
|
|
123
|
+
function readRange(cmd) { const ms = [...String(cmd).matchAll(/(\d+),(\d+)\s*p/g)]; if (!ms.length) return null; return [Math.min(...ms.map((m) => +m[1])), Math.max(...ms.map((m) => +m[2]))]; }
|
|
124
|
+
const rangeOverlap = (a, b) => !a || !b || (a[0] <= b[1] && b[0] <= a[1]);
|
|
125
|
+
function cmdKind(cmd) { const v = (String(cmd).split('|')[0].split('>')[0].trim().split(/\s+/)[0] || '').split('/').pop(); if (SEARCH_VERBS.has(v)) return 'search'; if (READ_VERBS.has(v)) return 'read'; return 'command'; }
|
|
126
|
+
function normalizeCommand(cmd) { return String(cmd || '').replace(/\s+/g, ' ').trim().replace(/["'][^"']{40,}["']/g, '"..."').replace(/\b\d{4,}\b/g, 'N').slice(0, 220); }
|
|
127
|
+
function searchPattern(cmd) { const match = String(cmd || '').match(/(?:rg|grep|ag|ack)\s+(?:-[^\s]+\s+)*['"]?([^'"\s][^'"]{0,80})/); return match ? match[1].replace(/\s+/g, ' ').trim() : normalizeCommand(cmd).slice(0, 80); }
|
|
128
|
+
function signals(text) { const out = new Set(); for (const m of String(text).matchAll(/#[0-9a-fA-F]{3,8}\b/g)) out.add('c:' + m[0].toLowerCase()); for (const m of String(text).matchAll(/--[a-zA-Z][\w-]{2,}/g)) out.add('v:' + m[0].toLowerCase()); for (const m of String(text).matchAll(/\b\d{1,4}(?:\.\d+)?(?:px|rem|em|vh|vw|ms)\b/g)) out.add('u:' + m[0].toLowerCase()); return out; }
|
|
129
|
+
function patchFiles(input) {
|
|
130
|
+
const files = new Set();
|
|
131
|
+
for (const line of String(input || '').split(/\n/)) {
|
|
132
|
+
const match = line.match(/^\*\*\* (?:Add|Update|Delete) File: (.+)$/) || line.match(/^\*\*\* Move to: (.+)$/);
|
|
133
|
+
const file = baseName(match?.[1]);
|
|
134
|
+
if (file) files.add(file);
|
|
135
|
+
}
|
|
136
|
+
return [...files];
|
|
137
|
+
}
|
|
138
|
+
function parseArgs(argv) {
|
|
139
|
+
const out = { _: [] };
|
|
140
|
+
for (let i = 0; i < argv.length; i++) {
|
|
141
|
+
const arg = argv[i];
|
|
142
|
+
if (!arg.startsWith('--')) {
|
|
143
|
+
out._.push(arg);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const [rawKey, inline] = arg.slice(2).split('=', 2);
|
|
147
|
+
const key = rawKey.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
148
|
+
if (inline != null) out[rawKey] = out[key] = inline;
|
|
149
|
+
else if (argv[i + 1] && !argv[i + 1].startsWith('--')) out[rawKey] = out[key] = argv[++i];
|
|
150
|
+
else out[rawKey] = out[key] = true;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
function normalizeScoringMode(value) {
|
|
155
|
+
const mode = String(value || '').trim().toLowerCase().replace(/_/g, '-');
|
|
156
|
+
if (['local', 'live', 'local-live', 'current'].includes(mode)) return 'local-live';
|
|
157
|
+
if (['episode', 'episode-outcome', 'outcome', 'offline-outcome'].includes(mode)) return 'episode-outcome';
|
|
158
|
+
console.error(`unknown scoring mode: ${value}`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const cadence = directSessionPath ? { rows: [] } : JSON.parse(fs.readFileSync(path.join(ECHO, 'forensics/commit-cadence.json'), 'utf8'));
|
|
163
|
+
const sample = directSessionPath ? { sessions: [] } : JSON.parse(fs.readFileSync(path.join(ECHO, 'dirty-context-sample.json'), 'utf8'));
|
|
164
|
+
const byKey = new Map(sample.sessions.filter((s) => (s.turns || 0) >= 8).map((s) => [[s.tool, s.project, s.turns, s.reads, s.totalTokens, s.inputTokens].join('|'), s]));
|
|
165
|
+
const idx = new Map(); walk(path.join(HOME, '.codex', 'sessions'), idx);
|
|
166
|
+
const row = directSessionPath
|
|
167
|
+
? { project: path.basename(directSessionPath, '.jsonl'), turns: wantTurns }
|
|
168
|
+
: (cadence.rows || []).find((c) => !c.usedEchoMem && c.tool === 'codex' && (c.project || '').includes(proj) && c.turns === wantTurns);
|
|
169
|
+
const s = directSessionPath ? null : row && byKey.get([row.tool, row.project, row.turns, row.reads, row.totalTok, row.inTok].join('|'));
|
|
170
|
+
const fp = directSessionPath || (s && idx.get(s.file));
|
|
171
|
+
if (!fp) { console.log('session not found'); process.exit(1); }
|
|
172
|
+
const sessionId = sessionIdFromFile(fp);
|
|
173
|
+
const payloadAccounting = loadPayloadAccounting(sessionId);
|
|
174
|
+
const payloadEvidenceByTurn = new Map((payloadAccounting?.turns || []).map((turn) => [turn.number, summarizePayloadEvidence(turn)]));
|
|
175
|
+
|
|
176
|
+
let turn = 0, baseTok = 0, toolsTok = 0, turnCtxTok = 0, imageSeq = 0;
|
|
177
|
+
const items = []; const editEpoch = new Map(); const compactionTurns = []; const usage = new Map(); const reasoningCalls = [];
|
|
178
|
+
const callKind = new Map(); const commitTurns = new Set(); const commitMsg = new Map(); const userMessages = new Map(); const readText = new Map(); let writeCorpus = ''; const CAP = 60000;
|
|
179
|
+
const requestTracker = createProviderRequestTracker();
|
|
180
|
+
const rl = readline.createInterface({ input: fs.createReadStream(fp), crlfDelay: Infinity });
|
|
181
|
+
for await (const line of rl) {
|
|
182
|
+
if (!line.trim()) continue; let r; try { r = JSON.parse(line); } catch { continue; }
|
|
183
|
+
const p = r.payload || {}; const pt = p.type;
|
|
184
|
+
if (r.type === 'session_meta' || pt === 'session_meta') { baseTok = Math.max(baseTok, Math.round((p.base_instructions?.text || '').length / R_TEXT)); toolsTok = Math.max(toolsTok, Math.round(JSON.stringify(p.dynamic_tools || []).length / R_CODE)); }
|
|
185
|
+
else if (r.type === 'turn_context' || (p && p.collaboration_mode)) turnCtxTok = Math.max(turnCtxTok, Math.round(JSON.stringify(p).length / R_TEXT));
|
|
186
|
+
else if (pt === 'user_message') { if (turn >= wantTurns) break; turn++; const txt = messageText(p); userMessages.set(turn, txt); items.push({ turn, kind: 'conv_user', tokens: Math.round(txt.length / R_TEXT) }); for (let i = 0; i < countUserImages(p); i++) items.push({ turn, kind: 'image', source: 'user_reference', target: 'user_reference', imageId: ++imageSeq, tokens: IMG_TOK }); }
|
|
187
|
+
else if (pt === 'token_count') { const request = acceptProviderRequest(p.info, requestTracker); if (request && turn > 0) { usage.set(turn, { input: request.inputTokens, cached: request.cachedInputTokens }); if (request.reasoningOutputTokens) reasoningCalls.push({ turn, tokens: request.reasoningOutputTokens }); } }
|
|
188
|
+
else if (turn === 0) continue;
|
|
189
|
+
else if (pt === 'message' && p.role === 'developer') { const txt = Array.isArray(p.content) ? p.content.map((b) => b?.text || '').join('') : ''; items.push({ turn, kind: 'conv_agent', tokens: Math.round(txt.length / R_TEXT) }); }
|
|
190
|
+
else if (pt === 'context_compacted') compactionTurns.push(turn);
|
|
191
|
+
else if (pt === 'agent_message') items.push({ turn, kind: 'conv_agent', tokens: Math.round((p.message || '').length / R_TEXT) });
|
|
192
|
+
else if (pt === 'patch_apply_end' && p.changes) for (const f of Object.keys(p.changes)) editEpoch.set(baseName(f), [...(editEpoch.get(baseName(f)) || []), turn]);
|
|
193
|
+
else if (pt === 'custom_tool_call' && typeof p.input === 'string') { const files = patchFiles(p.input); items.push({ turn, kind: 'written', file: files.length === 1 ? files[0] : undefined, files, tokens: Math.round(p.input.length / R_CODE) }); if (writeCorpus.length < 400000) writeCorpus += '\n' + p.input; }
|
|
194
|
+
else if (pt === 'function_call') { if (p.name === 'exec_command') { let a = p.arguments; if (typeof a === 'string') { try { a = JSON.parse(a); } catch { a = { cmd: a }; } } const cmd = a?.cmd || a?.command || ''; const cm = cmd.match(/commit[^"']*-m\s+["']([^"']+)["']/) || cmd.match(/-m\s+["']([^"']+)["']/); if (/git\s+(?:-[^\s]+\s+)*commit/.test(cmd)) { commitTurns.add(turn); if (cm) commitMsg.set(turn, cm[1]); } if (p.call_id) callKind.set(p.call_id, { kind: cmdKind(cmd), file: baseName(readFileArg(cmd)), range: readRange(cmd), command: cmd, normalizedCommand: normalizeCommand(cmd), searchPattern: searchPattern(cmd) }); } else if (p.call_id) callKind.set(p.call_id, { kind: 'command', target: p.name || 'tool_image', imageProducer: (p.name === 'view_image' || p.name === 'js'), command: p.name || 'tool_image', normalizedCommand: p.name || 'tool_image' }); }
|
|
195
|
+
else if (pt === 'function_call_output') { const m = callKind.get(p.call_id) || { kind: 'command' }; const out = p.output; const imageCount = countImagesDeep(out); if (isImageOut(out) || (m.imageProducer && typeof out !== 'string')) items.push({ turn, kind: 'image', source: 'tool_screenshot', target: m.target || 'tool_image', imageId: ++imageSeq, callId: p.call_id, imageOutput: true, imageCount: imageCount || 1, tokens: IMG_TOK }); else { const str = typeof out === 'string' ? out : JSON.stringify(out || ''); const mm = str.match(reTok); const tok = Math.min(mm ? +mm[1] : Math.round(str.length / R_CODE), TRUNC_CAP); const kind = m.kind === 'read' ? 'read' : (m.kind === 'search' ? 'search' : 'command'); items.push({ turn, kind, file: m.file, range: m.range, command: m.command, normalizedCommand: m.normalizedCommand, searchPattern: m.searchPattern, callId: p.call_id, imageOutput: imageCount > 0, imageCount: imageCount || undefined, tokens: tok }); if (kind === 'read' && m.file) { const cur = readText.get(m.file) || ''; if (cur.length < CAP) readText.set(m.file, cur + '\n' + str.slice(0, CAP)); } } }
|
|
196
|
+
}
|
|
197
|
+
items.forEach((it, seq) => { it.seq = seq; });
|
|
198
|
+
const userTurns = [...new Set(items.filter((i) => i.kind === 'conv_user' && i.turn).map((i) => i.turn))].sort((a, b) => a - b).filter((t) => (usage.get(t)?.input || 0) > 0);
|
|
199
|
+
const overhead = baseTok + toolsTok + turnCtxTok;
|
|
200
|
+
const lastCompBefore = (t) => { let c = 0; for (const x of compactionTurns) if (x < t) c = x; return c; };
|
|
201
|
+
const Fstar = new Set([...editEpoch.keys()]);
|
|
202
|
+
const groundTruth = chooseGroundTruthAnchors({ commitTurns, commitMessages: commitMsg, userMessages, userTurns });
|
|
203
|
+
const commits = groundTruth.turns.slice();
|
|
204
|
+
const anchorMsg = groundTruth.labelByTurn;
|
|
205
|
+
const episodeOf = (t) => { let e = 1; for (const c of commits) { if (t <= c) return e; e++; } return e; };
|
|
206
|
+
const firstUserTurn = userTurns[0] || 1;
|
|
207
|
+
const lastUserTurn = userTurns[userTurns.length - 1] || wantTurns;
|
|
208
|
+
const episodeStartTurn = (e) => e <= 1 ? firstUserTurn : ((commits[e - 2] || firstUserTurn - 1) + 1);
|
|
209
|
+
const episodeEndTurn = (e) => commits[e - 1] || lastUserTurn;
|
|
210
|
+
const writeSig = signals(writeCorpus); const tierOf = (f) => { if (!f) return 3; if (Fstar.has(f)) return 1; const txt = readText.get(f); if (!txt) return 3; const sig = signals(txt); let m = 0; for (const x of sig) if (writeSig.has(x)) m++; return (m >= 4 || (sig.size >= 8 && m / sig.size >= 0.15)) ? 2 : 3; };
|
|
211
|
+
const episodeOutcome = buildEpisodeOutcomeIndex();
|
|
212
|
+
|
|
213
|
+
const CONTENT = new Set(['read', 'search', 'command', 'image', 'written', 'conv_user', 'conv_agent']);
|
|
214
|
+
// a read is a true duplicate only if an earlier read of the SAME file overlapped the SAME range with no edit in between
|
|
215
|
+
const isDupRead = (it) => it.kind === 'read' && it.file && items.some((h) => h.kind === 'read' && h.file === it.file && h.turn < it.turn && rangeOverlap(h.range, it.range) && !(editEpoch.get(it.file) || []).some((e) => e > h.turn && e <= it.turn));
|
|
216
|
+
const maxReasoningAt = new Map();
|
|
217
|
+
for (const x of reasoningCalls) maxReasoningAt.set(x.turn, Math.max(maxReasoningAt.get(x.turn) || 0, x.tokens));
|
|
218
|
+
function reasoningSegmentsSinceCompaction(lastCompactionTurn, currentTurn) {
|
|
219
|
+
const segments = [];
|
|
220
|
+
let previousCumulative = 0;
|
|
221
|
+
for (let k = lastCompactionTurn + 1; k <= currentTurn; k++) {
|
|
222
|
+
const cumulative = Math.max(previousCumulative, maxReasoningAt.get(k) || previousCumulative);
|
|
223
|
+
const tokens = cumulative - previousCumulative;
|
|
224
|
+
if (tokens > 0) segments.push({ turn: k, kind: 'reasoning', tokens });
|
|
225
|
+
previousCumulative = cumulative;
|
|
226
|
+
}
|
|
227
|
+
return segments;
|
|
228
|
+
}
|
|
229
|
+
function transientKey(it) {
|
|
230
|
+
if (it.kind === 'search') return `search:${it.searchPattern || it.normalizedCommand || it.command || ''}`;
|
|
231
|
+
if (it.kind === 'command') return `command:${it.normalizedCommand || it.command || ''}`;
|
|
232
|
+
return '';
|
|
233
|
+
}
|
|
234
|
+
const userReferenceReplacement = (text) => /\b(use this instead|instead use|replace|updated|new mockup|new reference|ignore (?:the )?(?:previous|old)|actually use|latest version|use this version)\b/i.test(String(text || ''));
|
|
235
|
+
const hasEditAfter = (startTurn, endTurn) => [...editEpoch.values()].some((turns) => turns.some((e) => e > startTurn && e <= endTurn));
|
|
236
|
+
const hasLaterScreenshot = (it, endTurn) => items.some((h) => h.kind === 'image' && h.source === 'tool_screenshot' && h.turn > it.turn && h.turn <= endTurn && (h.target || 'tool_image') === (it.target || 'tool_image'));
|
|
237
|
+
const userReferenceSuperseded = (it, endTurn) => items.some((h) => h.kind === 'image' && h.source === 'user_reference' && h.turn > it.turn && h.turn <= endTurn && userReferenceReplacement(userMessages.get(h.turn)));
|
|
238
|
+
function imageBucket(it, currentTurn) {
|
|
239
|
+
if (it.source === 'user_reference') {
|
|
240
|
+
if (episodeOf(it.turn) !== episodeOf(currentTurn)) return 'opt_dead';
|
|
241
|
+
return userReferenceSuperseded(it, currentTurn) ? 'opt_dead' : 'keep_prod';
|
|
242
|
+
}
|
|
243
|
+
if (it.source === 'tool_screenshot') {
|
|
244
|
+
if (hasEditAfter(it.turn, currentTurn) || hasLaterScreenshot(it, currentTurn)) return 'opt_dead';
|
|
245
|
+
return 'keep_prod';
|
|
246
|
+
}
|
|
247
|
+
return 'opt_dead';
|
|
248
|
+
}
|
|
249
|
+
function buildEpisodeOutcomeIndex() {
|
|
250
|
+
const byEpisode = new Map();
|
|
251
|
+
const maxEpisode = Math.max(1, ...userTurns.map((t) => episodeOf(t)));
|
|
252
|
+
for (let e = 1; e <= maxEpisode; e++) {
|
|
253
|
+
const start = episodeStartTurn(e);
|
|
254
|
+
const end = episodeEndTurn(e);
|
|
255
|
+
const editedFiles = new Set();
|
|
256
|
+
for (const [file, turns] of editEpoch.entries()) {
|
|
257
|
+
if (turns.some((editTurn) => editTurn >= start && editTurn <= end)) editedFiles.add(file);
|
|
258
|
+
}
|
|
259
|
+
const writtenItems = items
|
|
260
|
+
.filter((it) => it.kind === 'written' && it.turn >= start && it.turn <= end)
|
|
261
|
+
.sort((a, b) => b.seq - a.seq);
|
|
262
|
+
const usefulWrittenSeqs = new Set();
|
|
263
|
+
const latestWrittenByFile = new Map();
|
|
264
|
+
const unfiledWritten = [];
|
|
265
|
+
for (const it of writtenItems) {
|
|
266
|
+
const files = it.files?.length ? it.files : (it.file ? [it.file] : []);
|
|
267
|
+
if (!files.length) {
|
|
268
|
+
unfiledWritten.push(it);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
for (const file of files) {
|
|
272
|
+
const list = latestWrittenByFile.get(file) || [];
|
|
273
|
+
if (list.length < 2) {
|
|
274
|
+
list.push(it);
|
|
275
|
+
usefulWrittenSeqs.add(it.seq);
|
|
276
|
+
latestWrittenByFile.set(file, list);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
for (const it of unfiledWritten.slice(0, 2)) usefulWrittenSeqs.add(it.seq);
|
|
281
|
+
const latestTransientByKey = new Map();
|
|
282
|
+
for (const it of items) {
|
|
283
|
+
if (it.turn < start || it.turn > end) continue;
|
|
284
|
+
if (it.kind !== 'search' && it.kind !== 'command') continue;
|
|
285
|
+
const key = transientKey(it);
|
|
286
|
+
if (!key) continue;
|
|
287
|
+
const prev = latestTransientByKey.get(key);
|
|
288
|
+
if (!prev || it.seq > prev.seq) latestTransientByKey.set(key, it);
|
|
289
|
+
}
|
|
290
|
+
byEpisode.set(e, { episode: e, start, end, editedFiles, usefulWrittenSeqs, latestTransientByKey });
|
|
291
|
+
}
|
|
292
|
+
return byEpisode;
|
|
293
|
+
}
|
|
294
|
+
function hasLaterDuplicateReadBeforeOutcome(it, outcome) {
|
|
295
|
+
if (it.kind !== 'read' || !it.file) return false;
|
|
296
|
+
return items.some((h) =>
|
|
297
|
+
h.kind === 'read' &&
|
|
298
|
+
h.file === it.file &&
|
|
299
|
+
h.turn > it.turn &&
|
|
300
|
+
h.turn <= outcome.end &&
|
|
301
|
+
rangeOverlap(h.range, it.range) &&
|
|
302
|
+
!(editEpoch.get(it.file) || []).some((e) => e > it.turn && e <= h.turn)
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
function readConnectsToEpisodeOutcome(it, outcome) {
|
|
306
|
+
if (it.kind !== 'read' || !it.file) return false;
|
|
307
|
+
if (outcome.editedFiles.size) return outcome.editedFiles.has(it.file);
|
|
308
|
+
return tierOf(it.file) <= 2;
|
|
309
|
+
}
|
|
310
|
+
function episodeOutcomeBucket(it, currentTurn) {
|
|
311
|
+
const e = episodeOf(currentTurn);
|
|
312
|
+
const outcome = episodeOutcome.get(e) || { start: episodeStartTurn(e), end: episodeEndTurn(e), editedFiles: new Set(), latestTransientByKey: new Map(), usefulWrittenSeqs: new Set() };
|
|
313
|
+
const outcomeIt = { ...it, scoringMode };
|
|
314
|
+
if (it.kind === 'conv_user') return episodeOf(it.turn) === e ? 'keep_prod' : 'opt_dead';
|
|
315
|
+
if (it.kind === 'conv_agent') return it.turn === outcome.end && currentTurn === outcome.end ? 'keep_prod' : 'opt_refind';
|
|
316
|
+
if (it.kind === 'search' || it.kind === 'command') {
|
|
317
|
+
const key = transientKey(it);
|
|
318
|
+
const latest = key ? outcome.latestTransientByKey.get(key) : null;
|
|
319
|
+
if (latest && latest.seq !== it.seq && latest.turn <= outcome.end) return 'opt_refind';
|
|
320
|
+
return it.turn === outcome.end && currentTurn === outcome.end ? 'keep_prod' : 'opt_refind';
|
|
321
|
+
}
|
|
322
|
+
if (it.kind === 'image') return imageBucket(outcomeIt, outcome.end);
|
|
323
|
+
if (it.kind === 'written') return outcome.usefulWrittenSeqs?.has(it.seq) ? 'keep_prod' : 'opt_dup';
|
|
324
|
+
if (it.kind === 'read') {
|
|
325
|
+
if (hasLaterDuplicateReadBeforeOutcome(it, outcome)) return 'opt_dup';
|
|
326
|
+
if (readConnectsToEpisodeOutcome(it, outcome)) return 'keep_prod';
|
|
327
|
+
return 'opt_dead';
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
function serializeEpisodeOutcomes() {
|
|
332
|
+
return [...episodeOutcome.values()].map((outcome) => {
|
|
333
|
+
const usefulWrittenItems = items
|
|
334
|
+
.filter((it) => outcome.usefulWrittenSeqs?.has(it.seq))
|
|
335
|
+
.map((it) => ({
|
|
336
|
+
seq: it.seq,
|
|
337
|
+
turn: it.turn,
|
|
338
|
+
kind: it.kind,
|
|
339
|
+
file: it.file || null,
|
|
340
|
+
files: it.files || [],
|
|
341
|
+
tokens: it.tokens
|
|
342
|
+
}));
|
|
343
|
+
const latestTransientItems = [...(outcome.latestTransientByKey || new Map()).entries()].map(([key, it]) => ({
|
|
344
|
+
key,
|
|
345
|
+
seq: it.seq,
|
|
346
|
+
turn: it.turn,
|
|
347
|
+
kind: it.kind,
|
|
348
|
+
command: it.command || null,
|
|
349
|
+
normalizedCommand: it.normalizedCommand || null,
|
|
350
|
+
searchPattern: it.searchPattern || null,
|
|
351
|
+
tokens: it.tokens
|
|
352
|
+
}));
|
|
353
|
+
return {
|
|
354
|
+
episode: outcome.episode,
|
|
355
|
+
start: outcome.start,
|
|
356
|
+
end: outcome.end,
|
|
357
|
+
editedFiles: [...outcome.editedFiles].sort(),
|
|
358
|
+
usefulWrittenItems,
|
|
359
|
+
latestTransientItems
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function sessionIdFromFile(file) {
|
|
364
|
+
const match = String(file || '').match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
365
|
+
return match?.[1] || null;
|
|
366
|
+
}
|
|
367
|
+
function loadPayloadAccounting(id) {
|
|
368
|
+
if (process.env.GOLDEN_PAYLOAD_ACCOUNTING === '0') return null;
|
|
369
|
+
const configured = process.env.GOLDEN_PAYLOAD_ACCOUNTING;
|
|
370
|
+
const fp = configured || (id ? path.join(PACKAGE_ROOT, 'data', `TURN_PAYLOAD_ACCOUNTING_${id}.json`) : null);
|
|
371
|
+
if (!fp || !fs.existsSync(fp)) return null;
|
|
372
|
+
try { return JSON.parse(fs.readFileSync(fp, 'utf8')); } catch { return null; }
|
|
373
|
+
}
|
|
374
|
+
function summarizePayloadEvidence(turn) {
|
|
375
|
+
const payload = turn.payload || null;
|
|
376
|
+
const comparison = turn.comparison || null;
|
|
377
|
+
return {
|
|
378
|
+
matched: Boolean(turn.join?.matched),
|
|
379
|
+
sqliteLogId: turn.join?.sqliteLogId || null,
|
|
380
|
+
model: payload?.model || null,
|
|
381
|
+
requestKind: payload?.request?.requestKind || null,
|
|
382
|
+
directlyComparable: comparison?.directlyComparable ?? null,
|
|
383
|
+
officialInputTokens: turn.jsonl?.usage?.inputTokens || null,
|
|
384
|
+
localRawPayloadTokens: payload?.rawPayloadTokens || null,
|
|
385
|
+
localInputItemTokens: payload?.input?.tokens || null,
|
|
386
|
+
localInputToolsInstructionsTokens: comparison?.localInputToolsInstructionsTokens || null,
|
|
387
|
+
rawVsOfficialRatio: comparison?.rawVsOfficialRatio || null,
|
|
388
|
+
caveat: comparison?.caveat || null,
|
|
389
|
+
topSemanticBuckets: (payload?.input?.bySemanticBucket || []).slice(0, 5).map((b) => ({ key: b.key, tokens: b.tokens, count: b.count })),
|
|
390
|
+
topInputItems: (payload?.input?.topItems || []).slice(0, 5).map((item) => ({
|
|
391
|
+
type: item.type,
|
|
392
|
+
role: item.role,
|
|
393
|
+
semanticBucket: item.semanticBucket,
|
|
394
|
+
metadataTurnId: item.metadataTurnId,
|
|
395
|
+
tokens: item.tokens,
|
|
396
|
+
preview: item.preview
|
|
397
|
+
}))
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const BANDS = ['keep_oh', 'keep_prod', 'opt_dup', 'opt_refind', 'opt_dead'];
|
|
401
|
+
const series = []; const agg = Object.fromEntries(BANDS.map((b) => [b, 0]));
|
|
402
|
+
function itemReason(it, bucket) {
|
|
403
|
+
if (it.scoringMode === 'episode-outcome') {
|
|
404
|
+
if (bucket === 'keep_prod') {
|
|
405
|
+
if (it.kind === 'conv_user') return 'same-episode user/product instruction';
|
|
406
|
+
if (it.kind === 'read') return 'read connects to episode outcome files';
|
|
407
|
+
if (it.kind === 'written') return 'latest two written versions by episode outcome';
|
|
408
|
+
if (it.kind === 'image' && it.source === 'user_reference') return 'reference still active at episode outcome';
|
|
409
|
+
if (it.kind === 'image') return 'visual evidence still active at episode outcome';
|
|
410
|
+
return 'active at episode outcome';
|
|
411
|
+
}
|
|
412
|
+
if (bucket === 'opt_dup') {
|
|
413
|
+
if (it.kind === 'written') return 'written version older than latest two before episode outcome';
|
|
414
|
+
if (it.kind === 'read') return 'same file/range was re-read before episode outcome';
|
|
415
|
+
return 'duplicate before episode outcome';
|
|
416
|
+
}
|
|
417
|
+
if (bucket === 'opt_dead') {
|
|
418
|
+
if (it.kind === 'read') return 'read does not connect to episode outcome files';
|
|
419
|
+
if (it.kind === 'image') return 'visual/reference context expired before episode outcome';
|
|
420
|
+
return 'does not connect to episode outcome';
|
|
421
|
+
}
|
|
422
|
+
if (bucket === 'opt_refind') return 'transient output did not survive as episode outcome context';
|
|
423
|
+
}
|
|
424
|
+
if (bucket === 'keep_prod') {
|
|
425
|
+
if (it.kind === 'conv_user') return 'current user request / product instruction';
|
|
426
|
+
if (it.kind === 'image' && it.source === 'user_reference') return 'active user reference image';
|
|
427
|
+
if (it.kind === 'image') return 'current visual evidence';
|
|
428
|
+
if (it.kind === 'written') return 'latest written work';
|
|
429
|
+
if (it.kind === 'read') return 'relevant file context';
|
|
430
|
+
return 'useful product context';
|
|
431
|
+
}
|
|
432
|
+
if (bucket === 'keep_oh') return 'required request overhead';
|
|
433
|
+
if (bucket === 'opt_dup') {
|
|
434
|
+
if (it.kind === 'read') return 'same file/range was read earlier without an intervening edit';
|
|
435
|
+
if (it.kind === 'written') return 'superseded earlier written version';
|
|
436
|
+
return 'duplicate or stale context';
|
|
437
|
+
}
|
|
438
|
+
if (bucket === 'opt_dead') {
|
|
439
|
+
if (it.kind === 'image') return 'expired or superseded visual context';
|
|
440
|
+
if (it.kind === 'read') return 'read has no detected connection to final work';
|
|
441
|
+
return 'dead or unrelated context';
|
|
442
|
+
}
|
|
443
|
+
if (bucket === 'opt_refind') {
|
|
444
|
+
if (it.kind === 'search') return 'search/re-discovery output';
|
|
445
|
+
if (it.kind === 'command') return 'command output used as external working memory';
|
|
446
|
+
if (it.kind === 'conv_agent') return 'agent self-talk / explanation residue';
|
|
447
|
+
if (it.kind === 'reasoning') return 'reasoning token residue';
|
|
448
|
+
return 're-find or reconciliation residue';
|
|
449
|
+
}
|
|
450
|
+
return bucket;
|
|
451
|
+
}
|
|
452
|
+
function publicItem(it, bucket, tok) {
|
|
453
|
+
return {
|
|
454
|
+
sourceTurn: it.turn,
|
|
455
|
+
kind: it.kind,
|
|
456
|
+
bucket,
|
|
457
|
+
tokens: Math.round(tok),
|
|
458
|
+
file: it.file || undefined,
|
|
459
|
+
files: it.files?.length ? it.files : undefined,
|
|
460
|
+
range: it.range || undefined,
|
|
461
|
+
command: it.command || undefined,
|
|
462
|
+
normalizedCommand: it.normalizedCommand || undefined,
|
|
463
|
+
searchPattern: it.searchPattern || undefined,
|
|
464
|
+
source: it.source || undefined,
|
|
465
|
+
target: it.target || undefined,
|
|
466
|
+
imageId: it.imageId || undefined,
|
|
467
|
+
callId: it.callId || undefined,
|
|
468
|
+
imageOutput: it.imageOutput || undefined,
|
|
469
|
+
imageCount: it.imageCount || undefined,
|
|
470
|
+
scoringMode: it.scoringMode || undefined,
|
|
471
|
+
reason: itemReason(it, bucket)
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function groupItems(list) {
|
|
475
|
+
const groups = new Map();
|
|
476
|
+
for (const it of list) {
|
|
477
|
+
const key = [it.sourceTurn || '', it.callId || '', it.bucket, it.kind, it.file || '', JSON.stringify(it.files || null), JSON.stringify(it.range || null), it.normalizedCommand || it.command || '', it.source || '', it.target || '', it.imageOutput ? 'image' : '', it.reason || ''].join('\u0000');
|
|
478
|
+
const g = groups.get(key) || { ...it, tokens: 0, count: 0 };
|
|
479
|
+
g.tokens += it.tokens;
|
|
480
|
+
g.count += it.count || 1;
|
|
481
|
+
groups.set(key, g);
|
|
482
|
+
}
|
|
483
|
+
return [...groups.values()].map((it) => ({ ...it, tokens: Math.round(it.tokens) }));
|
|
484
|
+
}
|
|
485
|
+
for (const t of userTurns) {
|
|
486
|
+
const lc = lastCompBefore(t);
|
|
487
|
+
const u = usage.get(t) || { input: 0, cached: 0 }; const C_t = u.input;
|
|
488
|
+
const reasoningSegments = reasoningSegmentsSinceCompaction(lc, t);
|
|
489
|
+
const reasoning = reasoningSegments.reduce((sum, segment) => sum + segment.tokens, 0);
|
|
490
|
+
const structureFloor = Math.round(SER_FRAC * C_t);
|
|
491
|
+
const budget = Math.max(0, C_t - overhead - reasoning - structureFloor);
|
|
492
|
+
const carried = items.filter((it) => it.turn <= t && CONTENT.has(it.kind)).slice().reverse();
|
|
493
|
+
const b = { keep_oh: overhead + structureFloor, keep_prod: 0, opt_dup: 0, opt_refind: 0, opt_dead: 0 };
|
|
494
|
+
const classifiedItems = [];
|
|
495
|
+
if (overhead + structureFloor > 0) classifiedItems.push(publicItem({ turn: t, kind: 'overhead' }, 'keep_oh', overhead + structureFloor));
|
|
496
|
+
for (const segment of reasoningSegments) {
|
|
497
|
+
const bucket = isEpisodeOutcome
|
|
498
|
+
? (segment.turn === t && t === episodeEndTurn(episodeOf(t)) ? 'keep_prod' : 'opt_refind')
|
|
499
|
+
: (segment.turn === t ? 'keep_prod' : 'opt_refind');
|
|
500
|
+
b[bucket] += segment.tokens;
|
|
501
|
+
classifiedItems.push(publicItem(isEpisodeOutcome ? { ...segment, scoringMode } : segment, bucket, segment.tokens));
|
|
502
|
+
}
|
|
503
|
+
let running = 0; const seenRead = new Set(); let seenWrite = false; const seenSameTurnTransient = new Set();
|
|
504
|
+
for (const it of carried) {
|
|
505
|
+
if (running >= budget) break;
|
|
506
|
+
const tok = Math.min(it.tokens, budget - running); running += tok;
|
|
507
|
+
let bucket = null;
|
|
508
|
+
if (isEpisodeOutcome) {
|
|
509
|
+
bucket = episodeOutcomeBucket(it, t);
|
|
510
|
+
} else {
|
|
511
|
+
if (it.kind === 'conv_user') bucket = 'keep_prod';
|
|
512
|
+
else if (it.kind === 'conv_agent') bucket = it.turn === t ? 'keep_prod' : 'opt_refind';
|
|
513
|
+
else if (it.kind === 'search' || it.kind === 'command') {
|
|
514
|
+
if (it.turn === t) {
|
|
515
|
+
const key = transientKey(it);
|
|
516
|
+
bucket = key && seenSameTurnTransient.has(key) ? 'opt_refind' : 'keep_prod';
|
|
517
|
+
if (key) seenSameTurnTransient.add(key);
|
|
518
|
+
} else {
|
|
519
|
+
bucket = 'opt_refind';
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else if (it.kind === 'image') bucket = imageBucket(it, t);
|
|
523
|
+
else if (it.kind === 'written') { if (!seenWrite) { bucket = 'keep_prod'; seenWrite = true; } else bucket = 'opt_dup'; }
|
|
524
|
+
else if (it.kind === 'read') { const tr = tierOf(it.file); if (isDupRead(it)) bucket = 'opt_dup'; else if (it.turn === t) bucket = 'keep_prod'; else if (tr === 3) bucket = 'opt_dead'; else bucket = 'keep_prod'; }
|
|
525
|
+
}
|
|
526
|
+
if (bucket) {
|
|
527
|
+
b[bucket] += tok;
|
|
528
|
+
const publicSource = isEpisodeOutcome ? { ...it, scoringMode } : it;
|
|
529
|
+
classifiedItems.push(publicItem(publicSource, bucket, tok));
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const sum = BANDS.reduce((a, x) => a + b[x], 0);
|
|
533
|
+
const residual = C_t - sum;
|
|
534
|
+
b.opt_refind += residual; // reconcile rounding / serialization that is not represented by a concrete item
|
|
535
|
+
if (residual > 0) classifiedItems.push(publicItem({ turn: t, kind: 'residual' }, 'opt_refind', residual));
|
|
536
|
+
for (const x of BANDS) agg[x] += b[x];
|
|
537
|
+
const opt = b.opt_dup + b.opt_refind + b.opt_dead;
|
|
538
|
+
const wasteItems = groupItems(classifiedItems.filter((it) => it.bucket === 'opt_dup' || it.bucket === 'opt_refind' || it.bucket === 'opt_dead'));
|
|
539
|
+
const rowOut = { turn: t, episode: episodeOf(t), commit: groundTruth.byTurn.has(t), anchorKind: groundTruth.byTurn.get(t)?.kind || null, C_t, ...b, optimizablePct: +(100 * opt / C_t).toFixed(1), wasteItems, sqlitePayload: payloadEvidenceByTurn.get(t) || { matched: false } };
|
|
540
|
+
if (debugItems) rowOut.allItems = groupItems(classifiedItems);
|
|
541
|
+
series.push(rowOut);
|
|
542
|
+
}
|
|
543
|
+
const billTot = series.reduce((a, r) => a + r.C_t, 0);
|
|
544
|
+
const share = (x) => +(100 * agg[x] / billTot).toFixed(1);
|
|
545
|
+
const optTot = agg.opt_dup + agg.opt_refind + agg.opt_dead;
|
|
546
|
+
|
|
547
|
+
console.log(`OPTIMIZABLE DETAIL — codex/${row.project} · ${row.turns} turns · ${commits.length} ${groundTruth.source} anchors · scoring=${scoringMode}\n`);
|
|
548
|
+
console.log(`=== ground-truth anchors ===`);
|
|
549
|
+
for (const c of commits) console.log(` ep${episodeOf(c)} @T${c} "${anchorMsg.get(c)}"`);
|
|
550
|
+
console.log(`\n=== overall partition of the ${(billTot / 1e6).toFixed(1)}M window ===`);
|
|
551
|
+
console.log(` KEEP overhead ${share('keep_oh')}%`);
|
|
552
|
+
console.log(` KEEP product ${share('keep_prod')}%`);
|
|
553
|
+
console.log(` ── KEEP total ${(share('keep_oh') + share('keep_prod')).toFixed(1)}%`);
|
|
554
|
+
console.log(` OPT dup (stale reread / superseded writes) ${share('opt_dup')}%`);
|
|
555
|
+
console.log(` OPT refind (re-grep / re-diff / reasoning / self) ${share('opt_refind')}%`);
|
|
556
|
+
console.log(` OPT dead (unrelated reads / piled screenshots) ${share('opt_dead')}%`);
|
|
557
|
+
console.log(` ══ OPTIMIZABLE ${(100 * optTot / billTot).toFixed(1)}%`);
|
|
558
|
+
|
|
559
|
+
const out = { session: `codex/${row.project}`, sessionId, turns: row.turns, scoringMode, scoringDescription: isEpisodeOutcome ? 'Offline scoring that compares retained items in each turn against the final/anchor turn of that item’s episode.' : 'Local-live-compatible scoring that classifies retained items from turn-local and carried-context signals.', commits: commits.map((c) => ({ turn: c, episode: episodeOf(c), message: anchorMsg.get(c), kind: groundTruth.byTurn.get(c)?.kind || 'anchor' })), groundTruth: { source: groundTruth.source, anchors: groundTruth.anchors }, Fstar: [...Fstar], debug: debugItems && isEpisodeOutcome ? { episodeOutcomes: serializeEpisodeOutcomes() } : undefined, payloadAccounting: payloadAccounting ? { source: payloadAccounting.session?.dbPath || null, status: payloadAccounting.quality?.status || null, hasMainModelPayloads: Boolean(payloadAccounting.quality?.hasMainModelPayloads), matchedTurns: payloadAccounting.session?.matchedTurns || 0, sqlitePayloadRows: payloadAccounting.session?.sqlitePayloadRows || 0, caveats: payloadAccounting.quality?.caveats || [], note: 'JSONL input_tokens remain authoritative; SQLite payload evidence is diagnostic unless main-model payload rows are present.' } : null, billTot, shares: Object.fromEntries(BANDS.map((x) => [x, share(x)])), optimizablePct: +(100 * optTot / billTot).toFixed(1), series };
|
|
560
|
+
const outputStem = isEpisodeOutcome ? `OPTIMIZABLE_DETAIL_EPISODE_OUTCOME_${proj}_${wantTurns}t` : `OPTIMIZABLE_DETAIL_${proj}_${wantTurns}t`;
|
|
561
|
+
fs.writeFileSync(path.join(ROOT, `${outputStem}.json`), JSON.stringify(out, null, 2));
|
|
562
|
+
if (process.env.GOLDEN_JSON_ONLY === '1') process.exit(0);
|
|
563
|
+
|
|
564
|
+
// ---- detailed chart, commit-message anchored ----
|
|
565
|
+
const W = 1480, H = 820, BG = '#15161a', PANEL = '#0f1014', GRID = '#2c2e36', TXT = '#9aa0ad', INK = '#f4f5f7';
|
|
566
|
+
const COL = { keep_prod: '#2E9E6B', keep_oh: '#5b606e', opt_dup: '#C23B3B', opt_refind: '#E8A23D', opt_dead: '#8A4FCF' };
|
|
567
|
+
const LBL = {
|
|
568
|
+
keep_prod: 'KEEP · current product (committed files + your spec)',
|
|
569
|
+
keep_oh: 'KEEP · overhead + serialize',
|
|
570
|
+
opt_dup: 'OPT · stale reread / superseded write versions (hold 1, not N)',
|
|
571
|
+
opt_refind: 'OPT · re-grep / re-diff / reasoning / self-talk (tool-instead-of-recall)',
|
|
572
|
+
opt_dead: 'OPT · unrelated reads + piled-up screenshots (irrelevant injection / expired view)',
|
|
573
|
+
};
|
|
574
|
+
const drawOrder = ['keep_prod', 'keep_oh', 'opt_dup', 'opt_refind', 'opt_dead']; // bottom→top: KEEP floor, then cleanable
|
|
575
|
+
const px = 64, py = 150, ph = 460, pw = W - 470;
|
|
576
|
+
const axisMax = Math.ceil(Math.max(...series.map((r) => r.C_t)) / 60000) * 60000;
|
|
577
|
+
const slot = pw / series.length, bw = slot * 0.82;
|
|
578
|
+
const svg = []; const P = (x) => svg.push(x);
|
|
579
|
+
P(`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" font-family="-apple-system,Segoe UI,Roboto,sans-serif"><rect width="${W}" height="${H}" fill="${BG}"/>`);
|
|
580
|
+
P(`<text x="40" y="36" font-size="18" font-weight="700" fill="${INK}">What's optimizable in each turn — and why — anchored to each ground truth · codex/${row.project} · ${row.turns} turns · ${scoringMode}</text>`);
|
|
581
|
+
P(`<text x="40" y="58" font-size="12.5" fill="${TXT}">Each bar = that turn's full input window (real tokens). Green/gray = the floor you must keep. Red/amber/purple = removable with a memory layer. The red line is the optimizable %.</text>`);
|
|
582
|
+
P(`<text x="40" y="76" font-size="12.5" fill="${TXT}">"Optimizable" = everything the model is carrying that is NOT the current product of its commit: copies it already had, things it re-found, things it read but never used.</text>`);
|
|
583
|
+
P(`<rect x="${px}" y="${py}" width="${pw}" height="${ph}" fill="${PANEL}" rx="4"/>`);
|
|
584
|
+
for (let v = 0; v <= axisMax; v += 60000) { const y = py + ph - v / axisMax * ph; P(`<line x1="${px}" y1="${y.toFixed(1)}" x2="${px + pw}" y2="${y.toFixed(1)}" stroke="${GRID}" stroke-width="0.6"/><text x="${px - 8}" y="${(y + 4).toFixed(1)}" font-size="10.5" fill="${TXT}" text-anchor="end">${Math.round(v / 1000)}k</text>`); }
|
|
585
|
+
// episode spans + commit message labels at the top
|
|
586
|
+
const epColors = ['#1b1d24', '#171922'];
|
|
587
|
+
let epStart = 0;
|
|
588
|
+
for (let e = 1; e <= commits.length; e++) {
|
|
589
|
+
const rows = series.map((r, i) => ({ r, i })).filter((o) => o.r.episode === e); if (!rows.length) continue;
|
|
590
|
+
const i0 = rows[0].i, i1 = rows[rows.length - 1].i;
|
|
591
|
+
const x0 = px + i0 * slot, x1 = px + (i1 + 1) * slot;
|
|
592
|
+
P(`<rect x="${x0.toFixed(1)}" y="${py}" width="${(x1 - x0).toFixed(1)}" height="${ph}" fill="${epColors[e % 2]}" opacity="0.5"/>`);
|
|
593
|
+
const cx = (x0 + x1) / 2; const msg = anchorMsg.get(commits[e - 1]) || '';
|
|
594
|
+
P(`<line x1="${x1.toFixed(1)}" y1="${py}" x2="${x1.toFixed(1)}" y2="${py + ph}" stroke="#3B6D11" stroke-width="0.9" stroke-dasharray="2,3" opacity="0.7"/>`);
|
|
595
|
+
// commit message above the span (wrap to 2 lines)
|
|
596
|
+
const words = msg.split(' '); let l1 = '', l2 = ''; for (const w of words) { if ((l1 + ' ' + w).length <= 26 || !l1) l1 += (l1 ? ' ' : '') + w; else l2 += (l2 ? ' ' : '') + w; }
|
|
597
|
+
P(`<text x="${cx.toFixed(1)}" y="${py - 30}" font-size="11" font-weight="700" fill="#9ED49B" text-anchor="middle">ep${e} · ${groundTruth.byTurn.get(commits[e - 1])?.kind || 'anchor'} @T${commits[e - 1]}</text>`);
|
|
598
|
+
P(`<text x="${cx.toFixed(1)}" y="${py - 16}" font-size="10.5" fill="${INK}" text-anchor="middle">"${l1}</text>`);
|
|
599
|
+
P(`<text x="${cx.toFixed(1)}" y="${py - 4}" font-size="10.5" fill="${INK}" text-anchor="middle">${l2 ? l2 + '"' : '"'}</text>`);
|
|
600
|
+
}
|
|
601
|
+
// compaction markers
|
|
602
|
+
for (const ct of compactionTurns) { const i = series.findIndex((r) => r.turn === ct); if (i < 0) continue; const x = px + i * slot + bw / 2; P(`<text x="${x.toFixed(1)}" y="${py + ph + 30}" font-size="12" fill="#c9c9d2" text-anchor="middle">↺</text>`); }
|
|
603
|
+
// bars
|
|
604
|
+
series.forEach((r, i) => { const x = px + i * slot; let yTop = py + ph; for (const c of drawOrder) { const h = r[c] / axisMax * ph; if (h <= 0.2) continue; yTop -= h; P(`<rect x="${x.toFixed(1)}" y="${yTop.toFixed(1)}" width="${bw.toFixed(1)}" height="${h.toFixed(1)}" fill="${COL[c]}"/>`); } });
|
|
605
|
+
// optimizable % line (0-100 on the same panel height)
|
|
606
|
+
const lp = series.map((r, i) => `${(px + i * slot + bw / 2).toFixed(1)},${(py + ph - r.optimizablePct / 100 * ph).toFixed(1)}`).join(' ');
|
|
607
|
+
P(`<polyline points="${lp}" fill="none" stroke="#ff5a5a" stroke-width="2" opacity="0.9"/>`);
|
|
608
|
+
series.forEach((r, i) => P(`<circle cx="${(px + i * slot + bw / 2).toFixed(1)}" cy="${(py + ph - r.optimizablePct / 100 * ph).toFixed(1)}" r="1.8" fill="#ff5a5a"/>`));
|
|
609
|
+
P(`<text x="${px + pw - 4}" y="${py + 14}" font-size="10" fill="#ff7a7a" text-anchor="end">optimizable % (right-read 0–100)</text>`);
|
|
610
|
+
// x labels
|
|
611
|
+
for (const tl of Array.from(new Set([1, Math.ceil(row.turns * 0.25), Math.ceil(row.turns * 0.5), Math.ceil(row.turns * 0.75), row.turns]))) { const i = series.findIndex((r) => r.turn === tl); if (i < 0) continue; const x = px + i * slot + bw / 2; P(`<text x="${x.toFixed(1)}" y="${py + ph + 16}" font-size="11" fill="${TXT}" text-anchor="middle">T${tl}</text>`); }
|
|
612
|
+
// legend
|
|
613
|
+
let ly = py + 10; const lx = px + pw + 28;
|
|
614
|
+
P(`<text x="${lx}" y="${ly - 14}" font-size="12.5" font-weight="700" fill="${INK}">bands (bottom → top)</text>`);
|
|
615
|
+
for (const c of drawOrder) { P(`<rect x="${lx}" y="${ly - 10}" width="13" height="13" rx="2" fill="${COL[c]}"/>`); const w = LBL[c].split(' '); P(`<text x="${lx + 19}" y="${ly}" font-size="10.8" fill="${c.startsWith('keep') ? '#cfd3da' : '#e7c9c9'}">${w[0]}</text>`); if (w[1]) P(`<text x="${lx + 19}" y="${ly + 13}" font-size="9.6" fill="${TXT}">${w[1]}</text>`); ly += w[1] ? 34 : 22; }
|
|
616
|
+
ly += 10;
|
|
617
|
+
P(`<text x="${lx}" y="${ly}" font-size="12.5" font-weight="700" fill="${INK}">overall — this ${(billTot / 1e6).toFixed(1)}M-token session</text>`); ly += 20;
|
|
618
|
+
const sumLines = [
|
|
619
|
+
['KEEP (floor, must carry)', (share('keep_oh') + share('keep_prod')).toFixed(1) + '%', '#9ED49B'],
|
|
620
|
+
[' · current product', share('keep_prod') + '%', '#cfd3da'],
|
|
621
|
+
[' · overhead', share('keep_oh') + '%', '#cfd3da'],
|
|
622
|
+
['OPTIMIZABLE (memory removes)', out.optimizablePct + '%', '#ff7a7a'],
|
|
623
|
+
[' · stale reread / dup writes', share('opt_dup') + '%', '#e7a3a3'],
|
|
624
|
+
[' · re-find (grep/diff/reason)', share('opt_refind') + '%', '#f0c98a'],
|
|
625
|
+
[' · dead / unrelated / view', share('opt_dead') + '%', '#c4a3e8'],
|
|
626
|
+
];
|
|
627
|
+
for (const [n, v, col] of sumLines) { P(`<text x="${lx}" y="${ly}" font-size="11.5" fill="${TXT}" font-family="ui-monospace,Menlo,monospace">${n}</text>`); P(`<text x="${lx + 248}" y="${ly}" font-size="11.5" font-weight="700" fill="${col}" text-anchor="end" font-family="ui-monospace,Menlo,monospace">${v}</text>`); ly += 19; }
|
|
628
|
+
ly += 14;
|
|
629
|
+
P(`<text x="${lx}" y="${ly}" font-size="11" font-weight="700" fill="${INK}">why each red band is removable</text>`); ly += 17;
|
|
630
|
+
for (const t of ['dup → you already had that exact copy', 'refind → you searched/diffed what you', ' already saw; recall ≠ re-dump', 'dead → read once for orientation, never', ' used to build this commit', '', 'enemy = RE-FEED: carry 220k to produce', '~2k of product sourced from a 400-tok spec']) { P(`<text x="${lx}" y="${ly}" font-size="10" fill="${TXT}">${t}</text>`); ly += 14; }
|
|
631
|
+
P('</svg>');
|
|
632
|
+
fs.writeFileSync(path.join(ROOT, `${outputStem}.svg`), svg.join('\n'));
|
|
633
|
+
console.log(`\nwrote ${outputStem}.json + .svg`);
|