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
package/keystore.mjs CHANGED
@@ -1,242 +1 @@
1
- // keystore.mjs — local ed25519 device identity for SigRank signed submit (D7 §4.5).
2
- //
3
- // Persists a per-device keypair at ~/.sigrank-mcp/identity.json (dir 0700, file 0600).
4
- // The PRIVATE key never leaves this machine; only the raw 32-byte public key (as
5
- // "ed25519:<base64>") is sent to the server at enroll time. The device_id (uuid) is
6
- // generated once and is the immutable binding target server-side.
7
- //
8
- // Public-key export MUST match the server's publicKeyFromAgent: generateKeyPairSync
9
- // exports SPKI DER with a 12-byte prefix; we strip it to the raw 32 bytes, base64 it,
10
- // and prefix "ed25519:". The server re-adds the SPKI prefix to verify. Round-trip is
11
- // locked by sign.test.mjs + the canon-parity fixture.
12
-
13
- import { homedir } from "node:os";
14
- import { join } from "node:path";
15
- import {
16
- mkdirSync,
17
- readFileSync,
18
- writeFileSync,
19
- existsSync,
20
- chmodSync,
21
- unlinkSync,
22
- readdirSync,
23
- copyFileSync,
24
- } from "node:fs";
25
- import { generateKeyPairSync, randomUUID } from "node:crypto";
26
-
27
- const DIR = join(homedir(), ".sigrank-mcp");
28
- const PATH = join(DIR, "identity.json");
29
- const BACKUP_PREFIX = "identity.json.bak-";
30
- const MAX_BACKUPS = 5;
31
-
32
- /** SPKI DER prefix length for an ed25519 public key (12 bytes before the raw 32). */
33
- const SPKI_PREFIX_LEN = 12;
34
-
35
- /** Resolve this package's version for the agent_version stamp (best-effort). */
36
- function agentVersion() {
37
- try {
38
- const pkg = JSON.parse(
39
- readFileSync(new URL("./package.json", import.meta.url), "utf-8"),
40
- );
41
- return `sigrank-mcp/${pkg.version}`;
42
- } catch {
43
- return "sigrank-mcp";
44
- }
45
- }
46
-
47
- export function keystorePath() {
48
- return PATH;
49
- }
50
-
51
- /** Read the stored identity, or null if absent/corrupt. */
52
- export function loadIdentity() {
53
- if (!existsSync(PATH)) return null;
54
- try {
55
- return JSON.parse(readFileSync(PATH, "utf-8"));
56
- } catch {
57
- return null;
58
- }
59
- }
60
-
61
- /** Write the identity with strict perms (dir 0700, file 0600). Backs up the existing
62
- * file before overwriting so a corrupt write or accidental reset can be recovered. */
63
- export function persistIdentity(identity) {
64
- mkdirSync(DIR, { recursive: true });
65
- try {
66
- chmodSync(DIR, 0o700);
67
- } catch {
68
- /* best-effort on platforms without chmod */
69
- }
70
- // Back up the existing identity before overwriting (resilience: if the write
71
- // fails or the file is later lost, the backup carries the binding + keys).
72
- if (existsSync(PATH)) {
73
- try {
74
- const stamp = Date.now();
75
- copyFileSync(PATH, join(DIR, `${BACKUP_PREFIX}${stamp}`));
76
- pruneBackups();
77
- } catch {
78
- /* best-effort — don't block the write */
79
- }
80
- }
81
- writeFileSync(PATH, `${JSON.stringify(identity, null, 2)}\n`, {
82
- mode: 0o600,
83
- });
84
- try {
85
- chmodSync(PATH, 0o600);
86
- } catch {
87
- /* best-effort */
88
- }
89
- return identity;
90
- }
91
-
92
- /** Build a fresh keypair record (pure — no fs). Exposed for tests. */
93
- export function generateIdentity({ device_id } = {}) {
94
- const { publicKey, privateKey } = generateKeyPairSync("ed25519");
95
- const spki = publicKey.export({ type: "spki", format: "der" });
96
- const rawPub = Buffer.from(spki).subarray(SPKI_PREFIX_LEN); // the raw 32 bytes
97
- const pkcs8 = privateKey.export({ type: "pkcs8", format: "der" });
98
- return {
99
- device_id: device_id || randomUUID(),
100
- codename: null,
101
- operator_id: null,
102
- public_key: `ed25519:${rawPub.toString("base64")}`,
103
- private_key_pkcs8_b64: Buffer.from(pkcs8).toString("base64"),
104
- agent_version: agentVersion(),
105
- enrolled_at: null,
106
- };
107
- }
108
-
109
- /**
110
- * Decide the binding (codename/operator_id/enrolled_at) to carry onto a freshly
111
- * generated identity. The binding is tied to device_id: if device_id is CHANGING
112
- * (a new device, e.g. after a reset/re-enroll where the old file was deleted), the
113
- * old binding is INVALIDATED — never preserve a stale codename/operator onto a
114
- * different device. That mismatch is the "Frankenstein identity" root cause: a new
115
- * device_id + an old codename → the server sees a mismatch → tags submissions
116
- * `unverified` → never ranks, yet `isSignedIn` reads the local codename as present.
117
- * Only reuse the binding when the SAME device_id is being kept. Pure — exposed for
118
- * tests (no fs, so the owner's live identity is never at risk during a test run).
119
- */
120
- export function bindingForFreshIdentity(existing, fresh) {
121
- if (!existing)
122
- return { codename: null, operator_id: null, enrolled_at: null };
123
- if (existing.device_id && fresh.device_id === existing.device_id) {
124
- return {
125
- codename: existing.codename ?? null,
126
- operator_id: existing.operator_id ?? null,
127
- enrolled_at: existing.enrolled_at ?? null,
128
- };
129
- }
130
- // device_id changed → the old binding belongs to a different device; drop it.
131
- return { codename: null, operator_id: null, enrolled_at: null };
132
- }
133
-
134
- /**
135
- * Load the existing identity, or generate + persist a fresh one. Idempotent: a
136
- * complete existing identity is returned untouched (never rotates a live key).
137
- * Self-healing: if the identity has keys + device_id but no codename (binding
138
- * lost via partial write / version transition), scans backups for a matching
139
- * device_id and restores the binding before returning.
140
- */
141
- export function ensureIdentity() {
142
- const existing = loadIdentity();
143
- if (
144
- existing?.private_key_pkcs8_b64 &&
145
- existing?.public_key &&
146
- existing?.device_id
147
- ) {
148
- // Self-healing: keys present but binding lost → try to restore from backups.
149
- if (!existing.codename || !existing.operator_id) {
150
- const restored = restoreBindingFromBackups(existing.device_id);
151
- if (restored) {
152
- existing.codename = restored.codename;
153
- existing.operator_id = restored.operator_id;
154
- existing.enrolled_at = restored.enrolled_at;
155
- return persistIdentity(existing);
156
- }
157
- }
158
- return existing;
159
- }
160
- // Regeneration path: a partial/corrupt record. The binding is tied to device_id —
161
- // if a new device_id is generated, the old codename/operator MUST NOT carry over
162
- // (bindingForFreshIdentity drops them). Only a reused device_id keeps its binding.
163
- const fresh = generateIdentity({ device_id: existing?.device_id });
164
- const binding = bindingForFreshIdentity(existing, fresh);
165
- fresh.codename = binding.codename;
166
- fresh.operator_id = binding.operator_id;
167
- fresh.enrolled_at = binding.enrolled_at;
168
- return persistIdentity(fresh);
169
- }
170
-
171
- /** Record a successful enrollment (codename/operator_id + enrolled_at) into the keystore. */
172
- export function recordEnrollment({ codename, operator_id }) {
173
- const id = ensureIdentity();
174
- // OVERWRITE from the server's enroll response — a new enroll is a new binding, full
175
- // stop. Never `??`-preserve a stale codename/operator onto this device (the other
176
- // half of the Frankenstein bug: a re-enroll used to keep the old codename when the
177
- // new one was absent). A 201 enrolled always carries both.
178
- id.codename = codename ?? null;
179
- id.operator_id = operator_id ?? null;
180
- id.enrolled_at = new Date().toISOString();
181
- return persistIdentity(id);
182
- }
183
-
184
- /** Clear the local identity (sign out). Next enroll provisions a fresh device_id. */
185
- export function clearIdentity() {
186
- try {
187
- if (existsSync(PATH)) unlinkSync(PATH);
188
- } catch {
189
- /* best-effort */
190
- }
191
- }
192
-
193
- /** Keep at most MAX_BACKUPS backup files (oldest deleted first). */
194
- function pruneBackups() {
195
- try {
196
- const files = readdirSync(DIR)
197
- .filter((f) => f.startsWith(BACKUP_PREFIX))
198
- .sort(); // timestamp-suffixed → lexicographic = chronological
199
- while (files.length > MAX_BACKUPS) {
200
- unlinkSync(join(DIR, files.shift()));
201
- }
202
- } catch {
203
- /* best-effort */
204
- }
205
- }
206
-
207
- /** Scan backup files for one with the same device_id that carries a codename +
208
- * operator_id binding. Returns { codename, operator_id, enrolled_at } or null.
209
- * Used by ensureIdentity() to self-heal when the live file lost its binding
210
- * (e.g. a partial write or version transition that preserved keys but dropped
211
- * the binding metadata). Only restores from a backup with the SAME device_id —
212
- * never crosses device boundaries (Frankenstein-identity guard). */
213
- export function restoreBindingFromBackups(device_id) {
214
- if (!device_id) return null;
215
- try {
216
- const files = readdirSync(DIR)
217
- .filter((f) => f.startsWith(BACKUP_PREFIX))
218
- .sort()
219
- .reverse(); // newest first — prefer the most recent binding
220
- for (const f of files) {
221
- try {
222
- const bak = JSON.parse(readFileSync(join(DIR, f), "utf-8"));
223
- if (
224
- bak.device_id === device_id &&
225
- bak.codename &&
226
- bak.operator_id
227
- ) {
228
- return {
229
- codename: bak.codename,
230
- operator_id: bak.operator_id,
231
- enrolled_at: bak.enrolled_at ?? null,
232
- };
233
- }
234
- } catch {
235
- /* skip corrupt backups */
236
- }
237
- }
238
- } catch {
239
- /* best-effort */
240
- }
241
- return null;
242
- }
1
+ export * from './identity/keystore.mjs';
@@ -0,0 +1,137 @@
1
+ // lib/constants.mjs — repo-wide constants that were previously inlined as
2
+ // literals across multiple files. Centralizing them here means a version bump
3
+ // or a client-map update lands in exactly one place.
4
+ //
5
+ // TERMS_VERSION / PRIVACY_VERSION — embedded in every enroll + submit
6
+ // payload (tools/enroll.mjs, tools/submit-paste.mjs,
7
+ // tools/tokenpull-submit.mjs, submit/index.mjs). They were inlined as
8
+ // "2026-07-21" in four files; a terms rev would have required finding
9
+ // and editing all four. Now: one export, imported everywhere.
10
+ //
11
+ // TOKSCALE_CLIENT_MAP — maps tokscale's raw client names to our platform
12
+ // IDs. Was duplicated verbatim in tools/index.mjs and
13
+ // tools/tokscale-breakdown.mjs (and again in tokscale_analytics.mjs);
14
+ // the three copies had already started to drift. One export here.
15
+ //
16
+ // isRankedAck — the "actually on the board" predicate (verified + persisted).
17
+ // Was inlined as `ack.verification_tier === "verified" && ack.persisted
18
+ // === true` in submit-paste, tokenpull-submit, and submit/index.mjs, with
19
+ // tokenpull-submit's multi-platform branch using a DIFFERENT looser
20
+ // predicate (`ack?.ranked ?? ack?.accepted ?? false`). One predicate here
21
+ // closes that loophole so every submit path agrees on what "ranked" means.
22
+
23
+ /** Current Terms of Service version the client attests to. */
24
+ export const TERMS_VERSION = "2026-07-21";
25
+
26
+ /** Current Privacy Policy version the client attests to. */
27
+ export const PRIVACY_VERSION = "2026-07-21";
28
+
29
+ /**
30
+ * The leaderboard sort metric the client requests from the server. Every
31
+ * leaderboard fetch site (tools/get-leaderboard.mjs, presentation/cli.mjs
32
+ * fetchBoard, presentation/tui.mjs, compare-self / optimize-efficiency /
33
+ * get-best-operator / compare-operators) MUST use this value — the server
34
+ * ranks by `yield_` and a different metric string would either 400 or return
35
+ * an unsorted board. Centralized here so a future metric rename lands in one
36
+ * place; the contract test in __tests__/contract/leaderboard-metric-contract.test.mjs
37
+ * guards against intra-repo drift and (in CI) cross-repo drift vs sigrank-app.
38
+ */
39
+ export const LEADERBOARD_METRIC = "yield_";
40
+
41
+ /**
42
+ * Map a raw tokscale `client` string to our canonical platform ID.
43
+ * Values mapped to `null` are skipped (synthetic / non-attributable).
44
+ * Values mapped to `"other"` have no native adapter and bucket together.
45
+ */
46
+ export const TOKSCALE_CLIENT_MAP = {
47
+ claude: "claude",
48
+ codex: "codex",
49
+ "devin-cli": "devin",
50
+ "devin-desktop": "devin",
51
+ gemini: "gemini",
52
+ amp: "amp",
53
+ kimi: "kimi",
54
+ qwen: "qwen",
55
+ goose: "goose",
56
+ kilo: "kilo",
57
+ kilocode: "kilo",
58
+ hermes: "hermes",
59
+ droid: "droid",
60
+ codebuff: "codebuff",
61
+ copilot: "copilot",
62
+ opencode: "opencode",
63
+ openclaw: "openclaw",
64
+ pi: "pi",
65
+ omp: "omp",
66
+ "oh-my-pi": "omp",
67
+ cursor: "other",
68
+ roocode: "other",
69
+ mux: "other",
70
+ crush: "other",
71
+ antigravity: "other",
72
+ "antigravity-cli": "other",
73
+ zed: "other",
74
+ kiro: "other",
75
+ trae: "other",
76
+ warp: "other",
77
+ cline: "other",
78
+ "9router": "other",
79
+ gjc: "other",
80
+ grok: "other",
81
+ jcode: "other",
82
+ commandcode: "other",
83
+ micode: "other",
84
+ junie: "other",
85
+ zcode: "other",
86
+ opencodereview: "other",
87
+ codebuddy: "other",
88
+ workbuddy: "other",
89
+ senpi: "other",
90
+ augment: "other",
91
+ kimchi: "other",
92
+ reasonix: "other",
93
+ "prime-agent": "other",
94
+ freebuff: "other",
95
+ synthetic: null,
96
+ };
97
+
98
+ /**
99
+ * Platforms whose local data tokscale CANNOT see, so `tokscale models --json`
100
+ * never lists them as an active client. pullActivePlatforms() auto-detect must
101
+ * probe these directly or their cascade row can never appear, even with GBs of
102
+ * native local telemetry (that was exactly the omp bug: tokscale reports
103
+ * claude/codex/copilot/gemini/grok/kimi/kiro/opencode/pi and nothing else, so
104
+ * a tokscale-detected machine silently dropped oh-my-pi entirely).
105
+ *
106
+ * The adapter pull itself filters out platforms with no data, so probing a
107
+ * platform the operator doesn't use costs one cheap missing-directory walk.
108
+ *
109
+ * As of 2026-08-08: oh-my-pi. Delete an entry once tokscale reports that client
110
+ * (TOKSCALE_CLIENT_MAP already maps the `omp` / `oh-my-pi` client names for
111
+ * when that happens).
112
+ */
113
+ export const TOKSCALE_BLIND_PLATFORMS = ["omp"];
114
+
115
+ /**
116
+ * The "actually on the board" predicate. A submission is ranked only when the
117
+ * server returns HTTP ok AND verification_tier === "verified" AND persisted
118
+ * === true. An unenrolled/revoked device gets 202 but is NEVER ranked.
119
+ *
120
+ * Use this everywhere a submit path decides whether to surface `ranked: true`.
121
+ * The previous tokenpull-submit multi-platform branch used a looser
122
+ * `ack?.ranked ?? ack?.accepted ?? false` which would mark a non-persisted
123
+ * 202 as ranked — this predicate closes that loophole.
124
+ *
125
+ * @param {{ ok: boolean }} res the fetch Response (or { ok } shim)
126
+ * @param {Record<string, unknown>} ack the parsed JSON ack body
127
+ * @returns {boolean}
128
+ */
129
+ export function isRankedAck(res, ack) {
130
+ return !!(
131
+ res &&
132
+ res.ok &&
133
+ ack &&
134
+ ack.verification_tier === "verified" &&
135
+ ack.persisted === true
136
+ );
137
+ }
@@ -0,0 +1,59 @@
1
+ // lib/pkg-version.mjs — single source of truth for the package version.
2
+ //
3
+ // Resolves the repo-root package.json by walking up from this module until it
4
+ // finds the package named "sigrank". This is robust to callers in any
5
+ // subdirectory (identity/, submit/, presentation/, tools/, scripts/) — the
6
+ // previous per-caller `new URL("./package.json", import.meta.url)` lookups
7
+ // resolved relative to the CALLER's own directory and hit non-existent files,
8
+ // falling back to a bare string. The agent_version stamp rides inside the
9
+ // SIGNED submit payload (agent.agent_version), so a wrong/missing version
10
+ // there is a high-priority bug — it survived because no test guarded it.
11
+ //
12
+ // One implementation, imported everywhere. Never throws: returns "unknown" on
13
+ // any failure so a missing/renamed package.json never breaks a submit.
14
+
15
+ import { readFileSync } from "node:fs";
16
+ import { dirname, join } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ /** The published package name (package.json "name"). Used to identify the
20
+ * repo-root package.json unambiguously past any nested package.json. */
21
+ const REPO_PKG_NAME = "sigrank";
22
+
23
+ /** Resolve the repo-root package version (best-effort).
24
+ * Returns the version string (e.g. "0.0.181") or "unknown" on any failure. */
25
+ export function pkgVersion() {
26
+ try {
27
+ let dir = dirname(fileURLToPath(import.meta.url));
28
+ for (let i = 0; i < 20; i++) {
29
+ const pkgPath = join(dir, "package.json");
30
+ let pkg;
31
+ try {
32
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
33
+ } catch {
34
+ // no package.json here — walk up
35
+ const parent = dirname(dir);
36
+ if (parent === dir) break;
37
+ dir = parent;
38
+ continue;
39
+ }
40
+ if (pkg.name === REPO_PKG_NAME) return pkg.version || "unknown";
41
+ // Found a package.json but it isn't ours (e.g. a nested dep) — keep walking.
42
+ const parent = dirname(dir);
43
+ if (parent === dir) {
44
+ // Reached the filesystem root without matching; use whatever we found.
45
+ return pkg.version || "unknown";
46
+ }
47
+ dir = parent;
48
+ }
49
+ return "unknown";
50
+ } catch {
51
+ return "unknown";
52
+ }
53
+ }
54
+
55
+ /** The full agent-version stamp, e.g. "sigrank-mcp/0.0.181".
56
+ * This is the value embedded in the signed payload's agent.agent_version. */
57
+ export function agentVersion() {
58
+ return `sigrank-mcp/${pkgVersion()}`;
59
+ }
package/manifest.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/anthropics/mcpb/main/schemas/mcpb-manifest-v0.4.schema.json",
3
+ "manifest_version": "0.4",
4
+ "name": "sigrank",
5
+ "version": "1.0.0",
6
+ "description": "Ranks AI coding operators by token cascade efficiency (Yield = cache_read \u00d7 output / input\u00b2). 23 tools for yield calculation, local token usage pulls, signed submission, and tokscale-powered analytics (market share, developer profiles, model trends, cost analysis, device profiles, MCP usage, competitive intelligence). Privacy-first: runs locally, only four token counts leave the machine.",
7
+ "author": {
8
+ "name": "SunrisesIllNeverSee",
9
+ "url": "https://github.com/SunrisesIllNeverSee"
10
+ },
11
+ "homepage": "https://signalaf.com",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/SunrisesIllNeverSee/sigrank-mcp"
15
+ },
16
+ "license": "MIT",
17
+ "server": {
18
+ "type": "node",
19
+ "entry_point": "index.mjs",
20
+ "mcp_config": {
21
+ "command": "npx",
22
+ "args": [
23
+ "sigrank"
24
+ ],
25
+ "env": {
26
+ "SIGRANK_API_BASE": "https://signalaf.com"
27
+ }
28
+ }
29
+ },
30
+ "user_config": {
31
+ "SIGRANK_API_BASE": {
32
+ "type": "string",
33
+ "title": "SigRank API Base URL",
34
+ "description": "Overrides the board host for the SigRank API. Defaults to https://signalaf.com.",
35
+ "default": "https://signalaf.com",
36
+ "required": false
37
+ }
38
+ },
39
+ "compatibility": {
40
+ "platforms": [
41
+ "darwin",
42
+ "win32",
43
+ "linux"
44
+ ]
45
+ }
46
+ }
package/narrate.mjs CHANGED
@@ -1,77 +1 @@
1
- /**
2
- * narrate.mjs — deterministic prose "card" for a cascade result.
3
- *
4
- * Port of _template() from ~/Desktop/moses-sigrank/narrate.py. The model path
5
- * (MiniCPM4-0.5B) is intentionally SKIPPED: the template is the trustworthy,
6
- * instant, auditable fallback — same numbers in → same card out, and it can never
7
- * emit a metric the cascade didn't produce. A model hook can layer behind this same
8
- * narrate() interface later without a rewrite.
9
- *
10
- * Token-only. No network, no randomness.
11
- */
12
-
13
- // Safe formatters: never emit NaN/Infinity/undefined into the card.
14
- const safeNum = (n) => (Number.isFinite(Number(n)) ? Number(n) : null);
15
- const comma = (n, dec) => {
16
- const v = safeNum(n);
17
- return v !== null
18
- ? v.toLocaleString("en-US", {
19
- minimumFractionDigits: dec,
20
- maximumFractionDigits: dec,
21
- })
22
- : "—";
23
- };
24
- const plain = (n, dec) => {
25
- const v = safeNum(n);
26
- return v !== null ? v.toFixed(dec) : "—";
27
- };
28
-
29
- /**
30
- * Given a cascade result ({ velocity, leverage, dev10x, pillars, class }) and an
31
- * optional subject name, return "**CLASS.** <one or two sentences>". Deterministic.
32
- */
33
- export function narrate(cascade, name = "This operator") {
34
- const klass = cascade.class || cascade.klass || "UNCLASSED";
35
- const v = safeNum(cascade.velocity);
36
- const l = safeNum(cascade.leverage);
37
-
38
- // "non-compounding" = a stateless pipe: no cache commits, so the cascade can't
39
- // form. cascade.mjs leaves dev10x null when cacheCreate is 0 (the cw/o term
40
- // collapses), which is exactly metrics.py's non_compounding flag.
41
- // Also catches zero-input sessions where velocity/leverage are null.
42
- const cw = cascade.pillars ? Number(cascade.pillars.cacheCreate) : NaN;
43
- const nonCompounding =
44
- cascade.dev10x == null || !(cw > 0) || v === null || l === null;
45
-
46
- let body;
47
- if (nonCompounding) {
48
- const leverageStr =
49
- l !== null
50
- ? `Leverage ${comma(l, 1)}x comes from reuse alone.`
51
- : "Leverage is undefined (no fresh input recorded).";
52
- const dev10xNote =
53
- cascade.dev10x == null
54
- ? " 10xDEV is undefined — the compounding loop has not formed yet."
55
- : "";
56
- body =
57
- `${name} runs a stateless pipe — no cache commits, so the cascade can't form. ` +
58
- `High read volume, but nothing is being built forward. ${leverageStr}${dev10xNote}`;
59
- } else if (v >= 1 && l >= 100) {
60
- body =
61
- `${name} holds both axes at once: ${plain(v, 1)}x generation AND ${comma(l, 0)}x memory leverage. ` +
62
- `A closed kinetic loop — the rare operator the leverage/generation tradeoff says shouldn't exist.`;
63
- } else if (l >= 10 && v < 1) {
64
- body =
65
- `${name} is an archival sponge — ${comma(l, 0)}x reuse but only ${plain(v, 2)}x generation. ` +
66
- `Holds context beautifully, executes little with it. The reuse number is inflated by a weak commitment stage.`;
67
- } else if (v >= 0.8 && l < 2) {
68
- body =
69
- `${name} is a volatile ingestor — ${plain(v, 2)}x generation but ${plain(l, 1)}x leverage. ` +
70
- `Fast on single shots, resets between turns. Memory doesn't persist into a compounding loop.`;
71
- } else {
72
- body =
73
- `${name} sits low on both axes: ${plain(v, 2)}x generation, ${plain(l, 1)}x leverage. ` +
74
- `A transient profile — neither building state nor converting input to output efficiently.`;
75
- }
76
- return `**${klass}.** ${body}`;
77
- }
1
+ export * from './presentation/narrate.mjs';
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "sigrank",
3
- "version": "0.18.5",
3
+ "version": "1.0.1",
4
4
  "author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
