sigrank 0.16.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -77
- package/adapters.mjs +493 -238
- package/badges.mjs +157 -56
- package/cascade.mjs +132 -70
- package/cli.mjs +1311 -893
- package/connect.mjs +2 -2
- package/index.mjs +78 -59
- package/keystore.mjs +64 -44
- package/narrate.mjs +34 -22
- package/package.json +1 -1
- package/preflight.mjs +101 -29
- package/sign.mjs +46 -27
- package/submit.mjs +137 -71
- package/tokenpull.mjs +325 -188
- package/tools.mjs +1702 -951
- package/tui.mjs +1860 -1081
package/tools.mjs
CHANGED
|
@@ -8,24 +8,34 @@
|
|
|
8
8
|
* narration card in ./narrate.mjs. Token-only, no transcript content.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
import {
|
|
12
|
+
cascade,
|
|
13
|
+
parsePillars,
|
|
14
|
+
detectMode,
|
|
15
|
+
qualityScore,
|
|
16
|
+
MODE_EXPECTED_YIELD,
|
|
17
|
+
} from "./cascade.mjs";
|
|
18
|
+
import { computeBadges } from "./badges.mjs";
|
|
19
|
+
import { narrate } from "./narrate.mjs";
|
|
20
|
+
import {
|
|
21
|
+
tokenpull as pullLocal,
|
|
22
|
+
tokenpullCodex as pullCodex,
|
|
23
|
+
tokenpullAny,
|
|
24
|
+
} from "./tokenpull.mjs";
|
|
25
|
+
import { ALL_PLATFORMS } from "./adapters.mjs";
|
|
26
|
+
import { ensureIdentity, recordEnrollment } from "./keystore.mjs";
|
|
27
|
+
import { submitSignedWindow } from "./submit.mjs";
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
29
|
+
import { execFile } from "node:child_process";
|
|
30
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
31
|
+
import os from "node:os";
|
|
32
|
+
import path from "node:path";
|
|
33
|
+
import { fileURLToPath } from "node:url";
|
|
24
34
|
|
|
25
35
|
// Resolve local node_modules/.bin for bundled deps (ccusage, tokscale)
|
|
26
|
-
const _pkgRoot = path.dirname(fileURLToPath(import.meta.url))
|
|
27
|
-
const _localBin = path.join(_pkgRoot,
|
|
28
|
-
const _envPath = `${_localBin}${process.env.PATH ?
|
|
36
|
+
const _pkgRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const _localBin = path.join(_pkgRoot, "node_modules", ".bin");
|
|
38
|
+
const _envPath = `${_localBin}${process.env.PATH ? ":" + process.env.PATH : ""}`;
|
|
29
39
|
|
|
30
40
|
// ASYNC FIX (2026-06-27): execFile wrapped in a Promise — replaces execSync for
|
|
31
41
|
// defense-in-depth (shell injection prevention + non-blocking). The platform param
|
|
@@ -37,11 +47,20 @@ function execFileAsync(cmd, args, timeoutMs) {
|
|
|
37
47
|
return new Promise((resolve, reject) => {
|
|
38
48
|
// NOTE: execFile does not accept a `stdio` option (it always pipes + buffers
|
|
39
49
|
// stdout/stderr against maxBuffer) — a previous `stdio` key here was silently ignored.
|
|
40
|
-
execFile(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
execFile(
|
|
51
|
+
cmd,
|
|
52
|
+
args,
|
|
53
|
+
{
|
|
54
|
+
timeout: timeoutMs,
|
|
55
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
56
|
+
env: { ...process.env, PATH: _envPath },
|
|
57
|
+
},
|
|
58
|
+
(err, stdout) => {
|
|
59
|
+
if (err) reject(err);
|
|
60
|
+
else resolve(stdout.toString());
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
});
|
|
45
64
|
}
|
|
46
65
|
|
|
47
66
|
// ── Verifier readers (sync, on-device, token-only) ────────────────────────────
|
|
@@ -53,90 +72,156 @@ function execFileAsync(cmd, args, timeoutMs) {
|
|
|
53
72
|
// (bunx tokscale, scan+read tokendash) for the TUI/CLI dashboard. Different data sources =
|
|
54
73
|
// different behavior; do NOT merge without understanding the trade-off.
|
|
55
74
|
|
|
56
|
-
async function _ccusagePillars(platform =
|
|
75
|
+
async function _ccusagePillars(platform = "claude") {
|
|
57
76
|
try {
|
|
58
|
-
const raw = await execFileAsync(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
const raw = await execFileAsync(
|
|
78
|
+
"ccusage",
|
|
79
|
+
[platform, "daily", "--json"],
|
|
80
|
+
15000,
|
|
81
|
+
);
|
|
82
|
+
const rows = JSON.parse(raw)?.daily ?? JSON.parse(raw);
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const result = {};
|
|
85
|
+
for (const [win, days] of Object.entries({
|
|
86
|
+
"7d": 7,
|
|
87
|
+
"30d": 30,
|
|
88
|
+
"90d": 90,
|
|
89
|
+
})) {
|
|
90
|
+
const since = new Date(now - days * 86400000);
|
|
91
|
+
let i = 0,
|
|
92
|
+
o = 0,
|
|
93
|
+
cw = 0,
|
|
94
|
+
cr = 0;
|
|
65
95
|
for (const r of rows) {
|
|
66
|
-
if (new Date(r.date ?? r.day ??
|
|
67
|
-
i
|
|
68
|
-
o
|
|
69
|
-
cw += r.cacheCreationTokens ?? r.cache_create_tokens
|
|
70
|
-
cr += r.cacheReadTokens
|
|
96
|
+
if (new Date(r.date ?? r.day ?? "1970") >= since) {
|
|
97
|
+
i += r.inputTokens ?? r.input_tokens ?? 0;
|
|
98
|
+
o += r.outputTokens ?? r.output_tokens ?? 0;
|
|
99
|
+
cw += r.cacheCreationTokens ?? r.cache_create_tokens ?? 0;
|
|
100
|
+
cr += r.cacheReadTokens ?? r.cache_read_tokens ?? 0;
|
|
71
101
|
}
|
|
72
102
|
}
|
|
73
|
-
result[win] = { input: i, output: o, cacheCreate: cw, cacheRead: cr }
|
|
103
|
+
result[win] = { input: i, output: o, cacheCreate: cw, cacheRead: cr };
|
|
74
104
|
}
|
|
75
|
-
let i=0,
|
|
105
|
+
let i = 0,
|
|
106
|
+
o = 0,
|
|
107
|
+
cw = 0,
|
|
108
|
+
cr = 0;
|
|
76
109
|
for (const r of rows) {
|
|
77
|
-
i
|
|
78
|
-
o
|
|
79
|
-
cw += r.cacheCreationTokens ?? r.cache_create_tokens
|
|
80
|
-
cr += r.cacheReadTokens
|
|
110
|
+
i += r.inputTokens ?? r.input_tokens ?? 0;
|
|
111
|
+
o += r.outputTokens ?? r.output_tokens ?? 0;
|
|
112
|
+
cw += r.cacheCreationTokens ?? r.cache_create_tokens ?? 0;
|
|
113
|
+
cr += r.cacheReadTokens ?? r.cache_read_tokens ?? 0;
|
|
81
114
|
}
|
|
82
|
-
result[
|
|
83
|
-
return result
|
|
84
|
-
} catch {
|
|
115
|
+
result["all"] = { input: i, output: o, cacheCreate: cw, cacheRead: cr };
|
|
116
|
+
return result;
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
85
120
|
}
|
|
86
121
|
|
|
87
122
|
async function _tokenDashPillars() {
|
|
88
|
-
const dbPath = path.join(os.homedir(),
|
|
89
|
-
if (!existsSync(dbPath)) return null
|
|
123
|
+
const dbPath = path.join(os.homedir(), ".claude", "token-dashboard.db");
|
|
124
|
+
if (!existsSync(dbPath)) return null;
|
|
90
125
|
try {
|
|
91
|
-
const raw = await execFileAsync(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
126
|
+
const raw = await execFileAsync(
|
|
127
|
+
"sqlite3",
|
|
128
|
+
[
|
|
129
|
+
dbPath,
|
|
130
|
+
"SELECT SUM(input_tokens),SUM(output_tokens),SUM(cache_create_5m_tokens)+SUM(cache_create_1h_tokens),SUM(cache_read_tokens) FROM messages",
|
|
131
|
+
],
|
|
132
|
+
5000,
|
|
133
|
+
);
|
|
134
|
+
const [i, o, cw, cr] = raw.trim().split("|").map(Number);
|
|
135
|
+
return {
|
|
136
|
+
all: {
|
|
137
|
+
input: i || 0,
|
|
138
|
+
output: o || 0,
|
|
139
|
+
cacheCreate: cw || 0,
|
|
140
|
+
cacheRead: cr || 0,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
97
146
|
}
|
|
98
147
|
|
|
99
|
-
async function _tokscalePillars(platform =
|
|
148
|
+
async function _tokscalePillars(platform = "claude") {
|
|
100
149
|
// Try the bundled tokscale CLI first (fresh data), fall back to saved report file.
|
|
101
150
|
try {
|
|
102
|
-
const raw = await execFileAsync(
|
|
103
|
-
const data = JSON.parse(raw)
|
|
104
|
-
const entries = Array.isArray(data?.entries)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
151
|
+
const raw = await execFileAsync("tokscale", ["models", "--json"], 60000);
|
|
152
|
+
const data = JSON.parse(raw);
|
|
153
|
+
const entries = Array.isArray(data?.entries)
|
|
154
|
+
? data.entries
|
|
155
|
+
: Array.isArray(data)
|
|
156
|
+
? data
|
|
157
|
+
: [];
|
|
158
|
+
const rows = entries.filter(
|
|
159
|
+
(e) =>
|
|
160
|
+
e &&
|
|
161
|
+
e.client === platform &&
|
|
162
|
+
e.model !== "<synthetic>" &&
|
|
163
|
+
e.model !== "unknown" &&
|
|
164
|
+
((Number(e.input) || 0) > 0 || (Number(e.output) || 0) > 0),
|
|
165
|
+
);
|
|
108
166
|
if (rows.length) {
|
|
109
|
-
const acc = rows.reduce(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
167
|
+
const acc = rows.reduce(
|
|
168
|
+
(a, e) => ({
|
|
169
|
+
input: a.input + (Number(e.input) || 0),
|
|
170
|
+
output: a.output + (Number(e.output) || 0),
|
|
171
|
+
cacheCreate: a.cacheCreate + (Number(e.cacheWrite) || 0),
|
|
172
|
+
cacheRead: a.cacheRead + (Number(e.cacheRead) || 0),
|
|
173
|
+
}),
|
|
174
|
+
{ input: 0, output: 0, cacheCreate: 0, cacheRead: 0 },
|
|
175
|
+
);
|
|
176
|
+
return { all: acc };
|
|
114
177
|
}
|
|
115
|
-
} catch {
|
|
178
|
+
} catch {
|
|
179
|
+
/* fall through to file-based read */
|
|
180
|
+
}
|
|
116
181
|
// Fallback: read saved tokscale_report.json if it exists
|
|
117
|
-
const p = path.join(os.homedir(),
|
|
118
|
-
if (!existsSync(p)) return null
|
|
182
|
+
const p = path.join(os.homedir(), "tokscale_report.json");
|
|
183
|
+
if (!existsSync(p)) return null;
|
|
119
184
|
try {
|
|
120
|
-
const data = JSON.parse(readFileSync(p,
|
|
121
|
-
const rows = (data.entries ?? []).filter(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
185
|
+
const data = JSON.parse(readFileSync(p, "utf8"));
|
|
186
|
+
const rows = (data.entries ?? []).filter(
|
|
187
|
+
(e) =>
|
|
188
|
+
e.client === platform &&
|
|
189
|
+
e.model !== "<synthetic>" &&
|
|
190
|
+
e.model !== "unknown" &&
|
|
191
|
+
(e.input > 0 || e.output > 0),
|
|
192
|
+
);
|
|
193
|
+
if (!rows.length) return null;
|
|
194
|
+
const acc = rows.reduce(
|
|
195
|
+
(a, e) => ({
|
|
196
|
+
input: a.input + (e.input ?? 0),
|
|
197
|
+
output: a.output + (e.output ?? 0),
|
|
198
|
+
cacheCreate: a.cacheCreate + (e.cacheWrite ?? 0),
|
|
199
|
+
cacheRead: a.cacheRead + (e.cacheRead ?? 0),
|
|
200
|
+
}),
|
|
201
|
+
{ input: 0, output: 0, cacheCreate: 0, cacheRead: 0 },
|
|
202
|
+
);
|
|
203
|
+
return { all: acc };
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
131
207
|
}
|
|
132
208
|
|
|
133
209
|
// Every board upload from the MCP is hashed + timestamped (ddmmyy) — provenance + dedup.
|
|
134
210
|
function uploadStamp(content) {
|
|
135
|
-
const hash = createHash(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
211
|
+
const hash = createHash("sha256")
|
|
212
|
+
.update(JSON.stringify(content))
|
|
213
|
+
.digest("hex");
|
|
214
|
+
const d = new Date();
|
|
215
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
216
|
+
const ddmmyy =
|
|
217
|
+
p(d.getUTCDate()) +
|
|
218
|
+
p(d.getUTCMonth() + 1) +
|
|
219
|
+
String(d.getUTCFullYear()).slice(-2);
|
|
220
|
+
return {
|
|
221
|
+
content_hash: hash,
|
|
222
|
+
submitted_ddmmyy: ddmmyy,
|
|
223
|
+
submitted_at: d.toISOString(),
|
|
224
|
+
};
|
|
140
225
|
}
|
|
141
226
|
|
|
142
227
|
// Pull a platform's local usage → 4 windows of canonical pillars. Routes through
|
|
@@ -145,26 +230,31 @@ function uploadStamp(content) {
|
|
|
145
230
|
async function pullByPlatform(platform, opts = {}) {
|
|
146
231
|
if (opts.adapter) {
|
|
147
232
|
// Test injection: bypass registry and use the mock adapter directly
|
|
148
|
-
if (platform ===
|
|
149
|
-
let ioRatio = 2.0
|
|
233
|
+
if (platform === "codex") {
|
|
234
|
+
let ioRatio = 2.0;
|
|
150
235
|
try {
|
|
151
|
-
const c = await pullLocal({})
|
|
152
|
-
const all = c.windows.find((w) => w.window ===
|
|
153
|
-
if (all && all.pillars.output > 0)
|
|
154
|
-
|
|
155
|
-
|
|
236
|
+
const c = await pullLocal({});
|
|
237
|
+
const all = c.windows.find((w) => w.window === "all");
|
|
238
|
+
if (all && all.pillars.output > 0)
|
|
239
|
+
ioRatio = all.pillars.input / all.pillars.output;
|
|
240
|
+
} catch {
|
|
241
|
+
/* no Claude data → Alpha 2.0 */
|
|
242
|
+
}
|
|
243
|
+
return pullCodex({ ioRatio, adapter: opts.adapter, now: opts.now });
|
|
156
244
|
}
|
|
157
|
-
return pullLocal({ adapter: opts.adapter, now: opts.now })
|
|
245
|
+
return pullLocal({ adapter: opts.adapter, now: opts.now });
|
|
158
246
|
}
|
|
159
|
-
return tokenpullAny(platform ||
|
|
247
|
+
return tokenpullAny(platform || "claude", opts);
|
|
160
248
|
}
|
|
161
249
|
|
|
162
|
-
export const DEFAULT_API_BASE =
|
|
250
|
+
export const DEFAULT_API_BASE =
|
|
251
|
+
process.env.SIGRANK_API_BASE || "https://signalaf.com";
|
|
163
252
|
/** Default network timeout in ms (override via opts.fetchTimeout or SIGRANK_FETCH_TIMEOUT). */
|
|
164
|
-
export const DEFAULT_FETCH_TIMEOUT =
|
|
253
|
+
export const DEFAULT_FETCH_TIMEOUT =
|
|
254
|
+
Number(process.env.SIGRANK_FETCH_TIMEOUT) || 10_000;
|
|
165
255
|
/** Max accepted length for a single paste/string arg (chars). Token counts are tiny; anything
|
|
166
256
|
* past this is malformed or abusive — reject cleanly before parsing/POSTing (E2 hardening). */
|
|
167
|
-
const MAX_INPUT = 1_000_000
|
|
257
|
+
const MAX_INPUT = 1_000_000;
|
|
168
258
|
|
|
169
259
|
// ── Smithery quality: output schemas + annotations ──────────────────────────
|
|
170
260
|
// MCP tool annotations hint to clients about side-effects, read-only status, etc.
|
|
@@ -173,349 +263,490 @@ const ANNOTATIONS = {
|
|
|
173
263
|
destructiveHint: { destructiveHint: false },
|
|
174
264
|
idempotentHint: { idempotentHint: true },
|
|
175
265
|
openWorldHint: { openWorldHint: false },
|
|
176
|
-
}
|
|
266
|
+
};
|
|
177
267
|
|
|
178
268
|
// Common output schema for cascade results (rank_paste, simulate_change, etc.)
|
|
179
269
|
const CASCADE_OUTPUT = {
|
|
180
|
-
type:
|
|
270
|
+
type: "object",
|
|
181
271
|
properties: {
|
|
182
|
-
yield_: {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
272
|
+
yield_: {
|
|
273
|
+
type: "number",
|
|
274
|
+
description:
|
|
275
|
+
"Υ Yield — the headline efficiency metric (Cache Reads × Output / Input²)",
|
|
276
|
+
},
|
|
277
|
+
snr: { type: "number", description: "Signal-to-noise ratio" },
|
|
278
|
+
leverage: {
|
|
279
|
+
type: "number",
|
|
280
|
+
description: "Cr/I — cache reads divided by input",
|
|
281
|
+
},
|
|
282
|
+
velocity: { type: "number", description: "O/I — output divided by input" },
|
|
283
|
+
tenx_dev: { type: "number", description: "10xDEV score" },
|
|
284
|
+
class: {
|
|
285
|
+
type: "string",
|
|
286
|
+
enum: ["Burner", "Builder", "10xer"],
|
|
287
|
+
description: "Operator class tier",
|
|
288
|
+
},
|
|
289
|
+
card: {
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "Deterministic prose summary of the cascade result",
|
|
292
|
+
},
|
|
293
|
+
warnings: {
|
|
294
|
+
type: "array",
|
|
295
|
+
items: { type: "string" },
|
|
296
|
+
description: "Parse or data warnings if any",
|
|
297
|
+
},
|
|
190
298
|
},
|
|
191
|
-
required: [
|
|
192
|
-
}
|
|
299
|
+
required: ["yield_", "class"],
|
|
300
|
+
};
|
|
193
301
|
|
|
194
302
|
const LEADERBOARD_OUTPUT = {
|
|
195
|
-
type:
|
|
303
|
+
type: "object",
|
|
196
304
|
properties: {
|
|
197
305
|
operators: {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
306
|
+
type: "array",
|
|
307
|
+
description: "Array of ranked operators sorted by yield",
|
|
308
|
+
items: {
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
codename: { type: "string", description: "Public display name" },
|
|
312
|
+
yield_: { type: "number", description: "Υ Yield metric" },
|
|
313
|
+
leverage: { type: "number", description: "Cr/I ratio" },
|
|
314
|
+
velocity: { type: "number", description: "O/I ratio" },
|
|
315
|
+
class: { type: "string", enum: ["Burner", "Builder", "10xer"] },
|
|
316
|
+
rank: { type: "integer", description: "1-based rank position" },
|
|
317
|
+
},
|
|
318
|
+
},
|
|
211
319
|
},
|
|
212
320
|
},
|
|
213
|
-
}
|
|
321
|
+
};
|
|
214
322
|
|
|
215
323
|
const OPERATOR_OUTPUT = {
|
|
216
|
-
type:
|
|
324
|
+
type: "object",
|
|
217
325
|
properties: {
|
|
218
|
-
codename: { type:
|
|
219
|
-
yield_: { type:
|
|
220
|
-
leverage: { type:
|
|
221
|
-
velocity: { type:
|
|
222
|
-
class: { type:
|
|
223
|
-
rank: { type:
|
|
326
|
+
codename: { type: "string", description: "Operator display name" },
|
|
327
|
+
yield_: { type: "number", description: "Υ Yield metric" },
|
|
328
|
+
leverage: { type: "number", description: "Cr/I ratio" },
|
|
329
|
+
velocity: { type: "number", description: "O/I ratio" },
|
|
330
|
+
class: { type: "string", enum: ["Burner", "Builder", "10xer"] },
|
|
331
|
+
rank: { type: "integer", description: "1-based rank position" },
|
|
224
332
|
windows: {
|
|
225
|
-
type:
|
|
226
|
-
description:
|
|
333
|
+
type: "array",
|
|
334
|
+
description: "Per-window breakdowns (7d, 30d, 90d, all-time)",
|
|
227
335
|
items: {
|
|
228
|
-
type:
|
|
336
|
+
type: "object",
|
|
229
337
|
properties: {
|
|
230
|
-
window: { type:
|
|
338
|
+
window: { type: "string", enum: ["7d", "30d", "90d", "all_time"] },
|
|
231
339
|
pillars: {
|
|
232
|
-
type:
|
|
340
|
+
type: "object",
|
|
233
341
|
properties: {
|
|
234
|
-
input: { type:
|
|
235
|
-
output: { type:
|
|
236
|
-
cacheCreate: { type:
|
|
237
|
-
cacheRead: { type:
|
|
342
|
+
input: { type: "integer" },
|
|
343
|
+
output: { type: "integer" },
|
|
344
|
+
cacheCreate: { type: "integer" },
|
|
345
|
+
cacheRead: { type: "integer" },
|
|
238
346
|
},
|
|
239
347
|
},
|
|
240
348
|
},
|
|
241
349
|
},
|
|
242
350
|
},
|
|
243
351
|
},
|
|
244
|
-
}
|
|
352
|
+
};
|
|
245
353
|
|
|
246
354
|
const SUBMIT_OUTPUT = {
|
|
247
|
-
type:
|
|
355
|
+
type: "object",
|
|
248
356
|
properties: {
|
|
249
|
-
status: {
|
|
357
|
+
status: {
|
|
358
|
+
type: "string",
|
|
359
|
+
enum: ["ok", "error", "skipped"],
|
|
360
|
+
description: "Submission status",
|
|
361
|
+
},
|
|
250
362
|
preview: CASCADE_OUTPUT,
|
|
251
|
-
server_response: {
|
|
252
|
-
|
|
363
|
+
server_response: {
|
|
364
|
+
type: "object",
|
|
365
|
+
description: "Server-side response including new rank if accepted",
|
|
366
|
+
},
|
|
367
|
+
reason: {
|
|
368
|
+
type: "string",
|
|
369
|
+
description: "Error or skip reason if status is not ok",
|
|
370
|
+
},
|
|
253
371
|
},
|
|
254
|
-
}
|
|
372
|
+
};
|
|
255
373
|
|
|
256
374
|
const TOKENPULL_OUTPUT = {
|
|
257
|
-
type:
|
|
375
|
+
type: "object",
|
|
258
376
|
properties: {
|
|
259
|
-
platform: { type:
|
|
260
|
-
generatedAt: { type:
|
|
377
|
+
platform: { type: "string", description: "Source platform name" },
|
|
378
|
+
generatedAt: { type: "string", description: "ISO timestamp of the pull" },
|
|
261
379
|
windows: {
|
|
262
|
-
type:
|
|
263
|
-
description:
|
|
380
|
+
type: "array",
|
|
381
|
+
description: "Per-window token usage + cascade results",
|
|
264
382
|
items: {
|
|
265
|
-
type:
|
|
383
|
+
type: "object",
|
|
266
384
|
properties: {
|
|
267
|
-
window: { type:
|
|
385
|
+
window: { type: "string", enum: ["7d", "30d", "90d", "all"] },
|
|
268
386
|
pillars: {
|
|
269
|
-
type:
|
|
387
|
+
type: "object",
|
|
270
388
|
properties: {
|
|
271
|
-
input: { type:
|
|
272
|
-
output: { type:
|
|
273
|
-
cacheCreate: { type:
|
|
274
|
-
cacheRead: { type:
|
|
389
|
+
input: { type: "integer" },
|
|
390
|
+
output: { type: "integer" },
|
|
391
|
+
cacheCreate: { type: "integer" },
|
|
392
|
+
cacheRead: { type: "integer" },
|
|
275
393
|
},
|
|
276
394
|
},
|
|
277
|
-
messages: {
|
|
278
|
-
|
|
395
|
+
messages: {
|
|
396
|
+
type: "integer",
|
|
397
|
+
description: "Number of messages in window",
|
|
398
|
+
},
|
|
399
|
+
estimated: {
|
|
400
|
+
type: "boolean",
|
|
401
|
+
description: "True if cacheCreate was estimated",
|
|
402
|
+
},
|
|
279
403
|
cascade: CASCADE_OUTPUT,
|
|
280
404
|
},
|
|
281
405
|
},
|
|
282
406
|
},
|
|
283
407
|
},
|
|
284
|
-
}
|
|
408
|
+
};
|
|
285
409
|
|
|
286
410
|
const ENROLL_OUTPUT = {
|
|
287
|
-
type:
|
|
411
|
+
type: "object",
|
|
288
412
|
properties: {
|
|
289
|
-
status: {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
413
|
+
status: {
|
|
414
|
+
type: "string",
|
|
415
|
+
enum: ["enrolled", "error"],
|
|
416
|
+
description: "Enrollment result",
|
|
417
|
+
},
|
|
418
|
+
codename: { type: "string", description: "Operator codename if enrolled" },
|
|
419
|
+
operator_id: { type: "string", description: "Operator ID if enrolled" },
|
|
420
|
+
device_id: { type: "string", description: "Local device ID" },
|
|
421
|
+
trust_status: { type: "string", description: "Trust level of the device" },
|
|
422
|
+
reason: { type: "string", description: "Error reason if status is error" },
|
|
295
423
|
},
|
|
296
|
-
}
|
|
424
|
+
};
|
|
297
425
|
|
|
298
426
|
const COMPARE_OUTPUT = {
|
|
299
|
-
type:
|
|
427
|
+
type: "object",
|
|
300
428
|
properties: {
|
|
301
|
-
platform: { type:
|
|
429
|
+
platform: { type: "string" },
|
|
302
430
|
sources: {
|
|
303
|
-
type:
|
|
304
|
-
description:
|
|
431
|
+
type: "array",
|
|
432
|
+
description: "Side-by-side comparison of each token source",
|
|
305
433
|
items: {
|
|
306
|
-
type:
|
|
434
|
+
type: "object",
|
|
307
435
|
properties: {
|
|
308
|
-
source: {
|
|
309
|
-
|
|
436
|
+
source: {
|
|
437
|
+
type: "string",
|
|
438
|
+
description:
|
|
439
|
+
"Source name (tokenpull, ccusage, token-dash, tokscale)",
|
|
440
|
+
},
|
|
441
|
+
pillars: { type: "object" },
|
|
310
442
|
cascade: CASCADE_OUTPUT,
|
|
311
|
-
delta_pct: {
|
|
443
|
+
delta_pct: {
|
|
444
|
+
type: "object",
|
|
445
|
+
description: "Delta % vs tokenpull baseline",
|
|
446
|
+
},
|
|
312
447
|
},
|
|
313
448
|
},
|
|
314
449
|
},
|
|
315
450
|
},
|
|
316
|
-
}
|
|
451
|
+
};
|
|
317
452
|
|
|
318
453
|
const SIMULATE_OUTPUT = {
|
|
319
|
-
type:
|
|
454
|
+
type: "object",
|
|
320
455
|
properties: {
|
|
321
456
|
current: CASCADE_OUTPUT,
|
|
322
457
|
simulated: CASCADE_OUTPUT,
|
|
323
|
-
yield_delta: {
|
|
324
|
-
|
|
325
|
-
|
|
458
|
+
yield_delta: {
|
|
459
|
+
type: "number",
|
|
460
|
+
description: "Υ Yield change (simulated - current)",
|
|
461
|
+
},
|
|
462
|
+
class_change: {
|
|
463
|
+
type: "string",
|
|
464
|
+
description: "Class tier change description",
|
|
465
|
+
},
|
|
466
|
+
metric_diffs: {
|
|
467
|
+
type: "object",
|
|
468
|
+
description: "Per-metric before/after diffs",
|
|
469
|
+
},
|
|
326
470
|
},
|
|
327
|
-
}
|
|
471
|
+
};
|
|
328
472
|
|
|
329
473
|
const DIAGNOSE_OUTPUT = {
|
|
330
|
-
type:
|
|
474
|
+
type: "object",
|
|
331
475
|
properties: {
|
|
332
|
-
pillars: { type:
|
|
476
|
+
pillars: { type: "object", description: "The 4 raw token pillars" },
|
|
333
477
|
cascade: CASCADE_OUTPUT,
|
|
334
478
|
diagnosis: {
|
|
335
|
-
type:
|
|
336
|
-
description:
|
|
479
|
+
type: "array",
|
|
480
|
+
description: "Ranked list of efficiency leaks found, worst first",
|
|
337
481
|
items: {
|
|
338
|
-
type:
|
|
482
|
+
type: "object",
|
|
339
483
|
properties: {
|
|
340
|
-
metric: {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
484
|
+
metric: {
|
|
485
|
+
type: "string",
|
|
486
|
+
description: "Which metric is underperforming",
|
|
487
|
+
},
|
|
488
|
+
severity: {
|
|
489
|
+
type: "string",
|
|
490
|
+
enum: ["critical", "warning", "info"],
|
|
491
|
+
description: "How bad the leak is",
|
|
492
|
+
},
|
|
493
|
+
finding: { type: "string", description: "What the analysis found" },
|
|
494
|
+
recommendation: {
|
|
495
|
+
type: "string",
|
|
496
|
+
description: "What to do about it",
|
|
497
|
+
},
|
|
498
|
+
estimated_yield_impact: {
|
|
499
|
+
type: "string",
|
|
500
|
+
description: "Estimated Υ improvement if fixed",
|
|
501
|
+
},
|
|
345
502
|
},
|
|
346
503
|
},
|
|
347
504
|
},
|
|
348
|
-
summary: {
|
|
505
|
+
summary: {
|
|
506
|
+
type: "string",
|
|
507
|
+
description: "One-line summary of the operator's cascade health",
|
|
508
|
+
},
|
|
349
509
|
},
|
|
350
|
-
}
|
|
510
|
+
};
|
|
351
511
|
|
|
352
512
|
const SUGGEST_OUTPUT = {
|
|
353
|
-
type:
|
|
513
|
+
type: "object",
|
|
354
514
|
properties: {
|
|
355
515
|
suggestions: {
|
|
356
|
-
type:
|
|
357
|
-
description:
|
|
516
|
+
type: "array",
|
|
517
|
+
description: "Ranked recommendations, highest Υ impact first",
|
|
358
518
|
items: {
|
|
359
|
-
type:
|
|
519
|
+
type: "object",
|
|
360
520
|
properties: {
|
|
361
|
-
rank: { type:
|
|
362
|
-
action: { type:
|
|
363
|
-
pillar: { type:
|
|
364
|
-
delta: { type:
|
|
365
|
-
simulated_yield: {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
521
|
+
rank: { type: "number", description: "1-based rank by Υ impact" },
|
|
522
|
+
action: { type: "string", description: "What to change" },
|
|
523
|
+
pillar: { type: "string", description: "Which pillar to adjust" },
|
|
524
|
+
delta: { type: "string", description: "How much to change it" },
|
|
525
|
+
simulated_yield: {
|
|
526
|
+
type: "number",
|
|
527
|
+
description: "Projected Υ after the change",
|
|
528
|
+
},
|
|
529
|
+
yield_delta: { type: "number", description: "Υ change vs current" },
|
|
530
|
+
class_after: {
|
|
531
|
+
type: "string",
|
|
532
|
+
description: "Projected class after the change",
|
|
533
|
+
},
|
|
534
|
+
rationale: { type: "string", description: "Why this helps" },
|
|
369
535
|
},
|
|
370
536
|
},
|
|
371
537
|
},
|
|
372
|
-
current_yield: {
|
|
373
|
-
|
|
374
|
-
|
|
538
|
+
current_yield: {
|
|
539
|
+
type: "number",
|
|
540
|
+
description: "Current Υ before any changes",
|
|
541
|
+
},
|
|
542
|
+
current_class: { type: "string", description: "Current class tier" },
|
|
543
|
+
best_single_change: {
|
|
544
|
+
type: "string",
|
|
545
|
+
description: "The single highest-impact change",
|
|
546
|
+
},
|
|
375
547
|
},
|
|
376
|
-
}
|
|
548
|
+
};
|
|
377
549
|
|
|
378
550
|
export const TOOLS = [
|
|
379
551
|
{
|
|
380
|
-
name:
|
|
552
|
+
name: "rank_paste",
|
|
381
553
|
description:
|
|
382
554
|
'Computes the SigRank yield cascade from a paste of token counts. Parses the input, runs the full cascade math locally (no network calls), and returns: Υ Yield (the headline efficiency metric, Υ = Cache Reads × Output / Input²), SNR (signal-to-noise ratio), Leverage ratio (Cr/I = cache reads divided by input), Velocity (O/I = output divided by input), 10xDEV score, operator class tier (Burner / Builder / 10xer), and a deterministic prose "card" summarizing the result in plain English. Accepts two input formats: (1) JSON object {"input":N,"output":N,"cacheCreate":N,"cacheRead":N} or (2) four whitespace-separated numbers in order: input output cacheCreate cacheRead. Returns an error if the input is malformed or has negative values. Use this for a quick one-off ranking without submitting to the board. Do NOT use this to submit your score — use submit_paste instead, which both ranks and publishes. Do NOT use this if you want to rank all four time windows at once — use rank_windows for that. After calling this, use submit_paste to publish the result if you want to appear on the leaderboard.',
|
|
383
|
-
annotations: {
|
|
555
|
+
annotations: {
|
|
556
|
+
...ANNOTATIONS.readOnlyHint,
|
|
557
|
+
...ANNOTATIONS.destructiveHint,
|
|
558
|
+
...ANNOTATIONS.idempotentHint,
|
|
559
|
+
...ANNOTATIONS.openWorldHint,
|
|
560
|
+
},
|
|
384
561
|
inputSchema: {
|
|
385
|
-
type:
|
|
562
|
+
type: "object",
|
|
386
563
|
properties: {
|
|
387
564
|
text: {
|
|
388
|
-
type:
|
|
389
|
-
description:
|
|
565
|
+
type: "string",
|
|
566
|
+
description:
|
|
567
|
+
'Token counts to rank. Two formats accepted: (1) JSON object {"input":N,"output":N,"cacheCreate":N,"cacheRead":N} where all values are non-negative integers, or (2) four whitespace-separated numbers in order: input output cacheCreate cacheRead. Get these from `ccusage` output, the Claude Max usage dashboard, tokscale, or any token reader. Example valid input: {"input":1000000,"output":500000,"cacheCreate":50000,"cacheRead":800000}',
|
|
390
568
|
},
|
|
391
569
|
},
|
|
392
|
-
required: [
|
|
393
|
-
description:
|
|
570
|
+
required: ["text"],
|
|
571
|
+
description:
|
|
572
|
+
"Requires the token counts as a string. No other parameters are accepted.",
|
|
394
573
|
},
|
|
395
574
|
outputSchema: CASCADE_OUTPUT,
|
|
396
575
|
},
|
|
397
576
|
{
|
|
398
|
-
name:
|
|
577
|
+
name: "get_leaderboard",
|
|
399
578
|
description:
|
|
400
579
|
"Fetches the live public SigRank leaderboard from signalaf.com. Reads all ranked operators sorted by yield (Υ = Cache Reads × Output / Input²) and returns an array of operator summaries. Each entry contains: codename (public display name), yield (Υ, the headline efficiency metric), leverage ratio (Cr/I = cache reads divided by input), velocity (O/I = output divided by input), class tier (Burner / Builder / 10xer), and rank position (integer, 1-based). Returns an empty array if no operators have submitted yet. Use this to see where operators stand overall, to find specific codenames for get_operator lookups, or to display the current rankings. Do NOT use this to check your own rank if you already know your codename — use get_operator instead for a single-operator profile with per-window breakdowns. After calling this, follow up with get_operator to get detailed metrics for any operator of interest.",
|
|
401
580
|
annotations: { ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
|
|
402
581
|
inputSchema: {
|
|
403
|
-
type:
|
|
582
|
+
type: "object",
|
|
404
583
|
properties: {},
|
|
405
|
-
description:
|
|
584
|
+
description:
|
|
585
|
+
"This tool takes no parameters. It always fetches the full public leaderboard.",
|
|
406
586
|
},
|
|
407
587
|
outputSchema: LEADERBOARD_OUTPUT,
|
|
408
588
|
},
|
|
409
589
|
{
|
|
410
|
-
name:
|
|
590
|
+
name: "get_operator",
|
|
411
591
|
description:
|
|
412
592
|
"Fetches one operator's live profile from the SigRank board by their codename. Reads the operator's current submission data from signalaf.com and returns their detailed metrics: yield (Υ), leverage ratio (Cr/I), velocity (O/I), class tier (Burner / Builder / 10xer), rank position (integer, 1-based), and per-window breakdowns for each time range (7d, 30d, 90d, all-time) with the four canonical pillars (input, output, cacheCreate, cacheRead) per window. Returns an error if the codename is not found on the board. Use this to look up any operator who has submitted to the board — codenames are public and visible on the leaderboard. Do NOT use this to browse all operators — use get_leaderboard for that. After calling this, you can use simulate_change to model what would happen if the operator adjusted their token mix.",
|
|
413
593
|
annotations: { ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
|
|
414
594
|
inputSchema: {
|
|
415
|
-
type:
|
|
595
|
+
type: "object",
|
|
416
596
|
properties: {
|
|
417
597
|
codename: {
|
|
418
|
-
type:
|
|
419
|
-
description:
|
|
598
|
+
type: "string",
|
|
599
|
+
description:
|
|
600
|
+
'The operator\'s public codename as shown on the SigRank leaderboard. Case-insensitive — "Ghost Falcon" and "ghost falcon" are equivalent. Must match a codename that exists on the board; returns an error if not found. To discover valid codenames, call get_leaderboard first.',
|
|
420
601
|
},
|
|
421
602
|
},
|
|
422
|
-
required: [
|
|
423
|
-
description:
|
|
603
|
+
required: ["codename"],
|
|
604
|
+
description:
|
|
605
|
+
"Requires the operator's codename. No other parameters are accepted.",
|
|
424
606
|
},
|
|
425
607
|
outputSchema: OPERATOR_OUTPUT,
|
|
426
608
|
},
|
|
427
609
|
{
|
|
428
|
-
name:
|
|
610
|
+
name: "submit_paste",
|
|
429
611
|
description:
|
|
430
|
-
|
|
431
|
-
annotations: {
|
|
612
|
+
"Ranks a paste of token counts AND publishes it to the live SigRank board at signalaf.com in one call. First computes the cascade locally for an instant preview (yield, leverage, velocity, class, card), then submits the raw paste to the board's web-paste endpoint, which re-parses and re-scores it server-side. The server score is authoritative — it may differ from the local preview if the board applies additional validation. Returns both the local preview and the server response (including the operator's new rank if accepted). A codename is required to publish — omit it for a local preview only (no board submission). Token-only, no auth required. Use this when you have token counts from ccusage or a dashboard and want to both see your score and publish it. Do NOT use this if you want to pull your local usage automatically — use tokenpull_submit for the zero-paste flow. Do NOT use this for multi-window dashboard pastes — use rank_windows to rank them first, then submit each window. After calling this, use get_operator with your codename to verify your submission appeared on the board.",
|
|
613
|
+
annotations: {
|
|
614
|
+
...ANNOTATIONS.destructiveHint,
|
|
615
|
+
...ANNOTATIONS.idempotentHint,
|
|
616
|
+
...ANNOTATIONS.openWorldHint,
|
|
617
|
+
},
|
|
432
618
|
inputSchema: {
|
|
433
|
-
type:
|
|
619
|
+
type: "object",
|
|
434
620
|
properties: {
|
|
435
621
|
text: {
|
|
436
|
-
type:
|
|
437
|
-
description:
|
|
622
|
+
type: "string",
|
|
623
|
+
description:
|
|
624
|
+
'Token counts to rank and submit. Two formats: (1) JSON {"input":N,"output":N,"cacheCreate":N,"cacheRead":N} from ccusage (preferred — the board parses this reliably), or (2) four whitespace-separated numbers: input output cacheCreate cacheRead. The 4-number form ranks locally but the board may reject it. Example: {"input":1000000,"output":500000,"cacheCreate":50000,"cacheRead":800000}',
|
|
438
625
|
},
|
|
439
626
|
codename: {
|
|
440
|
-
type:
|
|
441
|
-
description:
|
|
627
|
+
type: "string",
|
|
628
|
+
description:
|
|
629
|
+
'Operator codename to publish under on the leaderboard (e.g. "Ghost Falcon"). Required to submit — omit for local preview only (no board submission, just returns the local cascade result). Must be a non-empty string.',
|
|
442
630
|
},
|
|
443
631
|
},
|
|
444
|
-
required: [
|
|
445
|
-
description:
|
|
632
|
+
required: ["text"],
|
|
633
|
+
description:
|
|
634
|
+
"Requires token counts (text). Codename is optional but required for board submission — omit it for preview-only mode.",
|
|
446
635
|
},
|
|
447
636
|
outputSchema: SUBMIT_OUTPUT,
|
|
448
637
|
},
|
|
449
638
|
{
|
|
450
|
-
name:
|
|
639
|
+
name: "tokenpull",
|
|
451
640
|
description:
|
|
452
641
|
"Pull your LOCAL token usage from the platform's session logs and rank it across the four windows (7d/30d/90d/all-time) with the cascade — zero paste. Token-only: reads usage counts not message content. The numbers stay on your machine unless you submit them. Some platforms may have partial data (estimated=true when cacheCreate isn't available) or a dataGap note when the log format doesn't expose raw token counts.",
|
|
453
642
|
annotations: { ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
|
|
454
643
|
inputSchema: {
|
|
455
|
-
type:
|
|
644
|
+
type: "object",
|
|
456
645
|
properties: {
|
|
457
646
|
platform: {
|
|
458
|
-
type:
|
|
647
|
+
type: "string",
|
|
459
648
|
enum: ALL_PLATFORMS,
|
|
460
|
-
description: `source platform (default: claude). Supported: ${ALL_PLATFORMS.join(
|
|
649
|
+
description: `source platform (default: claude). Supported: ${ALL_PLATFORMS.join(", ")}. codex is estimated via io_ratio. Some platforms need setup (e.g. copilot requires COPILOT_OTEL_ENABLED=true).`,
|
|
461
650
|
},
|
|
462
651
|
},
|
|
463
652
|
},
|
|
464
653
|
outputSchema: TOKENPULL_OUTPUT,
|
|
465
654
|
},
|
|
466
655
|
{
|
|
467
|
-
name:
|
|
656
|
+
name: "tokenpull_submit",
|
|
468
657
|
description:
|
|
469
658
|
"Pull your LOCAL token usage from session logs AND publish it to the SigRank board in one call — the zero-paste flow. Reads the four canonical pillars (input, output, cacheCreate, cacheRead) per window from your local logs, computes the cascade, and submits each window to the board where it is re-scored server-side and tagged with the source platform. Requires a codename to publish; omit for a local preview only. Token-only — no prompt content is read or transmitted.",
|
|
470
|
-
annotations: {
|
|
659
|
+
annotations: {
|
|
660
|
+
...ANNOTATIONS.destructiveHint,
|
|
661
|
+
...ANNOTATIONS.idempotentHint,
|
|
662
|
+
...ANNOTATIONS.openWorldHint,
|
|
663
|
+
},
|
|
471
664
|
inputSchema: {
|
|
472
|
-
type:
|
|
665
|
+
type: "object",
|
|
473
666
|
properties: {
|
|
474
667
|
codename: {
|
|
475
|
-
type:
|
|
476
|
-
description:
|
|
668
|
+
type: "string",
|
|
669
|
+
description:
|
|
670
|
+
'Operator codename to publish under on the leaderboard (e.g. "Iron Lotus"). Required to submit — omit for local preview only.',
|
|
477
671
|
},
|
|
478
672
|
window: {
|
|
479
|
-
type:
|
|
480
|
-
enum: [
|
|
481
|
-
description:
|
|
673
|
+
type: "string",
|
|
674
|
+
enum: ["7d", "30d", "90d", "all"],
|
|
675
|
+
description:
|
|
676
|
+
'Submit only this time window (default: all 4 windows). Use "7d" for recent activity or "all" for all-time ranking.',
|
|
482
677
|
},
|
|
483
678
|
platform: {
|
|
484
|
-
type:
|
|
679
|
+
type: "string",
|
|
485
680
|
enum: ALL_PLATFORMS,
|
|
486
|
-
description: `Source platform to pull from (default: claude). Supported: ${ALL_PLATFORMS.join(
|
|
681
|
+
description: `Source platform to pull from (default: claude). Supported: ${ALL_PLATFORMS.join(", ")}. Each platform reads its own session logs locally.`,
|
|
487
682
|
},
|
|
488
683
|
},
|
|
489
684
|
},
|
|
490
685
|
outputSchema: SUBMIT_OUTPUT,
|
|
491
686
|
},
|
|
492
687
|
{
|
|
493
|
-
name:
|
|
688
|
+
name: "rank_windows",
|
|
494
689
|
description:
|
|
495
|
-
|
|
496
|
-
annotations: {
|
|
690
|
+
"Rank all four time windows (7d/30d/90d/all-time) in one call from a dashboard paste — paste the full table from ccusage, tokscale, or the Claude Max usage dashboard and get the cascade (Υ, SNR, Leverage, Velocity, 10xDEV, class, card) for each window. Each window is parsed and scored independently. Named keys required (input/output/cacheCreate/cacheRead); positional order is NOT safe here (dashboards list cache_read before cache_create — see WINDOWED_PROFILES gotcha). Omit windows you don't have — partial input is allowed (1–4 windows). Does NOT submit to the board; use tokenpull_submit for zero-paste publishing.",
|
|
691
|
+
annotations: {
|
|
692
|
+
...ANNOTATIONS.readOnlyHint,
|
|
693
|
+
...ANNOTATIONS.idempotentHint,
|
|
694
|
+
...ANNOTATIONS.openWorldHint,
|
|
695
|
+
},
|
|
497
696
|
inputSchema: {
|
|
498
|
-
type:
|
|
697
|
+
type: "object",
|
|
499
698
|
properties: {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
699
|
+
"7d": {
|
|
700
|
+
type: "string",
|
|
701
|
+
description:
|
|
702
|
+
"ccusage/tokscale paste or JSON for the 7-day window (optional)",
|
|
703
|
+
},
|
|
704
|
+
"30d": {
|
|
705
|
+
type: "string",
|
|
706
|
+
description:
|
|
707
|
+
"ccusage/tokscale paste or JSON for the 30-day window (optional)",
|
|
708
|
+
},
|
|
709
|
+
"90d": {
|
|
710
|
+
type: "string",
|
|
711
|
+
description:
|
|
712
|
+
"ccusage/tokscale paste or JSON for the 90-day window (optional)",
|
|
713
|
+
},
|
|
714
|
+
all: {
|
|
715
|
+
type: "string",
|
|
716
|
+
description:
|
|
717
|
+
"ccusage/tokscale paste or JSON for the all-time window (optional)",
|
|
718
|
+
},
|
|
719
|
+
source_tool: {
|
|
720
|
+
type: "string",
|
|
721
|
+
enum: [
|
|
722
|
+
"ccusage",
|
|
723
|
+
"tokscale",
|
|
724
|
+
"claude_max",
|
|
725
|
+
"token_dashboard",
|
|
726
|
+
"other",
|
|
727
|
+
],
|
|
728
|
+
description:
|
|
729
|
+
"which token reader produced the paste (for cross-tool variance tracking)",
|
|
730
|
+
},
|
|
505
731
|
},
|
|
506
732
|
// at least one window paste is required (runtime check backs this up)
|
|
507
|
-
anyOf: [
|
|
733
|
+
anyOf: [
|
|
734
|
+
{ required: ["7d"] },
|
|
735
|
+
{ required: ["30d"] },
|
|
736
|
+
{ required: ["90d"] },
|
|
737
|
+
{ required: ["all"] },
|
|
738
|
+
],
|
|
508
739
|
},
|
|
509
740
|
outputSchema: {
|
|
510
|
-
type:
|
|
741
|
+
type: "object",
|
|
511
742
|
properties: {
|
|
512
743
|
windows: {
|
|
513
|
-
type:
|
|
514
|
-
description:
|
|
744
|
+
type: "array",
|
|
745
|
+
description: "Cascade results per window",
|
|
515
746
|
items: {
|
|
516
|
-
type:
|
|
747
|
+
type: "object",
|
|
517
748
|
properties: {
|
|
518
|
-
window: { type:
|
|
749
|
+
window: { type: "string", enum: ["7d", "30d", "90d", "all"] },
|
|
519
750
|
...CASCADE_OUTPUT.properties,
|
|
520
751
|
},
|
|
521
752
|
},
|
|
@@ -524,176 +755,296 @@ export const TOOLS = [
|
|
|
524
755
|
},
|
|
525
756
|
},
|
|
526
757
|
{
|
|
527
|
-
name:
|
|
758
|
+
name: "watch_tokenpull",
|
|
528
759
|
description:
|
|
529
|
-
|
|
530
|
-
annotations: {
|
|
760
|
+
"One poll per call: pulls your local token logs and returns the current cascade for the watched window — the tool never blocks or loops. Re-call at your desired cadence to watch for changes (interval_s is advisory only and echoed back as poll_interval_s). With submit:true (and an enrolled device) each call may also sign + publish the watched window to the board, rate-limited to once per 5 min per platform+window; default is preview-only (no submit).",
|
|
761
|
+
annotations: {
|
|
762
|
+
...ANNOTATIONS.idempotentHint,
|
|
763
|
+
...ANNOTATIONS.openWorldHint,
|
|
764
|
+
},
|
|
531
765
|
inputSchema: {
|
|
532
|
-
type:
|
|
766
|
+
type: "object",
|
|
533
767
|
properties: {
|
|
534
|
-
platform:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
768
|
+
platform: {
|
|
769
|
+
type: "string",
|
|
770
|
+
enum: ALL_PLATFORMS,
|
|
771
|
+
description: "platform to watch (default: claude)",
|
|
772
|
+
},
|
|
773
|
+
interval_s: {
|
|
774
|
+
type: "number",
|
|
775
|
+
description:
|
|
776
|
+
"advisory poll cadence in seconds (default: 60, min: 10) — echoed back as poll_interval_s; does not make the call block or loop",
|
|
777
|
+
},
|
|
778
|
+
window: {
|
|
779
|
+
type: "string",
|
|
780
|
+
enum: ["7d", "30d", "90d", "all"],
|
|
781
|
+
description:
|
|
782
|
+
"which window to watch (default: 7d — most sensitive to recent activity)",
|
|
783
|
+
},
|
|
784
|
+
submit: {
|
|
785
|
+
type: "boolean",
|
|
786
|
+
description:
|
|
787
|
+
"auto-submit the watched window to the board as a VERIFIED operator each poll (requires `enroll`; default false = preview only)",
|
|
788
|
+
},
|
|
538
789
|
},
|
|
539
790
|
},
|
|
540
791
|
outputSchema: TOKENPULL_OUTPUT,
|
|
541
792
|
},
|
|
542
793
|
{
|
|
543
|
-
name:
|
|
794
|
+
name: "tokenpull_compare",
|
|
544
795
|
description:
|
|
545
|
-
|
|
796
|
+
"Pull token usage from ALL four local sources in parallel — tokenpull (JSONL canon), ccusage CLI, token-dashboard SQLite, and tokscale report — and return them side-by-side with delta % vs tokenpull as the baseline. Also computes the cascade (Υ, SNR, Leverage, class) for each source so you can see how each verifier scores. Useful for validating your numbers before submitting, or understanding discrepancies between tools. Claude only for token-dash; codex and others use tokenpull + ccusage + tokscale. Token-only, on-device.",
|
|
546
797
|
annotations: { ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
|
|
547
798
|
inputSchema: {
|
|
548
|
-
type:
|
|
799
|
+
type: "object",
|
|
549
800
|
properties: {
|
|
550
801
|
platform: {
|
|
551
|
-
type:
|
|
802
|
+
type: "string",
|
|
552
803
|
enum: ALL_PLATFORMS,
|
|
553
|
-
description:
|
|
804
|
+
description:
|
|
805
|
+
"platform to compare (default: claude). token-dash and App only available for claude.",
|
|
554
806
|
},
|
|
555
807
|
},
|
|
556
808
|
},
|
|
557
809
|
outputSchema: COMPARE_OUTPUT,
|
|
558
810
|
},
|
|
559
811
|
{
|
|
560
|
-
name:
|
|
812
|
+
name: "enroll",
|
|
561
813
|
description:
|
|
562
814
|
'Bind THIS device to your SigRank operator so your signed token runs cascade to the live board. Paste the key from signalaf.com → Settings → "New key" (or "Generate connect code"). On first run it generates + stores a local ed25519 keypair (~/.sigrank-mcp/identity.json); only the PUBLIC key is ever sent. Need a new key? Click "New key" at signalaf.com → Settings, then paste it here.',
|
|
563
|
-
annotations: {
|
|
815
|
+
annotations: {
|
|
816
|
+
...ANNOTATIONS.destructiveHint,
|
|
817
|
+
...ANNOTATIONS.idempotentHint,
|
|
818
|
+
...ANNOTATIONS.openWorldHint,
|
|
819
|
+
},
|
|
564
820
|
inputSchema: {
|
|
565
|
-
type:
|
|
821
|
+
type: "object",
|
|
566
822
|
properties: {
|
|
567
|
-
code: {
|
|
568
|
-
|
|
823
|
+
code: {
|
|
824
|
+
type: "string",
|
|
825
|
+
description:
|
|
826
|
+
"the key / connect code (SIGR-XXXXX-XXXXX-XXXXX) from Settings → New key (or Generate connect code)",
|
|
827
|
+
},
|
|
828
|
+
device_label: {
|
|
829
|
+
type: "string",
|
|
830
|
+
description:
|
|
831
|
+
"optional label for this device (default: hostname · agent version)",
|
|
832
|
+
},
|
|
569
833
|
},
|
|
570
|
-
required: [
|
|
834
|
+
required: ["code"],
|
|
571
835
|
},
|
|
572
836
|
outputSchema: ENROLL_OUTPUT,
|
|
573
837
|
},
|
|
574
838
|
{
|
|
575
|
-
name:
|
|
839
|
+
name: "submit_verified",
|
|
576
840
|
description:
|
|
577
|
-
|
|
578
|
-
annotations: {
|
|
841
|
+
"Publish your LOCAL token runs to the SigRank board as a VERIFIED operator — the enrolled, signed path. Reads your pillars (tokenpull), builds the canonical Schema 1.0 snapshot per window, ed25519-signs it with your device key, and POSTs to /api/v1/snapshots. Requires `npx sigrank-mcp enroll` first (a bound device). Only signed submissions from a trusted device rank on the board. Token-only; the private key never leaves your machine. Pass dry_run:true to inspect the exact signed payload without publishing.",
|
|
842
|
+
annotations: {
|
|
843
|
+
...ANNOTATIONS.destructiveHint,
|
|
844
|
+
...ANNOTATIONS.idempotentHint,
|
|
845
|
+
...ANNOTATIONS.openWorldHint,
|
|
846
|
+
},
|
|
579
847
|
inputSchema: {
|
|
580
|
-
type:
|
|
848
|
+
type: "object",
|
|
581
849
|
properties: {
|
|
582
|
-
window: {
|
|
583
|
-
|
|
584
|
-
|
|
850
|
+
window: {
|
|
851
|
+
type: "string",
|
|
852
|
+
enum: ["7d", "30d", "90d", "all"],
|
|
853
|
+
description: "submit only this window (default: all 4)",
|
|
854
|
+
},
|
|
855
|
+
platform: {
|
|
856
|
+
type: "string",
|
|
857
|
+
enum: [...ALL_PLATFORMS, "multi"],
|
|
858
|
+
description:
|
|
859
|
+
"source platform (default: claude). 'multi' = combined cascade summed across all locally-detected platforms (needs 2+ active); empty windows are skipped.",
|
|
860
|
+
},
|
|
861
|
+
dry_run: {
|
|
862
|
+
type: "boolean",
|
|
863
|
+
description:
|
|
864
|
+
"build + sign but do NOT publish — returns the exact payload that would be POSTed (token counts only), so you can inspect before submitting",
|
|
865
|
+
},
|
|
585
866
|
},
|
|
586
867
|
},
|
|
587
868
|
outputSchema: SUBMIT_OUTPUT,
|
|
588
869
|
},
|
|
589
870
|
{
|
|
590
|
-
name:
|
|
871
|
+
name: "simulate_change",
|
|
591
872
|
description:
|
|
592
873
|
"The first PRESCRIPTIVE SigRank tool — 'what if I changed my token mix?' Takes your current 4 pillars (input/output/cacheCreate/cacheRead) and one or more proposed changes, runs the canonical cascade on BOTH the current and simulated values, and returns the exact Υ Yield delta, class change, and per-metric diffs. This is the 'show me the payoff before I do the work' primitive: no network, no submission, pure local math. Use it to answer 'would increasing my cache-read by 50k tokens actually move my class?' before you change your workflow. Accepts the current pillars as JSON or 4 numbers (same as rank_paste) plus a `changes` object with any of the 4 pillar names mapped to new absolute values OR relative deltas (e.g. {cacheRead: '+50000'} or {input: 800000}).",
|
|
593
|
-
annotations: {
|
|
874
|
+
annotations: {
|
|
875
|
+
...ANNOTATIONS.readOnlyHint,
|
|
876
|
+
...ANNOTATIONS.idempotentHint,
|
|
877
|
+
...ANNOTATIONS.openWorldHint,
|
|
878
|
+
},
|
|
594
879
|
inputSchema: {
|
|
595
|
-
type:
|
|
880
|
+
type: "object",
|
|
596
881
|
properties: {
|
|
597
882
|
text: {
|
|
598
|
-
type:
|
|
599
|
-
description:
|
|
883
|
+
type: "string",
|
|
884
|
+
description:
|
|
885
|
+
'Current token pillars — ccusage JSON or "input output cacheCreate cacheRead" (same format as rank_paste).',
|
|
600
886
|
},
|
|
601
887
|
changes: {
|
|
602
|
-
type:
|
|
603
|
-
description:
|
|
888
|
+
type: "object",
|
|
889
|
+
description:
|
|
890
|
+
"Proposed changes to apply. Keys: input, output, cacheCreate, cacheRead. Values are either absolute numbers (replace) or strings starting with +/- for relative deltas (add/subtract). Omitted pillars are unchanged.",
|
|
604
891
|
properties: {
|
|
605
|
-
input:
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
892
|
+
input: {
|
|
893
|
+
type: ["number", "string"],
|
|
894
|
+
description:
|
|
895
|
+
'new input token count (absolute) or "+/-N" for a relative delta',
|
|
896
|
+
},
|
|
897
|
+
output: {
|
|
898
|
+
type: ["number", "string"],
|
|
899
|
+
description:
|
|
900
|
+
'new output token count (absolute) or "+/-N" for a relative delta',
|
|
901
|
+
},
|
|
902
|
+
cacheCreate: {
|
|
903
|
+
type: ["number", "string"],
|
|
904
|
+
description:
|
|
905
|
+
'new cache-create token count (absolute) or "+/-N" for a relative delta',
|
|
906
|
+
},
|
|
907
|
+
cacheRead: {
|
|
908
|
+
type: ["number", "string"],
|
|
909
|
+
description:
|
|
910
|
+
'new cache-read token count (absolute) or "+/-N" for a relative delta',
|
|
911
|
+
},
|
|
609
912
|
},
|
|
610
913
|
},
|
|
611
914
|
},
|
|
612
|
-
required: [
|
|
915
|
+
required: ["text", "changes"],
|
|
613
916
|
},
|
|
614
917
|
outputSchema: SIMULATE_OUTPUT,
|
|
615
918
|
},
|
|
616
919
|
{
|
|
617
|
-
name:
|
|
920
|
+
name: "diagnose_cascade",
|
|
618
921
|
description:
|
|
619
922
|
"Analyzes your token cascade and diagnoses where you're leaking efficiency. Takes your 4 pillars (input/output/cacheCreate/cacheRead) and produces a ranked list of efficiency leaks with severity (critical/warning/info), findings, and recommendations. Checks: cache leverage (are you rereading what you wrote?), velocity (are you generating enough output per input?), SNR (is your signal drowning in noise?), cache creation ratio (are you over-committing?), input bloat (is fresh input too high?), and 10xDEV (is the full cascade compounding?). Each finding includes an estimated Υ impact. Pure local math — no network, no submission. Use this BEFORE simulate_change to understand what's wrong, then use simulate_change to test fixes. Accepts the same input formats as rank_paste (JSON or 4 whitespace numbers).",
|
|
620
|
-
annotations: {
|
|
923
|
+
annotations: {
|
|
924
|
+
...ANNOTATIONS.readOnlyHint,
|
|
925
|
+
...ANNOTATIONS.idempotentHint,
|
|
926
|
+
...ANNOTATIONS.openWorldHint,
|
|
927
|
+
},
|
|
621
928
|
inputSchema: {
|
|
622
|
-
type:
|
|
929
|
+
type: "object",
|
|
623
930
|
properties: {
|
|
624
931
|
text: {
|
|
625
|
-
type:
|
|
626
|
-
description:
|
|
932
|
+
type: "string",
|
|
933
|
+
description:
|
|
934
|
+
'Token pillars — ccusage JSON or "input output cacheCreate cacheRead" (same format as rank_paste).',
|
|
627
935
|
},
|
|
628
936
|
},
|
|
629
|
-
required: [
|
|
937
|
+
required: ["text"],
|
|
630
938
|
},
|
|
631
939
|
outputSchema: DIAGNOSE_OUTPUT,
|
|
632
940
|
},
|
|
633
941
|
{
|
|
634
|
-
name:
|
|
942
|
+
name: "suggest_improvements",
|
|
635
943
|
description:
|
|
636
944
|
"Generates ranked, simulated improvement suggestions for your token cascade. Takes your 4 pillars, tests multiple improvement strategies (increase cache reads, reduce fresh input, increase output, optimize cache creation), simulates each with the canonical cascade engine, and returns them ranked by Υ yield impact. Each suggestion includes: the action, which pillar to change, how much to change it, the projected Υ after the change, the yield delta, the projected class tier, and a rationale. Also returns the single highest-impact change (best_single_change). Pure local math — no network, no submission. Use this after diagnose_cascade to get actionable next steps, then use simulate_change to fine-tune before committing. Accepts the same input formats as rank_paste.",
|
|
637
|
-
annotations: {
|
|
945
|
+
annotations: {
|
|
946
|
+
...ANNOTATIONS.readOnlyHint,
|
|
947
|
+
...ANNOTATIONS.idempotentHint,
|
|
948
|
+
...ANNOTATIONS.openWorldHint,
|
|
949
|
+
},
|
|
638
950
|
inputSchema: {
|
|
639
|
-
type:
|
|
951
|
+
type: "object",
|
|
640
952
|
properties: {
|
|
641
953
|
text: {
|
|
642
|
-
type:
|
|
643
|
-
description:
|
|
954
|
+
type: "string",
|
|
955
|
+
description:
|
|
956
|
+
'Token pillars — ccusage JSON or "input output cacheCreate cacheRead" (same format as rank_paste).',
|
|
644
957
|
},
|
|
645
958
|
},
|
|
646
|
-
required: [
|
|
959
|
+
required: ["text"],
|
|
647
960
|
},
|
|
648
961
|
outputSchema: SUGGEST_OUTPUT,
|
|
649
962
|
},
|
|
650
963
|
{
|
|
651
|
-
name:
|
|
964
|
+
name: "self_improve",
|
|
652
965
|
description:
|
|
653
966
|
"Runs the full self-improvement cycle in one call: (1) gets your current token pillars — either from the provided text or by running tokenpull on your local logs, (2) diagnoses where you're leaking efficiency (diagnose_cascade), (3) generates ranked improvement suggestions (suggest_improvements), (4) simulates the top suggestion (simulate_change), and (5) returns the complete cycle: diagnosis + suggestions + the simulated impact of the best change. This is the 'one-click optimize' tool — call it at the end of a session to see what to improve next time. If you provide pillars in `text`, it skips the tokenpull step. If you omit `text`, it runs tokenpull first (requires local ccusage logs). Pure local math — no network, no submission. The `scope` parameter adds mode detection (BUILD/EDIT/DEBUG/MAINTAIN/IDLE) and scoped analysis: 'daily' (default — current behavior + mode), 'weekly' (compound into weekly snapshots + report artifact), 'trend' (30d/90d trajectory analysis).",
|
|
654
|
-
annotations: {
|
|
967
|
+
annotations: {
|
|
968
|
+
...ANNOTATIONS.readOnlyHint,
|
|
969
|
+
...ANNOTATIONS.idempotentHint,
|
|
970
|
+
...ANNOTATIONS.openWorldHint,
|
|
971
|
+
},
|
|
655
972
|
inputSchema: {
|
|
656
|
-
type:
|
|
973
|
+
type: "object",
|
|
657
974
|
properties: {
|
|
658
975
|
text: {
|
|
659
|
-
type:
|
|
660
|
-
description:
|
|
976
|
+
type: "string",
|
|
977
|
+
description:
|
|
978
|
+
'Optional: token pillars — ccusage JSON or "input output cacheCreate cacheRead". If omitted, runs tokenpull to get current pillars from local logs.',
|
|
661
979
|
},
|
|
662
980
|
window: {
|
|
663
|
-
type:
|
|
664
|
-
enum: [
|
|
665
|
-
description:
|
|
981
|
+
type: "string",
|
|
982
|
+
enum: ["7d", "30d", "90d", "all"],
|
|
983
|
+
description:
|
|
984
|
+
"Which time window to pull when running tokenpull (default: 30d). Ignored if `text` is provided.",
|
|
666
985
|
},
|
|
667
986
|
scope: {
|
|
668
|
-
type:
|
|
669
|
-
enum: [
|
|
670
|
-
description:
|
|
987
|
+
type: "string",
|
|
988
|
+
enum: ["daily", "weekly", "trend"],
|
|
989
|
+
description:
|
|
990
|
+
'Analysis scope: "daily" (default — current behavior + mode detection), "weekly" (compound daily rows into weekly snapshots + report artifact with badges), "trend" (30d/90d trajectory + phase patterns). Daily modes never leave the machine — only weekly distribution goes in submitted reports.',
|
|
671
991
|
},
|
|
672
992
|
},
|
|
673
993
|
},
|
|
674
994
|
outputSchema: {
|
|
675
|
-
type:
|
|
995
|
+
type: "object",
|
|
676
996
|
properties: {
|
|
677
|
-
pillars: {
|
|
997
|
+
pillars: {
|
|
998
|
+
type: "object",
|
|
999
|
+
description: "The 4 raw token pillars used",
|
|
1000
|
+
},
|
|
678
1001
|
current_cascade: CASCADE_OUTPUT,
|
|
679
|
-
diagnosis: {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1002
|
+
diagnosis: {
|
|
1003
|
+
type: "array",
|
|
1004
|
+
description: "Efficiency leaks found (from diagnose_cascade)",
|
|
1005
|
+
},
|
|
1006
|
+
suggestions: {
|
|
1007
|
+
type: "array",
|
|
1008
|
+
description: "Ranked improvements (from suggest_improvements)",
|
|
1009
|
+
},
|
|
1010
|
+
best_simulation: {
|
|
1011
|
+
type: "object",
|
|
1012
|
+
description: "Simulated result of the top suggestion",
|
|
1013
|
+
},
|
|
1014
|
+
cycle_summary: {
|
|
1015
|
+
type: "string",
|
|
1016
|
+
description: "One-line summary of the full cycle",
|
|
1017
|
+
},
|
|
683
1018
|
// Scope-specific fields
|
|
684
|
-
mode: {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1019
|
+
mode: {
|
|
1020
|
+
type: "object",
|
|
1021
|
+
description:
|
|
1022
|
+
"Detected mode { mode, confidence } — present when scope is daily/weekly/trend",
|
|
1023
|
+
},
|
|
1024
|
+
quality_score: {
|
|
1025
|
+
type: "number",
|
|
1026
|
+
description: "Yield relative to mode expectation (daily scope)",
|
|
1027
|
+
},
|
|
1028
|
+
assessment: {
|
|
1029
|
+
type: "string",
|
|
1030
|
+
description: "One-line assessment for daily scope",
|
|
1031
|
+
},
|
|
1032
|
+
advice: {
|
|
1033
|
+
type: "string",
|
|
1034
|
+
description: "Advice for next session (daily scope)",
|
|
1035
|
+
},
|
|
1036
|
+
report: {
|
|
1037
|
+
type: "object",
|
|
1038
|
+
description: "Weekly report artifact (weekly scope)",
|
|
1039
|
+
},
|
|
1040
|
+
trend: { type: "object", description: "Trend analysis (trend scope)" },
|
|
690
1041
|
},
|
|
691
1042
|
},
|
|
692
1043
|
},
|
|
693
|
-
]
|
|
1044
|
+
];
|
|
694
1045
|
|
|
695
1046
|
// tokenpull window key → the board's window_type enum.
|
|
696
|
-
const WINDOW_TYPE = {
|
|
1047
|
+
const WINDOW_TYPE = { "7d": "7d", "30d": "30d", "90d": "90d", all: "all_time" };
|
|
697
1048
|
|
|
698
1049
|
// E3: client-side auto-submit cooldown for watch_tokenpull. The server already dedups
|
|
699
1050
|
// identical snapshots (exact hash → 422), but a noisy poll loop with submit:true could still
|
|
@@ -701,8 +1052,8 @@ const WINDOW_TYPE = { '7d': '7d', '30d': '30d', '90d': '90d', all: 'all_time' }
|
|
|
701
1052
|
// per platform+window (in-memory, per process). Keyed by platform:window so different
|
|
702
1053
|
// platforms/windows don't block each other, and only armed on a non-error submit so a
|
|
703
1054
|
// network failure doesn't lock out retries for 5 minutes.
|
|
704
|
-
const WATCH_SUBMIT_COOLDOWN_MS = 5 * 60 * 1000
|
|
705
|
-
const _lastWatchSubmitAt = new Map()
|
|
1055
|
+
const WATCH_SUBMIT_COOLDOWN_MS = 5 * 60 * 1000;
|
|
1056
|
+
const _lastWatchSubmitAt = new Map();
|
|
706
1057
|
|
|
707
1058
|
// ── Shared active-platform loader ───────────────────────────────────────────
|
|
708
1059
|
// THE single data path for "show my cascade across platforms" — used by `me`,
|
|
@@ -711,73 +1062,105 @@ const _lastWatchSubmitAt = new Map()
|
|
|
711
1062
|
// keeps only platforms with real local data, and sorts claude → codex → rest.
|
|
712
1063
|
// Pass `platforms` to scope it (e.g. ['claude'] for a fast first paint).
|
|
713
1064
|
export async function pullActivePlatforms({ platforms } = {}, opts = {}) {
|
|
714
|
-
const targets = platforms && platforms.length ? platforms : ALL_PLATFORMS
|
|
715
|
-
const settled = await Promise.allSettled(
|
|
1065
|
+
const targets = platforms && platforms.length ? platforms : ALL_PLATFORMS;
|
|
1066
|
+
const settled = await Promise.allSettled(
|
|
1067
|
+
targets.map((p) => callTool("tokenpull", { platform: p }, opts)),
|
|
1068
|
+
);
|
|
716
1069
|
const active = settled
|
|
717
|
-
.filter((r) => r.status ===
|
|
1070
|
+
.filter((r) => r.status === "fulfilled" && r.value)
|
|
718
1071
|
.map((r) => r.value)
|
|
719
|
-
.filter((d) =>
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1072
|
+
.filter((d) =>
|
|
1073
|
+
(d.windows || []).some(
|
|
1074
|
+
(w) => (w.pillars?.input ?? 0) + (w.pillars?.output ?? 0) > 0,
|
|
1075
|
+
),
|
|
1076
|
+
);
|
|
1077
|
+
const rank = (p) =>
|
|
1078
|
+
p === "claude" ? -2 : p === "codex" ? -1 : ALL_PLATFORMS.indexOf(p);
|
|
1079
|
+
active.sort((a, b) => rank(a.platform) - rank(b.platform));
|
|
1080
|
+
return active;
|
|
723
1081
|
}
|
|
724
1082
|
|
|
725
1083
|
export async function callTool(name, args, opts = {}) {
|
|
726
|
-
const apiBase = opts.apiBase || DEFAULT_API_BASE
|
|
727
|
-
const timeoutMs = opts.fetchTimeout ?? DEFAULT_FETCH_TIMEOUT
|
|
728
|
-
const rawFetch = opts.fetchImpl || fetch
|
|
1084
|
+
const apiBase = opts.apiBase || DEFAULT_API_BASE;
|
|
1085
|
+
const timeoutMs = opts.fetchTimeout ?? DEFAULT_FETCH_TIMEOUT;
|
|
1086
|
+
const rawFetch = opts.fetchImpl || fetch;
|
|
729
1087
|
|
|
730
1088
|
// Wrap every fetch with an AbortController timeout so a hung network call never
|
|
731
1089
|
// blocks the MCP client indefinitely.
|
|
732
1090
|
const doFetch = (url, init = {}) => {
|
|
733
|
-
const ac = new AbortController()
|
|
734
|
-
const timer = setTimeout(() => ac.abort(), timeoutMs)
|
|
735
|
-
return rawFetch(url, { ...init, signal: ac.signal }).finally(() =>
|
|
736
|
-
|
|
1091
|
+
const ac = new AbortController();
|
|
1092
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
1093
|
+
return rawFetch(url, { ...init, signal: ac.signal }).finally(() =>
|
|
1094
|
+
clearTimeout(timer),
|
|
1095
|
+
);
|
|
1096
|
+
};
|
|
737
1097
|
|
|
738
1098
|
const fetchJson = async (path) => {
|
|
739
|
-
const res = await doFetch(`${apiBase}${path}`, {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1099
|
+
const res = await doFetch(`${apiBase}${path}`, {
|
|
1100
|
+
headers: { accept: "application/json" },
|
|
1101
|
+
});
|
|
1102
|
+
if (!res.ok) throw new Error(`SigRank API ${path} → HTTP ${res.status}`);
|
|
1103
|
+
return res.json();
|
|
1104
|
+
};
|
|
743
1105
|
|
|
744
1106
|
// Helper: attach _parseWarnings from pillars onto the cascade result so they
|
|
745
1107
|
// are always visible in the tool output for review.
|
|
746
1108
|
const withParseWarnings = (pillars, cascadeResult) => {
|
|
747
1109
|
if (pillars._parseWarnings && pillars._parseWarnings.length > 0) {
|
|
748
|
-
const existing = cascadeResult.warnings || []
|
|
749
|
-
return {
|
|
1110
|
+
const existing = cascadeResult.warnings || [];
|
|
1111
|
+
return {
|
|
1112
|
+
...cascadeResult,
|
|
1113
|
+
warnings: [
|
|
1114
|
+
...existing,
|
|
1115
|
+
...pillars._parseWarnings.map((w) => `parse:${w}`),
|
|
1116
|
+
],
|
|
1117
|
+
};
|
|
750
1118
|
}
|
|
751
|
-
return cascadeResult
|
|
752
|
-
}
|
|
1119
|
+
return cascadeResult;
|
|
1120
|
+
};
|
|
753
1121
|
|
|
754
|
-
if (name ===
|
|
755
|
-
if (!args?.text)
|
|
1122
|
+
if (name === "rank_paste") {
|
|
1123
|
+
if (!args?.text)
|
|
1124
|
+
throw new Error("rank_paste requires a non-empty `text` argument.");
|
|
756
1125
|
// E2: reject oversized pastes before parsing (parity with submit_paste / rank_windows).
|
|
757
|
-
if (typeof args.text ===
|
|
758
|
-
return {
|
|
1126
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
1127
|
+
return {
|
|
1128
|
+
status: "error",
|
|
1129
|
+
reason: "input_too_large",
|
|
1130
|
+
detail: `text exceeds ${MAX_INPUT} chars (${args.text.length}). Paste only the token-count table, not full output.`,
|
|
1131
|
+
};
|
|
759
1132
|
}
|
|
760
|
-
const pillars = parsePillars(args.text)
|
|
761
|
-
const c = withParseWarnings(pillars, cascade(pillars))
|
|
762
|
-
return { ...c, card: narrate(c) }
|
|
1133
|
+
const pillars = parsePillars(args.text);
|
|
1134
|
+
const c = withParseWarnings(pillars, cascade(pillars));
|
|
1135
|
+
return { ...c, card: narrate(c) };
|
|
763
1136
|
}
|
|
764
|
-
if (name ===
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1137
|
+
if (name === "get_leaderboard")
|
|
1138
|
+
return fetchJson("/api/v1/leaderboard?metric=yield_");
|
|
1139
|
+
if (name === "get_operator") {
|
|
1140
|
+
const codename = String(args?.codename || "").trim();
|
|
1141
|
+
if (!codename)
|
|
1142
|
+
throw new Error("get_operator requires a non-empty `codename` argument.");
|
|
1143
|
+
return fetchJson(`/api/v1/operators/${encodeURIComponent(codename)}`);
|
|
769
1144
|
}
|
|
770
1145
|
|
|
771
|
-
if (name ===
|
|
1146
|
+
if (name === "enroll") {
|
|
772
1147
|
// Redeem a web connect code → bind this device. Generates/loads the local keypair;
|
|
773
1148
|
// sends ONLY the public key. operator binding happens server-side from the code row.
|
|
774
|
-
const code = String(args?.code ||
|
|
775
|
-
if (!code)
|
|
776
|
-
|
|
777
|
-
|
|
1149
|
+
const code = String(args?.code || "").trim();
|
|
1150
|
+
if (!code)
|
|
1151
|
+
throw new Error(
|
|
1152
|
+
"enroll requires a `code` — paste your connect code from signalaf.com → Settings → Connect a device.",
|
|
1153
|
+
);
|
|
1154
|
+
const id = opts.identity || ensureIdentity();
|
|
1155
|
+
const deviceLabel = String(
|
|
1156
|
+
args?.device_label || `${os.hostname()} · ${id.agent_version}`,
|
|
1157
|
+
).slice(0, 120);
|
|
778
1158
|
const res = await doFetch(`${apiBase}/api/v1/devices/enroll`, {
|
|
779
|
-
method:
|
|
780
|
-
headers: {
|
|
1159
|
+
method: "POST",
|
|
1160
|
+
headers: {
|
|
1161
|
+
"content-type": "application/json",
|
|
1162
|
+
accept: "application/json",
|
|
1163
|
+
},
|
|
781
1164
|
body: JSON.stringify({
|
|
782
1165
|
code,
|
|
783
1166
|
device_id: id.device_id,
|
|
@@ -785,82 +1168,126 @@ export async function callTool(name, args, opts = {}) {
|
|
|
785
1168
|
device_label: deviceLabel,
|
|
786
1169
|
agent_version: id.agent_version,
|
|
787
1170
|
}),
|
|
788
|
-
})
|
|
789
|
-
let ack
|
|
790
|
-
try {
|
|
791
|
-
|
|
1171
|
+
});
|
|
1172
|
+
let ack;
|
|
1173
|
+
try {
|
|
1174
|
+
ack = await res.json();
|
|
1175
|
+
} catch {
|
|
1176
|
+
ack = {};
|
|
1177
|
+
}
|
|
1178
|
+
if (res.status === 201 && ack.status === "enrolled") {
|
|
792
1179
|
// Persist the binding locally (skipped when a test injects opts.identity → no keystore write).
|
|
793
|
-
if (!opts.identity)
|
|
1180
|
+
if (!opts.identity)
|
|
1181
|
+
recordEnrollment({
|
|
1182
|
+
codename: ack.codename,
|
|
1183
|
+
operator_id: ack.operator_id,
|
|
1184
|
+
});
|
|
794
1185
|
return {
|
|
795
|
-
status:
|
|
1186
|
+
status: "enrolled",
|
|
796
1187
|
codename: ack.codename ?? null,
|
|
797
1188
|
operator_id: ack.operator_id ?? null,
|
|
798
1189
|
device_id: id.device_id,
|
|
799
|
-
trust_status: ack.trust_status ??
|
|
800
|
-
}
|
|
1190
|
+
trust_status: ack.trust_status ?? "trusted",
|
|
1191
|
+
};
|
|
801
1192
|
}
|
|
802
|
-
return {
|
|
1193
|
+
return {
|
|
1194
|
+
status: "error",
|
|
1195
|
+
httpStatus: res.status,
|
|
1196
|
+
reason: ack.reason || ack.status || `http_${res.status}`,
|
|
1197
|
+
detail: ack.detail ?? null,
|
|
1198
|
+
};
|
|
803
1199
|
}
|
|
804
1200
|
|
|
805
|
-
if (name ===
|
|
1201
|
+
if (name === "submit_verified") {
|
|
806
1202
|
// The enrolled, signed publish path → /api/v1/snapshots (only verified rows rank).
|
|
807
|
-
const id = opts.identity || ensureIdentity()
|
|
1203
|
+
const id = opts.identity || ensureIdentity();
|
|
808
1204
|
if (!id.codename || !id.operator_id) {
|
|
809
|
-
return {
|
|
1205
|
+
return {
|
|
1206
|
+
status: "not_enrolled",
|
|
1207
|
+
detail: "Run `npx sigrank-mcp enroll` to bind this device first.",
|
|
1208
|
+
};
|
|
810
1209
|
}
|
|
811
|
-
const platform = args?.platform ||
|
|
1210
|
+
const platform = args?.platform || "claude";
|
|
812
1211
|
|
|
813
1212
|
// MULTI: the combined cross-platform cascade. The dashboard already SUMS every
|
|
814
1213
|
// active platform's pillars (a "claude+codex" row) but never submitted it — this
|
|
815
1214
|
// is that missing submission. Aggregate every locally-detected platform's pillars
|
|
816
1215
|
// per window and publish as platform='multi' = the operator's TOTAL usage. Empty
|
|
817
1216
|
// windows are skipped so a no-activity window never lands as a degenerate row.
|
|
818
|
-
if (platform ===
|
|
819
|
-
const detected = []
|
|
1217
|
+
if (platform === "multi") {
|
|
1218
|
+
const detected = [];
|
|
820
1219
|
for (const p of ALL_PLATFORMS) {
|
|
821
|
-
const r = await pullByPlatform(p, opts).catch(() => null)
|
|
822
|
-
const live =
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1220
|
+
const r = await pullByPlatform(p, opts).catch(() => null);
|
|
1221
|
+
const live =
|
|
1222
|
+
r &&
|
|
1223
|
+
(r.windows || []).some(
|
|
1224
|
+
(w) =>
|
|
1225
|
+
w.pillars.input +
|
|
1226
|
+
w.pillars.output +
|
|
1227
|
+
w.pillars.cacheCreate +
|
|
1228
|
+
w.pillars.cacheRead >
|
|
1229
|
+
0,
|
|
1230
|
+
);
|
|
1231
|
+
if (live) detected.push(r);
|
|
826
1232
|
}
|
|
827
1233
|
if (detected.length < 2) {
|
|
828
1234
|
return {
|
|
829
|
-
platform:
|
|
830
|
-
|
|
831
|
-
|
|
1235
|
+
platform: "multi",
|
|
1236
|
+
codename: id.codename,
|
|
1237
|
+
operator_id: id.operator_id,
|
|
1238
|
+
status: "skipped",
|
|
1239
|
+
reason: "need_2_platforms",
|
|
1240
|
+
detail: `multi needs 2+ active platforms; found ${detected.length} (${detected.map((d) => d.platform).join(", ") || "none"}).`,
|
|
832
1241
|
windows: [],
|
|
833
|
-
}
|
|
1242
|
+
};
|
|
834
1243
|
}
|
|
835
|
-
const winKeys = args?.window
|
|
836
|
-
|
|
1244
|
+
const winKeys = args?.window
|
|
1245
|
+
? [args.window]
|
|
1246
|
+
: ["7d", "30d", "90d", "all"];
|
|
1247
|
+
const out = [];
|
|
837
1248
|
for (const wk of winKeys) {
|
|
838
|
-
const sum = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 }
|
|
839
|
-
let msgs = 0
|
|
1249
|
+
const sum = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 };
|
|
1250
|
+
let msgs = 0;
|
|
840
1251
|
for (const d of detected) {
|
|
841
|
-
const w = (d.windows || []).find((x) => x.window === wk)
|
|
842
|
-
if (!w) continue
|
|
843
|
-
sum.input += w.pillars.input || 0
|
|
844
|
-
sum.output += w.pillars.output || 0
|
|
845
|
-
sum.cacheCreate += w.pillars.cacheCreate || 0
|
|
846
|
-
sum.cacheRead += w.pillars.cacheRead || 0
|
|
847
|
-
msgs += w.messages || 0
|
|
1252
|
+
const w = (d.windows || []).find((x) => x.window === wk);
|
|
1253
|
+
if (!w) continue;
|
|
1254
|
+
sum.input += w.pillars.input || 0;
|
|
1255
|
+
sum.output += w.pillars.output || 0;
|
|
1256
|
+
sum.cacheCreate += w.pillars.cacheCreate || 0;
|
|
1257
|
+
sum.cacheRead += w.pillars.cacheRead || 0;
|
|
1258
|
+
msgs += w.messages || 0;
|
|
848
1259
|
}
|
|
849
|
-
if (sum.input + sum.output + sum.cacheCreate + sum.cacheRead <= 0)
|
|
850
|
-
|
|
851
|
-
const
|
|
852
|
-
|
|
1260
|
+
if (sum.input + sum.output + sum.cacheCreate + sum.cacheRead <= 0)
|
|
1261
|
+
continue; // skip empty window
|
|
1262
|
+
const report = _computeReportBlock(sum);
|
|
1263
|
+
const r = await submitSignedWindow(wk, sum, msgs, id, {
|
|
1264
|
+
apiBase,
|
|
1265
|
+
fetchImpl: doFetch,
|
|
1266
|
+
platform: "multi",
|
|
1267
|
+
now: opts.now,
|
|
1268
|
+
dryRun: !!args?.dry_run,
|
|
1269
|
+
report,
|
|
1270
|
+
});
|
|
1271
|
+
out.push({ window: wk, pillars: sum, ...r });
|
|
853
1272
|
}
|
|
854
|
-
return {
|
|
1273
|
+
return {
|
|
1274
|
+
platform: "multi",
|
|
1275
|
+
codename: id.codename,
|
|
1276
|
+
operator_id: id.operator_id,
|
|
1277
|
+
sources: detected.map((d) => d.platform),
|
|
1278
|
+
windows: out,
|
|
1279
|
+
};
|
|
855
1280
|
}
|
|
856
1281
|
|
|
857
|
-
const pulled = await pullByPlatform(platform, opts)
|
|
858
|
-
const targets = args?.window
|
|
859
|
-
|
|
1282
|
+
const pulled = await pullByPlatform(platform, opts);
|
|
1283
|
+
const targets = args?.window
|
|
1284
|
+
? pulled.windows.filter((w) => w.window === args.window)
|
|
1285
|
+
: pulled.windows;
|
|
1286
|
+
const out = [];
|
|
860
1287
|
for (const w of targets) {
|
|
861
1288
|
// Compute the cascade report block (mode + badges + health) for this window.
|
|
862
1289
|
// Pure math, computed locally, submitted alongside the 4 token pillars.
|
|
863
|
-
const report = _computeReportBlock(w.pillars)
|
|
1290
|
+
const report = _computeReportBlock(w.pillars);
|
|
864
1291
|
const r = await submitSignedWindow(w.window, w.pillars, w.messages, id, {
|
|
865
1292
|
apiBase,
|
|
866
1293
|
fetchImpl: doFetch,
|
|
@@ -868,22 +1295,33 @@ export async function callTool(name, args, opts = {}) {
|
|
|
868
1295
|
now: opts.now,
|
|
869
1296
|
dryRun: !!args?.dry_run,
|
|
870
1297
|
report,
|
|
871
|
-
})
|
|
872
|
-
out.push({ window: w.window, pillars: w.pillars, ...r })
|
|
1298
|
+
});
|
|
1299
|
+
out.push({ window: w.window, pillars: w.pillars, ...r });
|
|
873
1300
|
}
|
|
874
|
-
return {
|
|
1301
|
+
return {
|
|
1302
|
+
platform: pulled.platform,
|
|
1303
|
+
codename: id.codename,
|
|
1304
|
+
operator_id: id.operator_id,
|
|
1305
|
+
generatedAt: pulled.generatedAt,
|
|
1306
|
+
windows: out,
|
|
1307
|
+
};
|
|
875
1308
|
}
|
|
876
1309
|
|
|
877
|
-
if (name ===
|
|
878
|
-
if (!args?.text)
|
|
879
|
-
|
|
880
|
-
|
|
1310
|
+
if (name === "submit_paste") {
|
|
1311
|
+
if (!args?.text)
|
|
1312
|
+
throw new Error("submit_paste requires a non-empty `text` argument.");
|
|
1313
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
1314
|
+
return {
|
|
1315
|
+
status: "error",
|
|
1316
|
+
reason: "input_too_large",
|
|
1317
|
+
detail: `text exceeds ${MAX_INPUT} chars (${args.text.length}). Paste only the token-count table, not full output.`,
|
|
1318
|
+
};
|
|
881
1319
|
}
|
|
882
1320
|
// Local preview first — also validates the paste is parseable before any POST.
|
|
883
|
-
const pillars = parsePillars(args.text)
|
|
884
|
-
const c = withParseWarnings(pillars, cascade(pillars))
|
|
885
|
-
const codename = String(args?.codename ||
|
|
886
|
-
const card = narrate(c, codename ||
|
|
1321
|
+
const pillars = parsePillars(args.text);
|
|
1322
|
+
const c = withParseWarnings(pillars, cascade(pillars));
|
|
1323
|
+
const codename = String(args?.codename || "").trim();
|
|
1324
|
+
const card = narrate(c, codename || "This operator");
|
|
887
1325
|
|
|
888
1326
|
// No codename → cannot publish (the board endpoint requires it). Fail fast at the
|
|
889
1327
|
// tool boundary with a clear message instead of an opaque server 400.
|
|
@@ -891,104 +1329,198 @@ export async function callTool(name, args, opts = {}) {
|
|
|
891
1329
|
return {
|
|
892
1330
|
...c,
|
|
893
1331
|
card,
|
|
894
|
-
submission: {
|
|
895
|
-
|
|
1332
|
+
submission: {
|
|
1333
|
+
status: "not_submitted",
|
|
1334
|
+
reason: "codename_required",
|
|
1335
|
+
detail:
|
|
1336
|
+
"Pass a codename to publish to the board. Showing local preview only.",
|
|
1337
|
+
},
|
|
1338
|
+
};
|
|
896
1339
|
}
|
|
897
1340
|
|
|
898
1341
|
// Submit the RAW paste so the server re-parses + re-scores authoritatively — the
|
|
899
1342
|
// MCP's local cascade is only a preview; the board stays the single source of truth.
|
|
900
|
-
const stamp = uploadStamp({
|
|
1343
|
+
const stamp = uploadStamp({
|
|
1344
|
+
codename,
|
|
1345
|
+
pillars: c.pillars,
|
|
1346
|
+
source: "web_paste",
|
|
1347
|
+
});
|
|
901
1348
|
const res = await doFetch(`${apiBase}/api/v1/ingest-paste`, {
|
|
902
|
-
method:
|
|
903
|
-
headers: {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1349
|
+
method: "POST",
|
|
1350
|
+
headers: {
|
|
1351
|
+
"content-type": "application/json",
|
|
1352
|
+
accept: "application/json",
|
|
1353
|
+
},
|
|
1354
|
+
body: JSON.stringify({
|
|
1355
|
+
codename,
|
|
1356
|
+
raw_paste: String(args?.text || ""),
|
|
1357
|
+
...stamp,
|
|
1358
|
+
}),
|
|
1359
|
+
});
|
|
1360
|
+
let ack;
|
|
1361
|
+
try {
|
|
1362
|
+
ack = await res.json();
|
|
1363
|
+
} catch {
|
|
1364
|
+
ack = {
|
|
1365
|
+
status: "error",
|
|
1366
|
+
detail: `HTTP ${res.status} (non-JSON response)`,
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
const ranked = !!(
|
|
1370
|
+
res.ok &&
|
|
1371
|
+
ack.verification_tier === "verified" &&
|
|
1372
|
+
ack.persisted === true
|
|
1373
|
+
);
|
|
1374
|
+
return {
|
|
1375
|
+
...c,
|
|
1376
|
+
card,
|
|
1377
|
+
ranked,
|
|
1378
|
+
submission: { ...stamp, httpStatus: res.status, ranked, ...ack },
|
|
1379
|
+
};
|
|
910
1380
|
}
|
|
911
1381
|
|
|
912
|
-
if (name ===
|
|
1382
|
+
if (name === "tokenpull") {
|
|
913
1383
|
// Local read → 4 windows of pillars → cascade each. Token-only, on-device.
|
|
914
|
-
const platform = args?.platform ||
|
|
915
|
-
const pulled = await pullByPlatform(platform, opts)
|
|
1384
|
+
const platform = args?.platform || "claude";
|
|
1385
|
+
const pulled = await pullByPlatform(platform, opts);
|
|
916
1386
|
const windows = pulled.windows.map((w) => {
|
|
917
|
-
const c = cascade(w.pillars)
|
|
918
|
-
return {
|
|
919
|
-
|
|
920
|
-
|
|
1387
|
+
const c = cascade(w.pillars);
|
|
1388
|
+
return {
|
|
1389
|
+
window: w.window,
|
|
1390
|
+
messages: w.messages,
|
|
1391
|
+
pillars: w.pillars,
|
|
1392
|
+
cascade: c,
|
|
1393
|
+
card: narrate(c, `${w.window} ${platform}`),
|
|
1394
|
+
};
|
|
1395
|
+
});
|
|
1396
|
+
return {
|
|
1397
|
+
platform: pulled.platform,
|
|
1398
|
+
estimated: pulled.estimated || false,
|
|
1399
|
+
...(pulled.ioRatio ? { ioRatio: pulled.ioRatio } : {}),
|
|
1400
|
+
generatedAt: pulled.generatedAt,
|
|
1401
|
+
files: pulled.files,
|
|
1402
|
+
totalMessages: pulled.totalMessages,
|
|
1403
|
+
windows,
|
|
1404
|
+
};
|
|
921
1405
|
}
|
|
922
1406
|
|
|
923
|
-
if (name ===
|
|
1407
|
+
if (name === "tokenpull_submit") {
|
|
924
1408
|
// Pull local usage, then publish each window's CANONICAL pillars to the board
|
|
925
1409
|
// (server re-scores). The board stays platform-agnostic via the 4 pillars; the
|
|
926
1410
|
// source platform rides along as a tag. Conversion already happened in the adapter.
|
|
927
|
-
const codename = String(args?.codename ||
|
|
928
|
-
const pulled = await pullByPlatform(args?.platform ||
|
|
929
|
-
const targets = args?.window
|
|
930
|
-
|
|
1411
|
+
const codename = String(args?.codename || "").trim();
|
|
1412
|
+
const pulled = await pullByPlatform(args?.platform || "claude", opts);
|
|
1413
|
+
const targets = args?.window
|
|
1414
|
+
? pulled.windows.filter((w) => w.window === args.window)
|
|
1415
|
+
: pulled.windows;
|
|
1416
|
+
const out = [];
|
|
931
1417
|
for (const w of targets) {
|
|
932
|
-
const c = cascade(w.pillars)
|
|
933
|
-
const card = narrate(c, `${w.window} window`)
|
|
1418
|
+
const c = cascade(w.pillars);
|
|
1419
|
+
const card = narrate(c, `${w.window} window`);
|
|
934
1420
|
if (!codename) {
|
|
935
|
-
out.push({
|
|
936
|
-
|
|
1421
|
+
out.push({
|
|
1422
|
+
window: w.window,
|
|
1423
|
+
pillars: w.pillars,
|
|
1424
|
+
cascade: c,
|
|
1425
|
+
card,
|
|
1426
|
+
submission: { status: "not_submitted", reason: "codename_required" },
|
|
1427
|
+
});
|
|
1428
|
+
continue;
|
|
937
1429
|
}
|
|
938
1430
|
// canonical pillars → "input output cacheCreate cacheRead" (the parser's 4-bare-number form)
|
|
939
|
-
const rawPaste = `${w.pillars.input} ${w.pillars.output} ${w.pillars.cacheCreate} ${w.pillars.cacheRead}
|
|
940
|
-
const windowType = WINDOW_TYPE[w.window] || w.window
|
|
941
|
-
const stamp = uploadStamp({
|
|
1431
|
+
const rawPaste = `${w.pillars.input} ${w.pillars.output} ${w.pillars.cacheCreate} ${w.pillars.cacheRead}`;
|
|
1432
|
+
const windowType = WINDOW_TYPE[w.window] || w.window;
|
|
1433
|
+
const stamp = uploadStamp({
|
|
1434
|
+
codename,
|
|
1435
|
+
window: windowType,
|
|
1436
|
+
pillars: w.pillars,
|
|
1437
|
+
platform: pulled.platform,
|
|
1438
|
+
});
|
|
942
1439
|
const res = await doFetch(`${apiBase}/api/v1/ingest-paste`, {
|
|
943
|
-
method:
|
|
944
|
-
headers: {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1440
|
+
method: "POST",
|
|
1441
|
+
headers: {
|
|
1442
|
+
"content-type": "application/json",
|
|
1443
|
+
accept: "application/json",
|
|
1444
|
+
},
|
|
1445
|
+
body: JSON.stringify({
|
|
1446
|
+
codename,
|
|
1447
|
+
raw_paste: rawPaste,
|
|
1448
|
+
window_type: windowType,
|
|
1449
|
+
telemetry: { platform: { primary: pulled.platform } },
|
|
1450
|
+
...stamp,
|
|
1451
|
+
}),
|
|
1452
|
+
});
|
|
1453
|
+
let ack;
|
|
1454
|
+
try {
|
|
1455
|
+
ack = await res.json();
|
|
1456
|
+
} catch {
|
|
1457
|
+
ack = { status: "error", detail: `HTTP ${res.status} (non-JSON)` };
|
|
1458
|
+
}
|
|
949
1459
|
// ranked = actually on the board (verified + persisted), not just received — parity
|
|
950
1460
|
// with submit_verified (submit.mjs). An unenrolled/revoked device gets 202 but is NEVER ranked.
|
|
951
|
-
const ranked = !!(
|
|
952
|
-
|
|
1461
|
+
const ranked = !!(
|
|
1462
|
+
res.ok &&
|
|
1463
|
+
ack.verification_tier === "verified" &&
|
|
1464
|
+
ack.persisted === true
|
|
1465
|
+
);
|
|
1466
|
+
out.push({
|
|
1467
|
+
window: w.window,
|
|
1468
|
+
pillars: w.pillars,
|
|
1469
|
+
cascade: c,
|
|
1470
|
+
card,
|
|
1471
|
+
ranked,
|
|
1472
|
+
submission: { ...stamp, httpStatus: res.status, ranked, ...ack },
|
|
1473
|
+
});
|
|
953
1474
|
}
|
|
954
|
-
return {
|
|
1475
|
+
return {
|
|
1476
|
+
platform: pulled.platform,
|
|
1477
|
+
codename: codename || null,
|
|
1478
|
+
generatedAt: pulled.generatedAt,
|
|
1479
|
+
windows: out,
|
|
1480
|
+
};
|
|
955
1481
|
}
|
|
956
1482
|
|
|
957
|
-
if (name ===
|
|
1483
|
+
if (name === "rank_windows") {
|
|
958
1484
|
// Score up to 4 named window pastes independently. Named-key parsing only —
|
|
959
1485
|
// positional is unsafe here because dashboards list cache_read before cache_create
|
|
960
1486
|
// (the WINDOWED_PROFILES swap gotcha). Each window goes through parsePillars →
|
|
961
1487
|
// cascade → narrate individually; results are collected into a windows[] array
|
|
962
1488
|
// in the same shape as tokenpull output for easy follow-up with tokenpull_submit.
|
|
963
|
-
const WINDOW_KEYS = [
|
|
964
|
-
const sourceTool = args?.source_tool || null
|
|
1489
|
+
const WINDOW_KEYS = ["7d", "30d", "90d", "all"];
|
|
1490
|
+
const sourceTool = args?.source_tool || null;
|
|
965
1491
|
// E2: reject any oversized window paste up front (token tables are tiny).
|
|
966
1492
|
for (const wk of WINDOW_KEYS) {
|
|
967
|
-
const v = args?.[wk]
|
|
968
|
-
if (typeof v ===
|
|
969
|
-
return {
|
|
1493
|
+
const v = args?.[wk];
|
|
1494
|
+
if (typeof v === "string" && v.length > MAX_INPUT) {
|
|
1495
|
+
return {
|
|
1496
|
+
status: "error",
|
|
1497
|
+
reason: "input_too_large",
|
|
1498
|
+
detail: `window '${wk}' exceeds ${MAX_INPUT} chars (${v.length}). Paste only the token-count table.`,
|
|
1499
|
+
};
|
|
970
1500
|
}
|
|
971
1501
|
}
|
|
972
|
-
const windows = []
|
|
1502
|
+
const windows = [];
|
|
973
1503
|
for (const wk of WINDOW_KEYS) {
|
|
974
|
-
const text = args?.[wk]
|
|
975
|
-
if (!text || typeof text !==
|
|
976
|
-
const pillars = parsePillars(text)
|
|
977
|
-
const c = withParseWarnings(pillars, cascade(pillars))
|
|
978
|
-
const card = narrate(c, `${wk} window`)
|
|
979
|
-
windows.push({ window: wk, pillars, cascade: c, card })
|
|
1504
|
+
const text = args?.[wk];
|
|
1505
|
+
if (!text || typeof text !== "string" || !text.trim()) continue;
|
|
1506
|
+
const pillars = parsePillars(text);
|
|
1507
|
+
const c = withParseWarnings(pillars, cascade(pillars));
|
|
1508
|
+
const card = narrate(c, `${wk} window`);
|
|
1509
|
+
windows.push({ window: wk, pillars, cascade: c, card });
|
|
980
1510
|
}
|
|
981
1511
|
if (windows.length === 0) {
|
|
982
|
-
throw new Error(
|
|
1512
|
+
throw new Error(
|
|
1513
|
+
"rank_windows requires at least one window paste (7d, 30d, 90d, or all).",
|
|
1514
|
+
);
|
|
983
1515
|
}
|
|
984
1516
|
return {
|
|
985
1517
|
windows,
|
|
986
1518
|
source_tool: sourceTool,
|
|
987
|
-
note:
|
|
988
|
-
}
|
|
1519
|
+
note: "Local preview only — use tokenpull_submit to publish to the board.",
|
|
1520
|
+
};
|
|
989
1521
|
}
|
|
990
1522
|
|
|
991
|
-
if (name ===
|
|
1523
|
+
if (name === "watch_tokenpull") {
|
|
992
1524
|
// Poll the local token logs at a configurable interval and return the cascade
|
|
993
1525
|
// diff whenever new sessions appear. One poll cycle per MCP call — the client
|
|
994
1526
|
// is responsible for re-calling at the desired cadence (MCP tools are stateless;
|
|
@@ -996,44 +1528,63 @@ export async function callTool(name, args, opts = {}) {
|
|
|
996
1528
|
//
|
|
997
1529
|
// With submit:true + an enrolled device, this also signs + POSTs the watched window
|
|
998
1530
|
// to the verified ingest path each poll (the server dedups identical re-submits).
|
|
999
|
-
const platform = args?.platform ||
|
|
1000
|
-
const watchWindow = args?.window ||
|
|
1001
|
-
const intervalS = Math.max(10, Number(args?.interval_s) || 60)
|
|
1531
|
+
const platform = args?.platform || "claude";
|
|
1532
|
+
const watchWindow = args?.window || "7d";
|
|
1533
|
+
const intervalS = Math.max(10, Number(args?.interval_s) || 60);
|
|
1002
1534
|
|
|
1003
|
-
const pulled = await pullByPlatform(platform, opts)
|
|
1004
|
-
const win = pulled.windows.find((w) => w.window === watchWindow)
|
|
1005
|
-
if (!win)
|
|
1535
|
+
const pulled = await pullByPlatform(platform, opts);
|
|
1536
|
+
const win = pulled.windows.find((w) => w.window === watchWindow);
|
|
1537
|
+
if (!win)
|
|
1538
|
+
throw new Error(
|
|
1539
|
+
`watch_tokenpull: window '${watchWindow}' not found in pull result.`,
|
|
1540
|
+
);
|
|
1006
1541
|
|
|
1007
|
-
const c = cascade(win.pillars)
|
|
1008
|
-
const card = narrate(c, `${watchWindow} ${platform}`)
|
|
1542
|
+
const c = cascade(win.pillars);
|
|
1543
|
+
const card = narrate(c, `${watchWindow} ${platform}`);
|
|
1009
1544
|
|
|
1010
1545
|
// AUTH.WIRE (D7 §7): when submit is on AND the device is enrolled, sign + POST the
|
|
1011
1546
|
// watched window to the verified ingest path. Default OFF = preview only. The server
|
|
1012
1547
|
// dedups identical re-submits (exact snapshot_hash → 422), so re-calling is safe.
|
|
1013
|
-
let auth_submit = null
|
|
1548
|
+
let auth_submit = null;
|
|
1014
1549
|
if (args?.submit === true) {
|
|
1015
|
-
const id = opts.identity || ensureIdentity()
|
|
1550
|
+
const id = opts.identity || ensureIdentity();
|
|
1016
1551
|
if (id.codename && id.operator_id && id.private_key_pkcs8_b64) {
|
|
1017
1552
|
// E3: client-side cooldown — at most one auto-submit per platform+window per 5 min.
|
|
1018
1553
|
// Prevents a fast poll loop from churning the board even before the server's
|
|
1019
1554
|
// hash-dedup kicks in. Armed only on a non-error outcome so failed submits retry.
|
|
1020
|
-
const clockNow = typeof opts.now ===
|
|
1021
|
-
const cdKey = `${pulled.platform}:${watchWindow}
|
|
1022
|
-
const last = _lastWatchSubmitAt.get(cdKey)
|
|
1555
|
+
const clockNow = typeof opts.now === "number" ? opts.now : Date.now();
|
|
1556
|
+
const cdKey = `${pulled.platform}:${watchWindow}`;
|
|
1557
|
+
const last = _lastWatchSubmitAt.get(cdKey);
|
|
1023
1558
|
if (last != null && clockNow - last < WATCH_SUBMIT_COOLDOWN_MS) {
|
|
1024
|
-
const waitS = Math.ceil(
|
|
1025
|
-
|
|
1559
|
+
const waitS = Math.ceil(
|
|
1560
|
+
(WATCH_SUBMIT_COOLDOWN_MS - (clockNow - last)) / 1000,
|
|
1561
|
+
);
|
|
1562
|
+
auth_submit = {
|
|
1563
|
+
status: "cooldown",
|
|
1564
|
+
detail: `auto-submit for '${cdKey}' on cooldown — next in ~${waitS}s (max once / 5 min).`,
|
|
1565
|
+
retry_after_s: waitS,
|
|
1566
|
+
};
|
|
1026
1567
|
} else {
|
|
1027
|
-
auth_submit = await submitSignedWindow(
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1568
|
+
auth_submit = await submitSignedWindow(
|
|
1569
|
+
watchWindow,
|
|
1570
|
+
win.pillars,
|
|
1571
|
+
win.messages,
|
|
1572
|
+
id,
|
|
1573
|
+
{
|
|
1574
|
+
apiBase,
|
|
1575
|
+
fetchImpl: doFetch,
|
|
1576
|
+
platform: pulled.platform,
|
|
1577
|
+
now: opts.now,
|
|
1578
|
+
},
|
|
1579
|
+
);
|
|
1580
|
+
if (auth_submit?.status !== "error")
|
|
1581
|
+
_lastWatchSubmitAt.set(cdKey, clockNow);
|
|
1034
1582
|
}
|
|
1035
1583
|
} else {
|
|
1036
|
-
auth_submit = {
|
|
1584
|
+
auth_submit = {
|
|
1585
|
+
status: "not_enrolled",
|
|
1586
|
+
detail: "Run `npx sigrank-mcp enroll` to auto-submit verified runs.",
|
|
1587
|
+
};
|
|
1037
1588
|
}
|
|
1038
1589
|
}
|
|
1039
1590
|
return {
|
|
@@ -1046,154 +1597,210 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1046
1597
|
generatedAt: pulled.generatedAt,
|
|
1047
1598
|
poll_interval_s: intervalS,
|
|
1048
1599
|
auth_submit,
|
|
1049
|
-
note:
|
|
1050
|
-
}
|
|
1600
|
+
note: "One snapshot per call — re-call at your poll interval to detect changes.",
|
|
1601
|
+
};
|
|
1051
1602
|
}
|
|
1052
1603
|
|
|
1053
|
-
if (name ===
|
|
1054
|
-
const platform = args?.platform ||
|
|
1055
|
-
const WINS = [
|
|
1604
|
+
if (name === "tokenpull_compare") {
|
|
1605
|
+
const platform = args?.platform || "claude";
|
|
1606
|
+
const WINS = ["7d", "30d", "90d", "all"];
|
|
1056
1607
|
|
|
1057
1608
|
// Pull all four sources in parallel (verifiers are now async via execFile)
|
|
1058
1609
|
const [tpResult, ccPillars, tdPillars, tsPillars] = await Promise.all([
|
|
1059
1610
|
pullByPlatform(platform, opts).catch(() => null),
|
|
1060
1611
|
_ccusagePillars(platform).catch(() => null),
|
|
1061
|
-
(platform ===
|
|
1612
|
+
(platform === "claude"
|
|
1613
|
+
? _tokenDashPillars()
|
|
1614
|
+
: Promise.resolve(null)
|
|
1615
|
+
).catch(() => null),
|
|
1062
1616
|
_tokscalePillars(platform).catch(() => null),
|
|
1063
|
-
])
|
|
1617
|
+
]);
|
|
1064
1618
|
|
|
1065
1619
|
// Build tokenpull window lookup
|
|
1066
|
-
const tpByWin = {}
|
|
1067
|
-
for (const w of
|
|
1620
|
+
const tpByWin = {};
|
|
1621
|
+
for (const w of tpResult?.windows ?? []) tpByWin[w.window] = w.pillars;
|
|
1068
1622
|
|
|
1069
1623
|
// Helper: delta % vs tokenpull baseline
|
|
1070
|
-
const delta = (val, base) =>
|
|
1624
|
+
const delta = (val, base) =>
|
|
1625
|
+
base > 0 ? +(((val - base) / base) * 100).toFixed(1) : null;
|
|
1071
1626
|
|
|
1072
1627
|
// Build per-source per-window comparison
|
|
1073
1628
|
const SOURCES = [
|
|
1074
|
-
{
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1629
|
+
{
|
|
1630
|
+
source: "tokenpull",
|
|
1631
|
+
note: "JSONL deduped by msg id — canon source",
|
|
1632
|
+
byWin: tpByWin,
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
source: "ccusage",
|
|
1636
|
+
note: "ccusage CLI — monthly only",
|
|
1637
|
+
byWin: ccPillars ?? {},
|
|
1638
|
+
},
|
|
1639
|
+
{
|
|
1640
|
+
source: "token-dash",
|
|
1641
|
+
note: "token-dashboard SQLite — all-time only",
|
|
1642
|
+
byWin: tdPillars ?? {},
|
|
1643
|
+
},
|
|
1644
|
+
{
|
|
1645
|
+
source: "tokscale",
|
|
1646
|
+
note: "tokscale_report.json — all-time only",
|
|
1647
|
+
byWin: tsPillars ?? {},
|
|
1648
|
+
},
|
|
1649
|
+
];
|
|
1079
1650
|
|
|
1080
|
-
const comparison = {}
|
|
1651
|
+
const comparison = {};
|
|
1081
1652
|
for (const win of WINS) {
|
|
1082
|
-
const baseP = tpByWin[win]
|
|
1083
|
-
comparison[win] = SOURCES
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1653
|
+
const baseP = tpByWin[win];
|
|
1654
|
+
comparison[win] = SOURCES.filter((s) => s.byWin[win] != null).map((s) => {
|
|
1655
|
+
const p = s.byWin[win];
|
|
1656
|
+
const cas = cascade(p);
|
|
1657
|
+
const entry = {
|
|
1658
|
+
source: s.source,
|
|
1659
|
+
note: s.note,
|
|
1660
|
+
pillars: p,
|
|
1661
|
+
cascade: {
|
|
1662
|
+
yield: cas.yield,
|
|
1663
|
+
snr: cas.snr,
|
|
1664
|
+
leverage: cas.leverage,
|
|
1665
|
+
velocity: cas.velocity,
|
|
1666
|
+
dev10x: cas.dev10x,
|
|
1667
|
+
class: cas.class,
|
|
1668
|
+
},
|
|
1669
|
+
};
|
|
1670
|
+
if (s.source !== "tokenpull" && baseP) {
|
|
1671
|
+
entry.delta_vs_tokenpull = {
|
|
1672
|
+
input: delta(p.input, baseP.input),
|
|
1673
|
+
output: delta(p.output, baseP.output),
|
|
1674
|
+
cacheCreate: delta(p.cacheCreate, baseP.cacheCreate),
|
|
1675
|
+
cacheRead: delta(p.cacheRead, baseP.cacheRead),
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
return entry;
|
|
1679
|
+
});
|
|
1104
1680
|
}
|
|
1105
1681
|
|
|
1106
1682
|
// Sources available summary
|
|
1107
|
-
const available = SOURCES
|
|
1108
|
-
|
|
1109
|
-
|
|
1683
|
+
const available = SOURCES.filter(
|
|
1684
|
+
(s) => Object.keys(s.byWin).length > 0,
|
|
1685
|
+
).map((s) => s.source);
|
|
1110
1686
|
|
|
1111
1687
|
return {
|
|
1112
1688
|
platform,
|
|
1113
1689
|
estimated: tpResult?.estimated ?? false,
|
|
1114
1690
|
generatedAt: tpResult?.generatedAt ?? new Date().toISOString(),
|
|
1115
1691
|
sources_available: available,
|
|
1116
|
-
sources_missing: SOURCES.map(s => s.source).filter(
|
|
1692
|
+
sources_missing: SOURCES.map((s) => s.source).filter(
|
|
1693
|
+
(s) => !available.includes(s),
|
|
1694
|
+
),
|
|
1117
1695
|
comparison,
|
|
1118
|
-
}
|
|
1696
|
+
};
|
|
1119
1697
|
}
|
|
1120
1698
|
|
|
1121
|
-
if (name ===
|
|
1699
|
+
if (name === "simulate_change") {
|
|
1122
1700
|
// The first prescriptive tool — "what if I changed my token mix?"
|
|
1123
1701
|
// Pure local math: parse current pillars, apply proposed changes, run the
|
|
1124
1702
|
// cascade on both, return the delta. No network, no submission.
|
|
1125
|
-
if (!args?.text)
|
|
1126
|
-
|
|
1127
|
-
|
|
1703
|
+
if (!args?.text)
|
|
1704
|
+
throw new Error(
|
|
1705
|
+
"simulate_change requires a non-empty `text` argument (current pillars).",
|
|
1706
|
+
);
|
|
1707
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
1708
|
+
return {
|
|
1709
|
+
status: "error",
|
|
1710
|
+
reason: "input_too_large",
|
|
1711
|
+
detail: `text exceeds ${MAX_INPUT} chars.`,
|
|
1712
|
+
};
|
|
1128
1713
|
}
|
|
1129
|
-
if (!args?.changes || typeof args.changes !==
|
|
1130
|
-
throw new Error(
|
|
1714
|
+
if (!args?.changes || typeof args.changes !== "object") {
|
|
1715
|
+
throw new Error(
|
|
1716
|
+
"simulate_change requires a `changes` object with at least one pillar change.",
|
|
1717
|
+
);
|
|
1131
1718
|
}
|
|
1132
1719
|
|
|
1133
|
-
const currentPillars = parsePillars(args.text)
|
|
1134
|
-
const current = withParseWarnings(currentPillars, cascade(currentPillars))
|
|
1720
|
+
const currentPillars = parsePillars(args.text);
|
|
1721
|
+
const current = withParseWarnings(currentPillars, cascade(currentPillars));
|
|
1135
1722
|
|
|
1136
1723
|
// Apply changes: each pillar is either an absolute number (replace) or a
|
|
1137
1724
|
// string starting with +/- (relative delta). Omitted pillars are unchanged.
|
|
1138
|
-
const PILLAR_KEYS = [
|
|
1139
|
-
const simulated = { ...currentPillars }
|
|
1140
|
-
const appliedChanges = {}
|
|
1725
|
+
const PILLAR_KEYS = ["input", "output", "cacheCreate", "cacheRead"];
|
|
1726
|
+
const simulated = { ...currentPillars };
|
|
1727
|
+
const appliedChanges = {};
|
|
1141
1728
|
|
|
1142
1729
|
for (const key of PILLAR_KEYS) {
|
|
1143
|
-
if (args.changes[key] == null) continue
|
|
1144
|
-
const raw = args.changes[key]
|
|
1145
|
-
let newVal
|
|
1146
|
-
|
|
1147
|
-
if (typeof raw ===
|
|
1148
|
-
newVal = raw
|
|
1149
|
-
} else if (typeof raw ===
|
|
1150
|
-
const trimmed = raw.trim()
|
|
1151
|
-
if (trimmed.startsWith(
|
|
1730
|
+
if (args.changes[key] == null) continue;
|
|
1731
|
+
const raw = args.changes[key];
|
|
1732
|
+
let newVal;
|
|
1733
|
+
|
|
1734
|
+
if (typeof raw === "number") {
|
|
1735
|
+
newVal = raw;
|
|
1736
|
+
} else if (typeof raw === "string") {
|
|
1737
|
+
const trimmed = raw.trim();
|
|
1738
|
+
if (trimmed.startsWith("+") || trimmed.startsWith("-")) {
|
|
1152
1739
|
// Relative delta: add/subtract from current value
|
|
1153
|
-
const delta = Number(trimmed)
|
|
1740
|
+
const delta = Number(trimmed);
|
|
1154
1741
|
if (!Number.isFinite(delta)) {
|
|
1155
|
-
return {
|
|
1742
|
+
return {
|
|
1743
|
+
status: "error",
|
|
1744
|
+
reason: "invalid_change",
|
|
1745
|
+
detail: `changes.${key}: "${raw}" is not a valid relative delta.`,
|
|
1746
|
+
};
|
|
1156
1747
|
}
|
|
1157
|
-
newVal = currentPillars[key] + delta
|
|
1748
|
+
newVal = currentPillars[key] + delta;
|
|
1158
1749
|
} else {
|
|
1159
1750
|
// Absolute value as a string
|
|
1160
|
-
newVal = Number(trimmed)
|
|
1751
|
+
newVal = Number(trimmed);
|
|
1161
1752
|
}
|
|
1162
1753
|
} else {
|
|
1163
|
-
return {
|
|
1754
|
+
return {
|
|
1755
|
+
status: "error",
|
|
1756
|
+
reason: "invalid_change",
|
|
1757
|
+
detail: `changes.${key}: expected number or string, got ${typeof raw}.`,
|
|
1758
|
+
};
|
|
1164
1759
|
}
|
|
1165
1760
|
|
|
1166
1761
|
if (!Number.isFinite(newVal)) {
|
|
1167
|
-
return {
|
|
1762
|
+
return {
|
|
1763
|
+
status: "error",
|
|
1764
|
+
reason: "invalid_change",
|
|
1765
|
+
detail: `changes.${key}: result is not a finite number.`,
|
|
1766
|
+
};
|
|
1168
1767
|
}
|
|
1169
1768
|
// Clamp to non-negative — token counts can't be negative
|
|
1170
1769
|
if (newVal < 0) {
|
|
1171
|
-
return {
|
|
1770
|
+
return {
|
|
1771
|
+
status: "error",
|
|
1772
|
+
reason: "invalid_change",
|
|
1773
|
+
detail: `changes.${key}: result ${newVal} is negative — token counts must be >= 0.`,
|
|
1774
|
+
};
|
|
1172
1775
|
}
|
|
1173
1776
|
|
|
1174
|
-
simulated[key] = newVal
|
|
1777
|
+
simulated[key] = newVal;
|
|
1175
1778
|
appliedChanges[key] = {
|
|
1176
1779
|
from: currentPillars[key],
|
|
1177
1780
|
to: newVal,
|
|
1178
1781
|
delta: newVal - currentPillars[key],
|
|
1179
|
-
}
|
|
1782
|
+
};
|
|
1180
1783
|
}
|
|
1181
1784
|
|
|
1182
1785
|
if (Object.keys(appliedChanges).length === 0) {
|
|
1183
|
-
return {
|
|
1786
|
+
return {
|
|
1787
|
+
status: "error",
|
|
1788
|
+
reason: "no_changes",
|
|
1789
|
+
detail: "No pillar changes specified in the `changes` object.",
|
|
1790
|
+
};
|
|
1184
1791
|
}
|
|
1185
1792
|
|
|
1186
|
-
const simulatedResult = cascade(simulated)
|
|
1793
|
+
const simulatedResult = cascade(simulated);
|
|
1187
1794
|
|
|
1188
1795
|
// Compute deltas for every cascade metric
|
|
1189
1796
|
const metricDelta = (curr, sim) => {
|
|
1190
|
-
if (curr == null && sim == null) return null
|
|
1191
|
-
if (curr == null) return { from: null, to: sim, delta: null }
|
|
1192
|
-
if (sim == null) return { from: curr, to: null, delta: null }
|
|
1193
|
-
return { from: curr, to: sim, delta: Number((sim - curr).toFixed(4)) }
|
|
1194
|
-
}
|
|
1797
|
+
if (curr == null && sim == null) return null;
|
|
1798
|
+
if (curr == null) return { from: null, to: sim, delta: null };
|
|
1799
|
+
if (sim == null) return { from: curr, to: null, delta: null };
|
|
1800
|
+
return { from: curr, to: sim, delta: Number((sim - curr).toFixed(4)) };
|
|
1801
|
+
};
|
|
1195
1802
|
|
|
1196
|
-
const classChanged = current.class !== simulatedResult.class
|
|
1803
|
+
const classChanged = current.class !== simulatedResult.class;
|
|
1197
1804
|
|
|
1198
1805
|
return {
|
|
1199
1806
|
current: {
|
|
@@ -1206,7 +1813,12 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1206
1813
|
class: current.class,
|
|
1207
1814
|
},
|
|
1208
1815
|
simulated: {
|
|
1209
|
-
pillars: {
|
|
1816
|
+
pillars: {
|
|
1817
|
+
input: simulated.input,
|
|
1818
|
+
output: simulated.output,
|
|
1819
|
+
cacheCreate: simulated.cacheCreate,
|
|
1820
|
+
cacheRead: simulated.cacheRead,
|
|
1821
|
+
},
|
|
1210
1822
|
yield: simulatedResult.yield,
|
|
1211
1823
|
snr: simulatedResult.snr,
|
|
1212
1824
|
leverage: simulatedResult.leverage,
|
|
@@ -1223,135 +1835,160 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1223
1835
|
dev10x: metricDelta(current.dev10x, simulatedResult.dev10x),
|
|
1224
1836
|
},
|
|
1225
1837
|
class_changed: classChanged,
|
|
1226
|
-
...(classChanged
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1838
|
+
...(classChanged
|
|
1839
|
+
? { class_transition: `${current.class} → ${simulatedResult.class}` }
|
|
1840
|
+
: {}),
|
|
1841
|
+
...(simulatedResult.warnings
|
|
1842
|
+
? { simulated_warnings: simulatedResult.warnings }
|
|
1843
|
+
: {}),
|
|
1844
|
+
note: "Local simulation only — no submission. The actual score depends on server-side RS.xx weights and class thresholds.",
|
|
1845
|
+
};
|
|
1230
1846
|
}
|
|
1231
1847
|
|
|
1232
|
-
if (name ===
|
|
1233
|
-
if (!args?.text)
|
|
1234
|
-
|
|
1235
|
-
|
|
1848
|
+
if (name === "diagnose_cascade") {
|
|
1849
|
+
if (!args?.text)
|
|
1850
|
+
throw new Error(
|
|
1851
|
+
"diagnose_cascade requires a non-empty `text` argument (token pillars).",
|
|
1852
|
+
);
|
|
1853
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
1854
|
+
return {
|
|
1855
|
+
status: "error",
|
|
1856
|
+
reason: "input_too_large",
|
|
1857
|
+
detail: `text exceeds ${MAX_INPUT} chars.`,
|
|
1858
|
+
};
|
|
1236
1859
|
}
|
|
1237
1860
|
|
|
1238
|
-
const p = parsePillars(args.text)
|
|
1239
|
-
const result = withParseWarnings(p, cascade(p))
|
|
1240
|
-
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = p
|
|
1241
|
-
const diagnosis = []
|
|
1861
|
+
const p = parsePillars(args.text);
|
|
1862
|
+
const result = withParseWarnings(p, cascade(p));
|
|
1863
|
+
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = p;
|
|
1864
|
+
const diagnosis = [];
|
|
1242
1865
|
|
|
1243
1866
|
// Cache leverage check — the #1 cascade efficiency signal
|
|
1244
|
-
const leverage = result.leverage
|
|
1867
|
+
const leverage = result.leverage;
|
|
1245
1868
|
if (leverage !== null) {
|
|
1246
1869
|
if (leverage < 10) {
|
|
1247
1870
|
diagnosis.push({
|
|
1248
|
-
metric:
|
|
1249
|
-
severity:
|
|
1871
|
+
metric: "cache_leverage",
|
|
1872
|
+
severity: "critical",
|
|
1250
1873
|
finding: `Cache leverage is ${leverage}× — you're reading only ${leverage}× your fresh input from cache. TRANSMITTER-class operators hit 200×+.`,
|
|
1251
|
-
recommendation:
|
|
1874
|
+
recommendation:
|
|
1875
|
+
"Increase context reuse: load prior session context, use longer conversation threads, reference earlier outputs.",
|
|
1252
1876
|
estimated_yield_impact: `+${Math.round((1 - leverage / 50) * 100)}% Υ potential`,
|
|
1253
|
-
})
|
|
1877
|
+
});
|
|
1254
1878
|
} else if (leverage < 50) {
|
|
1255
1879
|
diagnosis.push({
|
|
1256
|
-
metric:
|
|
1257
|
-
severity:
|
|
1880
|
+
metric: "cache_leverage",
|
|
1881
|
+
severity: "warning",
|
|
1258
1882
|
finding: `Cache leverage is ${leverage}× — decent but below the ARCH+ threshold (~100×+).`,
|
|
1259
|
-
recommendation:
|
|
1883
|
+
recommendation:
|
|
1884
|
+
"Push cache reads higher by reusing prior context more aggressively.",
|
|
1260
1885
|
estimated_yield_impact: `+${Math.round((1 - leverage / 100) * 50)}% Υ potential`,
|
|
1261
|
-
})
|
|
1886
|
+
});
|
|
1262
1887
|
}
|
|
1263
1888
|
}
|
|
1264
1889
|
|
|
1265
1890
|
// Velocity check — output per input
|
|
1266
|
-
const velocity = result.velocity
|
|
1891
|
+
const velocity = result.velocity;
|
|
1267
1892
|
if (velocity !== null) {
|
|
1268
1893
|
if (velocity < 0.5) {
|
|
1269
1894
|
diagnosis.push({
|
|
1270
|
-
metric:
|
|
1271
|
-
severity:
|
|
1895
|
+
metric: "velocity",
|
|
1896
|
+
severity: "critical",
|
|
1272
1897
|
finding: `Velocity is ${velocity} — generating only ${velocity}× your input as output. You're reading more than you produce.`,
|
|
1273
|
-
recommendation:
|
|
1898
|
+
recommendation:
|
|
1899
|
+
"Increase output: ask the agent to generate more code/text per turn, reduce over-reading.",
|
|
1274
1900
|
estimated_yield_impact: `+${Math.round((0.5 - velocity) * 100)}% Υ per 0.1 velocity gain`,
|
|
1275
|
-
})
|
|
1901
|
+
});
|
|
1276
1902
|
} else if (velocity < 1.0) {
|
|
1277
1903
|
diagnosis.push({
|
|
1278
|
-
metric:
|
|
1279
|
-
severity:
|
|
1904
|
+
metric: "velocity",
|
|
1905
|
+
severity: "warning",
|
|
1280
1906
|
finding: `Velocity is ${velocity} — below 1.0 (output < input). Healthy operators hit 1.5×+.`,
|
|
1281
|
-
recommendation:
|
|
1907
|
+
recommendation:
|
|
1908
|
+
"Generate more output per input token — larger edits, more complete responses.",
|
|
1282
1909
|
estimated_yield_impact: `+${Math.round((1 - velocity) * 30)}% Υ potential`,
|
|
1283
|
-
})
|
|
1910
|
+
});
|
|
1284
1911
|
}
|
|
1285
1912
|
}
|
|
1286
1913
|
|
|
1287
1914
|
// SNR check — signal-to-noise
|
|
1288
|
-
const snr = result.snr
|
|
1915
|
+
const snr = result.snr;
|
|
1289
1916
|
if (snr !== null && snr < 0.3) {
|
|
1290
1917
|
diagnosis.push({
|
|
1291
|
-
metric:
|
|
1292
|
-
severity:
|
|
1918
|
+
metric: "snr",
|
|
1919
|
+
severity: "warning",
|
|
1293
1920
|
finding: `SNR is ${snr} — less than 30% of your token flow is output. Input is dominating.`,
|
|
1294
|
-
recommendation:
|
|
1295
|
-
|
|
1296
|
-
|
|
1921
|
+
recommendation:
|
|
1922
|
+
"Reduce fresh input (reuse context) or increase output generation.",
|
|
1923
|
+
estimated_yield_impact:
|
|
1924
|
+
"Indirect — improves both velocity and leverage",
|
|
1925
|
+
});
|
|
1297
1926
|
}
|
|
1298
1927
|
|
|
1299
1928
|
// Cache creation ratio — are you over-committing?
|
|
1300
1929
|
if (cw > 0 && o > 0) {
|
|
1301
|
-
const commitRatio = cw / o
|
|
1930
|
+
const commitRatio = cw / o;
|
|
1302
1931
|
if (commitRatio > 20) {
|
|
1303
1932
|
diagnosis.push({
|
|
1304
|
-
metric:
|
|
1305
|
-
severity:
|
|
1933
|
+
metric: "cache_creation",
|
|
1934
|
+
severity: "info",
|
|
1306
1935
|
finding: `Cache creation is ${commitRatio.toFixed(1)}× your output — high commitment. This is fine if you're rereading it (check leverage), but wasteful if not.`,
|
|
1307
|
-
recommendation:
|
|
1308
|
-
|
|
1309
|
-
|
|
1936
|
+
recommendation:
|
|
1937
|
+
"Ensure you're rereading committed context. If leverage is low, you're writing cache you never read.",
|
|
1938
|
+
estimated_yield_impact: "Cost reduction, not Υ directly",
|
|
1939
|
+
});
|
|
1310
1940
|
}
|
|
1311
1941
|
}
|
|
1312
1942
|
|
|
1313
1943
|
// Input bloat — is fresh input too high relative to total?
|
|
1314
|
-
const total = i + o + cw + cr
|
|
1944
|
+
const total = i + o + cw + cr;
|
|
1315
1945
|
if (total > 0) {
|
|
1316
|
-
const inputPct = (i / total) * 100
|
|
1946
|
+
const inputPct = (i / total) * 100;
|
|
1317
1947
|
if (inputPct > 10) {
|
|
1318
1948
|
diagnosis.push({
|
|
1319
|
-
metric:
|
|
1320
|
-
severity:
|
|
1949
|
+
metric: "input_bloat",
|
|
1950
|
+
severity: "warning",
|
|
1321
1951
|
finding: `Fresh input is ${inputPct.toFixed(1)}% of your total token flow — high. Efficient operators keep input under 1% by leaning on cache.`,
|
|
1322
|
-
recommendation:
|
|
1952
|
+
recommendation:
|
|
1953
|
+
"Reduce fresh input by reusing prior context instead of re-pasting it.",
|
|
1323
1954
|
estimated_yield_impact: `+${Math.round((inputPct - 1) * 5)}% Υ potential`,
|
|
1324
|
-
})
|
|
1955
|
+
});
|
|
1325
1956
|
}
|
|
1326
1957
|
}
|
|
1327
1958
|
|
|
1328
1959
|
// 10xDEV check — is the full cascade compounding?
|
|
1329
1960
|
if (result.dev10x === null && cw === 0) {
|
|
1330
1961
|
diagnosis.push({
|
|
1331
|
-
metric:
|
|
1332
|
-
severity:
|
|
1333
|
-
finding:
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1962
|
+
metric: "10xdev",
|
|
1963
|
+
severity: "critical",
|
|
1964
|
+
finding:
|
|
1965
|
+
"No cache creation — the cascade cannot compound. You're operating in a non-compounding mode (like ChatGPT without prompt caching).",
|
|
1966
|
+
recommendation:
|
|
1967
|
+
"Switch to a platform with prompt caching (Claude Code) or enable caching if available.",
|
|
1968
|
+
estimated_yield_impact: "Enables the full cascade — potentially 10×+ Υ",
|
|
1969
|
+
});
|
|
1337
1970
|
} else if (result.dev10x !== null && result.dev10x < 1.0) {
|
|
1338
1971
|
diagnosis.push({
|
|
1339
|
-
metric:
|
|
1340
|
-
severity:
|
|
1972
|
+
metric: "10xdev",
|
|
1973
|
+
severity: "info",
|
|
1341
1974
|
finding: `10xDEV is ${result.dev10x} — below 1.0 (BASE threshold). The cascade is compounding but not strongly.`,
|
|
1342
|
-
recommendation:
|
|
1343
|
-
|
|
1344
|
-
|
|
1975
|
+
recommendation:
|
|
1976
|
+
"Improve both leverage AND velocity — 10xDEV = log10(transmission × commitment × reuse).",
|
|
1977
|
+
estimated_yield_impact: "Class tier improvement",
|
|
1978
|
+
});
|
|
1345
1979
|
}
|
|
1346
1980
|
|
|
1347
1981
|
// Sort by severity (critical > warning > info)
|
|
1348
|
-
const sevOrder = { critical: 0, warning: 1, info: 2 }
|
|
1349
|
-
diagnosis.sort((a, b) => sevOrder[a.severity] - sevOrder[b.severity])
|
|
1982
|
+
const sevOrder = { critical: 0, warning: 1, info: 2 };
|
|
1983
|
+
diagnosis.sort((a, b) => sevOrder[a.severity] - sevOrder[b.severity]);
|
|
1350
1984
|
|
|
1351
|
-
const healthScore = diagnosis.filter(
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1985
|
+
const healthScore = diagnosis.filter(
|
|
1986
|
+
(d) => d.severity === "critical",
|
|
1987
|
+
).length;
|
|
1988
|
+
const summary =
|
|
1989
|
+
healthScore === 0
|
|
1990
|
+
? `Cascade is healthy — Υ ${result.yield}, class ${result.class}. ${diagnosis.length} minor optimizations available.`
|
|
1991
|
+
: `Cascade has ${healthScore} critical leak${healthScore > 1 ? "s" : ""} — Υ ${result.yield}, class ${result.class}. Fix the critical items first.`;
|
|
1355
1992
|
|
|
1356
1993
|
return {
|
|
1357
1994
|
pillars: p,
|
|
@@ -1366,273 +2003,348 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1366
2003
|
},
|
|
1367
2004
|
diagnosis,
|
|
1368
2005
|
summary,
|
|
1369
|
-
}
|
|
2006
|
+
};
|
|
1370
2007
|
}
|
|
1371
2008
|
|
|
1372
|
-
if (name ===
|
|
1373
|
-
if (!args?.text)
|
|
1374
|
-
|
|
1375
|
-
|
|
2009
|
+
if (name === "suggest_improvements") {
|
|
2010
|
+
if (!args?.text)
|
|
2011
|
+
throw new Error(
|
|
2012
|
+
"suggest_improvements requires a non-empty `text` argument (token pillars).",
|
|
2013
|
+
);
|
|
2014
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
2015
|
+
return {
|
|
2016
|
+
status: "error",
|
|
2017
|
+
reason: "input_too_large",
|
|
2018
|
+
detail: `text exceeds ${MAX_INPUT} chars.`,
|
|
2019
|
+
};
|
|
1376
2020
|
}
|
|
1377
2021
|
|
|
1378
|
-
const p = parsePillars(args.text)
|
|
1379
|
-
const current = withParseWarnings(p, cascade(p))
|
|
1380
|
-
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = p
|
|
2022
|
+
const p = parsePillars(args.text);
|
|
2023
|
+
const current = withParseWarnings(p, cascade(p));
|
|
2024
|
+
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = p;
|
|
1381
2025
|
|
|
1382
2026
|
// Generate candidate improvements, simulate each, rank by Υ impact
|
|
1383
|
-
const candidates = []
|
|
2027
|
+
const candidates = [];
|
|
1384
2028
|
|
|
1385
2029
|
// Strategy 1: Increase cache reads (the #1 lever)
|
|
1386
|
-
const crBoosts = cr > 0 ? [1.5, 2, 3, 5] : []
|
|
2030
|
+
const crBoosts = cr > 0 ? [1.5, 2, 3, 5] : [];
|
|
1387
2031
|
for (const mult of crBoosts) {
|
|
1388
|
-
const sim = cascade({ ...p, cacheRead: Math.round(cr * mult) })
|
|
2032
|
+
const sim = cascade({ ...p, cacheRead: Math.round(cr * mult) });
|
|
1389
2033
|
if (sim.yield !== null) {
|
|
1390
2034
|
candidates.push({
|
|
1391
2035
|
action: `Increase cache reads by ${Math.round((mult - 1) * 100)}%`,
|
|
1392
|
-
pillar:
|
|
2036
|
+
pillar: "cacheRead",
|
|
1393
2037
|
delta: `+${Math.round(cr * (mult - 1)).toLocaleString()}`,
|
|
1394
2038
|
simulated_yield: sim.yield,
|
|
1395
2039
|
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1396
2040
|
class_after: sim.class,
|
|
1397
|
-
rationale:
|
|
1398
|
-
|
|
2041
|
+
rationale:
|
|
2042
|
+
"Cache reads are the strongest Υ multiplier. More reuse = higher leverage = higher yield.",
|
|
2043
|
+
});
|
|
1399
2044
|
}
|
|
1400
2045
|
}
|
|
1401
2046
|
// For zero-cache operators, suggest enabling caching with a starter amount
|
|
1402
2047
|
if (cr === 0 && cw === 0) {
|
|
1403
|
-
const starterAmounts = [
|
|
2048
|
+
const starterAmounts = [
|
|
2049
|
+
Math.round(i * 10),
|
|
2050
|
+
Math.round(i * 50),
|
|
2051
|
+
Math.round(i * 100),
|
|
2052
|
+
];
|
|
1404
2053
|
for (const amt of starterAmounts) {
|
|
1405
|
-
const sim = cascade({
|
|
2054
|
+
const sim = cascade({
|
|
2055
|
+
...p,
|
|
2056
|
+
cacheCreate: Math.round(amt * 0.5),
|
|
2057
|
+
cacheRead: amt,
|
|
2058
|
+
});
|
|
1406
2059
|
if (sim.yield !== null && sim.yield > 0) {
|
|
1407
2060
|
candidates.push({
|
|
1408
2061
|
action: `Enable caching with ${amt.toLocaleString()} cache reads`,
|
|
1409
|
-
pillar:
|
|
2062
|
+
pillar: "cacheRead",
|
|
1410
2063
|
delta: `+${amt.toLocaleString()}`,
|
|
1411
2064
|
simulated_yield: sim.yield,
|
|
1412
2065
|
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1413
2066
|
class_after: sim.class,
|
|
1414
|
-
rationale:
|
|
1415
|
-
|
|
2067
|
+
rationale:
|
|
2068
|
+
"You have no cache — enabling it unlocks the cascade. Start by reusing prior context.",
|
|
2069
|
+
});
|
|
1416
2070
|
}
|
|
1417
2071
|
}
|
|
1418
2072
|
}
|
|
1419
2073
|
|
|
1420
2074
|
// Strategy 2: Reduce fresh input (Υ = Cr·O/I² — input is squared in the denominator)
|
|
1421
|
-
const inputReductions = [0.9, 0.75, 0.5]
|
|
2075
|
+
const inputReductions = [0.9, 0.75, 0.5];
|
|
1422
2076
|
for (const mult of inputReductions) {
|
|
1423
|
-
const newInput = Math.round(i * mult)
|
|
1424
|
-
if (newInput < 1) continue
|
|
1425
|
-
const sim = cascade({ ...p, input: newInput })
|
|
2077
|
+
const newInput = Math.round(i * mult);
|
|
2078
|
+
if (newInput < 1) continue;
|
|
2079
|
+
const sim = cascade({ ...p, input: newInput });
|
|
1426
2080
|
if (sim.yield !== null) {
|
|
1427
2081
|
candidates.push({
|
|
1428
2082
|
action: `Reduce fresh input by ${Math.round((1 - mult) * 100)}%`,
|
|
1429
|
-
pillar:
|
|
2083
|
+
pillar: "input",
|
|
1430
2084
|
delta: `-${Math.round(i * (1 - mult)).toLocaleString()}`,
|
|
1431
2085
|
simulated_yield: sim.yield,
|
|
1432
2086
|
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1433
2087
|
class_after: sim.class,
|
|
1434
|
-
rationale:
|
|
1435
|
-
|
|
2088
|
+
rationale:
|
|
2089
|
+
"Input is squared in the Υ denominator (Υ = Cr·O/I²). Reducing input has a quadratic payoff.",
|
|
2090
|
+
});
|
|
1436
2091
|
}
|
|
1437
2092
|
}
|
|
1438
2093
|
|
|
1439
2094
|
// Strategy 3: Increase output
|
|
1440
|
-
const outputBoosts = [1.25, 1.5, 2]
|
|
2095
|
+
const outputBoosts = [1.25, 1.5, 2];
|
|
1441
2096
|
for (const mult of outputBoosts) {
|
|
1442
|
-
const sim = cascade({ ...p, output: Math.round(o * mult) })
|
|
2097
|
+
const sim = cascade({ ...p, output: Math.round(o * mult) });
|
|
1443
2098
|
if (sim.yield !== null) {
|
|
1444
2099
|
candidates.push({
|
|
1445
2100
|
action: `Increase output by ${Math.round((mult - 1) * 100)}%`,
|
|
1446
|
-
pillar:
|
|
2101
|
+
pillar: "output",
|
|
1447
2102
|
delta: `+${Math.round(o * (mult - 1)).toLocaleString()}`,
|
|
1448
2103
|
simulated_yield: sim.yield,
|
|
1449
2104
|
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1450
2105
|
class_after: sim.class,
|
|
1451
|
-
rationale:
|
|
1452
|
-
|
|
2106
|
+
rationale:
|
|
2107
|
+
"Output is a linear multiplier in Υ. More output per session = higher yield.",
|
|
2108
|
+
});
|
|
1453
2109
|
}
|
|
1454
2110
|
}
|
|
1455
2111
|
|
|
1456
2112
|
// Strategy 4: Optimize cache creation (reduce if over-committing)
|
|
1457
2113
|
if (cw > o * 10) {
|
|
1458
|
-
const sim = cascade({ ...p, cacheCreate: Math.round(o * 5) })
|
|
2114
|
+
const sim = cascade({ ...p, cacheCreate: Math.round(o * 5) });
|
|
1459
2115
|
if (sim.yield !== null) {
|
|
1460
2116
|
candidates.push({
|
|
1461
|
-
action:
|
|
1462
|
-
pillar:
|
|
2117
|
+
action: "Reduce cache creation to 5× output",
|
|
2118
|
+
pillar: "cacheCreate",
|
|
1463
2119
|
delta: `-${Math.round(cw - o * 5).toLocaleString()}`,
|
|
1464
2120
|
simulated_yield: sim.yield,
|
|
1465
2121
|
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1466
2122
|
class_after: sim.class,
|
|
1467
|
-
rationale:
|
|
1468
|
-
|
|
2123
|
+
rationale:
|
|
2124
|
+
"You're over-committing cache (cw >> output). Trimming to a healthy ratio reduces cost without hurting yield.",
|
|
2125
|
+
});
|
|
1469
2126
|
}
|
|
1470
2127
|
}
|
|
1471
2128
|
|
|
1472
2129
|
// Sort by yield_delta descending, take top 8
|
|
1473
|
-
candidates.sort((a, b) => b.yield_delta - a.yield_delta)
|
|
1474
|
-
const top = candidates
|
|
2130
|
+
candidates.sort((a, b) => b.yield_delta - a.yield_delta);
|
|
2131
|
+
const top = candidates
|
|
2132
|
+
.slice(0, 8)
|
|
2133
|
+
.map((c, idx) => ({ rank: idx + 1, ...c }));
|
|
1475
2134
|
|
|
1476
|
-
const best = top[0]
|
|
2135
|
+
const best = top[0];
|
|
1477
2136
|
return {
|
|
1478
2137
|
suggestions: top,
|
|
1479
2138
|
current_yield: current.yield,
|
|
1480
2139
|
current_class: current.class,
|
|
1481
2140
|
best_single_change: best
|
|
1482
2141
|
? `${best.action} (Υ ${current.yield} → ${best.simulated_yield}, +${best.yield_delta} yield, class ${best.class_after})`
|
|
1483
|
-
:
|
|
1484
|
-
}
|
|
2142
|
+
: "No improvements found — your cascade is already optimized.",
|
|
2143
|
+
};
|
|
1485
2144
|
}
|
|
1486
2145
|
|
|
1487
|
-
if (name ===
|
|
2146
|
+
if (name === "self_improve") {
|
|
1488
2147
|
// The full self-improvement cycle: pull → diagnose → suggest → simulate.
|
|
1489
2148
|
// If text is provided, use it as pillars. If not, run tokenpull first.
|
|
1490
|
-
let pillars
|
|
1491
|
-
let pulledFrom =
|
|
2149
|
+
let pillars;
|
|
2150
|
+
let pulledFrom = "provided";
|
|
1492
2151
|
|
|
1493
2152
|
if (args?.text) {
|
|
1494
|
-
if (typeof args.text ===
|
|
1495
|
-
return {
|
|
2153
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
2154
|
+
return {
|
|
2155
|
+
status: "error",
|
|
2156
|
+
reason: "input_too_large",
|
|
2157
|
+
detail: `text exceeds ${MAX_INPUT} chars.`,
|
|
2158
|
+
};
|
|
1496
2159
|
}
|
|
1497
|
-
pillars = parsePillars(args.text)
|
|
2160
|
+
pillars = parsePillars(args.text);
|
|
1498
2161
|
} else {
|
|
1499
2162
|
// Run tokenpull to get current pillars from local logs
|
|
1500
|
-
const windowType = args?.window ||
|
|
2163
|
+
const windowType = args?.window || "30d";
|
|
1501
2164
|
try {
|
|
1502
|
-
const pullResult = await callTool(
|
|
1503
|
-
|
|
2165
|
+
const pullResult = await callTool(
|
|
2166
|
+
"tokenpull",
|
|
2167
|
+
{ window: windowType },
|
|
2168
|
+
opts,
|
|
2169
|
+
);
|
|
2170
|
+
if (pullResult?.status === "error" || !pullResult?.windows?.length) {
|
|
1504
2171
|
return {
|
|
1505
|
-
status:
|
|
1506
|
-
reason:
|
|
1507
|
-
detail:
|
|
2172
|
+
status: "error",
|
|
2173
|
+
reason: "tokenpull_failed",
|
|
2174
|
+
detail:
|
|
2175
|
+
"Could not pull pillars from local logs. Provide pillars via `text` argument instead.",
|
|
1508
2176
|
pull_result: pullResult,
|
|
1509
|
-
}
|
|
2177
|
+
};
|
|
1510
2178
|
}
|
|
1511
2179
|
// Use the first window's pillars
|
|
1512
|
-
const w = pullResult.windows[0]
|
|
1513
|
-
pillars = w.pillars
|
|
1514
|
-
pulledFrom = `tokenpull ${w.window || windowType}
|
|
2180
|
+
const w = pullResult.windows[0];
|
|
2181
|
+
pillars = w.pillars;
|
|
2182
|
+
pulledFrom = `tokenpull ${w.window || windowType}`;
|
|
1515
2183
|
} catch (e) {
|
|
1516
2184
|
return {
|
|
1517
|
-
status:
|
|
1518
|
-
reason:
|
|
2185
|
+
status: "error",
|
|
2186
|
+
reason: "tokenpull_error",
|
|
1519
2187
|
detail: String(e.message || e),
|
|
1520
|
-
hint:
|
|
1521
|
-
}
|
|
2188
|
+
hint: "Provide pillars via `text` argument instead.",
|
|
2189
|
+
};
|
|
1522
2190
|
}
|
|
1523
2191
|
}
|
|
1524
2192
|
|
|
1525
|
-
const currentResult = withParseWarnings(pillars, cascade(pillars))
|
|
1526
|
-
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = pillars
|
|
2193
|
+
const currentResult = withParseWarnings(pillars, cascade(pillars));
|
|
2194
|
+
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = pillars;
|
|
1527
2195
|
|
|
1528
2196
|
// ── Step 1: Diagnose ──────────────────────────────────────────────
|
|
1529
|
-
const diagnosis = []
|
|
1530
|
-
const leverage = currentResult.leverage
|
|
2197
|
+
const diagnosis = [];
|
|
2198
|
+
const leverage = currentResult.leverage;
|
|
1531
2199
|
if (leverage !== null && leverage < 10) {
|
|
1532
2200
|
diagnosis.push({
|
|
1533
|
-
metric:
|
|
2201
|
+
metric: "cache_leverage",
|
|
2202
|
+
severity: "critical",
|
|
1534
2203
|
finding: `Cache leverage is ${leverage}× — TRANSMITTER-class operators hit 200×+.`,
|
|
1535
|
-
recommendation:
|
|
1536
|
-
|
|
2204
|
+
recommendation:
|
|
2205
|
+
"Increase context reuse: load prior session context, use longer threads.",
|
|
2206
|
+
});
|
|
1537
2207
|
} else if (leverage !== null && leverage < 50) {
|
|
1538
2208
|
diagnosis.push({
|
|
1539
|
-
metric:
|
|
2209
|
+
metric: "cache_leverage",
|
|
2210
|
+
severity: "warning",
|
|
1540
2211
|
finding: `Cache leverage is ${leverage}× — below ARCH+ threshold (~100×+).`,
|
|
1541
|
-
recommendation:
|
|
1542
|
-
})
|
|
2212
|
+
recommendation: "Push cache reads higher by reusing prior context.",
|
|
2213
|
+
});
|
|
1543
2214
|
}
|
|
1544
2215
|
|
|
1545
|
-
const velocity = currentResult.velocity
|
|
2216
|
+
const velocity = currentResult.velocity;
|
|
1546
2217
|
if (velocity !== null && velocity < 0.5) {
|
|
1547
2218
|
diagnosis.push({
|
|
1548
|
-
metric:
|
|
2219
|
+
metric: "velocity",
|
|
2220
|
+
severity: "critical",
|
|
1549
2221
|
finding: `Velocity is ${velocity} — generating only ${velocity}× input as output.`,
|
|
1550
|
-
recommendation:
|
|
1551
|
-
})
|
|
2222
|
+
recommendation: "Increase output per turn, reduce over-reading.",
|
|
2223
|
+
});
|
|
1552
2224
|
} else if (velocity !== null && velocity < 1.0) {
|
|
1553
2225
|
diagnosis.push({
|
|
1554
|
-
metric:
|
|
2226
|
+
metric: "velocity",
|
|
2227
|
+
severity: "warning",
|
|
1555
2228
|
finding: `Velocity is ${velocity} — below 1.0. Healthy operators hit 1.5×+.`,
|
|
1556
|
-
recommendation:
|
|
1557
|
-
})
|
|
2229
|
+
recommendation: "Generate more output per input token.",
|
|
2230
|
+
});
|
|
1558
2231
|
}
|
|
1559
2232
|
|
|
1560
2233
|
if (currentResult.dev10x === null && cw === 0) {
|
|
1561
2234
|
diagnosis.push({
|
|
1562
|
-
metric:
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
2235
|
+
metric: "10xdev",
|
|
2236
|
+
severity: "critical",
|
|
2237
|
+
finding: "No cache creation — cascade cannot compound.",
|
|
2238
|
+
recommendation:
|
|
2239
|
+
"Switch to a platform with prompt caching (Claude Code).",
|
|
2240
|
+
});
|
|
1566
2241
|
}
|
|
1567
2242
|
|
|
1568
|
-
const total = i + o + cw + cr
|
|
2243
|
+
const total = i + o + cw + cr;
|
|
1569
2244
|
if (total > 0 && (i / total) * 100 > 10) {
|
|
1570
2245
|
diagnosis.push({
|
|
1571
|
-
metric:
|
|
2246
|
+
metric: "input_bloat",
|
|
2247
|
+
severity: "warning",
|
|
1572
2248
|
finding: `Fresh input is ${((i / total) * 100).toFixed(1)}% of total flow — efficient operators keep it under 1%.`,
|
|
1573
|
-
recommendation:
|
|
1574
|
-
})
|
|
2249
|
+
recommendation: "Reduce fresh input by reusing prior context.",
|
|
2250
|
+
});
|
|
1575
2251
|
}
|
|
1576
2252
|
|
|
1577
2253
|
// ── Step 2: Suggest (generate + simulate candidates) ──────────────
|
|
1578
|
-
const candidates = []
|
|
2254
|
+
const candidates = [];
|
|
1579
2255
|
// If cacheRead is 0, suggest absolute amounts instead of percentage boosts
|
|
1580
|
-
const crBoosts = cr > 0 ? [1.5, 2, 3, 5] : []
|
|
2256
|
+
const crBoosts = cr > 0 ? [1.5, 2, 3, 5] : [];
|
|
1581
2257
|
for (const mult of crBoosts) {
|
|
1582
|
-
const sim = cascade({ ...pillars, cacheRead: Math.round(cr * mult) })
|
|
1583
|
-
if (sim.yield !== null)
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
2258
|
+
const sim = cascade({ ...pillars, cacheRead: Math.round(cr * mult) });
|
|
2259
|
+
if (sim.yield !== null)
|
|
2260
|
+
candidates.push({
|
|
2261
|
+
action: `Increase cache reads by ${Math.round((mult - 1) * 100)}%`,
|
|
2262
|
+
pillar: "cacheRead",
|
|
2263
|
+
delta: `+${Math.round(cr * (mult - 1)).toLocaleString()}`,
|
|
2264
|
+
simulated_yield: sim.yield,
|
|
2265
|
+
yield_delta: Number(
|
|
2266
|
+
(sim.yield - (currentResult.yield ?? 0)).toFixed(2),
|
|
2267
|
+
),
|
|
2268
|
+
class_after: sim.class,
|
|
2269
|
+
rationale: "Cache reads are the strongest Υ multiplier.",
|
|
2270
|
+
});
|
|
1589
2271
|
}
|
|
1590
2272
|
// For zero-cache operators, suggest enabling caching with a starter amount
|
|
1591
2273
|
if (cr === 0 && cw === 0) {
|
|
1592
|
-
const starterAmounts = [
|
|
2274
|
+
const starterAmounts = [
|
|
2275
|
+
Math.round(i * 10),
|
|
2276
|
+
Math.round(i * 50),
|
|
2277
|
+
Math.round(i * 100),
|
|
2278
|
+
];
|
|
1593
2279
|
for (const amt of starterAmounts) {
|
|
1594
|
-
const sim = cascade({
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
2280
|
+
const sim = cascade({
|
|
2281
|
+
...pillars,
|
|
2282
|
+
cacheCreate: Math.round(amt * 0.5),
|
|
2283
|
+
cacheRead: amt,
|
|
2284
|
+
});
|
|
2285
|
+
if (sim.yield !== null && sim.yield > 0)
|
|
2286
|
+
candidates.push({
|
|
2287
|
+
action: `Enable caching with ${amt.toLocaleString()} cache reads`,
|
|
2288
|
+
pillar: "cacheRead",
|
|
2289
|
+
delta: `+${amt.toLocaleString()}`,
|
|
2290
|
+
simulated_yield: sim.yield,
|
|
2291
|
+
yield_delta: Number(
|
|
2292
|
+
(sim.yield - (currentResult.yield ?? 0)).toFixed(2),
|
|
2293
|
+
),
|
|
2294
|
+
class_after: sim.class,
|
|
2295
|
+
rationale:
|
|
2296
|
+
"You have no cache — enabling it unlocks the cascade. Start by reusing prior context.",
|
|
2297
|
+
});
|
|
1601
2298
|
}
|
|
1602
2299
|
}
|
|
1603
2300
|
for (const mult of [0.9, 0.75, 0.5]) {
|
|
1604
|
-
const newInput = Math.round(i * mult)
|
|
1605
|
-
if (newInput < 1) continue
|
|
1606
|
-
const sim = cascade({ ...pillars, input: newInput })
|
|
1607
|
-
if (sim.yield !== null)
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
2301
|
+
const newInput = Math.round(i * mult);
|
|
2302
|
+
if (newInput < 1) continue;
|
|
2303
|
+
const sim = cascade({ ...pillars, input: newInput });
|
|
2304
|
+
if (sim.yield !== null)
|
|
2305
|
+
candidates.push({
|
|
2306
|
+
action: `Reduce fresh input by ${Math.round((1 - mult) * 100)}%`,
|
|
2307
|
+
pillar: "input",
|
|
2308
|
+
delta: `-${Math.round(i * (1 - mult)).toLocaleString()}`,
|
|
2309
|
+
simulated_yield: sim.yield,
|
|
2310
|
+
yield_delta: Number(
|
|
2311
|
+
(sim.yield - (currentResult.yield ?? 0)).toFixed(2),
|
|
2312
|
+
),
|
|
2313
|
+
class_after: sim.class,
|
|
2314
|
+
rationale: "Input is squared in Υ denominator — quadratic payoff.",
|
|
2315
|
+
});
|
|
1613
2316
|
}
|
|
1614
2317
|
for (const mult of [1.25, 1.5, 2]) {
|
|
1615
|
-
const sim = cascade({ ...pillars, output: Math.round(o * mult) })
|
|
1616
|
-
if (sim.yield !== null)
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
2318
|
+
const sim = cascade({ ...pillars, output: Math.round(o * mult) });
|
|
2319
|
+
if (sim.yield !== null)
|
|
2320
|
+
candidates.push({
|
|
2321
|
+
action: `Increase output by ${Math.round((mult - 1) * 100)}%`,
|
|
2322
|
+
pillar: "output",
|
|
2323
|
+
delta: `+${Math.round(o * (mult - 1)).toLocaleString()}`,
|
|
2324
|
+
simulated_yield: sim.yield,
|
|
2325
|
+
yield_delta: Number(
|
|
2326
|
+
(sim.yield - (currentResult.yield ?? 0)).toFixed(2),
|
|
2327
|
+
),
|
|
2328
|
+
class_after: sim.class,
|
|
2329
|
+
rationale: "Output is a linear multiplier in Υ.",
|
|
2330
|
+
});
|
|
1622
2331
|
}
|
|
1623
|
-
candidates.sort((a, b) => b.yield_delta - a.yield_delta)
|
|
1624
|
-
const suggestions = candidates
|
|
2332
|
+
candidates.sort((a, b) => b.yield_delta - a.yield_delta);
|
|
2333
|
+
const suggestions = candidates
|
|
2334
|
+
.slice(0, 8)
|
|
2335
|
+
.map((c, idx) => ({ rank: idx + 1, ...c }));
|
|
1625
2336
|
|
|
1626
2337
|
// ── Step 3: Simulate the top suggestion ───────────────────────────
|
|
1627
|
-
const best = suggestions[0]
|
|
1628
|
-
let bestSimulation = null
|
|
2338
|
+
const best = suggestions[0];
|
|
2339
|
+
let bestSimulation = null;
|
|
1629
2340
|
if (best) {
|
|
1630
|
-
const simPillars = { ...pillars }
|
|
1631
|
-
const delta = parseInt(best.delta.replace(/[+\-,]/g,
|
|
1632
|
-
if (best.pillar ===
|
|
1633
|
-
else if (best.pillar ===
|
|
1634
|
-
|
|
1635
|
-
|
|
2341
|
+
const simPillars = { ...pillars };
|
|
2342
|
+
const delta = parseInt(best.delta.replace(/[+\-,]/g, ""), 10);
|
|
2343
|
+
if (best.pillar === "cacheRead") simPillars.cacheRead = cr + delta;
|
|
2344
|
+
else if (best.pillar === "input")
|
|
2345
|
+
simPillars.input = Math.max(i - delta, 1);
|
|
2346
|
+
else if (best.pillar === "output") simPillars.output = o + delta;
|
|
2347
|
+
const simResult = cascade(simPillars);
|
|
1636
2348
|
bestSimulation = {
|
|
1637
2349
|
action: best.action,
|
|
1638
2350
|
current_yield: currentResult.yield,
|
|
@@ -1641,58 +2353,67 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1641
2353
|
current_class: currentResult.class,
|
|
1642
2354
|
simulated_class: simResult.class,
|
|
1643
2355
|
class_changed: currentResult.class !== simResult.class,
|
|
1644
|
-
}
|
|
2356
|
+
};
|
|
1645
2357
|
}
|
|
1646
2358
|
|
|
1647
2359
|
// ── Step 4: Cycle summary ─────────────────────────────────────────
|
|
1648
|
-
const criticalCount = diagnosis.filter(
|
|
2360
|
+
const criticalCount = diagnosis.filter(
|
|
2361
|
+
(d) => d.severity === "critical",
|
|
2362
|
+
).length;
|
|
1649
2363
|
const cycleSummary = best
|
|
1650
2364
|
? `Pulled from ${pulledFrom}. Υ ${currentResult.yield} (${currentResult.class}). ${criticalCount} critical, ${diagnosis.length - criticalCount} other findings. Best: ${best.action} → Υ ${best.simulated_yield} (+${best.yield_delta}).`
|
|
1651
|
-
: `Pulled from ${pulledFrom}. Υ ${currentResult.yield} (${currentResult.class}). ${diagnosis.length} findings. No improvements suggested — cascade is optimized
|
|
2365
|
+
: `Pulled from ${pulledFrom}. Υ ${currentResult.yield} (${currentResult.class}). ${diagnosis.length} findings. No improvements suggested — cascade is optimized.`;
|
|
1652
2366
|
|
|
1653
2367
|
// ── Step 5: Scope-specific analysis ───────────────────────────────
|
|
1654
|
-
const scope = args?.scope ||
|
|
1655
|
-
const modeInfo = currentResult.mode // { mode, confidence }
|
|
1656
|
-
const scopeResult = {}
|
|
2368
|
+
const scope = args?.scope || "daily";
|
|
2369
|
+
const modeInfo = currentResult.mode; // { mode, confidence }
|
|
2370
|
+
const scopeResult = {};
|
|
1657
2371
|
|
|
1658
|
-
if (scope ===
|
|
2372
|
+
if (scope === "daily") {
|
|
1659
2373
|
// Daily: mode + quality score + assessment + advice
|
|
1660
|
-
const qs = qualityScore(currentResult.yield ?? 0, modeInfo.mode)
|
|
1661
|
-
const expected = MODE_EXPECTED_YIELD[modeInfo.mode] ?? 0
|
|
1662
|
-
const assessment = _dailyAssessment(
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
2374
|
+
const qs = qualityScore(currentResult.yield ?? 0, modeInfo.mode);
|
|
2375
|
+
const expected = MODE_EXPECTED_YIELD[modeInfo.mode] ?? 0;
|
|
2376
|
+
const assessment = _dailyAssessment(
|
|
2377
|
+
modeInfo.mode,
|
|
2378
|
+
currentResult.yield,
|
|
2379
|
+
qs,
|
|
2380
|
+
expected,
|
|
2381
|
+
);
|
|
2382
|
+
const advice = _dailyAdvice(modeInfo.mode, currentResult.yield, qs);
|
|
2383
|
+
scopeResult.mode = modeInfo;
|
|
2384
|
+
scopeResult.quality_score = Math.round(qs * 100) / 100;
|
|
2385
|
+
scopeResult.assessment = assessment;
|
|
2386
|
+
scopeResult.advice = advice;
|
|
1668
2387
|
}
|
|
1669
2388
|
|
|
1670
|
-
if (scope ===
|
|
2389
|
+
if (scope === "weekly" || scope === "trend") {
|
|
1671
2390
|
// Pull daily rows from ccusage to build weekly snapshots
|
|
1672
|
-
const dailyRows = await _pullDailyRows(opts)
|
|
1673
|
-
const weeklySnapshots = _compoundWeekly(dailyRows)
|
|
1674
|
-
const modeDistribution = _modeDistribution(weeklySnapshots)
|
|
1675
|
-
const modeWeightedYield = _modeWeightedYield(weeklySnapshots)
|
|
2391
|
+
const dailyRows = await _pullDailyRows(opts);
|
|
2392
|
+
const weeklySnapshots = _compoundWeekly(dailyRows);
|
|
2393
|
+
const modeDistribution = _modeDistribution(weeklySnapshots);
|
|
2394
|
+
const modeWeightedYield = _modeWeightedYield(weeklySnapshots);
|
|
1676
2395
|
|
|
1677
2396
|
// Compute badges
|
|
1678
|
-
const historyForBadges = weeklySnapshots.map(w => ({
|
|
2397
|
+
const historyForBadges = weeklySnapshots.map((w) => ({
|
|
1679
2398
|
date: w.weekStart,
|
|
1680
2399
|
mode: w.mode,
|
|
1681
2400
|
yield: w.yield,
|
|
1682
2401
|
pillars: w.pillars,
|
|
1683
|
-
}))
|
|
2402
|
+
}));
|
|
1684
2403
|
const badges = computeBadges({
|
|
1685
2404
|
pillars,
|
|
1686
2405
|
cascade: currentResult,
|
|
1687
2406
|
history: historyForBadges,
|
|
1688
2407
|
isVerified: false, // set by submit_verified
|
|
1689
2408
|
rank: null, // server-side
|
|
1690
|
-
})
|
|
2409
|
+
});
|
|
1691
2410
|
|
|
1692
|
-
if (scope ===
|
|
1693
|
-
scopeResult.mode = modeInfo
|
|
2411
|
+
if (scope === "weekly") {
|
|
2412
|
+
scopeResult.mode = modeInfo;
|
|
1694
2413
|
// Strip dailyModes from weekly_snapshots — privacy boundary (daily modes never leave the machine)
|
|
1695
|
-
const safeSnapshots = weeklySnapshots.map(
|
|
2414
|
+
const safeSnapshots = weeklySnapshots.map(
|
|
2415
|
+
({ dailyModes, ...rest }) => rest,
|
|
2416
|
+
);
|
|
1696
2417
|
scopeResult.report = {
|
|
1697
2418
|
current_mode: modeInfo.mode,
|
|
1698
2419
|
mode_confidence: modeInfo.confidence,
|
|
@@ -1702,15 +2423,15 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1702
2423
|
health_score: _healthScore(weeklySnapshots, modeWeightedYield),
|
|
1703
2424
|
weekly_snapshots: safeSnapshots,
|
|
1704
2425
|
badges,
|
|
1705
|
-
}
|
|
2426
|
+
};
|
|
1706
2427
|
}
|
|
1707
2428
|
|
|
1708
|
-
if (scope ===
|
|
1709
|
-
const yield7d = _yieldForDays(weeklySnapshots, 7)
|
|
1710
|
-
const yield30d = _yieldForDays(weeklySnapshots, 30)
|
|
1711
|
-
const yield90d = _yieldForDays(weeklySnapshots, 90)
|
|
1712
|
-
const trajectory = _trajectory(yield7d, yield30d, yield90d)
|
|
1713
|
-
scopeResult.mode = modeInfo
|
|
2429
|
+
if (scope === "trend") {
|
|
2430
|
+
const yield7d = _yieldForDays(weeklySnapshots, 7);
|
|
2431
|
+
const yield30d = _yieldForDays(weeklySnapshots, 30);
|
|
2432
|
+
const yield90d = _yieldForDays(weeklySnapshots, 90);
|
|
2433
|
+
const trajectory = _trajectory(yield7d, yield30d, yield90d);
|
|
2434
|
+
scopeResult.mode = modeInfo;
|
|
1714
2435
|
scopeResult.trend = {
|
|
1715
2436
|
yield_7d: yield7d,
|
|
1716
2437
|
yield_30d: yield30d,
|
|
@@ -1718,7 +2439,7 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1718
2439
|
trajectory,
|
|
1719
2440
|
mode_distribution: modeDistribution,
|
|
1720
2441
|
phase_pattern: _phasePattern(weeklySnapshots),
|
|
1721
|
-
}
|
|
2442
|
+
};
|
|
1722
2443
|
}
|
|
1723
2444
|
}
|
|
1724
2445
|
|
|
@@ -1739,10 +2460,10 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1739
2460
|
best_simulation: bestSimulation,
|
|
1740
2461
|
cycle_summary: cycleSummary,
|
|
1741
2462
|
...scopeResult,
|
|
1742
|
-
}
|
|
2463
|
+
};
|
|
1743
2464
|
}
|
|
1744
2465
|
|
|
1745
|
-
throw new Error(`Unknown tool: ${name}`)
|
|
2466
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1746
2467
|
}
|
|
1747
2468
|
|
|
1748
2469
|
// ── self_improve scope helpers ──────────────────────────────────────────────
|
|
@@ -1750,196 +2471,223 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1750
2471
|
/** Pull daily rows from ccusage to build mode history. Returns array of { date, pillars, yield, mode }. */
|
|
1751
2472
|
async function _pullDailyRows(opts = {}) {
|
|
1752
2473
|
try {
|
|
1753
|
-
const raw = await execFileAsync(
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
mode
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
2474
|
+
const raw = await execFileAsync(
|
|
2475
|
+
"ccusage",
|
|
2476
|
+
["claude", "daily", "--json"],
|
|
2477
|
+
15000,
|
|
2478
|
+
);
|
|
2479
|
+
const rows = JSON.parse(raw)?.daily ?? JSON.parse(raw);
|
|
2480
|
+
if (!Array.isArray(rows)) return [];
|
|
2481
|
+
return rows
|
|
2482
|
+
.map((r) => {
|
|
2483
|
+
const pillars = {
|
|
2484
|
+
input: r.inputTokens ?? r.input_tokens ?? 0,
|
|
2485
|
+
output: r.outputTokens ?? r.output_tokens ?? 0,
|
|
2486
|
+
cacheCreate: r.cacheCreationTokens ?? r.cache_create_tokens ?? 0,
|
|
2487
|
+
cacheRead: r.cacheReadTokens ?? r.cache_read_tokens ?? 0,
|
|
2488
|
+
};
|
|
2489
|
+
const cas = cascade(pillars);
|
|
2490
|
+
const mode = detectMode(pillars);
|
|
2491
|
+
return {
|
|
2492
|
+
date: r.date ?? r.day ?? null,
|
|
2493
|
+
pillars,
|
|
2494
|
+
yield: cas.yield ?? 0,
|
|
2495
|
+
mode: mode.mode,
|
|
2496
|
+
mode_confidence: mode.confidence,
|
|
2497
|
+
};
|
|
2498
|
+
})
|
|
2499
|
+
.filter((r) => r.date);
|
|
1773
2500
|
} catch {
|
|
1774
|
-
return []
|
|
2501
|
+
return [];
|
|
1775
2502
|
}
|
|
1776
2503
|
}
|
|
1777
2504
|
|
|
1778
2505
|
/** Compound daily rows into weekly snapshots. Each week = 7 days, starting Monday. */
|
|
1779
2506
|
function _compoundWeekly(dailyRows) {
|
|
1780
|
-
if (!dailyRows || dailyRows.length === 0) return []
|
|
2507
|
+
if (!dailyRows || dailyRows.length === 0) return [];
|
|
1781
2508
|
// Group by ISO week (week starts Monday)
|
|
1782
|
-
const weeks = new Map()
|
|
2509
|
+
const weeks = new Map();
|
|
1783
2510
|
for (const row of dailyRows) {
|
|
1784
|
-
const d = new Date(row.date)
|
|
1785
|
-
const day = d.getDay() || 7 // Sunday=0 → 7
|
|
1786
|
-
const monday = new Date(d)
|
|
1787
|
-
monday.setDate(d.getDate() - day + 1)
|
|
1788
|
-
const weekKey = monday.toISOString().slice(0, 10)
|
|
2511
|
+
const d = new Date(row.date);
|
|
2512
|
+
const day = d.getDay() || 7; // Sunday=0 → 7
|
|
2513
|
+
const monday = new Date(d);
|
|
2514
|
+
monday.setDate(d.getDate() - day + 1);
|
|
2515
|
+
const weekKey = monday.toISOString().slice(0, 10);
|
|
1789
2516
|
if (!weeks.has(weekKey)) {
|
|
1790
|
-
weeks.set(weekKey, { weekStart: weekKey, days: [] })
|
|
2517
|
+
weeks.set(weekKey, { weekStart: weekKey, days: [] });
|
|
1791
2518
|
}
|
|
1792
|
-
weeks.get(weekKey).days.push(row)
|
|
2519
|
+
weeks.get(weekKey).days.push(row);
|
|
1793
2520
|
}
|
|
1794
2521
|
// Compound each week
|
|
1795
|
-
return Array.from(weeks.values())
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
pillars
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
2522
|
+
return Array.from(weeks.values())
|
|
2523
|
+
.map((w) => {
|
|
2524
|
+
const pillars = w.days.reduce(
|
|
2525
|
+
(acc, d) => ({
|
|
2526
|
+
input: acc.input + d.pillars.input,
|
|
2527
|
+
output: acc.output + d.pillars.output,
|
|
2528
|
+
cacheCreate: acc.cacheCreate + d.pillars.cacheCreate,
|
|
2529
|
+
cacheRead: acc.cacheRead + d.pillars.cacheRead,
|
|
2530
|
+
}),
|
|
2531
|
+
{ input: 0, output: 0, cacheCreate: 0, cacheRead: 0 },
|
|
2532
|
+
);
|
|
2533
|
+
const cas = cascade(pillars);
|
|
2534
|
+
const mode = detectMode(pillars);
|
|
2535
|
+
return {
|
|
2536
|
+
weekStart: w.weekStart,
|
|
2537
|
+
pillars,
|
|
2538
|
+
yield: cas.yield ?? 0,
|
|
2539
|
+
mode: mode.mode,
|
|
2540
|
+
mode_confidence: mode.confidence,
|
|
2541
|
+
dayCount: w.days.length,
|
|
2542
|
+
dailyModes: w.days.map((d) => d.mode), // stays local — not in submitted report
|
|
2543
|
+
};
|
|
2544
|
+
})
|
|
2545
|
+
.sort((a, b) => a.weekStart.localeCompare(b.weekStart));
|
|
1814
2546
|
}
|
|
1815
2547
|
|
|
1816
2548
|
/** Compute mode distribution from weekly snapshots (weekly granularity = privacy boundary). */
|
|
1817
2549
|
function _modeDistribution(weeklySnapshots) {
|
|
1818
|
-
if (!weeklySnapshots || weeklySnapshots.length === 0) return {}
|
|
1819
|
-
const counts = { BUILD: 0, EDIT: 0, DEBUG: 0, MAINTAIN: 0, IDLE: 0 }
|
|
2550
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return {};
|
|
2551
|
+
const counts = { BUILD: 0, EDIT: 0, DEBUG: 0, MAINTAIN: 0, IDLE: 0 };
|
|
1820
2552
|
for (const w of weeklySnapshots) {
|
|
1821
|
-
counts[w.mode] = (counts[w.mode] || 0) + 1
|
|
2553
|
+
counts[w.mode] = (counts[w.mode] || 0) + 1;
|
|
1822
2554
|
}
|
|
1823
|
-
const total = weeklySnapshots.length
|
|
1824
|
-
const dist = {}
|
|
2555
|
+
const total = weeklySnapshots.length;
|
|
2556
|
+
const dist = {};
|
|
1825
2557
|
for (const [mode, count] of Object.entries(counts)) {
|
|
1826
|
-
if (count > 0) dist[mode] = Math.round((count / total) * 100) / 100
|
|
2558
|
+
if (count > 0) dist[mode] = Math.round((count / total) * 100) / 100;
|
|
1827
2559
|
}
|
|
1828
|
-
return dist
|
|
2560
|
+
return dist;
|
|
1829
2561
|
}
|
|
1830
2562
|
|
|
1831
2563
|
/** Mode-weighted yield — average yield weighted by mode distribution. */
|
|
1832
2564
|
function _modeWeightedYield(weeklySnapshots) {
|
|
1833
|
-
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0
|
|
1834
|
-
const total = weeklySnapshots.length
|
|
1835
|
-
const sum = weeklySnapshots.reduce((acc, w) => acc + (w.yield || 0), 0)
|
|
1836
|
-
return Math.round(sum / total)
|
|
2565
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0;
|
|
2566
|
+
const total = weeklySnapshots.length;
|
|
2567
|
+
const sum = weeklySnapshots.reduce((acc, w) => acc + (w.yield || 0), 0);
|
|
2568
|
+
return Math.round(sum / total);
|
|
1837
2569
|
}
|
|
1838
2570
|
|
|
1839
2571
|
/** Peak yield across all weekly snapshots. */
|
|
1840
2572
|
function _peakYield(weeklySnapshots) {
|
|
1841
|
-
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0
|
|
1842
|
-
return Math.max(...weeklySnapshots.map(w => w.yield || 0))
|
|
2573
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0;
|
|
2574
|
+
return Math.max(...weeklySnapshots.map((w) => w.yield || 0));
|
|
1843
2575
|
}
|
|
1844
2576
|
|
|
1845
2577
|
/** Health score (0-100) — weighted composite of consistency, momentum, quality. */
|
|
1846
2578
|
function _healthScore(weeklySnapshots, modeWeightedYield) {
|
|
1847
|
-
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0
|
|
2579
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0;
|
|
1848
2580
|
// Simplified Phase 1 formula:
|
|
1849
2581
|
// 40% — consistency (how often in MAINTAIN)
|
|
1850
2582
|
// 30% — momentum (recent yield vs older yield)
|
|
1851
2583
|
// 30% — quality (mode-weighted yield relative to MAINTAIN expected)
|
|
1852
|
-
const dist = _modeDistribution(weeklySnapshots)
|
|
1853
|
-
const maintainShare = dist.MAINTAIN || 0
|
|
1854
|
-
const consistency = Math.min(maintainShare, 1) * 40
|
|
2584
|
+
const dist = _modeDistribution(weeklySnapshots);
|
|
2585
|
+
const maintainShare = dist.MAINTAIN || 0;
|
|
2586
|
+
const consistency = Math.min(maintainShare, 1) * 40;
|
|
1855
2587
|
|
|
1856
2588
|
// Momentum: compare last 3 weeks to first 3 weeks
|
|
1857
|
-
const recent = weeklySnapshots.slice(-3)
|
|
1858
|
-
const older = weeklySnapshots.slice(0, 3)
|
|
1859
|
-
const recentAvg =
|
|
1860
|
-
|
|
1861
|
-
const
|
|
1862
|
-
|
|
2589
|
+
const recent = weeklySnapshots.slice(-3);
|
|
2590
|
+
const older = weeklySnapshots.slice(0, 3);
|
|
2591
|
+
const recentAvg =
|
|
2592
|
+
recent.reduce((a, w) => a + (w.yield || 0), 0) / (recent.length || 1);
|
|
2593
|
+
const olderAvg =
|
|
2594
|
+
older.reduce((a, w) => a + (w.yield || 0), 0) / (older.length || 1);
|
|
2595
|
+
const momentumRatio = olderAvg > 0 ? recentAvg / olderAvg : 1;
|
|
2596
|
+
const momentum = (Math.min(momentumRatio, 2) / 2) * 30;
|
|
1863
2597
|
|
|
1864
2598
|
// Quality: mode-weighted yield vs MAINTAIN expected (5000)
|
|
1865
|
-
const quality = Math.min(modeWeightedYield / 5000, 1) * 30
|
|
2599
|
+
const quality = Math.min(modeWeightedYield / 5000, 1) * 30;
|
|
1866
2600
|
|
|
1867
|
-
return Math.round(consistency + momentum + quality)
|
|
2601
|
+
return Math.round(consistency + momentum + quality);
|
|
1868
2602
|
}
|
|
1869
2603
|
|
|
1870
2604
|
/** Yield for the last N days (from weekly snapshots — approximates by summing recent weeks). */
|
|
1871
2605
|
function _yieldForDays(weeklySnapshots, days) {
|
|
1872
|
-
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0
|
|
1873
|
-
const weeksNeeded = Math.ceil(days / 7)
|
|
1874
|
-
const recent = weeklySnapshots.slice(-weeksNeeded)
|
|
1875
|
-
if (recent.length === 0) return 0
|
|
1876
|
-
const sum = recent.reduce((a, w) => a + (w.yield || 0), 0)
|
|
1877
|
-
return Math.round(sum / recent.length) // average weekly yield
|
|
2606
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0;
|
|
2607
|
+
const weeksNeeded = Math.ceil(days / 7);
|
|
2608
|
+
const recent = weeklySnapshots.slice(-weeksNeeded);
|
|
2609
|
+
if (recent.length === 0) return 0;
|
|
2610
|
+
const sum = recent.reduce((a, w) => a + (w.yield || 0), 0);
|
|
2611
|
+
return Math.round(sum / recent.length); // average weekly yield
|
|
1878
2612
|
}
|
|
1879
2613
|
|
|
1880
2614
|
/** Trajectory description from 7d/30d/90d yields. */
|
|
1881
2615
|
function _trajectory(y7, y30, y90) {
|
|
1882
|
-
if (y90 === 0) return
|
|
1883
|
-
const r7v30 = y30 > 0 ? y7 / y30 : 1
|
|
1884
|
-
const r30v90 = y90 > 0 ? y30 / y90 : 1
|
|
1885
|
-
if (r7v30 > 1.2 && r30v90 > 1.0) return
|
|
1886
|
-
if (r7v30 > 1.2) return
|
|
1887
|
-
if (r7v30 < 0.8 && r30v90 < 0.8) return
|
|
1888
|
-
if (r7v30 < 0.8) return
|
|
1889
|
-
if (r30v90 > 1.1) return
|
|
1890
|
-
return
|
|
2616
|
+
if (y90 === 0) return "insufficient_data";
|
|
2617
|
+
const r7v30 = y30 > 0 ? y7 / y30 : 1;
|
|
2618
|
+
const r30v90 = y90 > 0 ? y30 / y90 : 1;
|
|
2619
|
+
if (r7v30 > 1.2 && r30v90 > 1.0) return "accelerating";
|
|
2620
|
+
if (r7v30 > 1.2) return "recent_surge";
|
|
2621
|
+
if (r7v30 < 0.8 && r30v90 < 0.8) return "declining";
|
|
2622
|
+
if (r7v30 < 0.8) return "recent_dip";
|
|
2623
|
+
if (r30v90 > 1.1) return "steady_growth";
|
|
2624
|
+
return "stable";
|
|
1891
2625
|
}
|
|
1892
2626
|
|
|
1893
2627
|
/** Phase pattern description from mode distribution over time. */
|
|
1894
2628
|
function _phasePattern(weeklySnapshots) {
|
|
1895
|
-
if (!weeklySnapshots || weeklySnapshots.length < 2)
|
|
1896
|
-
|
|
1897
|
-
const
|
|
2629
|
+
if (!weeklySnapshots || weeklySnapshots.length < 2)
|
|
2630
|
+
return "insufficient_data";
|
|
2631
|
+
const modes = weeklySnapshots.map((w) => w.mode);
|
|
2632
|
+
const transitions = [];
|
|
1898
2633
|
for (let i = 1; i < modes.length; i++) {
|
|
1899
2634
|
if (modes[i] !== modes[i - 1]) {
|
|
1900
|
-
transitions.push(`${modes[i - 1]}→${modes[i]}`)
|
|
2635
|
+
transitions.push(`${modes[i - 1]}→${modes[i]}`);
|
|
1901
2636
|
}
|
|
1902
2637
|
}
|
|
1903
|
-
if (transitions.length === 0) return `consistent_${modes[0].toLowerCase()}
|
|
2638
|
+
if (transitions.length === 0) return `consistent_${modes[0].toLowerCase()}`;
|
|
1904
2639
|
// Check for smooth transitions (BUILD→MAINTAIN without DEBUG)
|
|
1905
|
-
const smooth = transitions.every(t => !t.includes(
|
|
1906
|
-
if (smooth && transitions.some(t => t ===
|
|
1907
|
-
|
|
1908
|
-
|
|
2640
|
+
const smooth = transitions.every((t) => !t.includes("DEBUG"));
|
|
2641
|
+
if (smooth && transitions.some((t) => t === "BUILD→MAINTAIN"))
|
|
2642
|
+
return "smooth_ramp";
|
|
2643
|
+
if (transitions.filter((t) => t.includes("DEBUG")).length > 2)
|
|
2644
|
+
return "erratic";
|
|
2645
|
+
return "cyclical";
|
|
1909
2646
|
}
|
|
1910
2647
|
|
|
1911
2648
|
/** Daily assessment string. */
|
|
1912
2649
|
function _dailyAssessment(mode, yieldVal, qualityScoreVal, expectedYield) {
|
|
1913
|
-
const qsPct = Math.round(qualityScoreVal * 100)
|
|
1914
|
-
if (mode ===
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
2650
|
+
const qsPct = Math.round(qualityScoreVal * 100);
|
|
2651
|
+
if (mode === "IDLE")
|
|
2652
|
+
return "You're idle — no significant token activity today.";
|
|
2653
|
+
if (mode === "MAINTAIN") {
|
|
2654
|
+
if (qualityScoreVal >= 0.5)
|
|
2655
|
+
return `You're in MAINTAIN mode. Yield ${yieldVal} is ${qsPct}% of MAINTAIN norm. The cascade is compounding.`;
|
|
2656
|
+
return `You're in MAINTAIN mode but yield ${yieldVal} is only ${qsPct}% of expected (${expectedYield}). You may be coasting — push for more output.`;
|
|
1918
2657
|
}
|
|
1919
|
-
if (mode ===
|
|
1920
|
-
if (qualityScoreVal >= 0.5)
|
|
1921
|
-
|
|
2658
|
+
if (mode === "BUILD") {
|
|
2659
|
+
if (qualityScoreVal >= 0.5)
|
|
2660
|
+
return `You're in BUILD mode. Yield ${yieldVal} is ${qsPct}% of BUILD norm. Greenfield work — expected to be low.`;
|
|
2661
|
+
return `You're in BUILD mode. Yield ${yieldVal} is ${qsPct}% of expected (${expectedYield}). Building is slow — keep going.`;
|
|
1922
2662
|
}
|
|
1923
|
-
if (mode ===
|
|
1924
|
-
if (qualityScoreVal >= 0.5)
|
|
1925
|
-
|
|
2663
|
+
if (mode === "EDIT") {
|
|
2664
|
+
if (qualityScoreVal >= 0.5)
|
|
2665
|
+
return `You're in EDIT mode. Yield ${yieldVal} is ${qsPct}% of EDIT norm. Fresh input but producing — good.`;
|
|
2666
|
+
return `You're in EDIT mode. Yield ${yieldVal} is ${qsPct}% of expected (${expectedYield}). Push for more output per input.`;
|
|
1926
2667
|
}
|
|
1927
|
-
if (mode ===
|
|
1928
|
-
if (qualityScoreVal >= 0.5)
|
|
1929
|
-
|
|
2668
|
+
if (mode === "DEBUG") {
|
|
2669
|
+
if (qualityScoreVal >= 0.5)
|
|
2670
|
+
return `You're in DEBUG mode. Yield ${yieldVal} is ${qsPct}% of DEBUG norm. Investigating — yield is expected to be low.`;
|
|
2671
|
+
return `You're in DEBUG mode. Yield ${yieldVal} is ${qsPct}% of expected (${expectedYield}). Debugging is slow — consider loading prior context.`;
|
|
1930
2672
|
}
|
|
1931
|
-
return `Mode: ${mode}. Yield: ${yieldVal}. Quality: ${qsPct}
|
|
2673
|
+
return `Mode: ${mode}. Yield: ${yieldVal}. Quality: ${qsPct}%.`;
|
|
1932
2674
|
}
|
|
1933
2675
|
|
|
1934
2676
|
/** Daily advice string. */
|
|
1935
2677
|
function _dailyAdvice(mode, yieldVal, qs) {
|
|
1936
|
-
if (mode ===
|
|
1937
|
-
|
|
1938
|
-
if (mode ===
|
|
1939
|
-
|
|
1940
|
-
if (mode ===
|
|
1941
|
-
|
|
1942
|
-
|
|
2678
|
+
if (mode === "MAINTAIN" && qs >= 0.5)
|
|
2679
|
+
return "Keep the cascade going. Don't reset context — let it compound.";
|
|
2680
|
+
if (mode === "MAINTAIN")
|
|
2681
|
+
return "You're in MAINTAIN but underperforming. Push the agent for more output per turn.";
|
|
2682
|
+
if (mode === "BUILD")
|
|
2683
|
+
return "When you're done building, load prior context to transition to MAINTAIN. The cascade rewards reuse.";
|
|
2684
|
+
if (mode === "EDIT")
|
|
2685
|
+
return "You're producing but using fresh input. Try to reuse prior context to boost leverage.";
|
|
2686
|
+
if (mode === "DEBUG")
|
|
2687
|
+
return "When you're done debugging, load prior context to return to MAINTAIN. Don't let the debug phase drag on.";
|
|
2688
|
+
if (mode === "IDLE")
|
|
2689
|
+
return "No activity detected. Start a session to build your cascade.";
|
|
2690
|
+
return "Keep working — the cascade will compound as you build context.";
|
|
1943
2691
|
}
|
|
1944
2692
|
|
|
1945
2693
|
/**
|
|
@@ -1949,22 +2697,25 @@ function _dailyAdvice(mode, yieldVal, qs) {
|
|
|
1949
2697
|
* Weekly granularity is the privacy boundary — no daily modes in the report.
|
|
1950
2698
|
*/
|
|
1951
2699
|
function _computeReportBlock(pillars) {
|
|
1952
|
-
const cas = cascade(pillars)
|
|
1953
|
-
const modeInfo = detectMode(pillars)
|
|
2700
|
+
const cas = cascade(pillars);
|
|
2701
|
+
const modeInfo = detectMode(pillars);
|
|
1954
2702
|
const badges = computeBadges({
|
|
1955
2703
|
pillars,
|
|
1956
2704
|
cascade: cas,
|
|
1957
2705
|
history: [], // no history available at submit time — badges computed from current pillars only
|
|
1958
2706
|
isVerified: true, // submit_verified is the signed agent path
|
|
1959
2707
|
rank: null,
|
|
1960
|
-
})
|
|
2708
|
+
});
|
|
1961
2709
|
return {
|
|
1962
2710
|
current_mode: modeInfo.mode,
|
|
1963
2711
|
mode_confidence: modeInfo.confidence,
|
|
1964
2712
|
mode_distribution: { [modeInfo.mode]: 1.0 }, // single-window submission
|
|
1965
2713
|
mode_weighted_yield: cas.yield ?? 0,
|
|
1966
2714
|
peak_yield: cas.yield ?? 0,
|
|
1967
|
-
health_score: _healthScore(
|
|
2715
|
+
health_score: _healthScore(
|
|
2716
|
+
[{ mode: modeInfo.mode, yield: cas.yield ?? 0 }],
|
|
2717
|
+
cas.yield ?? 0,
|
|
2718
|
+
),
|
|
1968
2719
|
badges,
|
|
1969
|
-
}
|
|
2720
|
+
};
|
|
1970
2721
|
}
|