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