5
- "description": "SigRank MCP server — the yield cascade + live leaderboard as MCP tools any agent can call",
5
+ "description": "SigRank MCP server — AI operator evaluation and token cascade efficiency tools. The yield cascade + live leaderboard as MCP tools any agent can call.",
6
6
  "mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
7
7
  "type": "module",
8
8
  "license": "MIT",
@@ -24,26 +24,43 @@
24
24
  },
25
25
  "scripts": {
26
26
  "start": "node index.mjs",
27
- "test": "node test.mjs && node sign.test.mjs && node --test __tests__/cascade.test.mjs __tests__/badges.test.mjs",
28
- "sync:platforms": "node scripts/sync-platforms.mjs"
27
+ "test": "node test.mjs && node __tests__/sign.test.mjs && node --test __tests__/cascade.test.mjs __tests__/badges.test.mjs __tests__/tui-input.test.mjs",
28
+ "sync:platforms": "node scripts/sync-platforms.mjs",
29
+ "sync:spine": "node scripts/sync-spine.mjs",
30
+ "spine:check": "node scripts/sync-spine.mjs --check",
31
+ "spine:diff": "node scripts/sync-spine.mjs --dry-run"
29
32
  },
30
33
  "files": [
31
34
  "index.mjs",
32
- "cli.mjs",
33
- "tui.mjs",
34
- "tools.mjs",
35
+ "proxy.mjs",
36
+ "lib/",
37
+ "tools/",
38
+ "analytics/",
39
+ "identity/",
40
+ "submit/",
41
+ "adapters/",
42
+ "presentation/",
43
+ "resources/",
44
+ "prompts/",
45
+ "badges.mjs",
46
+ "review.mjs",
35
47
  "cascade.mjs",
36
- "narrate.mjs",
37
- "tokenpull.mjs",
38
- "adapters.mjs",
39
- "sign.mjs",
40
48
  "keystore.mjs",
41
- "submit.mjs",
49
+ "sign.mjs",
42
50
  "connect.mjs",
43
- "badges.mjs",
51
+ "submit.mjs",
44
52
  "preflight.mjs",
45
- "banner.svg",
53
+ "adapters.mjs",
54
+ "tokenpull.mjs",
55
+ "tui.mjs",
56
+ "cli.mjs",
57
+ "narrate.mjs",
58
+ "tools.mjs",
59
+ "tokscale_analytics.mjs",
60
+ "manifest.json",
46
61
  "README.md",
62
+ "CHANGELOG.md",
63
+ "MCP_SERVER_DESCRIPTION.md",
47
64
  "LICENSE"
48
65
  ],
49
66
  "keywords": [
@@ -65,7 +82,15 @@
65
82
  ],
66
83
  "dependencies": {
67
84
  "@modelcontextprotocol/sdk": "^1.0.0",
68
- "ccusage": "^20.0.17",
69
- "tokscale": "^4.5.2"
85
+ "@sigrank/cascade": "^0.1.1",
86
+ "ccusage": "^20.0.19",
87
+ "tokscale": "^4.7.0"
88
+ },
89
+ "devDependencies": {
90
+ "@resvg/resvg-js": "^2.6.2",
91
+ "gifenc": "^1.0.3"
92
+ },
93
+ "overrides": {
94
+ "@hono/node-server": "^2.0.5"
70
95
  }
71
96
  }