sigrank 0.15.5 → 0.16.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 +22 -7
- package/adapters.mjs +24 -11
- package/badges.mjs +163 -0
- package/cascade.mjs +69 -0
- package/package.json +4 -2
- package/preflight.mjs +119 -0
- package/submit.mjs +38 -0
- package/tokenpull.mjs +13 -1
- package/tools.mjs +871 -2
- package/tui.mjs +2 -2
package/tools.mjs
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* narration card in ./narrate.mjs. Token-only, no transcript content.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { cascade, parsePillars } from './cascade.mjs'
|
|
11
|
+
import { cascade, parsePillars, detectMode, qualityScore, MODE_EXPECTED_YIELD } from './cascade.mjs'
|
|
12
|
+
import { computeBadges } from './badges.mjs'
|
|
12
13
|
import { narrate } from './narrate.mjs'
|
|
13
14
|
import { tokenpull as pullLocal, tokenpullCodex as pullCodex, tokenpullAny } from './tokenpull.mjs'
|
|
14
15
|
import { ALL_PLATFORMS } from './adapters.mjs'
|
|
@@ -325,6 +326,55 @@ const SIMULATE_OUTPUT = {
|
|
|
325
326
|
},
|
|
326
327
|
}
|
|
327
328
|
|
|
329
|
+
const DIAGNOSE_OUTPUT = {
|
|
330
|
+
type: 'object',
|
|
331
|
+
properties: {
|
|
332
|
+
pillars: { type: 'object', description: 'The 4 raw token pillars' },
|
|
333
|
+
cascade: CASCADE_OUTPUT,
|
|
334
|
+
diagnosis: {
|
|
335
|
+
type: 'array',
|
|
336
|
+
description: 'Ranked list of efficiency leaks found, worst first',
|
|
337
|
+
items: {
|
|
338
|
+
type: 'object',
|
|
339
|
+
properties: {
|
|
340
|
+
metric: { type: 'string', description: 'Which metric is underperforming' },
|
|
341
|
+
severity: { type: 'string', enum: ['critical', 'warning', 'info'], description: 'How bad the leak is' },
|
|
342
|
+
finding: { type: 'string', description: 'What the analysis found' },
|
|
343
|
+
recommendation: { type: 'string', description: 'What to do about it' },
|
|
344
|
+
estimated_yield_impact: { type: 'string', description: 'Estimated Υ improvement if fixed' },
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
summary: { type: 'string', description: 'One-line summary of the operator\'s cascade health' },
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const SUGGEST_OUTPUT = {
|
|
353
|
+
type: 'object',
|
|
354
|
+
properties: {
|
|
355
|
+
suggestions: {
|
|
356
|
+
type: 'array',
|
|
357
|
+
description: 'Ranked recommendations, highest Υ impact first',
|
|
358
|
+
items: {
|
|
359
|
+
type: 'object',
|
|
360
|
+
properties: {
|
|
361
|
+
rank: { type: 'number', description: '1-based rank by Υ impact' },
|
|
362
|
+
action: { type: 'string', description: 'What to change' },
|
|
363
|
+
pillar: { type: 'string', description: 'Which pillar to adjust' },
|
|
364
|
+
delta: { type: 'string', description: 'How much to change it' },
|
|
365
|
+
simulated_yield: { type: 'number', description: 'Projected Υ after the change' },
|
|
366
|
+
yield_delta: { type: 'number', description: 'Υ change vs current' },
|
|
367
|
+
class_after: { type: 'string', description: 'Projected class after the change' },
|
|
368
|
+
rationale: { type: 'string', description: 'Why this helps' },
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
current_yield: { type: 'number', description: 'Current Υ before any changes' },
|
|
373
|
+
current_class: { type: 'string', description: 'Current class tier' },
|
|
374
|
+
best_single_change: { type: 'string', description: 'The single highest-impact change' },
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
|
|
328
378
|
export const TOOLS = [
|
|
329
379
|
{
|
|
330
380
|
name: 'rank_paste',
|
|
@@ -563,6 +613,83 @@ export const TOOLS = [
|
|
|
563
613
|
},
|
|
564
614
|
outputSchema: SIMULATE_OUTPUT,
|
|
565
615
|
},
|
|
616
|
+
{
|
|
617
|
+
name: 'diagnose_cascade',
|
|
618
|
+
description:
|
|
619
|
+
"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: { ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.idempotentHint, ...ANNOTATIONS.openWorldHint },
|
|
621
|
+
inputSchema: {
|
|
622
|
+
type: 'object',
|
|
623
|
+
properties: {
|
|
624
|
+
text: {
|
|
625
|
+
type: 'string',
|
|
626
|
+
description: 'Token pillars — ccusage JSON or "input output cacheCreate cacheRead" (same format as rank_paste).',
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
required: ['text'],
|
|
630
|
+
},
|
|
631
|
+
outputSchema: DIAGNOSE_OUTPUT,
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
name: 'suggest_improvements',
|
|
635
|
+
description:
|
|
636
|
+
"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: { ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.idempotentHint, ...ANNOTATIONS.openWorldHint },
|
|
638
|
+
inputSchema: {
|
|
639
|
+
type: 'object',
|
|
640
|
+
properties: {
|
|
641
|
+
text: {
|
|
642
|
+
type: 'string',
|
|
643
|
+
description: 'Token pillars — ccusage JSON or "input output cacheCreate cacheRead" (same format as rank_paste).',
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
required: ['text'],
|
|
647
|
+
},
|
|
648
|
+
outputSchema: SUGGEST_OUTPUT,
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
name: 'self_improve',
|
|
652
|
+
description:
|
|
653
|
+
"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: { ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.idempotentHint, ...ANNOTATIONS.openWorldHint },
|
|
655
|
+
inputSchema: {
|
|
656
|
+
type: 'object',
|
|
657
|
+
properties: {
|
|
658
|
+
text: {
|
|
659
|
+
type: 'string',
|
|
660
|
+
description: 'Optional: token pillars — ccusage JSON or "input output cacheCreate cacheRead". If omitted, runs tokenpull to get current pillars from local logs.',
|
|
661
|
+
},
|
|
662
|
+
window: {
|
|
663
|
+
type: 'string',
|
|
664
|
+
enum: ['7d', '30d', '90d', 'all'],
|
|
665
|
+
description: 'Which time window to pull when running tokenpull (default: 30d). Ignored if `text` is provided.',
|
|
666
|
+
},
|
|
667
|
+
scope: {
|
|
668
|
+
type: 'string',
|
|
669
|
+
enum: ['daily', 'weekly', 'trend'],
|
|
670
|
+
description: '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
|
+
},
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
outputSchema: {
|
|
675
|
+
type: 'object',
|
|
676
|
+
properties: {
|
|
677
|
+
pillars: { type: 'object', description: 'The 4 raw token pillars used' },
|
|
678
|
+
current_cascade: CASCADE_OUTPUT,
|
|
679
|
+
diagnosis: { type: 'array', description: 'Efficiency leaks found (from diagnose_cascade)' },
|
|
680
|
+
suggestions: { type: 'array', description: 'Ranked improvements (from suggest_improvements)' },
|
|
681
|
+
best_simulation: { type: 'object', description: 'Simulated result of the top suggestion' },
|
|
682
|
+
cycle_summary: { type: 'string', description: 'One-line summary of the full cycle' },
|
|
683
|
+
// Scope-specific fields
|
|
684
|
+
mode: { type: 'object', description: 'Detected mode { mode, confidence } — present when scope is daily/weekly/trend' },
|
|
685
|
+
quality_score: { type: 'number', description: 'Yield relative to mode expectation (daily scope)' },
|
|
686
|
+
assessment: { type: 'string', description: 'One-line assessment for daily scope' },
|
|
687
|
+
advice: { type: 'string', description: 'Advice for next session (daily scope)' },
|
|
688
|
+
report: { type: 'object', description: 'Weekly report artifact (weekly scope)' },
|
|
689
|
+
trend: { type: 'object', description: 'Trend analysis (trend scope)' },
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
},
|
|
566
693
|
]
|
|
567
694
|
|
|
568
695
|
// tokenpull window key → the board's window_type enum.
|
|
@@ -720,7 +847,8 @@ export async function callTool(name, args, opts = {}) {
|
|
|
720
847
|
msgs += w.messages || 0
|
|
721
848
|
}
|
|
722
849
|
if (sum.input + sum.output + sum.cacheCreate + sum.cacheRead <= 0) continue // skip empty window
|
|
723
|
-
const
|
|
850
|
+
const report = _computeReportBlock(sum)
|
|
851
|
+
const r = await submitSignedWindow(wk, sum, msgs, id, { apiBase, fetchImpl: doFetch, platform: 'multi', now: opts.now, dryRun: !!args?.dry_run, report })
|
|
724
852
|
out.push({ window: wk, pillars: sum, ...r })
|
|
725
853
|
}
|
|
726
854
|
return { platform: 'multi', codename: id.codename, operator_id: id.operator_id, sources: detected.map((d) => d.platform), windows: out }
|
|
@@ -730,12 +858,16 @@ export async function callTool(name, args, opts = {}) {
|
|
|
730
858
|
const targets = args?.window ? pulled.windows.filter((w) => w.window === args.window) : pulled.windows
|
|
731
859
|
const out = []
|
|
732
860
|
for (const w of targets) {
|
|
861
|
+
// Compute the cascade report block (mode + badges + health) for this window.
|
|
862
|
+
// Pure math, computed locally, submitted alongside the 4 token pillars.
|
|
863
|
+
const report = _computeReportBlock(w.pillars)
|
|
733
864
|
const r = await submitSignedWindow(w.window, w.pillars, w.messages, id, {
|
|
734
865
|
apiBase,
|
|
735
866
|
fetchImpl: doFetch,
|
|
736
867
|
platform: pulled.platform,
|
|
737
868
|
now: opts.now,
|
|
738
869
|
dryRun: !!args?.dry_run,
|
|
870
|
+
report,
|
|
739
871
|
})
|
|
740
872
|
out.push({ window: w.window, pillars: w.pillars, ...r })
|
|
741
873
|
}
|
|
@@ -1097,5 +1229,742 @@ export async function callTool(name, args, opts = {}) {
|
|
|
1097
1229
|
}
|
|
1098
1230
|
}
|
|
1099
1231
|
|
|
1232
|
+
if (name === 'diagnose_cascade') {
|
|
1233
|
+
if (!args?.text) throw new Error('diagnose_cascade requires a non-empty `text` argument (token pillars).')
|
|
1234
|
+
if (typeof args.text === 'string' && args.text.length > MAX_INPUT) {
|
|
1235
|
+
return { status: 'error', reason: 'input_too_large', detail: `text exceeds ${MAX_INPUT} chars.` }
|
|
1236
|
+
}
|
|
1237
|
+
|
|
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 = []
|
|
1242
|
+
|
|
1243
|
+
// Cache leverage check — the #1 cascade efficiency signal
|
|
1244
|
+
const leverage = result.leverage
|
|
1245
|
+
if (leverage !== null) {
|
|
1246
|
+
if (leverage < 10) {
|
|
1247
|
+
diagnosis.push({
|
|
1248
|
+
metric: 'cache_leverage',
|
|
1249
|
+
severity: 'critical',
|
|
1250
|
+
finding: `Cache leverage is ${leverage}× — you're reading only ${leverage}× your fresh input from cache. TRANSMITTER-class operators hit 200×+.`,
|
|
1251
|
+
recommendation: 'Increase context reuse: load prior session context, use longer conversation threads, reference earlier outputs.',
|
|
1252
|
+
estimated_yield_impact: `+${Math.round((1 - leverage / 50) * 100)}% Υ potential`,
|
|
1253
|
+
})
|
|
1254
|
+
} else if (leverage < 50) {
|
|
1255
|
+
diagnosis.push({
|
|
1256
|
+
metric: 'cache_leverage',
|
|
1257
|
+
severity: 'warning',
|
|
1258
|
+
finding: `Cache leverage is ${leverage}× — decent but below the ARCH+ threshold (~100×+).`,
|
|
1259
|
+
recommendation: 'Push cache reads higher by reusing prior context more aggressively.',
|
|
1260
|
+
estimated_yield_impact: `+${Math.round((1 - leverage / 100) * 50)}% Υ potential`,
|
|
1261
|
+
})
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Velocity check — output per input
|
|
1266
|
+
const velocity = result.velocity
|
|
1267
|
+
if (velocity !== null) {
|
|
1268
|
+
if (velocity < 0.5) {
|
|
1269
|
+
diagnosis.push({
|
|
1270
|
+
metric: 'velocity',
|
|
1271
|
+
severity: 'critical',
|
|
1272
|
+
finding: `Velocity is ${velocity} — generating only ${velocity}× your input as output. You're reading more than you produce.`,
|
|
1273
|
+
recommendation: 'Increase output: ask the agent to generate more code/text per turn, reduce over-reading.',
|
|
1274
|
+
estimated_yield_impact: `+${Math.round((0.5 - velocity) * 100)}% Υ per 0.1 velocity gain`,
|
|
1275
|
+
})
|
|
1276
|
+
} else if (velocity < 1.0) {
|
|
1277
|
+
diagnosis.push({
|
|
1278
|
+
metric: 'velocity',
|
|
1279
|
+
severity: 'warning',
|
|
1280
|
+
finding: `Velocity is ${velocity} — below 1.0 (output < input). Healthy operators hit 1.5×+.`,
|
|
1281
|
+
recommendation: 'Generate more output per input token — larger edits, more complete responses.',
|
|
1282
|
+
estimated_yield_impact: `+${Math.round((1 - velocity) * 30)}% Υ potential`,
|
|
1283
|
+
})
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// SNR check — signal-to-noise
|
|
1288
|
+
const snr = result.snr
|
|
1289
|
+
if (snr !== null && snr < 0.3) {
|
|
1290
|
+
diagnosis.push({
|
|
1291
|
+
metric: 'snr',
|
|
1292
|
+
severity: 'warning',
|
|
1293
|
+
finding: `SNR is ${snr} — less than 30% of your token flow is output. Input is dominating.`,
|
|
1294
|
+
recommendation: 'Reduce fresh input (reuse context) or increase output generation.',
|
|
1295
|
+
estimated_yield_impact: 'Indirect — improves both velocity and leverage',
|
|
1296
|
+
})
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// Cache creation ratio — are you over-committing?
|
|
1300
|
+
if (cw > 0 && o > 0) {
|
|
1301
|
+
const commitRatio = cw / o
|
|
1302
|
+
if (commitRatio > 20) {
|
|
1303
|
+
diagnosis.push({
|
|
1304
|
+
metric: 'cache_creation',
|
|
1305
|
+
severity: 'info',
|
|
1306
|
+
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: "Ensure you're rereading committed context. If leverage is low, you're writing cache you never read.",
|
|
1308
|
+
estimated_yield_impact: 'Cost reduction, not Υ directly',
|
|
1309
|
+
})
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Input bloat — is fresh input too high relative to total?
|
|
1314
|
+
const total = i + o + cw + cr
|
|
1315
|
+
if (total > 0) {
|
|
1316
|
+
const inputPct = (i / total) * 100
|
|
1317
|
+
if (inputPct > 10) {
|
|
1318
|
+
diagnosis.push({
|
|
1319
|
+
metric: 'input_bloat',
|
|
1320
|
+
severity: 'warning',
|
|
1321
|
+
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: 'Reduce fresh input by reusing prior context instead of re-pasting it.',
|
|
1323
|
+
estimated_yield_impact: `+${Math.round((inputPct - 1) * 5)}% Υ potential`,
|
|
1324
|
+
})
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// 10xDEV check — is the full cascade compounding?
|
|
1329
|
+
if (result.dev10x === null && cw === 0) {
|
|
1330
|
+
diagnosis.push({
|
|
1331
|
+
metric: '10xdev',
|
|
1332
|
+
severity: 'critical',
|
|
1333
|
+
finding: "No cache creation — the cascade cannot compound. You're operating in a non-compounding mode (like ChatGPT without prompt caching).",
|
|
1334
|
+
recommendation: 'Switch to a platform with prompt caching (Claude Code) or enable caching if available.',
|
|
1335
|
+
estimated_yield_impact: 'Enables the full cascade — potentially 10×+ Υ',
|
|
1336
|
+
})
|
|
1337
|
+
} else if (result.dev10x !== null && result.dev10x < 1.0) {
|
|
1338
|
+
diagnosis.push({
|
|
1339
|
+
metric: '10xdev',
|
|
1340
|
+
severity: 'info',
|
|
1341
|
+
finding: `10xDEV is ${result.dev10x} — below 1.0 (BASE threshold). The cascade is compounding but not strongly.`,
|
|
1342
|
+
recommendation: 'Improve both leverage AND velocity — 10xDEV = log10(transmission × commitment × reuse).',
|
|
1343
|
+
estimated_yield_impact: 'Class tier improvement',
|
|
1344
|
+
})
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// 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])
|
|
1350
|
+
|
|
1351
|
+
const healthScore = diagnosis.filter((d) => d.severity === 'critical').length
|
|
1352
|
+
const summary = healthScore === 0
|
|
1353
|
+
? `Cascade is healthy — Υ ${result.yield}, class ${result.class}. ${diagnosis.length} minor optimizations available.`
|
|
1354
|
+
: `Cascade has ${healthScore} critical leak${healthScore > 1 ? 's' : ''} — Υ ${result.yield}, class ${result.class}. Fix the critical items first.`
|
|
1355
|
+
|
|
1356
|
+
return {
|
|
1357
|
+
pillars: p,
|
|
1358
|
+
cascade: {
|
|
1359
|
+
yield_: result.yield,
|
|
1360
|
+
snr: result.snr,
|
|
1361
|
+
leverage: result.leverage,
|
|
1362
|
+
velocity: result.velocity,
|
|
1363
|
+
tenx_dev: result.dev10x,
|
|
1364
|
+
class: result.class,
|
|
1365
|
+
warnings: result.warnings,
|
|
1366
|
+
},
|
|
1367
|
+
diagnosis,
|
|
1368
|
+
summary,
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if (name === 'suggest_improvements') {
|
|
1373
|
+
if (!args?.text) throw new Error('suggest_improvements requires a non-empty `text` argument (token pillars).')
|
|
1374
|
+
if (typeof args.text === 'string' && args.text.length > MAX_INPUT) {
|
|
1375
|
+
return { status: 'error', reason: 'input_too_large', detail: `text exceeds ${MAX_INPUT} chars.` }
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
const p = parsePillars(args.text)
|
|
1379
|
+
const current = withParseWarnings(p, cascade(p))
|
|
1380
|
+
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = p
|
|
1381
|
+
|
|
1382
|
+
// Generate candidate improvements, simulate each, rank by Υ impact
|
|
1383
|
+
const candidates = []
|
|
1384
|
+
|
|
1385
|
+
// Strategy 1: Increase cache reads (the #1 lever)
|
|
1386
|
+
const crBoosts = cr > 0 ? [1.5, 2, 3, 5] : []
|
|
1387
|
+
for (const mult of crBoosts) {
|
|
1388
|
+
const sim = cascade({ ...p, cacheRead: Math.round(cr * mult) })
|
|
1389
|
+
if (sim.yield !== null) {
|
|
1390
|
+
candidates.push({
|
|
1391
|
+
action: `Increase cache reads by ${Math.round((mult - 1) * 100)}%`,
|
|
1392
|
+
pillar: 'cacheRead',
|
|
1393
|
+
delta: `+${Math.round(cr * (mult - 1)).toLocaleString()}`,
|
|
1394
|
+
simulated_yield: sim.yield,
|
|
1395
|
+
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1396
|
+
class_after: sim.class,
|
|
1397
|
+
rationale: 'Cache reads are the strongest Υ multiplier. More reuse = higher leverage = higher yield.',
|
|
1398
|
+
})
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
// For zero-cache operators, suggest enabling caching with a starter amount
|
|
1402
|
+
if (cr === 0 && cw === 0) {
|
|
1403
|
+
const starterAmounts = [Math.round(i * 10), Math.round(i * 50), Math.round(i * 100)]
|
|
1404
|
+
for (const amt of starterAmounts) {
|
|
1405
|
+
const sim = cascade({ ...p, cacheCreate: Math.round(amt * 0.5), cacheRead: amt })
|
|
1406
|
+
if (sim.yield !== null && sim.yield > 0) {
|
|
1407
|
+
candidates.push({
|
|
1408
|
+
action: `Enable caching with ${amt.toLocaleString()} cache reads`,
|
|
1409
|
+
pillar: 'cacheRead',
|
|
1410
|
+
delta: `+${amt.toLocaleString()}`,
|
|
1411
|
+
simulated_yield: sim.yield,
|
|
1412
|
+
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1413
|
+
class_after: sim.class,
|
|
1414
|
+
rationale: 'You have no cache — enabling it unlocks the cascade. Start by reusing prior context.',
|
|
1415
|
+
})
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// Strategy 2: Reduce fresh input (Υ = Cr·O/I² — input is squared in the denominator)
|
|
1421
|
+
const inputReductions = [0.9, 0.75, 0.5]
|
|
1422
|
+
for (const mult of inputReductions) {
|
|
1423
|
+
const newInput = Math.round(i * mult)
|
|
1424
|
+
if (newInput < 1) continue
|
|
1425
|
+
const sim = cascade({ ...p, input: newInput })
|
|
1426
|
+
if (sim.yield !== null) {
|
|
1427
|
+
candidates.push({
|
|
1428
|
+
action: `Reduce fresh input by ${Math.round((1 - mult) * 100)}%`,
|
|
1429
|
+
pillar: 'input',
|
|
1430
|
+
delta: `-${Math.round(i * (1 - mult)).toLocaleString()}`,
|
|
1431
|
+
simulated_yield: sim.yield,
|
|
1432
|
+
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1433
|
+
class_after: sim.class,
|
|
1434
|
+
rationale: 'Input is squared in the Υ denominator (Υ = Cr·O/I²). Reducing input has a quadratic payoff.',
|
|
1435
|
+
})
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// Strategy 3: Increase output
|
|
1440
|
+
const outputBoosts = [1.25, 1.5, 2]
|
|
1441
|
+
for (const mult of outputBoosts) {
|
|
1442
|
+
const sim = cascade({ ...p, output: Math.round(o * mult) })
|
|
1443
|
+
if (sim.yield !== null) {
|
|
1444
|
+
candidates.push({
|
|
1445
|
+
action: `Increase output by ${Math.round((mult - 1) * 100)}%`,
|
|
1446
|
+
pillar: 'output',
|
|
1447
|
+
delta: `+${Math.round(o * (mult - 1)).toLocaleString()}`,
|
|
1448
|
+
simulated_yield: sim.yield,
|
|
1449
|
+
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1450
|
+
class_after: sim.class,
|
|
1451
|
+
rationale: 'Output is a linear multiplier in Υ. More output per session = higher yield.',
|
|
1452
|
+
})
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// Strategy 4: Optimize cache creation (reduce if over-committing)
|
|
1457
|
+
if (cw > o * 10) {
|
|
1458
|
+
const sim = cascade({ ...p, cacheCreate: Math.round(o * 5) })
|
|
1459
|
+
if (sim.yield !== null) {
|
|
1460
|
+
candidates.push({
|
|
1461
|
+
action: 'Reduce cache creation to 5× output',
|
|
1462
|
+
pillar: 'cacheCreate',
|
|
1463
|
+
delta: `-${Math.round(cw - o * 5).toLocaleString()}`,
|
|
1464
|
+
simulated_yield: sim.yield,
|
|
1465
|
+
yield_delta: Number((sim.yield - (current.yield ?? 0)).toFixed(2)),
|
|
1466
|
+
class_after: sim.class,
|
|
1467
|
+
rationale: 'You\'re over-committing cache (cw >> output). Trimming to a healthy ratio reduces cost without hurting yield.',
|
|
1468
|
+
})
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Sort by yield_delta descending, take top 8
|
|
1473
|
+
candidates.sort((a, b) => b.yield_delta - a.yield_delta)
|
|
1474
|
+
const top = candidates.slice(0, 8).map((c, idx) => ({ rank: idx + 1, ...c }))
|
|
1475
|
+
|
|
1476
|
+
const best = top[0]
|
|
1477
|
+
return {
|
|
1478
|
+
suggestions: top,
|
|
1479
|
+
current_yield: current.yield,
|
|
1480
|
+
current_class: current.class,
|
|
1481
|
+
best_single_change: best
|
|
1482
|
+
? `${best.action} (Υ ${current.yield} → ${best.simulated_yield}, +${best.yield_delta} yield, class ${best.class_after})`
|
|
1483
|
+
: 'No improvements found — your cascade is already optimized.',
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if (name === 'self_improve') {
|
|
1488
|
+
// The full self-improvement cycle: pull → diagnose → suggest → simulate.
|
|
1489
|
+
// If text is provided, use it as pillars. If not, run tokenpull first.
|
|
1490
|
+
let pillars
|
|
1491
|
+
let pulledFrom = 'provided'
|
|
1492
|
+
|
|
1493
|
+
if (args?.text) {
|
|
1494
|
+
if (typeof args.text === 'string' && args.text.length > MAX_INPUT) {
|
|
1495
|
+
return { status: 'error', reason: 'input_too_large', detail: `text exceeds ${MAX_INPUT} chars.` }
|
|
1496
|
+
}
|
|
1497
|
+
pillars = parsePillars(args.text)
|
|
1498
|
+
} else {
|
|
1499
|
+
// Run tokenpull to get current pillars from local logs
|
|
1500
|
+
const windowType = args?.window || '30d'
|
|
1501
|
+
try {
|
|
1502
|
+
const pullResult = await callTool('tokenpull', { window: windowType }, opts)
|
|
1503
|
+
if (pullResult?.status === 'error' || !pullResult?.windows?.length) {
|
|
1504
|
+
return {
|
|
1505
|
+
status: 'error',
|
|
1506
|
+
reason: 'tokenpull_failed',
|
|
1507
|
+
detail: 'Could not pull pillars from local logs. Provide pillars via `text` argument instead.',
|
|
1508
|
+
pull_result: pullResult,
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
// Use the first window's pillars
|
|
1512
|
+
const w = pullResult.windows[0]
|
|
1513
|
+
pillars = w.pillars
|
|
1514
|
+
pulledFrom = `tokenpull ${w.window || windowType}`
|
|
1515
|
+
} catch (e) {
|
|
1516
|
+
return {
|
|
1517
|
+
status: 'error',
|
|
1518
|
+
reason: 'tokenpull_error',
|
|
1519
|
+
detail: String(e.message || e),
|
|
1520
|
+
hint: 'Provide pillars via `text` argument instead.',
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
const currentResult = withParseWarnings(pillars, cascade(pillars))
|
|
1526
|
+
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = pillars
|
|
1527
|
+
|
|
1528
|
+
// ── Step 1: Diagnose ──────────────────────────────────────────────
|
|
1529
|
+
const diagnosis = []
|
|
1530
|
+
const leverage = currentResult.leverage
|
|
1531
|
+
if (leverage !== null && leverage < 10) {
|
|
1532
|
+
diagnosis.push({
|
|
1533
|
+
metric: 'cache_leverage', severity: 'critical',
|
|
1534
|
+
finding: `Cache leverage is ${leverage}× — TRANSMITTER-class operators hit 200×+.`,
|
|
1535
|
+
recommendation: 'Increase context reuse: load prior session context, use longer threads.',
|
|
1536
|
+
})
|
|
1537
|
+
} else if (leverage !== null && leverage < 50) {
|
|
1538
|
+
diagnosis.push({
|
|
1539
|
+
metric: 'cache_leverage', severity: 'warning',
|
|
1540
|
+
finding: `Cache leverage is ${leverage}× — below ARCH+ threshold (~100×+).`,
|
|
1541
|
+
recommendation: 'Push cache reads higher by reusing prior context.',
|
|
1542
|
+
})
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
const velocity = currentResult.velocity
|
|
1546
|
+
if (velocity !== null && velocity < 0.5) {
|
|
1547
|
+
diagnosis.push({
|
|
1548
|
+
metric: 'velocity', severity: 'critical',
|
|
1549
|
+
finding: `Velocity is ${velocity} — generating only ${velocity}× input as output.`,
|
|
1550
|
+
recommendation: 'Increase output per turn, reduce over-reading.',
|
|
1551
|
+
})
|
|
1552
|
+
} else if (velocity !== null && velocity < 1.0) {
|
|
1553
|
+
diagnosis.push({
|
|
1554
|
+
metric: 'velocity', severity: 'warning',
|
|
1555
|
+
finding: `Velocity is ${velocity} — below 1.0. Healthy operators hit 1.5×+.`,
|
|
1556
|
+
recommendation: 'Generate more output per input token.',
|
|
1557
|
+
})
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
if (currentResult.dev10x === null && cw === 0) {
|
|
1561
|
+
diagnosis.push({
|
|
1562
|
+
metric: '10xdev', severity: 'critical',
|
|
1563
|
+
finding: 'No cache creation — cascade cannot compound.',
|
|
1564
|
+
recommendation: 'Switch to a platform with prompt caching (Claude Code).',
|
|
1565
|
+
})
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
const total = i + o + cw + cr
|
|
1569
|
+
if (total > 0 && (i / total) * 100 > 10) {
|
|
1570
|
+
diagnosis.push({
|
|
1571
|
+
metric: 'input_bloat', severity: 'warning',
|
|
1572
|
+
finding: `Fresh input is ${((i / total) * 100).toFixed(1)}% of total flow — efficient operators keep it under 1%.`,
|
|
1573
|
+
recommendation: 'Reduce fresh input by reusing prior context.',
|
|
1574
|
+
})
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// ── Step 2: Suggest (generate + simulate candidates) ──────────────
|
|
1578
|
+
const candidates = []
|
|
1579
|
+
// If cacheRead is 0, suggest absolute amounts instead of percentage boosts
|
|
1580
|
+
const crBoosts = cr > 0 ? [1.5, 2, 3, 5] : []
|
|
1581
|
+
for (const mult of crBoosts) {
|
|
1582
|
+
const sim = cascade({ ...pillars, cacheRead: Math.round(cr * mult) })
|
|
1583
|
+
if (sim.yield !== null) candidates.push({
|
|
1584
|
+
action: `Increase cache reads by ${Math.round((mult - 1) * 100)}%`,
|
|
1585
|
+
pillar: 'cacheRead', delta: `+${Math.round(cr * (mult - 1)).toLocaleString()}`,
|
|
1586
|
+
simulated_yield: sim.yield, yield_delta: Number((sim.yield - (currentResult.yield ?? 0)).toFixed(2)),
|
|
1587
|
+
class_after: sim.class, rationale: 'Cache reads are the strongest Υ multiplier.',
|
|
1588
|
+
})
|
|
1589
|
+
}
|
|
1590
|
+
// For zero-cache operators, suggest enabling caching with a starter amount
|
|
1591
|
+
if (cr === 0 && cw === 0) {
|
|
1592
|
+
const starterAmounts = [Math.round(i * 10), Math.round(i * 50), Math.round(i * 100)]
|
|
1593
|
+
for (const amt of starterAmounts) {
|
|
1594
|
+
const sim = cascade({ ...pillars, cacheCreate: Math.round(amt * 0.5), cacheRead: amt })
|
|
1595
|
+
if (sim.yield !== null && sim.yield > 0) candidates.push({
|
|
1596
|
+
action: `Enable caching with ${amt.toLocaleString()} cache reads`,
|
|
1597
|
+
pillar: 'cacheRead', delta: `+${amt.toLocaleString()}`,
|
|
1598
|
+
simulated_yield: sim.yield, yield_delta: Number((sim.yield - (currentResult.yield ?? 0)).toFixed(2)),
|
|
1599
|
+
class_after: sim.class, rationale: 'You have no cache — enabling it unlocks the cascade. Start by reusing prior context.',
|
|
1600
|
+
})
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
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) candidates.push({
|
|
1608
|
+
action: `Reduce fresh input by ${Math.round((1 - mult) * 100)}%`,
|
|
1609
|
+
pillar: 'input', delta: `-${Math.round(i * (1 - mult)).toLocaleString()}`,
|
|
1610
|
+
simulated_yield: sim.yield, yield_delta: Number((sim.yield - (currentResult.yield ?? 0)).toFixed(2)),
|
|
1611
|
+
class_after: sim.class, rationale: 'Input is squared in Υ denominator — quadratic payoff.',
|
|
1612
|
+
})
|
|
1613
|
+
}
|
|
1614
|
+
for (const mult of [1.25, 1.5, 2]) {
|
|
1615
|
+
const sim = cascade({ ...pillars, output: Math.round(o * mult) })
|
|
1616
|
+
if (sim.yield !== null) candidates.push({
|
|
1617
|
+
action: `Increase output by ${Math.round((mult - 1) * 100)}%`,
|
|
1618
|
+
pillar: 'output', delta: `+${Math.round(o * (mult - 1)).toLocaleString()}`,
|
|
1619
|
+
simulated_yield: sim.yield, yield_delta: Number((sim.yield - (currentResult.yield ?? 0)).toFixed(2)),
|
|
1620
|
+
class_after: sim.class, rationale: 'Output is a linear multiplier in Υ.',
|
|
1621
|
+
})
|
|
1622
|
+
}
|
|
1623
|
+
candidates.sort((a, b) => b.yield_delta - a.yield_delta)
|
|
1624
|
+
const suggestions = candidates.slice(0, 8).map((c, idx) => ({ rank: idx + 1, ...c }))
|
|
1625
|
+
|
|
1626
|
+
// ── Step 3: Simulate the top suggestion ───────────────────────────
|
|
1627
|
+
const best = suggestions[0]
|
|
1628
|
+
let bestSimulation = null
|
|
1629
|
+
if (best) {
|
|
1630
|
+
const simPillars = { ...pillars }
|
|
1631
|
+
const delta = parseInt(best.delta.replace(/[+\-,]/g, ''), 10)
|
|
1632
|
+
if (best.pillar === 'cacheRead') simPillars.cacheRead = cr + delta
|
|
1633
|
+
else if (best.pillar === 'input') simPillars.input = Math.max(i - delta, 1)
|
|
1634
|
+
else if (best.pillar === 'output') simPillars.output = o + delta
|
|
1635
|
+
const simResult = cascade(simPillars)
|
|
1636
|
+
bestSimulation = {
|
|
1637
|
+
action: best.action,
|
|
1638
|
+
current_yield: currentResult.yield,
|
|
1639
|
+
simulated_yield: simResult.yield,
|
|
1640
|
+
yield_delta: best.yield_delta,
|
|
1641
|
+
current_class: currentResult.class,
|
|
1642
|
+
simulated_class: simResult.class,
|
|
1643
|
+
class_changed: currentResult.class !== simResult.class,
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// ── Step 4: Cycle summary ─────────────────────────────────────────
|
|
1648
|
+
const criticalCount = diagnosis.filter((d) => d.severity === 'critical').length
|
|
1649
|
+
const cycleSummary = best
|
|
1650
|
+
? `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.`
|
|
1652
|
+
|
|
1653
|
+
// ── Step 5: Scope-specific analysis ───────────────────────────────
|
|
1654
|
+
const scope = args?.scope || 'daily'
|
|
1655
|
+
const modeInfo = currentResult.mode // { mode, confidence }
|
|
1656
|
+
const scopeResult = {}
|
|
1657
|
+
|
|
1658
|
+
if (scope === 'daily') {
|
|
1659
|
+
// 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(modeInfo.mode, currentResult.yield, qs, expected)
|
|
1663
|
+
const advice = _dailyAdvice(modeInfo.mode, currentResult.yield, qs)
|
|
1664
|
+
scopeResult.mode = modeInfo
|
|
1665
|
+
scopeResult.quality_score = Math.round(qs * 100) / 100
|
|
1666
|
+
scopeResult.assessment = assessment
|
|
1667
|
+
scopeResult.advice = advice
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
if (scope === 'weekly' || scope === 'trend') {
|
|
1671
|
+
// 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)
|
|
1676
|
+
|
|
1677
|
+
// Compute badges
|
|
1678
|
+
const historyForBadges = weeklySnapshots.map(w => ({
|
|
1679
|
+
date: w.weekStart,
|
|
1680
|
+
mode: w.mode,
|
|
1681
|
+
yield: w.yield,
|
|
1682
|
+
pillars: w.pillars,
|
|
1683
|
+
}))
|
|
1684
|
+
const badges = computeBadges({
|
|
1685
|
+
pillars,
|
|
1686
|
+
cascade: currentResult,
|
|
1687
|
+
history: historyForBadges,
|
|
1688
|
+
isVerified: false, // set by submit_verified
|
|
1689
|
+
rank: null, // server-side
|
|
1690
|
+
})
|
|
1691
|
+
|
|
1692
|
+
if (scope === 'weekly') {
|
|
1693
|
+
scopeResult.mode = modeInfo
|
|
1694
|
+
// Strip dailyModes from weekly_snapshots — privacy boundary (daily modes never leave the machine)
|
|
1695
|
+
const safeSnapshots = weeklySnapshots.map(({ dailyModes, ...rest }) => rest)
|
|
1696
|
+
scopeResult.report = {
|
|
1697
|
+
current_mode: modeInfo.mode,
|
|
1698
|
+
mode_confidence: modeInfo.confidence,
|
|
1699
|
+
mode_distribution: modeDistribution,
|
|
1700
|
+
mode_weighted_yield: modeWeightedYield,
|
|
1701
|
+
peak_yield: _peakYield(weeklySnapshots),
|
|
1702
|
+
health_score: _healthScore(weeklySnapshots, modeWeightedYield),
|
|
1703
|
+
weekly_snapshots: safeSnapshots,
|
|
1704
|
+
badges,
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
if (scope === 'trend') {
|
|
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
|
|
1714
|
+
scopeResult.trend = {
|
|
1715
|
+
yield_7d: yield7d,
|
|
1716
|
+
yield_30d: yield30d,
|
|
1717
|
+
yield_90d: yield90d,
|
|
1718
|
+
trajectory,
|
|
1719
|
+
mode_distribution: modeDistribution,
|
|
1720
|
+
phase_pattern: _phasePattern(weeklySnapshots),
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
return {
|
|
1726
|
+
pillars,
|
|
1727
|
+
current_cascade: {
|
|
1728
|
+
yield_: currentResult.yield,
|
|
1729
|
+
snr: currentResult.snr,
|
|
1730
|
+
leverage: currentResult.leverage,
|
|
1731
|
+
velocity: currentResult.velocity,
|
|
1732
|
+
tenx_dev: currentResult.dev10x,
|
|
1733
|
+
class: currentResult.class,
|
|
1734
|
+
mode: modeInfo,
|
|
1735
|
+
warnings: currentResult.warnings,
|
|
1736
|
+
},
|
|
1737
|
+
diagnosis,
|
|
1738
|
+
suggestions,
|
|
1739
|
+
best_simulation: bestSimulation,
|
|
1740
|
+
cycle_summary: cycleSummary,
|
|
1741
|
+
...scopeResult,
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1100
1745
|
throw new Error(`Unknown tool: ${name}`)
|
|
1101
1746
|
}
|
|
1747
|
+
|
|
1748
|
+
// ── self_improve scope helpers ──────────────────────────────────────────────
|
|
1749
|
+
|
|
1750
|
+
/** Pull daily rows from ccusage to build mode history. Returns array of { date, pillars, yield, mode }. */
|
|
1751
|
+
async function _pullDailyRows(opts = {}) {
|
|
1752
|
+
try {
|
|
1753
|
+
const raw = await execFileAsync('ccusage', ['claude', 'daily', '--json'], 15000)
|
|
1754
|
+
const rows = JSON.parse(raw)?.daily ?? JSON.parse(raw)
|
|
1755
|
+
if (!Array.isArray(rows)) return []
|
|
1756
|
+
return rows.map(r => {
|
|
1757
|
+
const pillars = {
|
|
1758
|
+
input: r.inputTokens ?? r.input_tokens ?? 0,
|
|
1759
|
+
output: r.outputTokens ?? r.output_tokens ?? 0,
|
|
1760
|
+
cacheCreate: r.cacheCreationTokens ?? r.cache_create_tokens ?? 0,
|
|
1761
|
+
cacheRead: r.cacheReadTokens ?? r.cache_read_tokens ?? 0,
|
|
1762
|
+
}
|
|
1763
|
+
const cas = cascade(pillars)
|
|
1764
|
+
const mode = detectMode(pillars)
|
|
1765
|
+
return {
|
|
1766
|
+
date: r.date ?? r.day ?? null,
|
|
1767
|
+
pillars,
|
|
1768
|
+
yield: cas.yield ?? 0,
|
|
1769
|
+
mode: mode.mode,
|
|
1770
|
+
mode_confidence: mode.confidence,
|
|
1771
|
+
}
|
|
1772
|
+
}).filter(r => r.date)
|
|
1773
|
+
} catch {
|
|
1774
|
+
return []
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
/** Compound daily rows into weekly snapshots. Each week = 7 days, starting Monday. */
|
|
1779
|
+
function _compoundWeekly(dailyRows) {
|
|
1780
|
+
if (!dailyRows || dailyRows.length === 0) return []
|
|
1781
|
+
// Group by ISO week (week starts Monday)
|
|
1782
|
+
const weeks = new Map()
|
|
1783
|
+
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)
|
|
1789
|
+
if (!weeks.has(weekKey)) {
|
|
1790
|
+
weeks.set(weekKey, { weekStart: weekKey, days: [] })
|
|
1791
|
+
}
|
|
1792
|
+
weeks.get(weekKey).days.push(row)
|
|
1793
|
+
}
|
|
1794
|
+
// Compound each week
|
|
1795
|
+
return Array.from(weeks.values()).map(w => {
|
|
1796
|
+
const pillars = w.days.reduce((acc, d) => ({
|
|
1797
|
+
input: acc.input + d.pillars.input,
|
|
1798
|
+
output: acc.output + d.pillars.output,
|
|
1799
|
+
cacheCreate: acc.cacheCreate + d.pillars.cacheCreate,
|
|
1800
|
+
cacheRead: acc.cacheRead + d.pillars.cacheRead,
|
|
1801
|
+
}), { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 })
|
|
1802
|
+
const cas = cascade(pillars)
|
|
1803
|
+
const mode = detectMode(pillars)
|
|
1804
|
+
return {
|
|
1805
|
+
weekStart: w.weekStart,
|
|
1806
|
+
pillars,
|
|
1807
|
+
yield: cas.yield ?? 0,
|
|
1808
|
+
mode: mode.mode,
|
|
1809
|
+
mode_confidence: mode.confidence,
|
|
1810
|
+
dayCount: w.days.length,
|
|
1811
|
+
dailyModes: w.days.map(d => d.mode), // stays local — not in submitted report
|
|
1812
|
+
}
|
|
1813
|
+
}).sort((a, b) => a.weekStart.localeCompare(b.weekStart))
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
/** Compute mode distribution from weekly snapshots (weekly granularity = privacy boundary). */
|
|
1817
|
+
function _modeDistribution(weeklySnapshots) {
|
|
1818
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return {}
|
|
1819
|
+
const counts = { BUILD: 0, EDIT: 0, DEBUG: 0, MAINTAIN: 0, IDLE: 0 }
|
|
1820
|
+
for (const w of weeklySnapshots) {
|
|
1821
|
+
counts[w.mode] = (counts[w.mode] || 0) + 1
|
|
1822
|
+
}
|
|
1823
|
+
const total = weeklySnapshots.length
|
|
1824
|
+
const dist = {}
|
|
1825
|
+
for (const [mode, count] of Object.entries(counts)) {
|
|
1826
|
+
if (count > 0) dist[mode] = Math.round((count / total) * 100) / 100
|
|
1827
|
+
}
|
|
1828
|
+
return dist
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
/** Mode-weighted yield — average yield weighted by mode distribution. */
|
|
1832
|
+
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)
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
/** Peak yield across all weekly snapshots. */
|
|
1840
|
+
function _peakYield(weeklySnapshots) {
|
|
1841
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0
|
|
1842
|
+
return Math.max(...weeklySnapshots.map(w => w.yield || 0))
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
/** Health score (0-100) — weighted composite of consistency, momentum, quality. */
|
|
1846
|
+
function _healthScore(weeklySnapshots, modeWeightedYield) {
|
|
1847
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0
|
|
1848
|
+
// Simplified Phase 1 formula:
|
|
1849
|
+
// 40% — consistency (how often in MAINTAIN)
|
|
1850
|
+
// 30% — momentum (recent yield vs older yield)
|
|
1851
|
+
// 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
|
|
1855
|
+
|
|
1856
|
+
// 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 = recent.reduce((a, w) => a + (w.yield || 0), 0) / (recent.length || 1)
|
|
1860
|
+
const olderAvg = older.reduce((a, w) => a + (w.yield || 0), 0) / (older.length || 1)
|
|
1861
|
+
const momentumRatio = olderAvg > 0 ? recentAvg / olderAvg : 1
|
|
1862
|
+
const momentum = Math.min(momentumRatio, 2) / 2 * 30
|
|
1863
|
+
|
|
1864
|
+
// Quality: mode-weighted yield vs MAINTAIN expected (5000)
|
|
1865
|
+
const quality = Math.min(modeWeightedYield / 5000, 1) * 30
|
|
1866
|
+
|
|
1867
|
+
return Math.round(consistency + momentum + quality)
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/** Yield for the last N days (from weekly snapshots — approximates by summing recent weeks). */
|
|
1871
|
+
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
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
/** Trajectory description from 7d/30d/90d yields. */
|
|
1881
|
+
function _trajectory(y7, y30, y90) {
|
|
1882
|
+
if (y90 === 0) return 'insufficient_data'
|
|
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 'accelerating'
|
|
1886
|
+
if (r7v30 > 1.2) return 'recent_surge'
|
|
1887
|
+
if (r7v30 < 0.8 && r30v90 < 0.8) return 'declining'
|
|
1888
|
+
if (r7v30 < 0.8) return 'recent_dip'
|
|
1889
|
+
if (r30v90 > 1.1) return 'steady_growth'
|
|
1890
|
+
return 'stable'
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/** Phase pattern description from mode distribution over time. */
|
|
1894
|
+
function _phasePattern(weeklySnapshots) {
|
|
1895
|
+
if (!weeklySnapshots || weeklySnapshots.length < 2) return 'insufficient_data'
|
|
1896
|
+
const modes = weeklySnapshots.map(w => w.mode)
|
|
1897
|
+
const transitions = []
|
|
1898
|
+
for (let i = 1; i < modes.length; i++) {
|
|
1899
|
+
if (modes[i] !== modes[i - 1]) {
|
|
1900
|
+
transitions.push(`${modes[i - 1]}→${modes[i]}`)
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
if (transitions.length === 0) return `consistent_${modes[0].toLowerCase()}`
|
|
1904
|
+
// Check for smooth transitions (BUILD→MAINTAIN without DEBUG)
|
|
1905
|
+
const smooth = transitions.every(t => !t.includes('DEBUG'))
|
|
1906
|
+
if (smooth && transitions.some(t => t === 'BUILD→MAINTAIN')) return 'smooth_ramp'
|
|
1907
|
+
if (transitions.filter(t => t.includes('DEBUG')).length > 2) return 'erratic'
|
|
1908
|
+
return 'cyclical'
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
/** Daily assessment string. */
|
|
1912
|
+
function _dailyAssessment(mode, yieldVal, qualityScoreVal, expectedYield) {
|
|
1913
|
+
const qsPct = Math.round(qualityScoreVal * 100)
|
|
1914
|
+
if (mode === 'IDLE') return 'You\'re idle — no significant token activity today.'
|
|
1915
|
+
if (mode === 'MAINTAIN') {
|
|
1916
|
+
if (qualityScoreVal >= 0.5) return `You're in MAINTAIN mode. Yield ${yieldVal} is ${qsPct}% of MAINTAIN norm. The cascade is compounding.`
|
|
1917
|
+
return `You're in MAINTAIN mode but yield ${yieldVal} is only ${qsPct}% of expected (${expectedYield}). You may be coasting — push for more output.`
|
|
1918
|
+
}
|
|
1919
|
+
if (mode === 'BUILD') {
|
|
1920
|
+
if (qualityScoreVal >= 0.5) return `You're in BUILD mode. Yield ${yieldVal} is ${qsPct}% of BUILD norm. Greenfield work — expected to be low.`
|
|
1921
|
+
return `You're in BUILD mode. Yield ${yieldVal} is ${qsPct}% of expected (${expectedYield}). Building is slow — keep going.`
|
|
1922
|
+
}
|
|
1923
|
+
if (mode === 'EDIT') {
|
|
1924
|
+
if (qualityScoreVal >= 0.5) return `You're in EDIT mode. Yield ${yieldVal} is ${qsPct}% of EDIT norm. Fresh input but producing — good.`
|
|
1925
|
+
return `You're in EDIT mode. Yield ${yieldVal} is ${qsPct}% of expected (${expectedYield}). Push for more output per input.`
|
|
1926
|
+
}
|
|
1927
|
+
if (mode === 'DEBUG') {
|
|
1928
|
+
if (qualityScoreVal >= 0.5) return `You're in DEBUG mode. Yield ${yieldVal} is ${qsPct}% of DEBUG norm. Investigating — yield is expected to be low.`
|
|
1929
|
+
return `You're in DEBUG mode. Yield ${yieldVal} is ${qsPct}% of expected (${expectedYield}). Debugging is slow — consider loading prior context.`
|
|
1930
|
+
}
|
|
1931
|
+
return `Mode: ${mode}. Yield: ${yieldVal}. Quality: ${qsPct}%.`
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
/** Daily advice string. */
|
|
1935
|
+
function _dailyAdvice(mode, yieldVal, qs) {
|
|
1936
|
+
if (mode === 'MAINTAIN' && qs >= 0.5) return 'Keep the cascade going. Don\'t reset context — let it compound.'
|
|
1937
|
+
if (mode === 'MAINTAIN') return 'You\'re in MAINTAIN but underperforming. Push the agent for more output per turn.'
|
|
1938
|
+
if (mode === 'BUILD') return 'When you\'re done building, load prior context to transition to MAINTAIN. The cascade rewards reuse.'
|
|
1939
|
+
if (mode === 'EDIT') return 'You\'re producing but using fresh input. Try to reuse prior context to boost leverage.'
|
|
1940
|
+
if (mode === 'DEBUG') return 'When you\'re done debugging, load prior context to return to MAINTAIN. Don\'t let the debug phase drag on.'
|
|
1941
|
+
if (mode === 'IDLE') return 'No activity detected. Start a session to build your cascade.'
|
|
1942
|
+
return 'Keep working — the cascade will compound as you build context.'
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
/**
|
|
1946
|
+
* Compute the cascade report block for a submission payload.
|
|
1947
|
+
* Pure math — mode detection + badges + health score from the current pillars.
|
|
1948
|
+
* The server stores this as-is (does NOT recompute modes).
|
|
1949
|
+
* Weekly granularity is the privacy boundary — no daily modes in the report.
|
|
1950
|
+
*/
|
|
1951
|
+
function _computeReportBlock(pillars) {
|
|
1952
|
+
const cas = cascade(pillars)
|
|
1953
|
+
const modeInfo = detectMode(pillars)
|
|
1954
|
+
const badges = computeBadges({
|
|
1955
|
+
pillars,
|
|
1956
|
+
cascade: cas,
|
|
1957
|
+
history: [], // no history available at submit time — badges computed from current pillars only
|
|
1958
|
+
isVerified: true, // submit_verified is the signed agent path
|
|
1959
|
+
rank: null,
|
|
1960
|
+
})
|
|
1961
|
+
return {
|
|
1962
|
+
current_mode: modeInfo.mode,
|
|
1963
|
+
mode_confidence: modeInfo.confidence,
|
|
1964
|
+
mode_distribution: { [modeInfo.mode]: 1.0 }, // single-window submission
|
|
1965
|
+
mode_weighted_yield: cas.yield ?? 0,
|
|
1966
|
+
peak_yield: cas.yield ?? 0,
|
|
1967
|
+
health_score: _healthScore([{ mode: modeInfo.mode, yield: cas.yield ?? 0 }], cas.yield ?? 0),
|
|
1968
|
+
badges,
|
|
1969
|
+
}
|
|
1970
|
+
}
|