sigrank 0.18.5 → 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.
Files changed (80) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/MCP_SERVER_DESCRIPTION.md +172 -0
  3. package/README.md +94 -41
  4. package/adapters/index.mjs +1100 -0
  5. package/adapters/tokenpull.mjs +568 -0
  6. package/adapters.mjs +1 -857
  7. package/analytics/cascade.mjs +338 -0
  8. package/cascade.mjs +1 -274
  9. package/cli.mjs +1 -1853
  10. package/connect.mjs +1 -13
  11. package/identity/connect.mjs +13 -0
  12. package/identity/keystore.mjs +231 -0
  13. package/identity/sign.mjs +106 -0
  14. package/index.mjs +47 -162
  15. package/keystore.mjs +1 -242
  16. package/lib/constants.mjs +137 -0
  17. package/lib/pkg-version.mjs +59 -0
  18. package/manifest.json +46 -0
  19. package/narrate.mjs +1 -77
  20. package/package.json +41 -16
  21. package/preflight.mjs +1 -191
  22. package/presentation/cli.mjs +1926 -0
  23. package/presentation/narrate.mjs +219 -0
  24. package/presentation/tui-themes.mjs +189 -0
  25. package/presentation/tui.mjs +3316 -0
  26. package/prompts/check-my-efficiency.md +1 -0
  27. package/prompts/compare-with-leader.md +1 -0
  28. package/prompts/simulate-improvement.md +1 -0
  29. package/proxy.mjs +492 -0
  30. package/resources/class-tiers.md +81 -0
  31. package/resources/data-policy.md +31 -0
  32. package/resources/install-guide.md +59 -0
  33. package/resources/privacy-model.md +44 -0
  34. package/resources/scoring-formula.md +24 -0
  35. package/review.mjs +263 -0
  36. package/sign.mjs +1 -106
  37. package/submit/index.mjs +297 -0
  38. package/submit/preflight.mjs +191 -0
  39. package/submit.mjs +1 -303
  40. package/tokenpull.mjs +1 -576
  41. package/tokscale_analytics.mjs +864 -0
  42. package/tools/_framing.mjs +217 -0
  43. package/tools/_helpers.mjs +253 -0
  44. package/tools/_peer-matching.mjs +361 -0
  45. package/tools/_report.mjs +262 -0
  46. package/tools/_schemas.mjs +492 -0
  47. package/tools/_verifiers.mjs +148 -0
  48. package/tools/compare-operators.mjs +80 -0
  49. package/tools/compare-self.mjs +124 -0
  50. package/tools/describe-power-user.mjs +78 -0
  51. package/tools/diagnose-cascade.mjs +192 -0
  52. package/tools/discover-peers.mjs +198 -0
  53. package/tools/enroll.mjs +119 -0
  54. package/tools/get-best-operator.mjs +59 -0
  55. package/tools/get-leaderboard.mjs +27 -0
  56. package/tools/get-operator.mjs +33 -0
  57. package/tools/index.mjs +229 -0
  58. package/tools/optimize-efficiency.mjs +170 -0
  59. package/tools/rank-paste.mjs +51 -0
  60. package/tools/rank-windows.mjs +120 -0
  61. package/tools/self-improve.mjs +423 -0
  62. package/tools/simulate-change.mjs +207 -0
  63. package/tools/submit-paste.mjs +121 -0
  64. package/tools/submit-verified.mjs +153 -0
  65. package/tools/suggest-improvements.mjs +168 -0
  66. package/tools/tokenpull-compare.mjs +123 -0
  67. package/tools/tokenpull-submit.mjs +235 -0
  68. package/tools/tokenpull.mjs +113 -0
  69. package/tools/tokscale-breakdown.mjs +119 -0
  70. package/tools/tokscale-competitive-intel.mjs +45 -0
  71. package/tools/tokscale-cost-analysis.mjs +31 -0
  72. package/tools/tokscale-developer-profile.mjs +36 -0
  73. package/tools/tokscale-device-profile.mjs +35 -0
  74. package/tools/tokscale-market-share.mjs +38 -0
  75. package/tools/tokscale-mcp-usage.mjs +33 -0
  76. package/tools/tokscale-model-trends.mjs +32 -0
  77. package/tools/watch-tokenpull.mjs +134 -0
  78. package/tools.mjs +1 -3897
  79. package/tui.mjs +4 -2650
  80. package/banner.svg +0 -7
