sigrank 0.0.181 → 0.0.183
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 +23 -22
- package/adapters/index.mjs +55 -22
- package/adapters/tokenpull.mjs +20 -28
- package/analytics/cascade.mjs +117 -12
- package/identity/keystore.mjs +1 -12
- package/lib/constants.mjs +111 -0
- package/lib/pkg-version.mjs +59 -0
- package/package.json +2 -1
- package/presentation/cli.mjs +51 -53
- package/presentation/tui.mjs +37 -40
- package/resources/class-tiers.md +79 -21
- package/resources/privacy-model.md +8 -5
- package/submit/index.mjs +8 -21
- package/tokscale_analytics.mjs +5 -16
- package/tools/_framing.mjs +95 -17
- package/tools/_helpers.mjs +5 -10
- package/tools/_schemas.mjs +68 -38
- package/tools/compare-operators.mjs +2 -1
- package/tools/compare-self.mjs +4 -2
- package/tools/describe-power-user.mjs +16 -6
- package/tools/diagnose-cascade.mjs +1 -1
- package/tools/enroll.mjs +1 -3
- package/tools/get-best-operator.mjs +2 -1
- package/tools/get-leaderboard.mjs +3 -2
- package/tools/get-operator.mjs +1 -1
- package/tools/index.mjs +4 -42
- package/tools/optimize-efficiency.mjs +15 -5
- package/tools/rank-paste.mjs +1 -1
- package/tools/self-improve.mjs +1 -1
- package/tools/submit-paste.mjs +13 -10
- package/tools/tokenpull-submit.mjs +11 -10
- package/tools/tokscale-breakdown.mjs +2 -42
package/presentation/cli.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* npx sigrank watch --window 7d watch one window only (optional filter)
|
|
16
16
|
*
|
|
17
17
|
* Color palette mirrors the SigRank web dark theme:
|
|
18
|
-
* gold =
|
|
18
|
+
* gold = rank #1 headline
|
|
19
19
|
* cyan = active metrics / your row highlight
|
|
20
20
|
* dim = secondary data, separators
|
|
21
21
|
* red = negative movement / delta
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { callTool, DEFAULT_API_BASE, pullActivePlatforms } from "../tools.mjs";
|
|
26
|
-
import { classify } from "../cascade.mjs";
|
|
26
|
+
import { cascade, classify, CLASS_TIERS, UNCLASSED, tierOf } from "../cascade.mjs";
|
|
27
|
+
import { ALL_PLATFORMS } from "../adapters.mjs";
|
|
27
28
|
import { ensureIdentity, keystorePath } from "../keystore.mjs";
|
|
28
29
|
import { submitSignedWindow } from "../submit.mjs";
|
|
30
|
+
import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
|
|
29
31
|
import { execFile } from "child_process";
|
|
30
32
|
import { existsSync, readFileSync } from "fs";
|
|
31
33
|
import os from "os";
|
|
@@ -88,8 +90,10 @@ const green = (str) => paint(c.green, str);
|
|
|
88
90
|
const red = (str) => paint(c.red, str);
|
|
89
91
|
|
|
90
92
|
// ── Class tier → color ─────────────────────────────────────────────────────
|
|
93
|
+
// Color is keyed on the BASE TIER (8 entries in CLASS_TIERS). classify() returns
|
|
94
|
+
// a full sub-stage string (e.g. "REFINER II") — tierOf() extracts the base tier
|
|
95
|
+
// for color lookup. TRANSMITTER is not a class. UNCLASSED for no-data.
|
|
91
96
|
const CLASS_COLOR = {
|
|
92
|
-
TRANSMITTER: (s) => paint(c.boldGold, s),
|
|
93
97
|
"ARCH+": (s) => paint(c.boldCyan, s),
|
|
94
98
|
ARCH: (s) => paint(c.cyan, s),
|
|
95
99
|
POWER: (s) => paint(c.boldWhite, s),
|
|
@@ -98,8 +102,16 @@ const CLASS_COLOR = {
|
|
|
98
102
|
REFINER: (s) => paint(c.blue, s),
|
|
99
103
|
BEARER: (s) => paint(c.dim, s),
|
|
100
104
|
IGNITER: (s) => paint(c.dim, s),
|
|
105
|
+
[UNCLASSED]: (s) => paint(c.dim, s),
|
|
101
106
|
};
|
|
102
|
-
const colorClass = (cls) => (CLASS_COLOR[cls] ?? ((s) => s))(cls);
|
|
107
|
+
const colorClass = (cls) => (CLASS_COLOR[tierOf(cls)] ?? CLASS_COLOR[UNCLASSED] ?? ((s) => s))(cls);
|
|
108
|
+
// Guard: every canonical tier must have a color here.
|
|
109
|
+
for (const t of CLASS_TIERS) {
|
|
110
|
+
if (!CLASS_COLOR[t]) {
|
|
111
|
+
// eslint-disable-next-line no-console
|
|
112
|
+
console.warn(`[cli] CLASS_COLOR map missing color for tier "${t}"`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
103
115
|
|
|
104
116
|
// ── Terminal utils ──────────────────────────────────────────────────────────
|
|
105
117
|
const CLEAR_SCREEN = `${ESC}2J${ESC}H`;
|
|
@@ -234,7 +246,7 @@ function renderBoardHeader(window = "30d") {
|
|
|
234
246
|
|
|
235
247
|
async function fetchBoard(window = "30d") {
|
|
236
248
|
const res = await fetch(
|
|
237
|
-
`${DEFAULT_API_BASE}/api/v1/leaderboard?window=${window}&metric
|
|
249
|
+
`${DEFAULT_API_BASE}/api/v1/leaderboard?window=${window}&metric=${LEADERBOARD_METRIC}`,
|
|
238
250
|
{
|
|
239
251
|
headers: {
|
|
240
252
|
accept: "application/json",
|
|
@@ -469,8 +481,10 @@ async function tokscalePillars(platform = "claude") {
|
|
|
469
481
|
}
|
|
470
482
|
|
|
471
483
|
function appPillars() {
|
|
472
|
-
// App numbers from screenshots
|
|
473
|
-
//
|
|
484
|
+
// ⚠ STALE HARDCODED DATA — App numbers from screenshots, all-time, per model
|
|
485
|
+
// (no cache fields). Captured 2026-06-23. NOT refreshed automatically. The
|
|
486
|
+
// compare view uses this only as a static reference point; it does NOT feed
|
|
487
|
+
// the ranked submit path. Update manually when a fresh screenshot is taken.
|
|
474
488
|
return {
|
|
475
489
|
all: {
|
|
476
490
|
input: 6_378_000, // sum of all models: 5.6M + 102.1K + 92.9K + 130.3K + 418.9K + 33.5K
|
|
@@ -544,39 +558,32 @@ function fmtDelta(a, b) {
|
|
|
544
558
|
return d > 0 ? green(label) : red(label);
|
|
545
559
|
}
|
|
546
560
|
|
|
547
|
-
// Compute cascade metrics from raw pillars
|
|
561
|
+
// Compute cascade metrics from raw pillars. Fix 3: this was a third copy of
|
|
562
|
+
// the cascade math with its own divide-by-zero policy (safeI = max(i,1) clamp),
|
|
563
|
+
// which let the CLI's Υ drift from the MCP tools' Υ for the same tokens. It now
|
|
564
|
+
// forwards to the single shared cascade() from analytics/cascade.mjs (null-guard
|
|
565
|
+
// policy, identical to rank_paste / submit_verified) and only adds the
|
|
566
|
+
// CLI-display-only `efficiency` + `total` fields the compare view renders.
|
|
548
567
|
function cascadeFromPillars(p) {
|
|
549
568
|
if (!p) return null;
|
|
569
|
+
const c = cascade(p);
|
|
550
570
|
const i = p.input ?? 0;
|
|
551
571
|
const o = p.output ?? 0;
|
|
552
572
|
const cw = p.cacheCreate ?? 0;
|
|
553
573
|
const cr = p.cacheRead ?? 0;
|
|
554
|
-
if (i === 0 && o === 0) return null;
|
|
555
|
-
const safeI = Math.max(i, 1);
|
|
556
574
|
const total = i + o + cw + cr;
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
// dev10x = log10(T × C × R) — only when all four pillars present
|
|
562
|
-
let dev10x = null;
|
|
563
|
-
if (cw > 0 && o > 0 && i > 0 && cr > 0) {
|
|
564
|
-
const T = o / i,
|
|
565
|
-
C = cw / o,
|
|
566
|
-
R = cr / cw;
|
|
567
|
-
dev10x = Math.log10(T * C * R);
|
|
568
|
-
}
|
|
569
|
-
// efficiency = ((cr+cw+o)/i) / 4.0
|
|
570
|
-
const efficiency = (cr + cw + o) / safeI / 4.0;
|
|
571
|
-
const cls = classify(yield_, dev10x);
|
|
575
|
+
// efficiency = ((cr+cw+o)/i)/4.0 — display-only derived metric (not in the
|
|
576
|
+
// canonical cascade). Null when input is 0 (matches the old safeI→0 behavior
|
|
577
|
+
// would have been div-by-zero; the shared cascade already nulls yield there).
|
|
578
|
+
const efficiency = i > 0 ? (cr + cw + o) / i / 4.0 : null;
|
|
572
579
|
return {
|
|
573
|
-
yield:
|
|
574
|
-
velocity,
|
|
575
|
-
leverage,
|
|
576
|
-
snr,
|
|
577
|
-
dev10x,
|
|
580
|
+
yield: c.yield,
|
|
581
|
+
velocity: c.velocity,
|
|
582
|
+
leverage: c.leverage,
|
|
583
|
+
snr: c.snr,
|
|
584
|
+
dev10x: c.dev10x,
|
|
578
585
|
efficiency,
|
|
579
|
-
class:
|
|
586
|
+
class: c.class,
|
|
580
587
|
total,
|
|
581
588
|
};
|
|
582
589
|
}
|
|
@@ -868,11 +875,18 @@ async function runWatch({
|
|
|
868
875
|
id,
|
|
869
876
|
{ platform: plat },
|
|
870
877
|
);
|
|
878
|
+
// Pass 6: gate the ✓ on r.ranked (verified + persisted), NOT on
|
|
879
|
+
// r.status === "received". An unenrolled/revoked device gets HTTP
|
|
880
|
+
// 202 "received" but is NEVER ranked — gating on "received" would
|
|
881
|
+
// show a green ✓ to an operator who didn't actually make the board.
|
|
882
|
+
// Mirrors the isRankedAck() predicate used by every submit path.
|
|
871
883
|
lastSubmitMsg.set(
|
|
872
884
|
key,
|
|
873
|
-
r.
|
|
874
|
-
? green(`✓ tier=${r.verification_tier || "—"}`)
|
|
875
|
-
: red(
|
|
885
|
+
r.ranked
|
|
886
|
+
? green(`✓ tier=${r.verification_tier || "—"} (ranked)`)
|
|
887
|
+
: red(
|
|
888
|
+
`✗ ${r.reason || r.status || "not ranked"}${r.verification_tier ? ` (tier=${r.verification_tier})` : ""}`,
|
|
889
|
+
),
|
|
876
890
|
);
|
|
877
891
|
} catch (e) {
|
|
878
892
|
lastSubmitMsg.set(key, red(`✗ ${e.message}`));
|
|
@@ -934,25 +948,9 @@ async function runWatch({
|
|
|
934
948
|
// - live board position
|
|
935
949
|
// - [S] submit [B] board [Q] quit
|
|
936
950
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
"devin",
|
|
941
|
-
"amp",
|
|
942
|
-
"gemini",
|
|
943
|
-
"kimi",
|
|
944
|
-
"qwen",
|
|
945
|
-
"goose",
|
|
946
|
-
"kilo",
|
|
947
|
-
"hermes",
|
|
948
|
-
"droid",
|
|
949
|
-
"codebuff",
|
|
950
|
-
"copilot",
|
|
951
|
-
"opencode",
|
|
952
|
-
"openclaw",
|
|
953
|
-
"pi",
|
|
954
|
-
"other",
|
|
955
|
-
];
|
|
951
|
+
// ALL_PLATFORMS is imported from ../adapters.mjs (single source of truth —
|
|
952
|
+
// was redefined inline here, which drifted from the adapter registry whenever
|
|
953
|
+
// a platform was added there but not in this list).
|
|
956
954
|
|
|
957
955
|
async function runSigRank() {
|
|
958
956
|
write(HIDE_CURSOR);
|
package/presentation/tui.mjs
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { callTool, DEFAULT_API_BASE, pullActivePlatforms } from "../tools.mjs";
|
|
16
16
|
import { ALL_PLATFORMS } from "../adapters.mjs";
|
|
17
|
+
import { cascade, CLASS_TIERS, UNCLASSED, tierOf } from "../cascade.mjs";
|
|
17
18
|
import { freshVerifierPillars } from "../tokenpull.mjs";
|
|
18
19
|
import { isSignedIn, isCodeChar } from "../connect.mjs";
|
|
19
20
|
import { loadIdentity, clearIdentity } from "../keystore.mjs";
|
|
@@ -21,17 +22,12 @@ import { execFile } from "child_process";
|
|
|
21
22
|
import { existsSync, readFileSync } from "fs";
|
|
22
23
|
import os from "os";
|
|
23
24
|
import path from "path";
|
|
24
|
-
import {
|
|
25
|
+
import { pkgVersion } from "../lib/pkg-version.mjs";
|
|
26
|
+
import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
|
|
25
27
|
|
|
26
|
-
// Version read from
|
|
27
|
-
// never hardcode, that's what caused the version drift).
|
|
28
|
-
const VERSION = (
|
|
29
|
-
try {
|
|
30
|
-
return createRequire(import.meta.url)("./package.json").version;
|
|
31
|
-
} catch {
|
|
32
|
-
return "?";
|
|
33
|
-
}
|
|
34
|
-
})();
|
|
28
|
+
// Version read from the shared pkg-version helper (single source of truth —
|
|
29
|
+
// matches cli.mjs; never hardcode, that's what caused the version drift).
|
|
30
|
+
const VERSION = pkgVersion();
|
|
35
31
|
|
|
36
32
|
// ── ANSI ───────────────────────────────────────────────────────────────────
|
|
37
33
|
const ESC = "\x1b[";
|
|
@@ -205,8 +201,10 @@ const fmtMov = (n) =>
|
|
|
205
201
|
n == null || n === 0 ? dim("—") : n > 0 ? green(`+${n}`) : red(`${n}`);
|
|
206
202
|
|
|
207
203
|
// ── Class tier colors ────────────────────────────────────────────────────────
|
|
204
|
+
// Color is keyed on the BASE TIER (8 entries in CLASS_TIERS). classify() returns
|
|
205
|
+
// a full sub-stage string (e.g. "REFINER II") — tierOf() extracts the base tier
|
|
206
|
+
// for color lookup. TRANSMITTER is not a class — it's a temporary peak badge.
|
|
208
207
|
const CLS = {
|
|
209
|
-
TRANSMITTER: (s) => paint(c.boldGold, s),
|
|
210
208
|
"ARCH+": (s) => paint(c.boldCyan, s),
|
|
211
209
|
ARCH: (s) => paint(c.cyan, s),
|
|
212
210
|
POWER: (s) => paint(c.boldWhite, s),
|
|
@@ -215,35 +213,29 @@ const CLS = {
|
|
|
215
213
|
REFINER: (s) => paint(c.blue, s),
|
|
216
214
|
BEARER: (s) => paint(c.dim, s),
|
|
217
215
|
IGNITER: (s) => paint(c.dim, s),
|
|
216
|
+
[UNCLASSED]: (s) => paint(c.dim, s),
|
|
218
217
|
};
|
|
219
|
-
const colorCls = (cls) => (CLS[cls] ?? ((s) => s))(cls);
|
|
218
|
+
const colorCls = (cls) => (CLS[tierOf(cls)] ?? CLS[UNCLASSED] ?? ((s) => s))(cls);
|
|
219
|
+
|
|
220
|
+
// Guard: every canonical tier must have a color, so a new tier added in
|
|
221
|
+
// analytics/cascade.mjs can't silently render uncolored here.
|
|
222
|
+
for (const t of CLASS_TIERS) {
|
|
223
|
+
if (!CLS[t]) {
|
|
224
|
+
// eslint-disable-next-line no-console
|
|
225
|
+
console.warn(`[tui] CLS map missing color for tier "${t}"`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
220
228
|
|
|
221
|
-
// ── Cascade math
|
|
229
|
+
// ── Cascade math ───────────────────────────────────────────────────────────
|
|
230
|
+
// Fix 3: the inline cascadeFrom() (a third copy of the cascade math with its
|
|
231
|
+
// own null-guard policy) is replaced by the single shared cascade() from
|
|
232
|
+
// analytics/cascade.mjs. CLI, TUI, and MCP tools now report identical Υ for
|
|
233
|
+
// identical tokens. cascadeFrom() keeps the old name as a thin alias so the
|
|
234
|
+
// many call sites below don't need rewriting; it forwards to the shared
|
|
235
|
+
// implementation and returns null only when there are no pillars at all.
|
|
222
236
|
function cascadeFrom(p) {
|
|
223
237
|
if (!p) return null;
|
|
224
|
-
|
|
225
|
-
o = p.output ?? 0,
|
|
226
|
-
cr = p.cacheRead ?? 0;
|
|
227
|
-
if (i === 0 || o === 0) return null;
|
|
228
|
-
const leverage = cr / i;
|
|
229
|
-
const velocity = o / i;
|
|
230
|
-
const yld = leverage * velocity;
|
|
231
|
-
const dev10x =
|
|
232
|
-
i > 0 && o > 0 && (p.cacheCreate ?? 0) > 0 && cr > 0
|
|
233
|
-
? Math.log10(cr / i)
|
|
234
|
-
: null;
|
|
235
|
-
const snr = o / (i + o);
|
|
236
|
-
|
|
237
|
-
let cls = "IGNITER";
|
|
238
|
-
if (yld >= 1000 || dev10x >= 3) cls = "TRANSMITTER";
|
|
239
|
-
else if (dev10x != null && dev10x >= 1.45) cls = "ARCH+";
|
|
240
|
-
else if (dev10x != null && dev10x >= 1.35) cls = "ARCH";
|
|
241
|
-
else if (dev10x != null && dev10x >= 1.2) cls = "POWER";
|
|
242
|
-
else if (dev10x != null && dev10x >= 1.0) cls = "BASE";
|
|
243
|
-
else if (dev10x != null && dev10x >= 0) cls = "SEEKER";
|
|
244
|
-
else if (dev10x != null && dev10x >= -0.3) cls = "REFINER";
|
|
245
|
-
|
|
246
|
-
return { yield: yld, snr, leverage, velocity, dev10x, class: cls };
|
|
238
|
+
return cascade(p);
|
|
247
239
|
}
|
|
248
240
|
|
|
249
241
|
// ── Unicode bar chart (no dep) ───────────────────────────────────────────────
|
|
@@ -309,7 +301,7 @@ function sparkline(values) {
|
|
|
309
301
|
|
|
310
302
|
async function loadDashboardData() {
|
|
311
303
|
const boardPromise = Promise.race([
|
|
312
|
-
fetch(`${DEFAULT_API_BASE}/api/v1/leaderboard?window=30d&metric
|
|
304
|
+
fetch(`${DEFAULT_API_BASE}/api/v1/leaderboard?window=30d&metric=${LEADERBOARD_METRIC}`, {
|
|
313
305
|
headers: { accept: "application/json" },
|
|
314
306
|
})
|
|
315
307
|
.then((r) => (r.ok ? r.json() : null))
|
|
@@ -386,7 +378,7 @@ async function loadBoardData(window = "30d") {
|
|
|
386
378
|
// Primary: submissions endpoint (ranked submission rows).
|
|
387
379
|
try {
|
|
388
380
|
const res = await fetch(
|
|
389
|
-
`${DEFAULT_API_BASE}/api/v1/submissions?window=${window}&metric
|
|
381
|
+
`${DEFAULT_API_BASE}/api/v1/submissions?window=${window}&metric=${LEADERBOARD_METRIC}`,
|
|
390
382
|
{
|
|
391
383
|
headers: { accept: "application/json" },
|
|
392
384
|
},
|
|
@@ -401,7 +393,7 @@ async function loadBoardData(window = "30d") {
|
|
|
401
393
|
// Fallback: existing leaderboard endpoint + current render (pre-deploy safety).
|
|
402
394
|
try {
|
|
403
395
|
const res = await fetch(
|
|
404
|
-
`${DEFAULT_API_BASE}/api/v1/leaderboard?window=${window}&metric
|
|
396
|
+
`${DEFAULT_API_BASE}/api/v1/leaderboard?window=${window}&metric=${LEADERBOARD_METRIC}`,
|
|
405
397
|
{
|
|
406
398
|
headers: { accept: "application/json" },
|
|
407
399
|
},
|
|
@@ -796,7 +788,12 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
|
|
|
796
788
|
const wk = firstWin[d.platform];
|
|
797
789
|
const wd = d.windows?.find((x) => x.window === wk);
|
|
798
790
|
const cas = wd && cascadeFrom(wd.pillars);
|
|
799
|
-
|
|
791
|
+
// cascadeFrom returns null only when wd.pillars is falsy; otherwise it
|
|
792
|
+
// forwards to cascade() which always returns an object (degenerate input
|
|
793
|
+
// surfaces as null fields + warnings[], not a null return). The guard
|
|
794
|
+
// below skips rows with no pillars at all; downstream `chk(val == null)`
|
|
795
|
+
// guards handle degenerate-but-present pillars.
|
|
796
|
+
if (cas) return cas;
|
|
800
797
|
}
|
|
801
798
|
return null;
|
|
802
799
|
})();
|
package/resources/class-tiers.md
CHANGED
|
@@ -1,23 +1,81 @@
|
|
|
1
1
|
# SigRank Class Tiers
|
|
2
2
|
|
|
3
|
-
Every operator is classified into one of
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
3
|
+
Every operator is classified into one of **24 experience stages** — 8 tiers ×
|
|
4
|
+
3 sub-stages (I/II/III) — based on their **total tokens** (input + output +
|
|
5
|
+
cacheCreate + cacheRead). The ladder mirrors the server's
|
|
6
|
+
`RS05_CLASS_THRESHOLDS` (`lib/analytics/ruleset.ts` in sigrank-app) — the
|
|
7
|
+
client's `classify()` in `analytics/cascade.mjs` is the canonical local source
|
|
8
|
+
of truth. The TUI, CLI, MCP output schemas, and this doc all read from that
|
|
9
|
+
one list.
|
|
10
|
+
|
|
11
|
+
The class is an **experience** axis — it measures how much volume the operator
|
|
12
|
+
has accumulated. It is separate from the **cascade archetype** (the operator's
|
|
13
|
+
token-flow shape: kinetic loop, archival sponge, volatile ingestor, etc.) and
|
|
14
|
+
from the **TRANSMITTER peak badge** (a temporary state — see below).
|
|
15
|
+
|
|
16
|
+
A 25th value, **UNCLASSED**, is returned when total tokens are null or
|
|
17
|
+
non-finite (empty / all-zero session). It is distinct from IGNITER III so
|
|
18
|
+
callers can tell "no data" from "bottom tier".
|
|
19
|
+
|
|
20
|
+
## The 8 base tiers (K.01–K.08)
|
|
21
|
+
|
|
22
|
+
Each tier has 3 sub-stages (I/II/III) split by total-token thresholds. The
|
|
23
|
+
sub-stage is the granular experience level; the base tier is the display
|
|
24
|
+
grouping (color, glyph, meaning).
|
|
25
|
+
|
|
26
|
+
### ARCH+ (K.01) — total ≥ 7.07T
|
|
27
|
+
- **Sub-stages:** ARCH+ I (≥ 7.07T), ARCH+ II (≥ 7.07T), ARCH+ III (≥ 7.07T)
|
|
28
|
+
- **Profile:** Deepest field experience. Volume that became architecture.
|
|
29
|
+
- **Note:** Aspirational — currently 1 operator in the static board.
|
|
30
|
+
|
|
31
|
+
### ARCH (K.02) — total ≥ 68.8B
|
|
32
|
+
- **Sub-stages:** ARCH I (≥ 186B), ARCH II (≥ 98.5B), ARCH III (≥ 68.8B)
|
|
33
|
+
- **Profile:** System builders. Sustained volume, coherent output.
|
|
34
|
+
|
|
35
|
+
### POWER (K.03) — total ≥ 19.1B
|
|
36
|
+
- **Sub-stages:** POWER I (≥ 40.0B), POWER II (≥ 27.0B), POWER III (≥ 19.1B)
|
|
37
|
+
- **Profile:** Above the center. Volume compounding.
|
|
38
|
+
|
|
39
|
+
### BASE (K.04) — total ≥ 7.7B
|
|
40
|
+
- **Sub-stages:** BASE I (≥ 14.0B), BASE II (≥ 10.2B), BASE III (≥ 7.7B)
|
|
41
|
+
- **Profile:** The center of the field. The average operator's experience.
|
|
42
|
+
|
|
43
|
+
### SEEKER (K.05) — total ≥ 3.0B
|
|
44
|
+
- **Sub-stages:** SEEKER I (≥ 5.4B), SEEKER II (≥ 4.0B), SEEKER III (≥ 3.0B)
|
|
45
|
+
- **Profile:** Approaching the center. Experience accumulating.
|
|
46
|
+
|
|
47
|
+
### REFINER (K.06) — total ≥ 1.3B
|
|
48
|
+
- **Sub-stages:** REFINER I (≥ 2.4B), REFINER II (≥ 1.8B), REFINER III (≥ 1.3B)
|
|
49
|
+
- **Profile:** Practicing with purpose. Early sustained volume.
|
|
50
|
+
|
|
51
|
+
### BEARER (K.07) — total ≥ 432M
|
|
52
|
+
- **Sub-stages:** BEARER I (≥ 984M), BEARER II (≥ 715M), BEARER III (≥ 432M)
|
|
53
|
+
- **Profile:** Quiet accumulation. The first real volume.
|
|
54
|
+
|
|
55
|
+
### IGNITER (K.08) — total ≥ 0
|
|
56
|
+
- **Sub-stages:** IGNITER I (≥ 216M), IGNITER II (≥ 89.0M), IGNITER III (≥ 0)
|
|
57
|
+
- **Profile:** Dormant potential. The still soul. Waiting.
|
|
58
|
+
|
|
59
|
+
## UNCLASSED (no data)
|
|
60
|
+
- **Profile:** Empty session — all four pillars are zero.
|
|
61
|
+
- **Fix:** Run some sessions; the stage will resolve once there is data.
|
|
62
|
+
|
|
63
|
+
## TRANSMITTER (peak badge, not a class)
|
|
64
|
+
|
|
65
|
+
TRANSMITTER is **not** on the experience ladder. It is a temporary peak badge
|
|
66
|
+
(RS.08) that any experience tier can earn during a high-frequency,
|
|
67
|
+
high-resonance window. An operator "transmits" when they hit both:
|
|
68
|
+
|
|
69
|
+
- **High frequency:** token throughput (total tokens in window) ≥ 1B
|
|
70
|
+
- **High resonance:** SIGNA RATE (composite signal quality) ≥ 85
|
|
71
|
+
|
|
72
|
+
The badge is per-window (daily/weekly) and lapses when frequency or resonance
|
|
73
|
+
drops. The server evaluates it via `evaluateTransmitterBadge()` in
|
|
74
|
+
`lib/analytics/transmitter-badge.ts`; the client does not evaluate the badge
|
|
75
|
+
locally (the owner is still calibrating the thresholds). The server is the
|
|
76
|
+
authority.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
Tiers are recalculated on every submission. Your stage can change between
|
|
81
|
+
windows (7d, 30d, 90d, all-time) as total tokens accumulate.
|
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## What leaves your machine
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Every submission path sends only four numbers:**
|
|
6
6
|
- Input tokens
|
|
7
7
|
- Output tokens
|
|
8
8
|
- Cache create tokens
|
|
9
9
|
- Cache read tokens
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
That's the entire payload — no prompts, no code, no file contents, no conversation text. The MCP parses your input locally and sends only the four token counts to the server, which re-parses and re-scores them authoritatively.
|
|
12
|
+
|
|
13
|
+
- **Signed path** (`submit_verified`, `watch_tokenpull` with `submit:true`): the four numbers travel with the device's public key, codename, window, and an ed25519 signature. The board verifies the signature without seeing your data.
|
|
14
|
+
- **Paste path** (`submit_paste`, `tokenpull_submit`): the MCP parses your pasted token counts locally, then sends only the four canonical numbers to the server's web-paste endpoint. Even if you paste mixed text (prose + numbers), only the extracted token counts are transmitted — the raw text never leaves your machine.
|
|
12
15
|
|
|
13
16
|
## How it works
|
|
14
17
|
|
|
15
18
|
1. **Local-first:** All token pulling happens on your machine. SigRank reads session logs from ~/.claude, ~/.codex, ~/.local/share/amp, etc.
|
|
16
|
-
2. **Token counts only:** The MCP tools extract integer counts from log metadata. The actual content of your conversations is never read, parsed, or transmitted.
|
|
17
|
-
3. **Signed submission:**
|
|
18
|
-
4. **
|
|
19
|
+
2. **Token counts only:** The MCP tools extract integer counts from log metadata. The actual content of your conversations is never read, parsed, or transmitted. On the paste path, the MCP parses the paste locally and sends only the four extracted numbers — the raw paste text stays on your machine.
|
|
20
|
+
3. **Signed submission:** Ranked submissions (`submit_verified`, `watch_tokenpull` with `submit:true`) are ed25519-signed with a device-bound key generated locally at enrollment. The board verifies the signature without seeing your data. Paste submissions (`submit_paste`, `tokenpull_submit`) are unsigned and go through the web-paste endpoint with a codename only — but still send only the four token counts, not the raw paste.
|
|
21
|
+
4. **Read tools need no auth:** No API keys, no OAuth, no account needed to read the leaderboard or operator profiles. **Enrollment requires a connect code** from signalaf.com → Settings → New key (the code binds your device's public key to your operator server-side); a codename alone is not enough for the signed/ranked path.
|
|
19
22
|
|
|
20
23
|
## What SigRank can NOT see
|
|
21
24
|
|
package/submit/index.mjs
CHANGED
|
@@ -11,22 +11,15 @@
|
|
|
11
11
|
|
|
12
12
|
import { snapshotHash, signPayload } from "../identity/sign.mjs";
|
|
13
13
|
import { preflight } from "./preflight.mjs";
|
|
14
|
-
import {
|
|
14
|
+
import { pkgVersion } from "../lib/pkg-version.mjs";
|
|
15
|
+
import {
|
|
16
|
+
TERMS_VERSION,
|
|
17
|
+
PRIVACY_VERSION,
|
|
18
|
+
isRankedAck,
|
|
19
|
+
} from "../lib/constants.mjs";
|
|
15
20
|
|
|
16
21
|
const WINDOW_TYPE = { "7d": "7d", "30d": "30d", "90d": "90d", all: "all_time" };
|
|
17
22
|
const WINDOW_SPAN_DAYS = { "7d": 7, "30d": 30, "90d": 90, all_time: 3650 };
|
|
18
|
-
|
|
19
|
-
/** Resolve this package's version for the User-Agent stamp (best-effort). */
|
|
20
|
-
function _pkgVersion() {
|
|
21
|
-
try {
|
|
22
|
-
const pkg = JSON.parse(
|
|
23
|
-
readFileSync(new URL("./package.json", import.meta.url), "utf-8"),
|
|
24
|
-
);
|
|
25
|
-
return pkg.version;
|
|
26
|
-
} catch {
|
|
27
|
-
return "unknown";
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
23
|
const PLATFORM_ENUM = new Set([
|
|
31
24
|
"claude",
|
|
32
25
|
"chatgpt",
|
|
@@ -38,8 +31,6 @@ const PLATFORM_ENUM = new Set([
|
|
|
38
31
|
]);
|
|
39
32
|
// Provenance tag only — the board ranks by Υ (no RS.xx weights, §0.1).
|
|
40
33
|
const RULESET_VERSION = "sigrank-token-1";
|
|
41
|
-
const TERMS_VERSION = "2026-07-21";
|
|
42
|
-
const PRIVACY_VERSION = "2026-07-21";
|
|
43
34
|
const DAY_MS = 86_400_000;
|
|
44
35
|
|
|
45
36
|
const clamp = (x, lo, hi) => Math.max(lo, Math.min(hi, x));
|
|
@@ -200,7 +191,7 @@ export async function submitSignedWindow(
|
|
|
200
191
|
const ctrl = new AbortController();
|
|
201
192
|
const timer = setTimeout(() => ctrl.abort(), 15_000);
|
|
202
193
|
const headers = {
|
|
203
|
-
"user-agent": `node/${process.version} sigrank-mcp/${
|
|
194
|
+
"user-agent": `node/${process.version} sigrank-mcp/${pkgVersion()}`,
|
|
204
195
|
...(init.headers || {}),
|
|
205
196
|
};
|
|
206
197
|
return fetch(url, {
|
|
@@ -296,11 +287,7 @@ export async function submitSignedWindow(
|
|
|
296
287
|
persisted: ack.persisted ?? null,
|
|
297
288
|
// ranked = actually on the board (verified + written), not just "received".
|
|
298
289
|
// An unenrolled/revoked device gets HTTP 202 received but is NEVER ranked.
|
|
299
|
-
ranked:
|
|
300
|
-
res.ok &&
|
|
301
|
-
ack.verification_tier === "verified" &&
|
|
302
|
-
ack.persisted === true
|
|
303
|
-
),
|
|
290
|
+
ranked: isRankedAck(res, ack),
|
|
304
291
|
snapshot_hash: payload.agent.snapshot_hash,
|
|
305
292
|
reason: res.ok ? null : ack.reason || ack.status || `http_${res.status}`,
|
|
306
293
|
detail: ack.detail ?? null,
|
package/tokscale_analytics.mjs
CHANGED
|
@@ -33,6 +33,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
33
33
|
import os from "node:os";
|
|
34
34
|
import path from "node:path";
|
|
35
35
|
import { fileURLToPath } from "node:url";
|
|
36
|
+
import { TOKSCALE_CLIENT_MAP } from "./lib/constants.mjs";
|
|
36
37
|
|
|
37
38
|
// Resolve local node_modules/.bin for the bundled tokscale binary.
|
|
38
39
|
const _pkgRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -85,23 +86,11 @@ export function num(x) {
|
|
|
85
86
|
return Number.isFinite(n) ? n : 0;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
// Map tokscale's client slugs to canonical platform names
|
|
89
|
+
// Map tokscale's client slugs to canonical platform names.
|
|
90
|
+
// TOKSCALE_CLIENT_MAP is imported from lib/constants.mjs (single source of
|
|
91
|
+
// truth — was duplicated here and in tools/index.mjs +
|
|
92
|
+
// tools/tokscale-breakdown.mjs, and the copies had started to drift).
|
|
89
93
|
// `synthetic` → null (filtered). unmapped slugs → "other".
|
|
90
|
-
const TOKSCALE_CLIENT_MAP = {
|
|
91
|
-
claude: "claude", codex: "codex", "devin-cli": "devin",
|
|
92
|
-
"devin-desktop": "devin", gemini: "gemini", amp: "amp",
|
|
93
|
-
kimi: "kimi", qwen: "qwen", goose: "goose", kilo: "kilo",
|
|
94
|
-
kilocode: "kilo", hermes: "hermes", droid: "droid",
|
|
95
|
-
codebuff: "codebuff", copilot: "copilot", opencode: "opencode",
|
|
96
|
-
openclaw: "openclaw", pi: "pi",
|
|
97
|
-
cursor: "other", roocode: "other", mux: "other", crush: "other",
|
|
98
|
-
antigravity: "other", "antigravity-cli": "other", zed: "other",
|
|
99
|
-
kiro: "other", trae: "other", warp: "other", cline: "other",
|
|
100
|
-
gjc: "other", grok: "other", jcode: "other", commandcode: "other",
|
|
101
|
-
micode: "other", junie: "other", zcode: "other",
|
|
102
|
-
opencodereview: "other", codebuddy: "other", workbuddy: "other",
|
|
103
|
-
synthetic: null,
|
|
104
|
-
};
|
|
105
94
|
|
|
106
95
|
/** Human-readable label for a tokscale client slug. */
|
|
107
96
|
const CLIENT_LABELS = {
|