sigrank 0.0.178 → 0.0.180
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -0
- package/MCP_SERVER_DESCRIPTION.md +141 -0
- package/README.md +8 -0
- package/adapters/index.mjs +865 -0
- package/adapters/tokenpull.mjs +576 -0
- package/adapters.mjs +1 -865
- package/analytics/cascade.mjs +274 -0
- package/cascade.mjs +1 -274
- package/cli.mjs +1 -1853
- package/connect.mjs +1 -13
- package/identity/connect.mjs +13 -0
- package/identity/keystore.mjs +242 -0
- package/identity/sign.mjs +106 -0
- package/index.mjs +43 -159
- package/keystore.mjs +1 -242
- package/narrate.mjs +1 -77
- package/package.json +32 -15
- package/preflight.mjs +1 -191
- package/presentation/cli.mjs +1874 -0
- package/presentation/narrate.mjs +77 -0
- package/presentation/tui.mjs +2668 -0
- package/prompts/check-my-efficiency.md +1 -0
- package/prompts/compare-with-leader.md +1 -0
- package/prompts/simulate-improvement.md +1 -0
- package/resources/class-tiers.md +23 -0
- package/resources/data-policy.md +27 -0
- package/resources/install-guide.md +59 -0
- package/resources/privacy-model.md +30 -0
- package/resources/scoring-formula.md +28 -0
- package/review.mjs +263 -0
- package/sign.mjs +1 -106
- package/submit/index.mjs +309 -0
- package/submit/preflight.mjs +191 -0
- package/submit.mjs +1 -303
- package/tokenpull.mjs +1 -576
- package/tokscale_analytics.mjs +875 -0
- package/tools/_framing.mjs +139 -0
- package/tools/_helpers.mjs +258 -0
- package/tools/_report.mjs +262 -0
- package/tools/_schemas.mjs +394 -0
- package/tools/_verifiers.mjs +148 -0
- package/tools/compare-operators.mjs +79 -0
- package/tools/compare-self.mjs +122 -0
- package/tools/describe-power-user.mjs +68 -0
- package/tools/diagnose-cascade.mjs +192 -0
- package/tools/enroll.mjs +121 -0
- package/tools/get-best-operator.mjs +58 -0
- package/tools/get-leaderboard.mjs +26 -0
- package/tools/get-operator.mjs +33 -0
- package/tools/index.mjs +246 -0
- package/tools/optimize-efficiency.mjs +160 -0
- package/tools/rank-paste.mjs +51 -0
- package/tools/rank-windows.mjs +120 -0
- package/tools/self-improve.mjs +423 -0
- package/tools/simulate-change.mjs +207 -0
- package/tools/submit-paste.mjs +118 -0
- package/tools/submit-verified.mjs +153 -0
- package/tools/suggest-improvements.mjs +168 -0
- package/tools/tokenpull-compare.mjs +123 -0
- package/tools/tokenpull-submit.mjs +234 -0
- package/tools/tokenpull.mjs +113 -0
- package/tools/tokscale-breakdown.mjs +159 -0
- package/tools/tokscale-competitive-intel.mjs +45 -0
- package/tools/tokscale-cost-analysis.mjs +31 -0
- package/tools/tokscale-developer-profile.mjs +36 -0
- package/tools/tokscale-device-profile.mjs +35 -0
- package/tools/tokscale-market-share.mjs +38 -0
- package/tools/tokscale-mcp-usage.mjs +33 -0
- package/tools/tokscale-model-trends.mjs +32 -0
- package/tools/watch-tokenpull.mjs +134 -0
- package/tools.mjs +1 -3897
- package/tui.mjs +4 -2650
- package/banner.svg +0 -7
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/self-improve.mjs — self_improve tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { cascade, parsePillars, detectMode, qualityScore, MODE_EXPECTED_YIELD } from "../analytics/cascade.mjs";
|
|
6
|
+
import { computeBadges } from "../badges.mjs";
|
|
7
|
+
import { CASCADE_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
8
|
+
import { MAX_INPUT, withParseWarnings } from "./_helpers.mjs";
|
|
9
|
+
import {
|
|
10
|
+
_pullDailyRows,
|
|
11
|
+
_compoundWeekly,
|
|
12
|
+
_modeDistribution,
|
|
13
|
+
_modeWeightedYield,
|
|
14
|
+
_peakYield,
|
|
15
|
+
_healthScore,
|
|
16
|
+
_yieldForDays,
|
|
17
|
+
_trajectory,
|
|
18
|
+
_phasePattern,
|
|
19
|
+
_dailyAssessment,
|
|
20
|
+
_dailyAdvice,
|
|
21
|
+
} from "./_report.mjs";
|
|
22
|
+
|
|
23
|
+
export const TOOL_DEF = {
|
|
24
|
+
name: "self_improve",
|
|
25
|
+
description:
|
|
26
|
+
"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).",
|
|
27
|
+
annotations: {
|
|
28
|
+
title: "Self-improve plan",
|
|
29
|
+
...ANNOTATIONS.readOnlyHint,
|
|
30
|
+
...ANNOTATIONS.idempotentHint,
|
|
31
|
+
...ANNOTATIONS.openWorldHint,
|
|
32
|
+
},
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
text: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description:
|
|
39
|
+
'Optional: token pillars — ccusage JSON or "input output cacheCreate cacheRead". If omitted, runs tokenpull to get current pillars from local logs.',
|
|
40
|
+
},
|
|
41
|
+
window: {
|
|
42
|
+
type: "string",
|
|
43
|
+
enum: ["7d", "30d", "90d", "all"],
|
|
44
|
+
description:
|
|
45
|
+
"Which time window to pull when running tokenpull (default: 30d). Ignored if `text` is provided.",
|
|
46
|
+
},
|
|
47
|
+
scope: {
|
|
48
|
+
type: "string",
|
|
49
|
+
enum: ["daily", "weekly", "trend"],
|
|
50
|
+
description:
|
|
51
|
+
'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.',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
outputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
pillars: {
|
|
59
|
+
type: "object",
|
|
60
|
+
description: "The 4 raw token pillars used",
|
|
61
|
+
},
|
|
62
|
+
current_cascade: CASCADE_OUTPUT,
|
|
63
|
+
diagnosis: {
|
|
64
|
+
type: "array",
|
|
65
|
+
description: "Efficiency leaks found (from diagnose_cascade)",
|
|
66
|
+
},
|
|
67
|
+
suggestions: {
|
|
68
|
+
type: "array",
|
|
69
|
+
description: "Ranked improvements (from suggest_improvements)",
|
|
70
|
+
},
|
|
71
|
+
best_simulation: {
|
|
72
|
+
type: "object",
|
|
73
|
+
description: "Simulated result of the top suggestion",
|
|
74
|
+
},
|
|
75
|
+
cycle_summary: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "One-line summary of the full cycle",
|
|
78
|
+
},
|
|
79
|
+
// Scope-specific fields
|
|
80
|
+
mode: {
|
|
81
|
+
type: "object",
|
|
82
|
+
description:
|
|
83
|
+
"Detected mode { mode, confidence } — present when scope is daily/weekly/trend",
|
|
84
|
+
},
|
|
85
|
+
quality_score: {
|
|
86
|
+
type: "number",
|
|
87
|
+
description: "Yield relative to mode expectation (daily scope)",
|
|
88
|
+
},
|
|
89
|
+
assessment: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "One-line assessment for daily scope",
|
|
92
|
+
},
|
|
93
|
+
advice: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "Advice for next session (daily scope)",
|
|
96
|
+
},
|
|
97
|
+
report: {
|
|
98
|
+
type: "object",
|
|
99
|
+
description: "Weekly report artifact (weekly scope)",
|
|
100
|
+
},
|
|
101
|
+
trend: { type: "object", description: "Trend analysis (trend scope)" },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export async function handleSelfImprove(args, ctx) {
|
|
107
|
+
// The full self-improvement cycle: pull → diagnose → suggest → simulate.
|
|
108
|
+
// If text is provided, use it as pillars. If not, run tokenpull first.
|
|
109
|
+
let pillars;
|
|
110
|
+
let pulledFrom = "provided";
|
|
111
|
+
|
|
112
|
+
if (args?.text) {
|
|
113
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
114
|
+
return {
|
|
115
|
+
status: "error",
|
|
116
|
+
reason: "input_too_large",
|
|
117
|
+
detail: `text exceeds ${MAX_INPUT} chars.`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
pillars = parsePillars(args.text);
|
|
121
|
+
} else {
|
|
122
|
+
// Run tokenpull to get current pillars from local logs
|
|
123
|
+
const windowType = args?.window || "30d";
|
|
124
|
+
try {
|
|
125
|
+
const pullResult = await ctx.callTool(
|
|
126
|
+
"tokenpull",
|
|
127
|
+
{ window: windowType },
|
|
128
|
+
);
|
|
129
|
+
if (pullResult?.status === "error" || !pullResult?.windows?.length) {
|
|
130
|
+
return {
|
|
131
|
+
status: "error",
|
|
132
|
+
reason: "tokenpull_failed",
|
|
133
|
+
detail:
|
|
134
|
+
"Could not pull pillars from local logs. Provide pillars via `text` argument instead.",
|
|
135
|
+
pull_result: pullResult,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Use the first window's pillars
|
|
139
|
+
const w = pullResult.windows[0];
|
|
140
|
+
pillars = w.pillars;
|
|
141
|
+
pulledFrom = `tokenpull ${w.window || windowType}`;
|
|
142
|
+
} catch (e) {
|
|
143
|
+
return {
|
|
144
|
+
status: "error",
|
|
145
|
+
reason: "tokenpull_error",
|
|
146
|
+
detail: String(e.message || e),
|
|
147
|
+
hint: "Provide pillars via `text` argument instead.",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const currentResult = withParseWarnings(pillars, cascade(pillars));
|
|
153
|
+
const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = pillars;
|
|
154
|
+
|
|
155
|
+
// ── Step 1: Diagnose ──────────────────────────────────────────────
|
|
156
|
+
const diagnosis = [];
|
|
157
|
+
const leverage = currentResult.leverage;
|
|
158
|
+
if (leverage !== null && leverage < 10) {
|
|
159
|
+
diagnosis.push({
|
|
160
|
+
metric: "cache_leverage",
|
|
161
|
+
severity: "critical",
|
|
162
|
+
finding: `Cache leverage is ${leverage}× — TRANSMITTER-class operators hit 200×+.`,
|
|
163
|
+
recommendation:
|
|
164
|
+
"Increase context reuse: load prior session context, use longer threads.",
|
|
165
|
+
});
|
|
166
|
+
} else if (leverage !== null && leverage < 50) {
|
|
167
|
+
diagnosis.push({
|
|
168
|
+
metric: "cache_leverage",
|
|
169
|
+
severity: "warning",
|
|
170
|
+
finding: `Cache leverage is ${leverage}× — below ARCH+ threshold (~100×+).`,
|
|
171
|
+
recommendation: "Push cache reads higher by reusing prior context.",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const velocity = currentResult.velocity;
|
|
176
|
+
if (velocity !== null && velocity < 0.5) {
|
|
177
|
+
diagnosis.push({
|
|
178
|
+
metric: "velocity",
|
|
179
|
+
severity: "critical",
|
|
180
|
+
finding: `Velocity is ${velocity} — generating only ${velocity}× input as output.`,
|
|
181
|
+
recommendation: "Increase output per turn, reduce over-reading.",
|
|
182
|
+
});
|
|
183
|
+
} else if (velocity !== null && velocity < 1.0) {
|
|
184
|
+
diagnosis.push({
|
|
185
|
+
metric: "velocity",
|
|
186
|
+
severity: "warning",
|
|
187
|
+
finding: `Velocity is ${velocity} — below 1.0. Healthy operators hit 1.5×+.`,
|
|
188
|
+
recommendation: "Generate more output per input token.",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (currentResult.dev10x === null && cw === 0) {
|
|
193
|
+
diagnosis.push({
|
|
194
|
+
metric: "10xdev",
|
|
195
|
+
severity: "critical",
|
|
196
|
+
finding: "No cache creation — cascade cannot compound.",
|
|
197
|
+
recommendation:
|
|
198
|
+
"Switch to a platform with prompt caching (Claude Code).",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const total = i + o + cw + cr;
|
|
203
|
+
if (total > 0 && (i / total) * 100 > 10) {
|
|
204
|
+
diagnosis.push({
|
|
205
|
+
metric: "input_bloat",
|
|
206
|
+
severity: "warning",
|
|
207
|
+
finding: `Fresh input is ${((i / total) * 100).toFixed(1)}% of total flow — efficient operators keep it under 1%.`,
|
|
208
|
+
recommendation: "Reduce fresh input by reusing prior context.",
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Step 2: Suggest (generate + simulate candidates) ──────────────
|
|
213
|
+
const candidates = [];
|
|
214
|
+
// If cacheRead is 0, suggest absolute amounts instead of percentage boosts
|
|
215
|
+
const crBoosts = cr > 0 ? [1.5, 2, 3, 5] : [];
|
|
216
|
+
for (const mult of crBoosts) {
|
|
217
|
+
const sim = cascade({ ...pillars, cacheRead: Math.round(cr * mult) });
|
|
218
|
+
if (sim.yield !== null)
|
|
219
|
+
candidates.push({
|
|
220
|
+
action: `Increase cache reads by ${Math.round((mult - 1) * 100)}%`,
|
|
221
|
+
pillar: "cacheRead",
|
|
222
|
+
delta: `+${Math.round(cr * (mult - 1)).toLocaleString()}`,
|
|
223
|
+
simulated_yield: sim.yield,
|
|
224
|
+
yield_delta: Number(
|
|
225
|
+
(sim.yield - (currentResult.yield ?? 0)).toFixed(2),
|
|
226
|
+
),
|
|
227
|
+
class_after: sim.class,
|
|
228
|
+
rationale: "Cache reads are the strongest Υ multiplier.",
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// For zero-cache operators, suggest enabling caching with a starter amount
|
|
232
|
+
if (cr === 0 && cw === 0) {
|
|
233
|
+
const starterAmounts = [
|
|
234
|
+
Math.round(i * 10),
|
|
235
|
+
Math.round(i * 50),
|
|
236
|
+
Math.round(i * 100),
|
|
237
|
+
];
|
|
238
|
+
for (const amt of starterAmounts) {
|
|
239
|
+
const sim = cascade({
|
|
240
|
+
...pillars,
|
|
241
|
+
cacheCreate: Math.round(amt * 0.5),
|
|
242
|
+
cacheRead: amt,
|
|
243
|
+
});
|
|
244
|
+
if (sim.yield !== null && sim.yield > 0)
|
|
245
|
+
candidates.push({
|
|
246
|
+
action: `Enable caching with ${amt.toLocaleString()} cache reads`,
|
|
247
|
+
pillar: "cacheRead",
|
|
248
|
+
delta: `+${amt.toLocaleString()}`,
|
|
249
|
+
simulated_yield: sim.yield,
|
|
250
|
+
yield_delta: Number(
|
|
251
|
+
(sim.yield - (currentResult.yield ?? 0)).toFixed(2),
|
|
252
|
+
),
|
|
253
|
+
class_after: sim.class,
|
|
254
|
+
rationale:
|
|
255
|
+
"You have no cache — enabling it unlocks the cascade. Start by reusing prior context.",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const mult of [0.9, 0.75, 0.5]) {
|
|
260
|
+
const newInput = Math.round(i * mult);
|
|
261
|
+
if (newInput < 1) continue;
|
|
262
|
+
const sim = cascade({ ...pillars, input: newInput });
|
|
263
|
+
if (sim.yield !== null)
|
|
264
|
+
candidates.push({
|
|
265
|
+
action: `Reduce fresh input by ${Math.round((1 - mult) * 100)}%`,
|
|
266
|
+
pillar: "input",
|
|
267
|
+
delta: `-${Math.round(i * (1 - mult)).toLocaleString()}`,
|
|
268
|
+
simulated_yield: sim.yield,
|
|
269
|
+
yield_delta: Number(
|
|
270
|
+
(sim.yield - (currentResult.yield ?? 0)).toFixed(2),
|
|
271
|
+
),
|
|
272
|
+
class_after: sim.class,
|
|
273
|
+
rationale: "Input is squared in Υ denominator — quadratic payoff.",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
for (const mult of [1.25, 1.5, 2]) {
|
|
277
|
+
const sim = cascade({ ...pillars, output: Math.round(o * mult) });
|
|
278
|
+
if (sim.yield !== null)
|
|
279
|
+
candidates.push({
|
|
280
|
+
action: `Increase output by ${Math.round((mult - 1) * 100)}%`,
|
|
281
|
+
pillar: "output",
|
|
282
|
+
delta: `+${Math.round(o * (mult - 1)).toLocaleString()}`,
|
|
283
|
+
simulated_yield: sim.yield,
|
|
284
|
+
yield_delta: Number(
|
|
285
|
+
(sim.yield - (currentResult.yield ?? 0)).toFixed(2),
|
|
286
|
+
),
|
|
287
|
+
class_after: sim.class,
|
|
288
|
+
rationale: "Output is a linear multiplier in Υ.",
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
candidates.sort((a, b) => b.yield_delta - a.yield_delta);
|
|
292
|
+
const suggestions = candidates
|
|
293
|
+
.slice(0, 8)
|
|
294
|
+
.map((c, idx) => ({ rank: idx + 1, ...c }));
|
|
295
|
+
|
|
296
|
+
// ── Step 3: Simulate the top suggestion ───────────────────────────
|
|
297
|
+
const best = suggestions[0];
|
|
298
|
+
let bestSimulation = null;
|
|
299
|
+
if (best) {
|
|
300
|
+
const simPillars = { ...pillars };
|
|
301
|
+
const delta = parseInt(best.delta.replace(/[+\-,]/g, ""), 10);
|
|
302
|
+
if (best.pillar === "cacheRead") simPillars.cacheRead = cr + delta;
|
|
303
|
+
else if (best.pillar === "input")
|
|
304
|
+
simPillars.input = Math.max(i - delta, 1);
|
|
305
|
+
else if (best.pillar === "output") simPillars.output = o + delta;
|
|
306
|
+
const simResult = cascade(simPillars);
|
|
307
|
+
bestSimulation = {
|
|
308
|
+
action: best.action,
|
|
309
|
+
current_yield: currentResult.yield,
|
|
310
|
+
simulated_yield: simResult.yield,
|
|
311
|
+
yield_delta: best.yield_delta,
|
|
312
|
+
current_class: currentResult.class,
|
|
313
|
+
simulated_class: simResult.class,
|
|
314
|
+
class_changed: currentResult.class !== simResult.class,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ── Step 4: Cycle summary ─────────────────────────────────────────
|
|
319
|
+
const criticalCount = diagnosis.filter(
|
|
320
|
+
(d) => d.severity === "critical",
|
|
321
|
+
).length;
|
|
322
|
+
const cycleSummary = best
|
|
323
|
+
? `Pulled from ${pulledFrom}. Υ ${currentResult.yield} (${currentResult.class}). ${criticalCount} critical, ${diagnosis.length - criticalCount} other findings. Best: ${best.action} → Υ ${best.simulated_yield} (+${best.yield_delta}).`
|
|
324
|
+
: `Pulled from ${pulledFrom}. Υ ${currentResult.yield} (${currentResult.class}). ${diagnosis.length} findings. No improvements suggested — cascade is optimized.`;
|
|
325
|
+
|
|
326
|
+
// ── Step 5: Scope-specific analysis ───────────────────────────────
|
|
327
|
+
const scope = args?.scope || "daily";
|
|
328
|
+
const modeInfo = currentResult.mode; // { mode, confidence }
|
|
329
|
+
const scopeResult = {};
|
|
330
|
+
|
|
331
|
+
if (scope === "daily") {
|
|
332
|
+
// Daily: mode + quality score + assessment + advice
|
|
333
|
+
const qs = qualityScore(currentResult.yield ?? 0, modeInfo.mode);
|
|
334
|
+
const expected = MODE_EXPECTED_YIELD[modeInfo.mode] ?? 0;
|
|
335
|
+
const assessment = _dailyAssessment(
|
|
336
|
+
modeInfo.mode,
|
|
337
|
+
currentResult.yield,
|
|
338
|
+
qs,
|
|
339
|
+
expected,
|
|
340
|
+
);
|
|
341
|
+
const advice = _dailyAdvice(modeInfo.mode, currentResult.yield, qs);
|
|
342
|
+
scopeResult.mode = modeInfo;
|
|
343
|
+
scopeResult.quality_score = Math.round(qs * 100) / 100;
|
|
344
|
+
scopeResult.assessment = assessment;
|
|
345
|
+
scopeResult.advice = advice;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (scope === "weekly" || scope === "trend") {
|
|
349
|
+
// Pull daily rows from ccusage to build weekly snapshots
|
|
350
|
+
const dailyRows = await _pullDailyRows(ctx.opts);
|
|
351
|
+
const weeklySnapshots = _compoundWeekly(dailyRows);
|
|
352
|
+
const modeDistribution = _modeDistribution(weeklySnapshots);
|
|
353
|
+
const modeWeightedYield = _modeWeightedYield(weeklySnapshots);
|
|
354
|
+
|
|
355
|
+
// Compute badges
|
|
356
|
+
const historyForBadges = weeklySnapshots.map((w) => ({
|
|
357
|
+
date: w.weekStart,
|
|
358
|
+
mode: w.mode,
|
|
359
|
+
yield: w.yield,
|
|
360
|
+
pillars: w.pillars,
|
|
361
|
+
}));
|
|
362
|
+
const badges = computeBadges({
|
|
363
|
+
pillars,
|
|
364
|
+
cascade: currentResult,
|
|
365
|
+
history: historyForBadges,
|
|
366
|
+
isVerified: false, // set by submit_verified
|
|
367
|
+
rank: null, // server-side
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
if (scope === "weekly") {
|
|
371
|
+
scopeResult.mode = modeInfo;
|
|
372
|
+
// Strip dailyModes from weekly_snapshots — privacy boundary (daily modes never leave the machine)
|
|
373
|
+
const safeSnapshots = weeklySnapshots.map(
|
|
374
|
+
({ dailyModes, ...rest }) => rest,
|
|
375
|
+
);
|
|
376
|
+
scopeResult.report = {
|
|
377
|
+
current_mode: modeInfo.mode,
|
|
378
|
+
mode_confidence: modeInfo.confidence,
|
|
379
|
+
mode_distribution: modeDistribution,
|
|
380
|
+
mode_weighted_yield: modeWeightedYield,
|
|
381
|
+
peak_yield: _peakYield(weeklySnapshots),
|
|
382
|
+
health_score: _healthScore(weeklySnapshots, modeWeightedYield),
|
|
383
|
+
weekly_snapshots: safeSnapshots,
|
|
384
|
+
badges,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (scope === "trend") {
|
|
389
|
+
const yield7d = _yieldForDays(weeklySnapshots, 7);
|
|
390
|
+
const yield30d = _yieldForDays(weeklySnapshots, 30);
|
|
391
|
+
const yield90d = _yieldForDays(weeklySnapshots, 90);
|
|
392
|
+
const trajectory = _trajectory(yield7d, yield30d, yield90d);
|
|
393
|
+
scopeResult.mode = modeInfo;
|
|
394
|
+
scopeResult.trend = {
|
|
395
|
+
yield_7d: yield7d,
|
|
396
|
+
yield_30d: yield30d,
|
|
397
|
+
yield_90d: yield90d,
|
|
398
|
+
trajectory,
|
|
399
|
+
mode_distribution: modeDistribution,
|
|
400
|
+
phase_pattern: _phasePattern(weeklySnapshots),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
pillars,
|
|
407
|
+
current_cascade: {
|
|
408
|
+
yield_: currentResult.yield,
|
|
409
|
+
snr: currentResult.snr,
|
|
410
|
+
leverage: currentResult.leverage,
|
|
411
|
+
velocity: currentResult.velocity,
|
|
412
|
+
tenx_dev: currentResult.dev10x,
|
|
413
|
+
class: currentResult.class,
|
|
414
|
+
mode: modeInfo,
|
|
415
|
+
warnings: currentResult.warnings,
|
|
416
|
+
},
|
|
417
|
+
diagnosis,
|
|
418
|
+
suggestions,
|
|
419
|
+
best_simulation: bestSimulation,
|
|
420
|
+
cycle_summary: cycleSummary,
|
|
421
|
+
...scopeResult,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/simulate-change.mjs — simulate_change tool.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { cascade, parsePillars } from "../analytics/cascade.mjs";
|
|
6
|
+
import { SIMULATE_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
7
|
+
import { MAX_INPUT, withParseWarnings } from "./_helpers.mjs";
|
|
8
|
+
|
|
9
|
+
export const TOOL_DEF = {
|
|
10
|
+
name: "simulate_change",
|
|
11
|
+
description:
|
|
12
|
+
"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}).",
|
|
13
|
+
annotations: {
|
|
14
|
+
title: "Simulate metric change",
|
|
15
|
+
...ANNOTATIONS.readOnlyHint,
|
|
16
|
+
...ANNOTATIONS.idempotentHint,
|
|
17
|
+
...ANNOTATIONS.openWorldHint,
|
|
18
|
+
},
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
text: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description:
|
|
25
|
+
'Current token pillars — ccusage JSON or "input output cacheCreate cacheRead" (same format as rank_paste).',
|
|
26
|
+
},
|
|
27
|
+
changes: {
|
|
28
|
+
type: "object",
|
|
29
|
+
description:
|
|
30
|
+
"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.",
|
|
31
|
+
properties: {
|
|
32
|
+
input: {
|
|
33
|
+
type: ["number", "string"],
|
|
34
|
+
description:
|
|
35
|
+
'new input token count (absolute) or "+/-N" for a relative delta',
|
|
36
|
+
},
|
|
37
|
+
output: {
|
|
38
|
+
type: ["number", "string"],
|
|
39
|
+
description:
|
|
40
|
+
'new output token count (absolute) or "+/-N" for a relative delta',
|
|
41
|
+
},
|
|
42
|
+
cacheCreate: {
|
|
43
|
+
type: ["number", "string"],
|
|
44
|
+
description:
|
|
45
|
+
'new cache-create token count (absolute) or "+/-N" for a relative delta',
|
|
46
|
+
},
|
|
47
|
+
cacheRead: {
|
|
48
|
+
type: ["number", "string"],
|
|
49
|
+
description:
|
|
50
|
+
'new cache-read token count (absolute) or "+/-N" for a relative delta',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
required: ["text", "changes"],
|
|
56
|
+
},
|
|
57
|
+
outputSchema: SIMULATE_OUTPUT,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export async function handleSimulateChange(args) {
|
|
61
|
+
// The first prescriptive tool — "what if I changed my token mix?"
|
|
62
|
+
// Pure local math: parse current pillars, apply proposed changes, run the
|
|
63
|
+
// cascade on both, return the delta. No network, no submission.
|
|
64
|
+
if (!args?.text)
|
|
65
|
+
throw new Error(
|
|
66
|
+
"simulate_change requires a non-empty `text` argument (current pillars).",
|
|
67
|
+
);
|
|
68
|
+
if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
|
|
69
|
+
return {
|
|
70
|
+
status: "error",
|
|
71
|
+
reason: "input_too_large",
|
|
72
|
+
detail: `text exceeds ${MAX_INPUT} chars.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (!args?.changes || typeof args.changes !== "object") {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"simulate_change requires a `changes` object with at least one pillar change.",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const currentPillars = parsePillars(args.text);
|
|
82
|
+
const current = withParseWarnings(currentPillars, cascade(currentPillars));
|
|
83
|
+
|
|
84
|
+
// Apply changes: each pillar is either an absolute number (replace) or a
|
|
85
|
+
// string starting with +/- (relative delta). Omitted pillars are unchanged.
|
|
86
|
+
const PILLAR_KEYS = ["input", "output", "cacheCreate", "cacheRead"];
|
|
87
|
+
const simulated = { ...currentPillars };
|
|
88
|
+
const appliedChanges = {};
|
|
89
|
+
|
|
90
|
+
for (const key of PILLAR_KEYS) {
|
|
91
|
+
if (args.changes[key] == null) continue;
|
|
92
|
+
const raw = args.changes[key];
|
|
93
|
+
let newVal;
|
|
94
|
+
|
|
95
|
+
if (typeof raw === "number") {
|
|
96
|
+
newVal = raw;
|
|
97
|
+
} else if (typeof raw === "string") {
|
|
98
|
+
const trimmed = raw.trim();
|
|
99
|
+
if (trimmed.startsWith("+") || trimmed.startsWith("-")) {
|
|
100
|
+
// Relative delta: add/subtract from current value
|
|
101
|
+
const delta = Number(trimmed);
|
|
102
|
+
if (!Number.isFinite(delta)) {
|
|
103
|
+
return {
|
|
104
|
+
status: "error",
|
|
105
|
+
reason: "invalid_change",
|
|
106
|
+
detail: `changes.${key}: "${raw}" is not a valid relative delta.`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
newVal = currentPillars[key] + delta;
|
|
110
|
+
} else {
|
|
111
|
+
// Absolute value as a string
|
|
112
|
+
newVal = Number(trimmed);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
return {
|
|
116
|
+
status: "error",
|
|
117
|
+
reason: "invalid_change",
|
|
118
|
+
detail: `changes.${key}: expected number or string, got ${typeof raw}.`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!Number.isFinite(newVal)) {
|
|
123
|
+
return {
|
|
124
|
+
status: "error",
|
|
125
|
+
reason: "invalid_change",
|
|
126
|
+
detail: `changes.${key}: result is not a finite number.`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// Clamp to non-negative — token counts can't be negative
|
|
130
|
+
if (newVal < 0) {
|
|
131
|
+
return {
|
|
132
|
+
status: "error",
|
|
133
|
+
reason: "invalid_change",
|
|
134
|
+
detail: `changes.${key}: result ${newVal} is negative — token counts must be >= 0.`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
simulated[key] = newVal;
|
|
139
|
+
appliedChanges[key] = {
|
|
140
|
+
from: currentPillars[key],
|
|
141
|
+
to: newVal,
|
|
142
|
+
delta: newVal - currentPillars[key],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (Object.keys(appliedChanges).length === 0) {
|
|
147
|
+
return {
|
|
148
|
+
status: "error",
|
|
149
|
+
reason: "no_changes",
|
|
150
|
+
detail: "No pillar changes specified in the `changes` object.",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const simulatedResult = cascade(simulated);
|
|
155
|
+
|
|
156
|
+
// Compute deltas for every cascade metric
|
|
157
|
+
const metricDelta = (curr, sim) => {
|
|
158
|
+
if (curr == null && sim == null) return null;
|
|
159
|
+
if (curr == null) return { from: null, to: sim, delta: null };
|
|
160
|
+
if (sim == null) return { from: curr, to: null, delta: null };
|
|
161
|
+
return { from: curr, to: sim, delta: Number((sim - curr).toFixed(4)) };
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const classChanged = current.class !== simulatedResult.class;
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
current: {
|
|
168
|
+
pillars: currentPillars,
|
|
169
|
+
yield: current.yield,
|
|
170
|
+
snr: current.snr,
|
|
171
|
+
leverage: current.leverage,
|
|
172
|
+
velocity: current.velocity,
|
|
173
|
+
dev10x: current.dev10x,
|
|
174
|
+
class: current.class,
|
|
175
|
+
},
|
|
176
|
+
simulated: {
|
|
177
|
+
pillars: {
|
|
178
|
+
input: simulated.input,
|
|
179
|
+
output: simulated.output,
|
|
180
|
+
cacheCreate: simulated.cacheCreate,
|
|
181
|
+
cacheRead: simulated.cacheRead,
|
|
182
|
+
},
|
|
183
|
+
yield: simulatedResult.yield,
|
|
184
|
+
snr: simulatedResult.snr,
|
|
185
|
+
leverage: simulatedResult.leverage,
|
|
186
|
+
velocity: simulatedResult.velocity,
|
|
187
|
+
dev10x: simulatedResult.dev10x,
|
|
188
|
+
class: simulatedResult.class,
|
|
189
|
+
},
|
|
190
|
+
changes: appliedChanges,
|
|
191
|
+
deltas: {
|
|
192
|
+
yield: metricDelta(current.yield, simulatedResult.yield),
|
|
193
|
+
snr: metricDelta(current.snr, simulatedResult.snr),
|
|
194
|
+
leverage: metricDelta(current.leverage, simulatedResult.leverage),
|
|
195
|
+
velocity: metricDelta(current.velocity, simulatedResult.velocity),
|
|
196
|
+
dev10x: metricDelta(current.dev10x, simulatedResult.dev10x),
|
|
197
|
+
},
|
|
198
|
+
class_changed: classChanged,
|
|
199
|
+
...(classChanged
|
|
200
|
+
? { class_transition: `${current.class} → ${simulatedResult.class}` }
|
|
201
|
+
: {}),
|
|
202
|
+
...(simulatedResult.warnings
|
|
203
|
+
? { simulated_warnings: simulatedResult.warnings }
|
|
204
|
+
: {}),
|
|
205
|
+
note: "Local simulation only — no submission. The actual score depends on server-side RS.xx weights and class thresholds.",
|
|
206
|
+
};
|
|
207
|
+
}
|