@@ -0,0 +1,80 @@
1
+ /**
2
+ * tools/compare-operators.mjs — compare_operators tool.
3
+ */
4
+
5
+ import { COMPARE_OPERATORS_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
6
+ import { _behavioralFraming, _classMeaning, _competitiveLayer } from "./_framing.mjs";
7
+ import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
8
+
9
+ export const TOOL_DEF = {
10
+ name: "compare_operators",
11
+ description:
12
+ "Compares two operators side-by-side with a behavioral verdict. Fetches both profiles from the board and returns their yield, leverage, velocity, class, and rank side-by-side, plus a verdict explaining who is more efficient and why in power-user language. Use this when users ask 'compare operator X vs Y' or 'who is more efficient' or 'how do two AI users compare'. Intent: COMPARE_OPERATORS.",
13
+ annotations: { title: "Compare two operators", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ codename_a: {
18
+ type: "string",
19
+ description:
20
+ "First operator's codename from the SigRank leaderboard. Case-insensitive.",
21
+ },
22
+ codename_b: {
23
+ type: "string",
24
+ description:
25
+ "Second operator's codename from the SigRank leaderboard. Case-insensitive.",
26
+ },
27
+ },
28
+ required: ["codename_a", "codename_b"],
29
+ description:
30
+ "Requires both codenames. Both must exist on the board.",
31
+ },
32
+ outputSchema: COMPARE_OPERATORS_OUTPUT,
33
+ };
34
+
35
+ export async function handleCompareOperators(args, ctx) {
36
+ const nameA = String(args?.codename_a || "").trim();
37
+ const nameB = String(args?.codename_b || "").trim();
38
+ if (!nameA || !nameB)
39
+ throw new Error(
40
+ "compare_operators requires both `codename_a` and `codename_b`.",
41
+ );
42
+
43
+ const [opA, opB, board] = await Promise.all([
44
+ ctx.fetchJson(`/api/v1/operators/${encodeURIComponent(nameA)}`),
45
+ ctx.fetchJson(`/api/v1/operators/${encodeURIComponent(nameB)}`),
46
+ ctx.fetchJson(`/api/v1/leaderboard?metric=${LEADERBOARD_METRIC}`),
47
+ ]);
48
+
49
+ const yieldA = opA.yield_ || 0;
50
+ const yieldB = opB.yield_ || 0;
51
+ const delta = yieldA - yieldB;
52
+
53
+ const winner = yieldA > yieldB ? opA : opB;
54
+ const loser = yieldA > yieldB ? opB : opA;
55
+ const verdict = `${winner.codename} is more token-efficient (${winner.yield_?.toLocaleString?.() || winner.yield_} vs ${loser.yield_?.toLocaleString?.() || loser.yield_} Υ). ${_behavioralFraming(winner)} ${loser.codename} ${_classMeaning(loser.class).toLowerCase()}`;
56
+
57
+ return {
58
+ operator_a: {
59
+ codename: opA.codename,
60
+ yield_: opA.yield_,
61
+ leverage: opA.leverage,
62
+ velocity: opA.velocity,
63
+ class: opA.class,
64
+ rank: opA.rank,
65
+ competitive: _competitiveLayer(opA, board),
66
+ },
67
+ operator_b: {
68
+ codename: opB.codename,
69
+ yield_: opB.yield_,
70
+ leverage: opB.leverage,
71
+ velocity: opB.velocity,
72
+ class: opB.class,
73
+ rank: opB.rank,
74
+ competitive: _competitiveLayer(opB, board),
75
+ },
76
+ verdict,
77
+ yield_delta: delta,
78
+ cta: "Compare me to others",
79
+ };
80
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * tools/compare-self.mjs — compare_self tool.
3
+ */
4
+
5
+ import { cascade, parsePillars } from "../analytics/cascade.mjs";
6
+ import { COMPARE_SELF_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
7
+ import { MAX_INPUT, withParseWarnings } from "./_helpers.mjs";
8
+ import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
9
+ import {
10
+ _powerUserAssessment,
11
+ _classMeaning,
12
+ _improvementSuggestion,
13
+ _competitiveLayer,
14
+ _competitiveSummary,
15
+ FALLBACK_CLASS,
16
+ } from "./_framing.mjs";
17
+
18
+ export const TOOL_DEF = {
19
+ name: "compare_self",
20
+ description:
21
+ "Compares an operator's metrics against board averages and power-user archetypes, returning a behavioral assessment. Accepts either a codename (fetches from the board) or raw token pillars (computes locally). Returns: your yield/leverage/velocity/class/rank, a power-user assessment mapping your class tier to AI power-user language, comparison vs board averages (your percentile), and one actionable suggestion to improve. Use this when users ask 'how do I measure up to other AI users?' or 'am I a power user?' or 'compare me to others'. Intent: COMPARE_SELF.",
22
+ annotations: { title: "Compare self to board", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.openWorldHint },
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ codename: {
27
+ type: "string",
28
+ description:
29
+ "Your codename on the SigRank leaderboard. If provided, fetches your live profile from the board. Case-insensitive.",
30
+ },
31
+ text: {
32
+ type: "string",
33
+ description:
34
+ 'Alternative: raw token pillars to score locally (ccusage JSON or "input output cacheCreate cacheRead"). Use this if you are not on the board yet but want to see how you would compare.',
35
+ },
36
+ },
37
+ description:
38
+ "Provide either `codename` (to fetch from the board) or `text` (to score locally). At least one is required.",
39
+ },
40
+ outputSchema: COMPARE_SELF_OUTPUT,
41
+ };
42
+
43
+ export async function handleCompareSelf(args, ctx) {
44
+ const codename = String(args?.codename || "").trim();
45
+ const text = String(args?.text || "").trim();
46
+
47
+ if (!codename && !text)
48
+ throw new Error(
49
+ "compare_self requires either `codename` (to fetch from the board) or `text` (raw token pillars to score locally).",
50
+ );
51
+
52
+ let yourMetrics;
53
+ if (codename) {
54
+ yourMetrics = await ctx.fetchJson(
55
+ `/api/v1/operators/${encodeURIComponent(codename)}`,
56
+ );
57
+ } else {
58
+ if (text.length > MAX_INPUT) {
59
+ return {
60
+ error: "input_too_large",
61
+ detail: `text exceeds ${MAX_INPUT} chars.`,
62
+ };
63
+ }
64
+ const pillars = parsePillars(text);
65
+ const c = withParseWarnings(pillars, cascade(pillars));
66
+ yourMetrics = {
67
+ codename: "you (local)",
68
+ yield_: c.yield,
69
+ leverage: c.leverage,
70
+ velocity: c.velocity,
71
+ class: c.class,
72
+ rank: null,
73
+ };
74
+ }
75
+
76
+ // Fetch board for comparison
77
+ const board = await ctx.fetchJson(`/api/v1/leaderboard?metric=${LEADERBOARD_METRIC}`);
78
+ const allOps = board.operators || board || [];
79
+ const yields = allOps.map((o) => o.yield_ || 0).sort((a, b) => a - b);
80
+ const avgYield = yields.length
81
+ ? yields.reduce((s, y) => s + y, 0) / yields.length
82
+ : 0;
83
+ const yourYield = yourMetrics.yield_ || 0;
84
+ const percentile = yields.length
85
+ ? Math.round(
86
+ (yields.filter((y) => y < yourYield).length / yields.length) * 100,
87
+ )
88
+ : 0;
89
+
90
+ const klass = yourMetrics.class || FALLBACK_CLASS;
91
+ const powerUserAssessment = _powerUserAssessment(klass, yourMetrics);
92
+ const classMeaning = _classMeaning(klass);
93
+
94
+ const yieldVsAvg = yields.length
95
+ ? yourYield > avgYield
96
+ ? `${(yourYield / avgYield).toFixed(1)}× the board average`
97
+ : `${((yourYield / avgYield) * 100).toFixed(0)}% of the board average`
98
+ : "board has no other operators";
99
+
100
+ const suggestion = _improvementSuggestion(klass, yourMetrics);
101
+
102
+ // Competitive layer per SHARED_DESIGN_DECISIONS.md §3/§4/§5
103
+ const competitive = _competitiveLayer(yourMetrics, board);
104
+ const competitiveSummary = _competitiveSummary(yourMetrics, board);
105
+
106
+ return {
107
+ your_metrics: yourMetrics,
108
+ power_user_assessment: powerUserAssessment,
109
+ comparison: {
110
+ your_yield_vs_avg: yieldVsAvg,
111
+ your_class_meaning: classMeaning,
112
+ percentile,
113
+ rank: competitive.rank,
114
+ total_operators: competitive.total_operators,
115
+ class_tier: competitive.class_tier,
116
+ delta_from_average: competitive.delta_from_average,
117
+ delta_from_top: competitive.delta_from_top,
118
+ },
119
+ competitive_summary: competitiveSummary,
120
+ shareable_url: competitive.shareable_url,
121
+ suggestion,
122
+ cta: "See where I stand",
123
+ };
124
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * tools/describe-power-user.mjs — describe_power_user tool.
3
+ */
4
+
5
+ import { ANNOTATIONS, CLASS_ENUM } from "./_schemas.mjs";
6
+ import { CLASS_TIERS, UNCLASSED } from "../analytics/cascade.mjs";
7
+ import { DEFAULT_API_BASE } from "./_helpers.mjs";
8
+
9
+ export const TOOL_DEF = {
10
+ name: "describe_power_user",
11
+ description:
12
+ "Returns an explanatory description of what makes an AI power user, anchored in SigRank's metrics and operator classes. Explains the yield metric, leverage, velocity, and how the 8 experience tiers (ARCH+ / ARCH / POWER / BASE / SEEKER / REFINER / BEARER / IGNITER, each with 3 sub-stages I/II/III, plus UNCLASSED for no-data) map to power-user behavior patterns. Use this when users ask 'what is an AI power user?' or 'what makes a good AI user?' or 'describe advanced AI user behavior'. Intent: DESCRIBE_POWER_USER (Informational).",
13
+ annotations: { title: "Describe power user", ...ANNOTATIONS.readOnlyHint, ...ANNOTATIONS.idempotentHint },
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {},
17
+ description:
18
+ "This tool takes no parameters. It returns a static explanatory response about AI power users.",
19
+ },
20
+ outputSchema: {
21
+ type: "object",
22
+ properties: {
23
+ description: { type: "string", description: "What is an AI power user" },
24
+ metrics_explained: {
25
+ type: "object",
26
+ description: "How SigRank metrics map to power-user behavior",
27
+ properties: {
28
+ yield_: { type: "string", description: "What yield measures in power-user terms" },
29
+ leverage: { type: "string", description: "What leverage means for power users" },
30
+ velocity: { type: "string", description: "What velocity means for power users" },
31
+ },
32
+ },
33
+ class_tiers: {
34
+ type: "array",
35
+ description: "Operator class tiers and their power-user meaning",
36
+ items: {
37
+ type: "object",
38
+ properties: {
39
+ class: { type: "string", enum: CLASS_ENUM },
40
+ meaning: { type: "string" },
41
+ },
42
+ },
43
+ },
44
+ link: { type: "string", description: "URL to learn more" },
45
+ },
46
+ },
47
+ };
48
+
49
+ export async function handleDescribePowerUser() {
50
+ return {
51
+ description:
52
+ "An AI power user isn't someone who sends the most tokens — it's someone who compounds signal. " +
53
+ "Power users build workflows where cached context does the heavy lifting, fresh input stays lean, " +
54
+ "and output per session is high. SigRank quantifies this with the yield metric (Υ = cache_read × output / input²).",
55
+ metrics_explained: {
56
+ yield_: "Yield (Υ) measures how well you compound signal, not how much you burn. High yield = your cached context is doing work for you.",
57
+ leverage: "Leverage (Cr/I) measures how much you reuse prior work vs starting fresh. High leverage = you're building on cached results, not re-explaining everything.",
58
+ velocity: "Velocity (O/I) measures how much output you get per token spent. High velocity = you're productive, not just active.",
59
+ },
60
+ // The 8 base tiers + UNCLASSED, in descending cut order (matches
61
+ // analytics/cascade.mjs CLASS_TIERS). Each tier has 3 sub-stages (I/II/III)
62
+ // — 24 stages total. TRANSMITTER is a peak badge, not a class.
63
+ class_tiers: [
64
+ { class: "ARCH+", sub_stages: ["ARCH+ I", "ARCH+ II", "ARCH+ III"], meaning: "Deepest field experience — volume that became architecture. The AI power user archetype." },
65
+ { class: "ARCH", sub_stages: ["ARCH I", "ARCH II", "ARCH III"], meaning: "System builder — sustained volume, coherent output, heavy cache reuse." },
66
+ { class: "POWER", sub_stages: ["POWER I", "POWER II", "POWER III"], meaning: "Above the center — volume compounding, building momentum." },
67
+ { class: "BASE", sub_stages: ["BASE I", "BASE II", "BASE III"], meaning: "The center of the field — the average operator's experience. Building momentum." },
68
+ { class: "SEEKER", sub_stages: ["SEEKER I", "SEEKER II", "SEEKER III"], meaning: "Approaching the center — experience accumulating, reuse patterns forming." },
69
+ { class: "REFINER", sub_stages: ["REFINER I", "REFINER II", "REFINER III"], meaning: "Practicing with purpose — early sustained volume, refining context reuse." },
70
+ { class: "BEARER", sub_stages: ["BEARER I", "BEARER II", "BEARER III"], meaning: "Quiet accumulation — the first real volume. Session continuity starting." },
71
+ { class: "IGNITER", sub_stages: ["IGNITER I", "IGNITER II", "IGNITER III"], meaning: "Entry — dormant potential. First sessions, minimal accumulated volume." },
72
+ { class: UNCLASSED, sub_stages: null, meaning: "No cascade data yet — run sessions and re-rank to see your tier." },
73
+ ],
74
+ link: "https://signalaf.com/score — check your class tier and yield",
75
+ shareable_url: `${DEFAULT_API_BASE}/score`,
76
+ cta: "Learn the scoring",
77
+ };
78
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * tools/diagnose-cascade.mjs — diagnose_cascade tool.
3
+ */
4
+
5
+ import { cascade, parsePillars } from "../analytics/cascade.mjs";
6
+ import { DIAGNOSE_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
7
+ import { MAX_INPUT, withParseWarnings } from "./_helpers.mjs";
8
+
9
+ export const TOOL_DEF = {
10
+ name: "diagnose_cascade",
11
+ description:
12
+ "Analyzes your token cascade and diagnoses where you're leaking efficiency. Takes your 4 pillars (input/output/cacheCreate/cacheRead) and produces a ranked list of efficiency leaks with severity (critical/warning/info), findings, and recommendations. Checks: cache leverage (are you rereading what you wrote?), velocity (are you generating enough output per input?), SNR (is your signal drowning in noise?), cache creation ratio (are you over-committing?), input bloat (is fresh input too high?), and 10xDEV (is the full cascade compounding?). Each finding includes an estimated Υ impact. Pure local math — no network, no submission. Use this BEFORE simulate_change to understand what's wrong, then use simulate_change to test fixes. Accepts the same input formats as rank_paste (JSON or 4 whitespace numbers).",
13
+ annotations: {
14
+ title: "Diagnose cascade breakdown",
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
+ 'Token pillars — ccusage JSON or "input output cacheCreate cacheRead" (same format as rank_paste).',
26
+ },
27
+ },
28
+ required: ["text"],
29
+ },
30
+ outputSchema: DIAGNOSE_OUTPUT,
31
+ };
32
+
33
+ export async function handleDiagnoseCascade(args) {
34
+ if (!args?.text)
35
+ throw new Error(
36
+ "diagnose_cascade requires a non-empty `text` argument (token pillars).",
37
+ );
38
+ if (typeof args.text === "string" && args.text.length > MAX_INPUT) {
39
+ return {
40
+ status: "error",
41
+ reason: "input_too_large",
42
+ detail: `text exceeds ${MAX_INPUT} chars.`,
43
+ };
44
+ }
45
+
46
+ const p = parsePillars(args.text);
47
+ const result = withParseWarnings(p, cascade(p));
48
+ const { input: i, output: o, cacheCreate: cw, cacheRead: cr } = p;
49
+ const diagnosis = [];
50
+
51
+ // Cache leverage check — the #1 cascade efficiency signal
52
+ const leverage = result.leverage;
53
+ if (leverage !== null) {
54
+ if (leverage < 10) {
55
+ diagnosis.push({
56
+ metric: "cache_leverage",
57
+ severity: "critical",
58
+ finding: `Cache leverage is ${leverage}× — you're reading only ${leverage}× your fresh input from cache. Top-tier operators hit 200×+.`,
59
+ recommendation:
60
+ "Increase context reuse: load prior session context, use longer conversation threads, reference earlier outputs.",
61
+ estimated_yield_impact: `+${Math.round((1 - leverage / 50) * 100)}% Υ potential`,
62
+ });
63
+ } else if (leverage < 50) {
64
+ diagnosis.push({
65
+ metric: "cache_leverage",
66
+ severity: "warning",
67
+ finding: `Cache leverage is ${leverage}× — decent but below the ARCH+ threshold (~100×+).`,
68
+ recommendation:
69
+ "Push cache reads higher by reusing prior context more aggressively.",
70
+ estimated_yield_impact: `+${Math.round((1 - leverage / 100) * 50)}% Υ potential`,
71
+ });
72
+ }
73
+ }
74
+
75
+ // Velocity check — output per input
76
+ const velocity = result.velocity;
77
+ if (velocity !== null) {
78
+ if (velocity < 0.5) {
79
+ diagnosis.push({
80
+ metric: "velocity",
81
+ severity: "critical",
82
+ finding: `Velocity is ${velocity} — generating only ${velocity}× your input as output. You're reading more than you produce.`,
83
+ recommendation:
84
+ "Increase output: ask the agent to generate more code/text per turn, reduce over-reading.",
85
+ estimated_yield_impact: `+${Math.round((0.5 - velocity) * 100)}% Υ per 0.1 velocity gain`,
86
+ });
87
+ } else if (velocity < 1.0) {
88
+ diagnosis.push({
89
+ metric: "velocity",
90
+ severity: "warning",
91
+ finding: `Velocity is ${velocity} — below 1.0 (output < input). Healthy operators hit 1.5×+.`,
92
+ recommendation:
93
+ "Generate more output per input token — larger edits, more complete responses.",
94
+ estimated_yield_impact: `+${Math.round((1 - velocity) * 30)}% Υ potential`,
95
+ });
96
+ }
97
+ }
98
+
99
+ // SNR check — signal-to-noise
100
+ const snr = result.snr;
101
+ if (snr !== null && snr < 0.3) {
102
+ diagnosis.push({
103
+ metric: "snr",
104
+ severity: "warning",
105
+ finding: `SNR is ${snr} — less than 30% of your token flow is output. Input is dominating.`,
106
+ recommendation:
107
+ "Reduce fresh input (reuse context) or increase output generation.",
108
+ estimated_yield_impact:
109
+ "Indirect — improves both velocity and leverage",
110
+ });
111
+ }
112
+
113
+ // Cache creation ratio — are you over-committing?
114
+ if (cw > 0 && o > 0) {
115
+ const commitRatio = cw / o;
116
+ if (commitRatio > 20) {
117
+ diagnosis.push({
118
+ metric: "cache_creation",
119
+ severity: "info",
120
+ finding: `Cache creation is ${commitRatio.toFixed(1)}× your output — high commitment. This is fine if you're rereading it (check leverage), but wasteful if not.`,
121
+ recommendation:
122
+ "Ensure you're rereading committed context. If leverage is low, you're writing cache you never read.",
123
+ estimated_yield_impact: "Cost reduction, not Υ directly",
124
+ });
125
+ }
126
+ }
127
+
128
+ // Input bloat — is fresh input too high relative to total?
129
+ const total = i + o + cw + cr;
130
+ if (total > 0) {
131
+ const inputPct = (i / total) * 100;
132
+ if (inputPct > 10) {
133
+ diagnosis.push({
134
+ metric: "input_bloat",
135
+ severity: "warning",
136
+ finding: `Fresh input is ${inputPct.toFixed(1)}% of your total token flow — high. Efficient operators keep input under 1% by leaning on cache.`,
137
+ recommendation:
138
+ "Reduce fresh input by reusing prior context instead of re-pasting it.",
139
+ estimated_yield_impact: `+${Math.round((inputPct - 1) * 5)}% Υ potential`,
140
+ });
141
+ }
142
+ }
143
+
144
+ // 10xDEV check — is the full cascade compounding?
145
+ if (result.dev10x === null && cw === 0) {
146
+ diagnosis.push({
147
+ metric: "10xdev",
148
+ severity: "critical",
149
+ finding:
150
+ "No cache creation — the cascade cannot compound. You're operating in a non-compounding mode (like ChatGPT without prompt caching).",
151
+ recommendation:
152
+ "Switch to a platform with prompt caching (Claude Code) or enable caching if available.",
153
+ estimated_yield_impact: "Enables the full cascade — potentially 10×+ Υ",
154
+ });
155
+ } else if (result.dev10x !== null && result.dev10x < 1.0) {
156
+ diagnosis.push({
157
+ metric: "10xdev",
158
+ severity: "info",
159
+ finding: `10xDEV is ${result.dev10x} — below 1.0 (BASE threshold). The cascade is compounding but not strongly.`,
160
+ recommendation:
161
+ "Improve both leverage AND velocity — 10xDEV = log10(transmission × commitment × reuse).",
162
+ estimated_yield_impact: "Class tier improvement",
163
+ });
164
+ }
165
+
166
+ // Sort by severity (critical > warning > info)
167
+ const sevOrder = { critical: 0, warning: 1, info: 2 };
168
+ diagnosis.sort((a, b) => sevOrder[a.severity] - sevOrder[b.severity]);
169
+
170
+ const healthScore = diagnosis.filter(
171
+ (d) => d.severity === "critical",
172
+ ).length;
173
+ const summary =
174
+ healthScore === 0
175
+ ? `Cascade is healthy — Υ ${result.yield}, class ${result.class}. ${diagnosis.length} minor optimizations available.`
176
+ : `Cascade has ${healthScore} critical leak${healthScore > 1 ? "s" : ""} — Υ ${result.yield}, class ${result.class}. Fix the critical items first.`;
177
+
178
+ return {
179
+ pillars: p,
180
+ cascade: {
181
+ yield_: result.yield,
182
+ snr: result.snr,
183
+ leverage: result.leverage,
184
+ velocity: result.velocity,
185
+ tenx_dev: result.dev10x,
186
+ class: result.class,
187
+ warnings: result.warnings,
188
+ },
189
+ diagnosis,
190
+ summary,
191
+ };
192
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * tools/discover-peers.mjs — discover_peers tool.
3
+ *
4
+ * Finds mentors, peers, and complementary operators for YOUR operator on the
5
+ * SigRank leaderboard. Uses the enrolled device identity (ensureIdentity) so
6
+ * the operator's agent doesn't need to pass a codename — it knows who you are.
7
+ *
8
+ * This is the "make connections" piece of the agent workflow loop:
9
+ * enroll → submit_verified → discover_peers → self_improve → submit again
10
+ *
11
+ * Fetches the operator's profile + the full leaderboard from signalaf.com's
12
+ * public API, then runs pure client-side peer-matching logic:
13
+ * - Mentors: 1-2 class tiers above, ranked by cascade shape similarity
14
+ * - Peers: same class tier, ranked by yield proximity
15
+ * - Complementary: operators whose strength maps to your weakness
16
+ *
17
+ * For each mentor, computes the pillar delta — the specific token difference
18
+ * that explains the yield gap (e.g. "12× your cache reads").
19
+ */
20
+
21
+ import { ensureIdentity } from "../identity/keystore.mjs";
22
+ import { ANNOTATIONS } from "./_schemas.mjs";
23
+ import { DEFAULT_API_BASE } from "./_helpers.mjs";
24
+ import {
25
+ findMentors,
26
+ findPeers,
27
+ findComplementary,
28
+ peerSummary,
29
+ tierRank,
30
+ } from "./_peer-matching.mjs";
31
+ import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
32
+
33
+ export const TOOL_DEF = {
34
+ name: "discover_peers",
35
+ description:
36
+ "Discovers mentors, peers, and complementary operators for YOUR operator on the SigRank leaderboard. Uses your enrolled device identity — no codename needed. Finds operators you should learn from: (1) Mentors — 1-2 class tiers above you with similar cascade shapes, including the specific pillar delta that explains the yield gap (e.g. '12× your cache reads'). (2) Peers — same class tier, ranked by yield proximity. (3) Complementary — operators whose strength is your weakness. Use this after submit_verified to find who to learn from, then chain into self_improve with the mentor's pillar deltas as context. Requires enrollment (npx sigrank-mcp enroll). Intent: DISCOVER_PEERS.",
37
+ annotations: {
38
+ title: "Discover peers and mentors",
39
+ ...ANNOTATIONS.readOnlyHint,
40
+ ...ANNOTATIONS.openWorldHint,
41
+ },
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ platform: {
46
+ type: "string",
47
+ description:
48
+ "Filter peers by platform (default: your operator's primary platform). Use 'all' to search across all platforms.",
49
+ },
50
+ n: {
51
+ type: "integer",
52
+ description:
53
+ "Number of operators to return per category (default: 5, max: 20).",
54
+ minimum: 1,
55
+ maximum: 20,
56
+ },
57
+ },
58
+ description:
59
+ "Optional: platform filter and result count. Uses your enrolled identity to determine your operator.",
60
+ },
61
+ outputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ your_profile: {
65
+ type: "object",
66
+ description: "Your operator's current profile on the board",
67
+ properties: {
68
+ codename: { type: "string" },
69
+ class_tier: { type: "string" },
70
+ platform: { type: "string" },
71
+ yield_: { type: "number" },
72
+ leverage: { type: "number" },
73
+ velocity: { type: "number" },
74
+ rank: { type: "integer" },
75
+ },
76
+ },
77
+ mentors: {
78
+ type: "array",
79
+ description:
80
+ "Operators 1-2 class tiers above you with similar cascade shapes, including pillar deltas",
81
+ },
82
+ peers: {
83
+ type: "array",
84
+ description: "Operators in your class tier, ranked by yield proximity",
85
+ },
86
+ complementary: {
87
+ type: "array",
88
+ description: "Operators whose strength maps to your weakness",
89
+ },
90
+ summary: { type: "string", description: "One-line summary of findings" },
91
+ cta: { type: "string" },
92
+ },
93
+ },
94
+ };
95
+
96
+ export async function handleDiscoverPeers(args, ctx) {
97
+ const id = ctx.opts.identity || ensureIdentity();
98
+ if (!id.codename) {
99
+ return {
100
+ status: "not_enrolled",
101
+ detail:
102
+ "Run `npx sigrank-mcp enroll` to bind this device first, then submit_verified to appear on the board.",
103
+ };
104
+ }
105
+
106
+ const apiBase = ctx.apiBase || DEFAULT_API_BASE;
107
+ const n = Math.min(20, Math.max(1, args?.n ?? 5));
108
+ const platformFilter = args?.platform === "all" ? null : args?.platform;
109
+
110
+ // Fetch the operator's profile and the full leaderboard in parallel.
111
+ const [profile, board] = await Promise.all([
112
+ ctx.fetchJson(`/api/v1/operators/${encodeURIComponent(id.codename)}`),
113
+ ctx.fetchJson(`/api/v1/leaderboard?metric=${LEADERBOARD_METRIC}&limit=2000`),
114
+ ]);
115
+
116
+ // Find the operator's entry in the board (has raw token pillars + class_tier).
117
+ const entries = board.entries || board.operators || board || [];
118
+ const boardEntry = entries.find(
119
+ (op) => op.codename?.toLowerCase() === id.codename?.toLowerCase(),
120
+ );
121
+
122
+ if (!boardEntry) {
123
+ return {
124
+ status: "not_on_board",
125
+ codename: id.codename,
126
+ detail:
127
+ "You're enrolled but not on the leaderboard yet. Run submit_verified to publish your snapshots.",
128
+ profile,
129
+ };
130
+ }
131
+
132
+ // Merge profile + board entry (board entry has raw pillars, profile has
133
+ // per-window breakdowns). Use board entry as the primary source since it
134
+ // has the fields peer-matching needs.
135
+ const operator = {
136
+ ...boardEntry,
137
+ ...profile,
138
+ // Ensure board entry fields take precedence for peer-matching
139
+ class_tier: boardEntry.class_tier || boardEntry.class || profile.class_tier,
140
+ yield_: boardEntry.yield_ ?? profile.yield_,
141
+ leverage: boardEntry.leverage ?? profile.leverage,
142
+ velocity: boardEntry.velocity ?? profile.velocity,
143
+ snr: boardEntry.snr ?? profile.snr,
144
+ platform: boardEntry.platform || profile.platform,
145
+ };
146
+
147
+ const myTier = tierRank(operator.class_tier);
148
+ if (myTier < 0) {
149
+ // UNCLASSED — no class to match on. Return complementary only.
150
+ const complementary = findComplementary(operator, entries, { n, apiBase, platform: platformFilter });
151
+ return {
152
+ your_profile: {
153
+ codename: operator.codename,
154
+ class_tier: operator.class_tier,
155
+ platform: operator.platform,
156
+ yield_: operator.yield_,
157
+ leverage: operator.leverage,
158
+ velocity: operator.velocity,
159
+ rank: operator.rank,
160
+ },
161
+ mentors: [],
162
+ peers: [],
163
+ complementary,
164
+ summary: `You're UNCLASSED — no class tier to match peers on. ${complementary.length} complementary operators found based on cascade dimensions. Submit more snapshots to get classified.`,
165
+ cta: "Submit snapshots",
166
+ };
167
+ }
168
+
169
+ const opts = { n, apiBase, platform: platformFilter };
170
+ const mentors = findMentors(operator, entries, opts);
171
+ const peers = findPeers(operator, entries, opts);
172
+ const complementary = findComplementary(operator, entries, opts);
173
+
174
+ // Add shareable URLs to mentors
175
+ const mentorsWithUrls = mentors.map((m) => ({
176
+ ...m,
177
+ shareable_url: `${apiBase}/operator/${encodeURIComponent(m.codename)}`,
178
+ }));
179
+
180
+ const summary = peerSummary(operator, mentors, peers, complementary);
181
+
182
+ return {
183
+ your_profile: {
184
+ codename: operator.codename,
185
+ class_tier: operator.class_tier,
186
+ platform: operator.platform,
187
+ yield_: operator.yield_,
188
+ leverage: operator.leverage,
189
+ velocity: operator.velocity,
190
+ rank: operator.rank,
191
+ },
192
+ mentors: mentorsWithUrls,
193
+ peers,
194
+ complementary,
195
+ summary,
196
+ cta: "Study their cascade",
197
+ };
198
+ }