sigrank 0.19.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +158 -0
- package/MCP_SERVER_DESCRIPTION.md +172 -0
- package/README.md +89 -41
- package/adapters/index.mjs +1100 -0
- package/adapters/tokenpull.mjs +568 -0
- package/adapters.mjs +1 -865
- package/analytics/cascade.mjs +338 -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 +231 -0
- package/identity/sign.mjs +106 -0
- package/index.mjs +47 -162
- package/keystore.mjs +1 -242
- package/lib/constants.mjs +137 -0
- package/lib/pkg-version.mjs +59 -0
- package/manifest.json +46 -0
- package/narrate.mjs +1 -77
- package/package.json +41 -16
- package/preflight.mjs +1 -191
- package/presentation/cli.mjs +1926 -0
- package/presentation/narrate.mjs +219 -0
- package/presentation/tui-themes.mjs +189 -0
- package/presentation/tui.mjs +3316 -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/proxy.mjs +492 -0
- package/resources/class-tiers.md +81 -0
- package/resources/data-policy.md +31 -0
- package/resources/install-guide.md +59 -0
- package/resources/privacy-model.md +44 -0
- package/resources/scoring-formula.md +24 -0
- package/review.mjs +263 -0
- package/sign.mjs +1 -106
- package/submit/index.mjs +297 -0
- package/submit/preflight.mjs +191 -0
- package/submit.mjs +1 -303
- package/tokenpull.mjs +1 -576
- package/tokscale_analytics.mjs +864 -0
- package/tools/_framing.mjs +217 -0
- package/tools/_helpers.mjs +253 -0
- package/tools/_peer-matching.mjs +361 -0
- package/tools/_report.mjs +262 -0
- package/tools/_schemas.mjs +492 -0
- package/tools/_verifiers.mjs +148 -0
- package/tools/compare-operators.mjs +80 -0
- package/tools/compare-self.mjs +124 -0
- package/tools/describe-power-user.mjs +78 -0
- package/tools/diagnose-cascade.mjs +192 -0
- package/tools/discover-peers.mjs +198 -0
- package/tools/enroll.mjs +119 -0
- package/tools/get-best-operator.mjs +59 -0
- package/tools/get-leaderboard.mjs +27 -0
- package/tools/get-operator.mjs +33 -0
- package/tools/index.mjs +229 -0
- package/tools/optimize-efficiency.mjs +170 -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 +121 -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 +235 -0
- package/tools/tokenpull.mjs +113 -0
- package/tools/tokscale-breakdown.mjs +119 -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,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/_peer-matching.mjs — pure peer-discovery logic for discover_peers.
|
|
3
|
+
*
|
|
4
|
+
* Shared between _02_sigrank-mcp (operator's agent, uses enrolled identity)
|
|
5
|
+
* and _04_bestuser-router-mcp (marketing layer, takes codename arg). The
|
|
6
|
+
* canonical version lives here; _04 vendors a copy.
|
|
7
|
+
*
|
|
8
|
+
* All functions are pure — no network, no side effects. They operate on
|
|
9
|
+
* leaderboard entry objects as returned by the signalaf.com API:
|
|
10
|
+
* { codename, class_tier, platform, yield_, leverage, velocity, snr,
|
|
11
|
+
* input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens,
|
|
12
|
+
* rank, ... }
|
|
13
|
+
*
|
|
14
|
+
* Three categorization functions:
|
|
15
|
+
* findMentors — operators 1-2 class tiers above, ranked by cascade shape similarity
|
|
16
|
+
* findPeers — operators in the same class tier, ranked by yield proximity
|
|
17
|
+
* findComplementary — operators whose strength maps to the operator's weakness
|
|
18
|
+
*
|
|
19
|
+
* Plus pillar delta computation: the specific token difference that explains
|
|
20
|
+
* the yield gap between an operator and a mentor.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { CLASS_TIERS, UNCLASSED, tierOf } from "../analytics/cascade.mjs";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Class tier rank index (0 = ARCH+ top, 7 = IGNITER bottom, -1 = UNCLASSED).
|
|
27
|
+
* Used to determine who is "above" an operator in the experience ladder.
|
|
28
|
+
*/
|
|
29
|
+
export function tierRank(klass) {
|
|
30
|
+
const base = tierOf(klass);
|
|
31
|
+
if (base === UNCLASSED || base == null) return -1;
|
|
32
|
+
return CLASS_TIERS.indexOf(base);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Normalize a cascade shape into a 3D vector for similarity comparison.
|
|
37
|
+
* The three dimensions are the cascade ratios that define an operator's
|
|
38
|
+
* "shape" independent of scale:
|
|
39
|
+
* - leverage (Cr/I): how much they reuse prior context
|
|
40
|
+
* - velocity (O/I): how much output they get per input token
|
|
41
|
+
* - snr (O/(I+O)): signal cleanliness
|
|
42
|
+
*
|
|
43
|
+
* Each is log-scaled and normalized to [0,1] so operators at different scales
|
|
44
|
+
* can be compared by shape, not absolute magnitude. An operator with 100x
|
|
45
|
+
* leverage and one with 1000x leverage have similar *shapes* — both are
|
|
46
|
+
* high-leverage operators, just at different scales.
|
|
47
|
+
*/
|
|
48
|
+
export function cascadeShape(op) {
|
|
49
|
+
const l = Number(op.leverage) || 0;
|
|
50
|
+
const v = Number(op.velocity) || 0;
|
|
51
|
+
const s = Number(op.snr) || 0;
|
|
52
|
+
// Log-scale, clamp to [0,1]. Log thresholds chosen so typical ranges map well:
|
|
53
|
+
// leverage: 0-1000x → log1p(0)-log1p(1000) ≈ 0-6.9
|
|
54
|
+
// velocity: 0-10x → log1p(0)-log1p(10) ≈ 0-2.4
|
|
55
|
+
// snr: 0-1 → log1p(0)-log1p(1) ≈ 0-0.69
|
|
56
|
+
const norm = (val, max) => Math.min(1, Math.log1p(Math.max(0, val)) / Math.log1p(max));
|
|
57
|
+
return [
|
|
58
|
+
norm(l, 1000),
|
|
59
|
+
norm(v, 10),
|
|
60
|
+
norm(s, 1),
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Euclidean distance between two cascade shapes, normalized to [0,1].
|
|
66
|
+
* 0 = identical shape, 1 = maximally different. Used to rank mentors:
|
|
67
|
+
* closer shape = "they were where you are but scaled up."
|
|
68
|
+
*/
|
|
69
|
+
export function shapeDistance(opA, opB) {
|
|
70
|
+
const a = cascadeShape(opA);
|
|
71
|
+
const b = cascadeShape(opB);
|
|
72
|
+
let sum = 0;
|
|
73
|
+
for (let i = 0; i < a.length; i++) {
|
|
74
|
+
const d = a[i] - b[i];
|
|
75
|
+
sum += d * d;
|
|
76
|
+
}
|
|
77
|
+
return Math.sqrt(sum / a.length);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Similarity score = 1 - distance. Higher = more similar cascade shape.
|
|
82
|
+
*/
|
|
83
|
+
export function shapeSimilarity(opA, opB) {
|
|
84
|
+
return 1 - shapeDistance(opA, opB);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Compute the pillar delta between an operator and a mentor.
|
|
89
|
+
* Returns the specific token differences that explain the yield gap,
|
|
90
|
+
* expressed as both absolute tokens and multipliers.
|
|
91
|
+
*
|
|
92
|
+
* { pillar, mentor_value, operator_value, multiplier, absolute_delta, explanation }
|
|
93
|
+
*
|
|
94
|
+
* The pillar with the largest yield impact is listed first. Since yield =
|
|
95
|
+
* (cacheRead × output) / input², the impact ranking is:
|
|
96
|
+
* 1. cache_read (linear multiplier in yield)
|
|
97
|
+
* 2. input (quadratic denominator — reducing input has squared impact)
|
|
98
|
+
* 3. output (linear multiplier in yield)
|
|
99
|
+
* 4. cache_create (enables future cache reads — indirect)
|
|
100
|
+
*/
|
|
101
|
+
export function pillarDelta(operator, mentor) {
|
|
102
|
+
const opI = Number(operator.input_tokens) || 0;
|
|
103
|
+
const opO = Number(operator.output_tokens) || 0;
|
|
104
|
+
const opCr = Number(operator.cache_read_tokens) || 0;
|
|
105
|
+
const opCw = Number(operator.cache_creation_tokens) || 0;
|
|
106
|
+
|
|
107
|
+
const mtI = Number(mentor.input_tokens) || 0;
|
|
108
|
+
const mtO = Number(mentor.output_tokens) || 0;
|
|
109
|
+
const mtCr = Number(mentor.cache_read_tokens) || 0;
|
|
110
|
+
const mtCw = Number(mentor.cache_creation_tokens) || 0;
|
|
111
|
+
|
|
112
|
+
const safeMult = (mt, op) => (op > 0 ? mt / op : mt > 0 ? Infinity : 1);
|
|
113
|
+
const fmtMult = (m) => (m === Infinity ? "∞" : `${m.toFixed(1)}×`);
|
|
114
|
+
|
|
115
|
+
const deltas = [
|
|
116
|
+
{
|
|
117
|
+
pillar: "cache_read",
|
|
118
|
+
mentor_value: mtCr,
|
|
119
|
+
operator_value: opCr,
|
|
120
|
+
multiplier: safeMult(mtCr, opCr),
|
|
121
|
+
absolute_delta: mtCr - opCr,
|
|
122
|
+
explanation:
|
|
123
|
+
opCr > 0
|
|
124
|
+
? `${fmtMult(safeMult(mtCr, opCr))} your cache reads — the strongest yield multiplier`
|
|
125
|
+
: `You have no cache reads; mentor has ${mtCr.toLocaleString()} — this is the entire yield gap`,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
pillar: "input",
|
|
129
|
+
mentor_value: mtI,
|
|
130
|
+
operator_value: opI,
|
|
131
|
+
multiplier: safeMult(mtI, opI),
|
|
132
|
+
absolute_delta: mtI - opI,
|
|
133
|
+
explanation:
|
|
134
|
+
mtI < opI
|
|
135
|
+
? `${fmtMult(opI / mtI)} less input than you — input² is in the denominator, so leaner input has quadratic payoff`
|
|
136
|
+
: `${fmtMult(safeMult(mtI, opI))} your input — they use more fresh tokens but compensate with cache reuse`,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
pillar: "output",
|
|
140
|
+
mentor_value: mtO,
|
|
141
|
+
operator_value: opO,
|
|
142
|
+
multiplier: safeMult(mtO, opO),
|
|
143
|
+
absolute_delta: mtO - opO,
|
|
144
|
+
explanation: `${fmtMult(safeMult(mtO, opO))} your output — linear yield multiplier`,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
pillar: "cache_create",
|
|
148
|
+
mentor_value: mtCw,
|
|
149
|
+
operator_value: opCw,
|
|
150
|
+
multiplier: safeMult(mtCw, opCw),
|
|
151
|
+
absolute_delta: mtCw - opCw,
|
|
152
|
+
explanation:
|
|
153
|
+
opCw > 0
|
|
154
|
+
? `${fmtMult(safeMult(mtCw, opCw))} your cache writes — enables future cache reads`
|
|
155
|
+
: `You have no cache writes; mentor has ${mtCw.toLocaleString()} — they're investing in future cache`,
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
// Sort by yield impact: cache_read first (strongest), then input (quadratic),
|
|
160
|
+
// then output (linear), then cache_create (indirect).
|
|
161
|
+
const order = { cache_read: 0, input: 1, output: 2, cache_create: 3 };
|
|
162
|
+
deltas.sort((a, b) => order[a.pillar] - order[b.pillar]);
|
|
163
|
+
|
|
164
|
+
return deltas;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Find mentors: operators 1-2 class tiers above, ranked by cascade shape
|
|
169
|
+
* similarity. These are operators who were where you are and broke through.
|
|
170
|
+
*
|
|
171
|
+
* @param {object} operator — the operator's leaderboard entry
|
|
172
|
+
* @param {array} board — all leaderboard entries
|
|
173
|
+
* @param {object} opts — { platform: string|null, n: number }
|
|
174
|
+
* @returns {array} mentors sorted by similarity (descending)
|
|
175
|
+
*/
|
|
176
|
+
export function findMentors(operator, board, opts = {}) {
|
|
177
|
+
const n = Math.min(20, Math.max(1, opts.n ?? 5));
|
|
178
|
+
const myTier = tierRank(operator.class_tier || operator.class);
|
|
179
|
+
if (myTier < 0) return []; // UNCLASSED — no mentors
|
|
180
|
+
|
|
181
|
+
const myPlatform = operator.platform;
|
|
182
|
+
|
|
183
|
+
return board
|
|
184
|
+
.filter((op) => op.codename !== operator.codename)
|
|
185
|
+
.filter((op) => {
|
|
186
|
+
if (opts.platform && op.platform !== opts.platform) return false;
|
|
187
|
+
if (!opts.platform && myPlatform && op.platform !== myPlatform) return false;
|
|
188
|
+
return true;
|
|
189
|
+
})
|
|
190
|
+
.filter((op) => {
|
|
191
|
+
const theirTier = tierRank(op.class_tier || op.class);
|
|
192
|
+
// 1-2 tiers above (lower index = higher tier)
|
|
193
|
+
return theirTier >= 0 && theirTier < myTier && theirTier >= myTier - 2;
|
|
194
|
+
})
|
|
195
|
+
.map((op) => ({
|
|
196
|
+
...op,
|
|
197
|
+
similarity_score: Math.round(shapeSimilarity(operator, op) * 100) / 100,
|
|
198
|
+
pillar_delta: pillarDelta(operator, op),
|
|
199
|
+
}))
|
|
200
|
+
.sort((a, b) => b.similarity_score - a.similarity_score)
|
|
201
|
+
.slice(0, n);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Find peers: operators in the same class tier (same base tier, any sub-stage),
|
|
206
|
+
* ranked by yield proximity. These are your direct competitors.
|
|
207
|
+
*
|
|
208
|
+
* @param {object} operator
|
|
209
|
+
* @param {array} board
|
|
210
|
+
* @param {object} opts
|
|
211
|
+
* @returns {array} peers sorted by yield proximity (ascending delta)
|
|
212
|
+
*/
|
|
213
|
+
export function findPeers(operator, board, opts = {}) {
|
|
214
|
+
const n = Math.min(20, Math.max(1, opts.n ?? 5));
|
|
215
|
+
const myTier = tierOf(operator.class_tier || operator.class);
|
|
216
|
+
if (myTier === UNCLASSED || myTier == null) return [];
|
|
217
|
+
|
|
218
|
+
const myPlatform = operator.platform;
|
|
219
|
+
const myYield = Number(operator.yield_) || 0;
|
|
220
|
+
|
|
221
|
+
return board
|
|
222
|
+
.filter((op) => op.codename !== operator.codename)
|
|
223
|
+
.filter((op) => {
|
|
224
|
+
if (opts.platform && op.platform !== opts.platform) return false;
|
|
225
|
+
if (!opts.platform && myPlatform && op.platform !== myPlatform) return false;
|
|
226
|
+
return true;
|
|
227
|
+
})
|
|
228
|
+
.filter((op) => tierOf(op.class_tier || op.class) === myTier)
|
|
229
|
+
.map((op) => ({
|
|
230
|
+
codename: op.codename,
|
|
231
|
+
class_tier: op.class_tier || op.class,
|
|
232
|
+
yield_: op.yield_,
|
|
233
|
+
leverage: op.leverage,
|
|
234
|
+
velocity: op.velocity,
|
|
235
|
+
platform: op.platform,
|
|
236
|
+
rank: op.rank,
|
|
237
|
+
yield_delta_from_you: Math.round((Number(op.yield_) || 0) - myYield),
|
|
238
|
+
shareable_url: op.codename
|
|
239
|
+
? `${opts.apiBase || "https://signalaf.com"}/operator/${encodeURIComponent(op.codename)}`
|
|
240
|
+
: null,
|
|
241
|
+
}))
|
|
242
|
+
.sort((a, b) => Math.abs(a.yield_delta_from_you) - Math.abs(b.yield_delta_from_you))
|
|
243
|
+
.slice(0, n);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Find complementary operators: those whose strongest dimension is the
|
|
248
|
+
* operator's weakest. Surfaces operators whose strengths map to your gaps.
|
|
249
|
+
*
|
|
250
|
+
* Determines the operator's weakest cascade dimension (leverage, velocity, or
|
|
251
|
+
* snr) relative to the board, then finds operators who are strongest in that
|
|
252
|
+
* dimension.
|
|
253
|
+
*
|
|
254
|
+
* @param {object} operator
|
|
255
|
+
* @param {array} board
|
|
256
|
+
* @param {object} opts
|
|
257
|
+
* @returns {array} complementary operators with strength/weakness annotation
|
|
258
|
+
*/
|
|
259
|
+
export function findComplementary(operator, board, opts = {}) {
|
|
260
|
+
const n = Math.min(20, Math.max(1, opts.n ?? 5));
|
|
261
|
+
const myPlatform = operator.platform;
|
|
262
|
+
|
|
263
|
+
const filtered = board
|
|
264
|
+
.filter((op) => op.codename !== operator.codename)
|
|
265
|
+
.filter((op) => {
|
|
266
|
+
if (opts.platform && op.platform !== opts.platform) return false;
|
|
267
|
+
if (!opts.platform && myPlatform && op.platform !== myPlatform) return false;
|
|
268
|
+
return true;
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (filtered.length === 0) return [];
|
|
272
|
+
|
|
273
|
+
// Compute board averages for each dimension
|
|
274
|
+
const avg = (key) =>
|
|
275
|
+
filtered.reduce((s, op) => s + (Number(op[key]) || 0), 0) / filtered.length;
|
|
276
|
+
|
|
277
|
+
const avgLeverage = avg("leverage");
|
|
278
|
+
const avgVelocity = avg("velocity");
|
|
279
|
+
const avgSnr = avg("snr");
|
|
280
|
+
|
|
281
|
+
// Find the operator's weakest dimension (furthest below average)
|
|
282
|
+
const myLeverage = Number(operator.leverage) || 0;
|
|
283
|
+
const myVelocity = Number(operator.velocity) || 0;
|
|
284
|
+
const mySnr = Number(operator.snr) || 0;
|
|
285
|
+
|
|
286
|
+
const weaknesses = [
|
|
287
|
+
{ dimension: "leverage", value: myLeverage, avg: avgLeverage, gap: avgLeverage - myLeverage },
|
|
288
|
+
{ dimension: "velocity", value: myVelocity, avg: avgVelocity, gap: avgVelocity - myVelocity },
|
|
289
|
+
{ dimension: "snr", value: mySnr, avg: avgSnr, gap: avgSnr - mySnr },
|
|
290
|
+
].sort((a, b) => b.gap - a.gap); // largest gap first
|
|
291
|
+
|
|
292
|
+
const weakest = weaknesses[0];
|
|
293
|
+
|
|
294
|
+
// If the operator is above average in all dimensions, find the dimension
|
|
295
|
+
// where they're closest to average (their "relatively weakest")
|
|
296
|
+
const targetDimension = weakest.gap > 0 ? weakest.dimension : weaknesses[0].dimension;
|
|
297
|
+
|
|
298
|
+
// Find operators strongest in that dimension
|
|
299
|
+
return filtered
|
|
300
|
+
.map((op) => ({
|
|
301
|
+
codename: op.codename,
|
|
302
|
+
class_tier: op.class_tier || op.class,
|
|
303
|
+
yield_: op.yield_,
|
|
304
|
+
platform: op.platform,
|
|
305
|
+
rank: op.rank,
|
|
306
|
+
strength_dimension: targetDimension,
|
|
307
|
+
strength_value: Number(op[targetDimension]) || 0,
|
|
308
|
+
your_weakness: {
|
|
309
|
+
dimension: targetDimension,
|
|
310
|
+
your_value: Number(operator[targetDimension]) || 0,
|
|
311
|
+
board_average: Math.round(weakest.avg * 100) / 100,
|
|
312
|
+
},
|
|
313
|
+
shareable_url: op.codename
|
|
314
|
+
? `${opts.apiBase || "https://signalaf.com"}/operator/${encodeURIComponent(op.codename)}`
|
|
315
|
+
: null,
|
|
316
|
+
}))
|
|
317
|
+
.sort((a, b) => b.strength_value - a.strength_value)
|
|
318
|
+
.slice(0, n);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Generate the summary line for discover_peers output.
|
|
323
|
+
*/
|
|
324
|
+
export function peerSummary(operator, mentors, peers, complementary) {
|
|
325
|
+
const klass = operator.class_tier || operator.class || UNCLASSED;
|
|
326
|
+
const platform = operator.platform || "all";
|
|
327
|
+
const parts = [];
|
|
328
|
+
|
|
329
|
+
parts.push(`You're ${klass} on ${platform}.`);
|
|
330
|
+
|
|
331
|
+
if (mentors.length > 0) {
|
|
332
|
+
const topMentor = mentors[0];
|
|
333
|
+
const topDelta = topMentor.pillar_delta[0];
|
|
334
|
+
parts.push(
|
|
335
|
+
`${mentors.length} mentor${mentors.length > 1 ? "s" : ""} found — operators whose cascade shape is closest to yours but 1-2 tiers higher.`,
|
|
336
|
+
);
|
|
337
|
+
if (topDelta) {
|
|
338
|
+
parts.push(`Key gap: ${topDelta.explanation.toLowerCase()}.`);
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
const myTier = tierRank(klass);
|
|
342
|
+
if (myTier === 0) {
|
|
343
|
+
parts.push("You're at the top tier (ARCH+) — you are the mentor.");
|
|
344
|
+
} else {
|
|
345
|
+
parts.push("No mentors found in the 1-2 tier range above you.");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (peers.length > 0) {
|
|
350
|
+
parts.push(`${peers.length} direct peer${peers.length > 1 ? "s" : ""} in the ${tierOf(klass)} tier.`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (complementary.length > 0) {
|
|
354
|
+
const comp = complementary[0];
|
|
355
|
+
parts.push(
|
|
356
|
+
`Study ${comp.codename} for ${comp.strength_dimension} (${comp.strength_value.toFixed(1)} vs your ${(Number(operator[comp.strength_dimension]) || 0).toFixed(1)}).`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return parts.join(" ");
|
|
361
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/_report.mjs — cascade report blocks + self-improvement scope helpers.
|
|
3
|
+
*
|
|
4
|
+
* Used by submit_verified (per-window report payload) and self_improve
|
|
5
|
+
* (weekly/trend scope analysis).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { cascade, detectMode, qualityScore, MODE_EXPECTED_YIELD } from "../analytics/cascade.mjs";
|
|
9
|
+
import { computeBadges } from "../badges.mjs";
|
|
10
|
+
import { execFileAsync } from "./_helpers.mjs";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Compute the cascade report block for a submission payload.
|
|
14
|
+
* Pure math — mode detection + badges + health score from the current pillars.
|
|
15
|
+
* The server stores this as-is (does NOT recompute modes).
|
|
16
|
+
* Weekly granularity is the privacy boundary — no daily modes in the report.
|
|
17
|
+
*/
|
|
18
|
+
export function _computeReportBlock(pillars) {
|
|
19
|
+
const cas = cascade(pillars);
|
|
20
|
+
const modeInfo = detectMode(pillars);
|
|
21
|
+
const badges = computeBadges({
|
|
22
|
+
pillars,
|
|
23
|
+
cascade: cas,
|
|
24
|
+
history: [], // no history available at submit time — badges computed from current pillars only
|
|
25
|
+
isVerified: true, // submit_verified is the signed agent path
|
|
26
|
+
rank: null,
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
current_mode: modeInfo.mode,
|
|
30
|
+
mode_confidence: modeInfo.confidence,
|
|
31
|
+
mode_distribution: { [modeInfo.mode]: 1.0 }, // single-window submission
|
|
32
|
+
mode_weighted_yield: cas.yield ?? 0,
|
|
33
|
+
peak_yield: cas.yield ?? 0,
|
|
34
|
+
health_score: _healthScore(
|
|
35
|
+
[{ mode: modeInfo.mode, yield: cas.yield ?? 0 }],
|
|
36
|
+
cas.yield ?? 0,
|
|
37
|
+
),
|
|
38
|
+
badges,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Pull daily rows from ccusage to build mode history. Returns array of { date, pillars, yield, mode }. */
|
|
43
|
+
export async function _pullDailyRows(opts = {}) {
|
|
44
|
+
try {
|
|
45
|
+
const raw = await execFileAsync(
|
|
46
|
+
"ccusage",
|
|
47
|
+
["claude", "daily", "--json"],
|
|
48
|
+
15000,
|
|
49
|
+
);
|
|
50
|
+
const rows = JSON.parse(raw)?.daily ?? JSON.parse(raw);
|
|
51
|
+
if (!Array.isArray(rows)) return [];
|
|
52
|
+
return rows
|
|
53
|
+
.map((r) => {
|
|
54
|
+
const pillars = {
|
|
55
|
+
input: r.inputTokens ?? r.input_tokens ?? 0,
|
|
56
|
+
output: r.outputTokens ?? r.output_tokens ?? 0,
|
|
57
|
+
cacheCreate: r.cacheCreationTokens ?? r.cache_create_tokens ?? 0,
|
|
58
|
+
cacheRead: r.cacheReadTokens ?? r.cache_read_tokens ?? 0,
|
|
59
|
+
};
|
|
60
|
+
const cas = cascade(pillars);
|
|
61
|
+
const mode = detectMode(pillars);
|
|
62
|
+
return {
|
|
63
|
+
date: r.date ?? r.day ?? null,
|
|
64
|
+
pillars,
|
|
65
|
+
yield: cas.yield ?? 0,
|
|
66
|
+
mode: mode.mode,
|
|
67
|
+
mode_confidence: mode.confidence,
|
|
68
|
+
};
|
|
69
|
+
})
|
|
70
|
+
.filter((r) => r.date);
|
|
71
|
+
} catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Compound daily rows into weekly snapshots. Each week = 7 days, starting Monday. */
|
|
77
|
+
export function _compoundWeekly(dailyRows) {
|
|
78
|
+
if (!dailyRows || dailyRows.length === 0) return [];
|
|
79
|
+
// Group by ISO week (week starts Monday)
|
|
80
|
+
const weeks = new Map();
|
|
81
|
+
for (const row of dailyRows) {
|
|
82
|
+
const d = new Date(row.date);
|
|
83
|
+
const day = d.getDay() || 7; // Sunday=0 → 7
|
|
84
|
+
const monday = new Date(d);
|
|
85
|
+
monday.setDate(d.getDate() - day + 1);
|
|
86
|
+
const weekKey = monday.toISOString().slice(0, 10);
|
|
87
|
+
if (!weeks.has(weekKey)) {
|
|
88
|
+
weeks.set(weekKey, { weekStart: weekKey, days: [] });
|
|
89
|
+
}
|
|
90
|
+
weeks.get(weekKey).days.push(row);
|
|
91
|
+
}
|
|
92
|
+
// Compound each week
|
|
93
|
+
return Array.from(weeks.values())
|
|
94
|
+
.map((w) => {
|
|
95
|
+
const pillars = w.days.reduce(
|
|
96
|
+
(acc, d) => ({
|
|
97
|
+
input: acc.input + d.pillars.input,
|
|
98
|
+
output: acc.output + d.pillars.output,
|
|
99
|
+
cacheCreate: acc.cacheCreate + d.pillars.cacheCreate,
|
|
100
|
+
cacheRead: acc.cacheRead + d.pillars.cacheRead,
|
|
101
|
+
}),
|
|
102
|
+
{ input: 0, output: 0, cacheCreate: 0, cacheRead: 0 },
|
|
103
|
+
);
|
|
104
|
+
const cas = cascade(pillars);
|
|
105
|
+
const mode = detectMode(pillars);
|
|
106
|
+
return {
|
|
107
|
+
weekStart: w.weekStart,
|
|
108
|
+
pillars,
|
|
109
|
+
yield: cas.yield ?? 0,
|
|
110
|
+
mode: mode.mode,
|
|
111
|
+
mode_confidence: mode.confidence,
|
|
112
|
+
dayCount: w.days.length,
|
|
113
|
+
dailyModes: w.days.map((d) => d.mode), // stays local — not in submitted report
|
|
114
|
+
};
|
|
115
|
+
})
|
|
116
|
+
.sort((a, b) => a.weekStart.localeCompare(b.weekStart));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Compute mode distribution from weekly snapshots (weekly granularity = privacy boundary). */
|
|
120
|
+
export function _modeDistribution(weeklySnapshots) {
|
|
121
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return {};
|
|
122
|
+
const counts = { BUILD: 0, EDIT: 0, DEBUG: 0, MAINTAIN: 0, IDLE: 0 };
|
|
123
|
+
for (const w of weeklySnapshots) {
|
|
124
|
+
counts[w.mode] = (counts[w.mode] || 0) + 1;
|
|
125
|
+
}
|
|
126
|
+
const total = weeklySnapshots.length;
|
|
127
|
+
const dist = {};
|
|
128
|
+
for (const [mode, count] of Object.entries(counts)) {
|
|
129
|
+
if (count > 0) dist[mode] = Math.round((count / total) * 100) / 100;
|
|
130
|
+
}
|
|
131
|
+
return dist;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Mode-weighted yield — average yield weighted by mode distribution. */
|
|
135
|
+
export function _modeWeightedYield(weeklySnapshots) {
|
|
136
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0;
|
|
137
|
+
const total = weeklySnapshots.length;
|
|
138
|
+
const sum = weeklySnapshots.reduce((acc, w) => acc + (w.yield || 0), 0);
|
|
139
|
+
return Math.round(sum / total);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Peak yield across all weekly snapshots. */
|
|
143
|
+
export function _peakYield(weeklySnapshots) {
|
|
144
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0;
|
|
145
|
+
return Math.max(...weeklySnapshots.map((w) => w.yield || 0));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Health score (0-100) — weighted composite of consistency, momentum, quality. */
|
|
149
|
+
export function _healthScore(weeklySnapshots, modeWeightedYield) {
|
|
150
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0;
|
|
151
|
+
// Simplified Phase 1 formula:
|
|
152
|
+
// 40% — consistency (how often in MAINTAIN)
|
|
153
|
+
// 30% — momentum (recent yield vs older yield)
|
|
154
|
+
// 30% — quality (mode-weighted yield relative to MAINTAIN expected)
|
|
155
|
+
const dist = _modeDistribution(weeklySnapshots);
|
|
156
|
+
const maintainShare = dist.MAINTAIN || 0;
|
|
157
|
+
const consistency = Math.min(maintainShare, 1) * 40;
|
|
158
|
+
|
|
159
|
+
// Momentum: compare last 3 weeks to first 3 weeks
|
|
160
|
+
const recent = weeklySnapshots.slice(-3);
|
|
161
|
+
const older = weeklySnapshots.slice(0, 3);
|
|
162
|
+
const recentAvg =
|
|
163
|
+
recent.reduce((a, w) => a + (w.yield || 0), 0) / (recent.length || 1);
|
|
164
|
+
const olderAvg =
|
|
165
|
+
older.reduce((a, w) => a + (w.yield || 0), 0) / (older.length || 1);
|
|
166
|
+
const momentumRatio = olderAvg > 0 ? recentAvg / olderAvg : 1;
|
|
167
|
+
const momentum = (Math.min(momentumRatio, 2) / 2) * 30;
|
|
168
|
+
|
|
169
|
+
// Quality: mode-weighted yield vs MAINTAIN expected (5000)
|
|
170
|
+
const quality = Math.min(modeWeightedYield / 5000, 1) * 30;
|
|
171
|
+
|
|
172
|
+
return Math.round(consistency + momentum + quality);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Yield for the last N days (from weekly snapshots — approximates by summing recent weeks). */
|
|
176
|
+
export function _yieldForDays(weeklySnapshots, days) {
|
|
177
|
+
if (!weeklySnapshots || weeklySnapshots.length === 0) return 0;
|
|
178
|
+
const weeksNeeded = Math.ceil(days / 7);
|
|
179
|
+
const recent = weeklySnapshots.slice(-weeksNeeded);
|
|
180
|
+
if (recent.length === 0) return 0;
|
|
181
|
+
const sum = recent.reduce((a, w) => a + (w.yield || 0), 0);
|
|
182
|
+
return Math.round(sum / recent.length); // average weekly yield
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Trajectory description from 7d/30d/90d yields. */
|
|
186
|
+
export function _trajectory(y7, y30, y90) {
|
|
187
|
+
if (y90 === 0) return "insufficient_data";
|
|
188
|
+
const r7v30 = y30 > 0 ? y7 / y30 : 1;
|
|
189
|
+
const r30v90 = y90 > 0 ? y30 / y90 : 1;
|
|
190
|
+
if (r7v30 > 1.2 && r30v90 > 1.0) return "accelerating";
|
|
191
|
+
if (r7v30 > 1.2) return "recent_surge";
|
|
192
|
+
if (r7v30 < 0.8 && r30v90 < 0.8) return "declining";
|
|
193
|
+
if (r7v30 < 0.8) return "recent_dip";
|
|
194
|
+
if (r30v90 > 1.1) return "steady_growth";
|
|
195
|
+
return "stable";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Phase pattern description from mode distribution over time. */
|
|
199
|
+
export function _phasePattern(weeklySnapshots) {
|
|
200
|
+
if (!weeklySnapshots || weeklySnapshots.length < 2)
|
|
201
|
+
return "insufficient_data";
|
|
202
|
+
const modes = weeklySnapshots.map((w) => w.mode);
|
|
203
|
+
const transitions = [];
|
|
204
|
+
for (let i = 1; i < modes.length; i++) {
|
|
205
|
+
if (modes[i] !== modes[i - 1]) {
|
|
206
|
+
transitions.push(`${modes[i - 1]}→${modes[i]}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (transitions.length === 0) return `consistent_${modes[0].toLowerCase()}`;
|
|
210
|
+
// Check for smooth transitions (BUILD→MAINTAIN without DEBUG)
|
|
211
|
+
const smooth = transitions.every((t) => !t.includes("DEBUG"));
|
|
212
|
+
if (smooth && transitions.some((t) => t === "BUILD→MAINTAIN"))
|
|
213
|
+
return "smooth_ramp";
|
|
214
|
+
if (transitions.filter((t) => t.includes("DEBUG")).length > 2)
|
|
215
|
+
return "erratic";
|
|
216
|
+
return "cyclical";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Daily assessment string. */
|
|
220
|
+
export function _dailyAssessment(mode, yieldVal, qualityScoreVal, expectedYield) {
|
|
221
|
+
const qsPct = Math.round(qualityScoreVal * 100);
|
|
222
|
+
if (mode === "IDLE")
|
|
223
|
+
return "You're idle — no significant token activity today.";
|
|
224
|
+
if (mode === "MAINTAIN") {
|
|
225
|
+
if (qualityScoreVal >= 0.5)
|
|
226
|
+
return `You're in MAINTAIN mode. Yield ${yieldVal} is ${qsPct}% of MAINTAIN norm. The cascade is compounding.`;
|
|
227
|
+
return `You're in MAINTAIN mode but yield ${yieldVal} is only ${qsPct}% of expected (${expectedYield}). You may be coasting — push for more output.`;
|
|
228
|
+
}
|
|
229
|
+
if (mode === "BUILD") {
|
|
230
|
+
if (qualityScoreVal >= 0.5)
|
|
231
|
+
return `You're in BUILD mode. Yield ${yieldVal} is ${qsPct}% of BUILD norm. Greenfield work — expected to be low.`;
|
|
232
|
+
return `You're in BUILD mode. Yield ${yieldVal} is ${qsPct}% of expected (${expectedYield}). Building is slow — keep going.`;
|
|
233
|
+
}
|
|
234
|
+
if (mode === "EDIT") {
|
|
235
|
+
if (qualityScoreVal >= 0.5)
|
|
236
|
+
return `You're in EDIT mode. Yield ${yieldVal} is ${qsPct}% of EDIT norm. Fresh input but producing — good.`;
|
|
237
|
+
return `You're in EDIT mode. Yield ${yieldVal} is ${qsPct}% of expected (${expectedYield}). Push for more output per input.`;
|
|
238
|
+
}
|
|
239
|
+
if (mode === "DEBUG") {
|
|
240
|
+
if (qualityScoreVal >= 0.5)
|
|
241
|
+
return `You're in DEBUG mode. Yield ${yieldVal} is ${qsPct}% of DEBUG norm. Investigating — yield is expected to be low.`;
|
|
242
|
+
return `You're in DEBUG mode. Yield ${yieldVal} is ${qsPct}% of expected (${expectedYield}). Debugging is slow — consider loading prior context.`;
|
|
243
|
+
}
|
|
244
|
+
return `Mode: ${mode}. Yield: ${yieldVal}. Quality: ${qsPct}%.`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Daily advice string. */
|
|
248
|
+
export function _dailyAdvice(mode, yieldVal, qs) {
|
|
249
|
+
if (mode === "MAINTAIN" && qs >= 0.5)
|
|
250
|
+
return "Keep the cascade going. Don't reset context — let it compound.";
|
|
251
|
+
if (mode === "MAINTAIN")
|
|
252
|
+
return "You're in MAINTAIN but underperforming. Push the agent for more output per turn.";
|
|
253
|
+
if (mode === "BUILD")
|
|
254
|
+
return "When you're done building, load prior context to transition to MAINTAIN. The cascade rewards reuse.";
|
|
255
|
+
if (mode === "EDIT")
|
|
256
|
+
return "You're producing but using fresh input. Try to reuse prior context to boost leverage.";
|
|
257
|
+
if (mode === "DEBUG")
|
|
258
|
+
return "When you're done debugging, load prior context to return to MAINTAIN. Don't let the debug phase drag on.";
|
|
259
|
+
if (mode === "IDLE")
|
|
260
|
+
return "No activity detected. Start a session to build your cascade.";
|
|
261
|
+
return "Keep working — the cascade will compound as you build context.";
|
|
262
|
+
}
|