sigrank 0.0.193 → 0.0.195

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.
@@ -904,38 +904,104 @@ export const otherAdapter = {
904
904
  // File cap: a real omp tree runs to 20k+ transcripts, past walkFiles' 10_000 default,
905
905
  // which would silently drop half the operator's tokens — pass an explicit cap.
906
906
  const OMP_MAX_FILES = 500_000;
907
+
908
+ // ── Stage 1: bounded-concurrency + line-guard optimization ───────────────────
909
+ // Based on George-RD's measured findings (PR #32 review comment):
910
+ // - CPU-bound, not disk-bound: 90.5% of one thread, tree fully page-cached
911
+ // - c=8 is optimal: 45.06s → 34.92s (1.29x), regresses at c12/c16
912
+ // - Line guard MUST pass both "message" AND "session" lines, or it breaks
913
+ // the (session_id, entry_id) dedup key — session header lines carry the id
914
+ // - Two-term guard: 0 false negatives across 2.18M lines
915
+ // - All 504 duplicate keys carry byte-identical pillar tuples → order-safe
916
+ // - Only ~1-3% end-to-end win from the guard alone (omp's skippable lines are
917
+ // 19.8% of lines but 15.3% of bytes); the guard costs 1.2s but saves parse
918
+ // on lines that can't contain usage. Folded into concurrency, not standalone.
919
+ const OMP_CONCURRENCY = 8;
920
+
921
+ /** Check if a line could contain an omp usage record or session header.
922
+ * Conservative: false positives are fine, false negatives are not.
923
+ * Must match both "message" (usage) and "session" (dedup key) lines. */
924
+ function ompLineCouldMatter(line) {
925
+ // Fast substring check before JSON.parse — rejects ~20% of lines that are
926
+ // neither message nor session entries (tool calls, reasoning, etc.)
927
+ return line.includes('"message"') || line.includes('"session"');
928
+ }
929
+
930
+ /** Parse a single omp transcript file and yield usage records.
931
+ * Isolated so it can be called concurrently from the bounded pool. */
932
+ function* parseOmpFile(text, path) {
933
+ if (!text) return;
934
+ let sid = null;
935
+ for (const line of text.split("\n")) {
936
+ const s = line.trim();
937
+ if (!s) continue;
938
+ // Line guard: skip lines that can't be message or session entries.
939
+ // George-RD verified 0 false negatives across 2.18M lines with this guard.
940
+ if (!ompLineCouldMatter(s)) continue;
941
+ let ev;
942
+ try {
943
+ ev = JSON.parse(s);
944
+ } catch {
945
+ continue;
946
+ }
947
+ if (!ev) continue;
948
+ if (ev.type === "session") {
949
+ sid = ev.id || null;
950
+ continue;
951
+ }
952
+ if (ev.type !== "message") continue;
953
+ const u = ev.message && ev.message.usage;
954
+ if (!u) continue;
955
+ const input = Number(u.input || 0);
956
+ const output = Number(u.output || 0); // reasoningTokens ALREADY inside — TRAP 1
957
+ const cacheCreate = Number(u.cacheWrite || 0);
958
+ const cacheRead = Number(u.cacheRead || 0);
959
+ if (input + output + cacheCreate + cacheRead === 0) continue;
960
+ yield {
961
+ id: ev.id || null,
962
+ sid,
963
+ ts: ev.timestamp || null,
964
+ input,
965
+ output,
966
+ cacheCreate,
967
+ cacheRead,
968
+ file: path,
969
+ };
970
+ }
971
+ }
972
+
907
973
  export const ompAdapter = {
908
974
  platform: "omp",
909
975
  defaultRoot: () => join(homedir(), ".omp", "agent", "sessions"),
910
976
  async *messages(root) {
911
977
  for (const r of roots("OMP_DATA_DIR", root)) {
978
+ // Collect all file paths first (readdir is 0.8% of scan time — negligible)
979
+ const paths = [];
912
980
  for await (const path of walkFiles(r, isJsonl, { n: 0 }, OMP_MAX_FILES)) {
913
- const text = await readUtf8(path);
914
- let sid = null;
915
- for (const [ev] of parseJsonl(text, path)) {
916
- if (!ev) continue;
917
- if (ev.type === "session") {
918
- sid = ev.id || null; // line-2 header: session half of the dedup key
919
- continue;
920
- }
921
- if (ev.type !== "message") continue;
922
- const u = ev.message && ev.message.usage;
923
- if (!u) continue;
924
- const input = Number(u.input || 0);
925
- const output = Number(u.output || 0); // reasoningTokens ALREADY inside — TRAP 1
926
- const cacheCreate = Number(u.cacheWrite || 0);
927
- const cacheRead = Number(u.cacheRead || 0);
928
- if (input + output + cacheCreate + cacheRead === 0) continue;
929
- yield {
930
- id: ev.id || null,
931
- sid,
932
- ts: ev.timestamp || null,
933
- input,
934
- output,
935
- cacheCreate,
936
- cacheRead,
937
- file: path,
938
- };
981
+ paths.push(path);
982
+ }
983
+
984
+ // Bounded-concurrency read+parse: George-RD measured c=8 as optimal
985
+ // (1.29x speedup, 45s → 35s). Order-safe: all 504 duplicate keys carry
986
+ // byte-identical pillar tuples, so completion order can't change totals.
987
+ const concurrency = OMP_CONCURRENCY;
988
+ const inflight = new Set();
989
+ const queue = [...paths];
990
+
991
+ while (queue.length > 0 || inflight.size > 0) {
992
+ while (inflight.size < concurrency && queue.length > 0) {
993
+ const path = queue.shift();
994
+ const p = (async () => {
995
+ const text = await readUtf8(path);
996
+ return { path, records: [...parseOmpFile(text, path)] };
997
+ })();
998
+ inflight.add(p);
999
+ p.finally(() => inflight.delete(p));
1000
+ }
1001
+ const done = await Promise.race(inflight);
1002
+ inflight.delete(done);
1003
+ for (const record of done.records) {
1004
+ yield record;
939
1005
  }
940
1006
  }
941
1007
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigrank",
3
- "version": "0.0.193",
3
+ "version": "0.0.195",
4
4
  "author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
5
5
  "description": "SigRank MCP server — the yield cascade + live leaderboard as MCP tools any agent can call",
6
6
  "mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
@@ -29,6 +29,25 @@ import { LEADERBOARD_METRIC } from "../lib/constants.mjs";
29
29
  // matches cli.mjs; never hardcode, that's what caused the version drift).
30
30
  const VERSION = pkgVersion();
31
31
 
32
+ // Expensive platform readers that the Dashboard only runs on explicit user
33
+ // request. They remain in ALL_PLATFORMS and are still available to me, watch,
34
+ // Compare, and MCP tool calls when the caller explicitly selects the platform.
35
+ export const DASHBOARD_MANUAL_PLATFORMS = Object.freeze(["omp"]);
36
+ export function dashboardAutoPlatforms(platforms = ALL_PLATFORMS) {
37
+ return platforms.filter((p) => !DASHBOARD_MANUAL_PLATFORMS.includes(p));
38
+ }
39
+ export function dashboardEnabledPlatforms({
40
+ includeManual = false,
41
+ platforms = ALL_PLATFORMS,
42
+ } = {}) {
43
+ const automatic = dashboardAutoPlatforms(platforms);
44
+ if (!includeManual) return automatic;
45
+ return [
46
+ ...automatic,
47
+ ...DASHBOARD_MANUAL_PLATFORMS.filter((p) => platforms.includes(p)),
48
+ ];
49
+ }
50
+
32
51
  // ── ANSI ───────────────────────────────────────────────────────────────────
33
52
  const ESC = "\x1b[";
34
53
  const c = {
@@ -299,7 +318,7 @@ function sparkline(values) {
299
318
  // (tools.mjs keeps its own `_ccusagePillars`/`_tokscalePillars`/`_tokenDashPillars` —
300
319
  // still used by the `tokenpull_compare` MCP tool.)
301
320
 
302
- async function loadDashboardData() {
321
+ async function loadDashboardData({ includeOmp = false } = {}) {
303
322
  const boardPromise = Promise.race([
304
323
  fetch(`${DEFAULT_API_BASE}/api/v1/leaderboard?window=30d&metric=${LEADERBOARD_METRIC}`, {
305
324
  headers: { accept: "application/json" },
@@ -324,25 +343,55 @@ async function loadDashboardData() {
324
343
  tdPillars: null,
325
344
  boardData,
326
345
  _loading: true,
327
- _remaining: ALL_PLATFORMS.filter((p) => p !== "claude"),
346
+ _remaining: dashboardEnabledPlatforms({
347
+ includeManual: includeOmp,
348
+ }).filter((p) => p !== "claude"),
349
+ _ompAvailable: true,
350
+ _ompEnabled: includeOmp,
351
+ _ompLoading: false,
352
+ _ompScanned: false,
328
353
  };
329
354
  }
330
355
 
331
- /** FIX 0: fill the remaining platforms after the primary render. Mutates dashData.active. */
332
- async function fillDashboardRest(dashData) {
356
+ const dashboardPlatformRank = (p) =>
357
+ p === "claude" ? -2 : p === "codex" ? -1 : ALL_PLATFORMS.indexOf(p);
358
+
359
+ async function pullDashboardPlatform(dashData, platform) {
360
+ try {
361
+ const active = await pullActivePlatforms({ platforms: [platform] });
362
+ for (const d of active) {
363
+ if (!dashData.active.find((a) => a.platform === d.platform))
364
+ dashData.active.push(d);
365
+ }
366
+ dashData.active.sort(
367
+ (a, b) =>
368
+ dashboardPlatformRank(a.platform) - dashboardPlatformRank(b.platform),
369
+ );
370
+ return { ok: true, found: active.some((d) => d.platform === platform) };
371
+ } catch {
372
+ return { ok: false, found: false };
373
+ }
374
+ }
375
+
376
+ /**
377
+ * FIX 0: fill the remaining platforms after the primary render. Mutates
378
+ * dashData.active and calls onPlatformLoaded after each individual scan so the
379
+ * TUI can paint rows progressively.
380
+ */
381
+ async function fillDashboardRest(
382
+ dashData,
383
+ { shouldLoadPlatform, onPlatformStarted, onPlatformLoaded } = {},
384
+ ) {
333
385
  if (!dashData?._remaining) return false;
334
- // Unify: fill the remaining platforms through the SAME shared loader.
335
- const rest = await pullActivePlatforms({
336
- platforms: dashData._remaining,
337
- }).catch(() => []);
338
- for (const d of rest) {
339
- if (!dashData.active.find((a) => a.platform === d.platform))
340
- dashData.active.push(d);
386
+ // Pull one platform at a time. Some adapters scan large session trees, so
387
+ // waiting for the entire batch would prevent the dashboard from updating
388
+ // while the slowest platform is still being read.
389
+ for (const platform of dashData._remaining) {
390
+ if (shouldLoadPlatform && !shouldLoadPlatform(platform)) continue;
391
+ await onPlatformStarted?.(platform);
392
+ const result = await pullDashboardPlatform(dashData, platform);
393
+ await onPlatformLoaded?.(platform, result);
341
394
  }
342
- // stable display order: claude → codex → rest (matches the shared loader)
343
- const rank = (p) =>
344
- p === "claude" ? -2 : p === "codex" ? -1 : ALL_PLATFORMS.indexOf(p);
345
- dashData.active.sort((a, b) => rank(a.platform) - rank(b.platform));
346
395
  dashData._loading = false;
347
396
  dashData._remaining = null;
348
397
  return true;
@@ -647,7 +696,7 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
647
696
  // Count is derived from the registry — a hardcoded number drifts every time an
648
697
  // adapter is added (it read "14 platforms" while ALL_PLATFORMS held 17).
649
698
  emit(
650
- ` ${dim(` reading token logs… (${ALL_PLATFORMS.length} platforms, ~5s · press [R] to refresh)`)}`,
699
+ ` ${dim(` reading token logs… (${dashboardAutoPlatforms().length} automatic · omp is manual [O])`)}`,
651
700
  );
652
701
  }
653
702
 
@@ -1952,6 +2001,7 @@ export async function runTui({
1952
2001
  let submitPreview = false; // FIX I1: [S] opens a preview grid before sending (see→confirm→send)
1953
2002
  let boardYouOnly = false; // FIX I2: [Y] toggles the Board to "just me" (hybrid model)
1954
2003
  let cascadeScroll = 0; // SCROLL-VIEW (2026-06-27): cascade-section scroll offset (Dashboard only)
2004
+ let ompEnabled = false; // Session-only Dashboard toggle; deliberately OFF on every launch.
1955
2005
 
1956
2006
  // ── Redraw (buffered: renders into memory, then paints as a locked frame)
1957
2007
  const redraw = async () => {
@@ -1993,7 +2043,12 @@ export async function runTui({
1993
2043
  ? ` ${dim("↑↓ scroll cascade")}`
1994
2044
  : ""
1995
2045
  : "";
1996
- setFooter(readFooter(`${hint}${scrollHint}${submitHint}`));
2046
+ const ompHint = !dashData?._ompAvailable
2047
+ ? ""
2048
+ : dashData._ompLoading
2049
+ ? ` ${dim(ompEnabled ? "omp: on · scanning…" : "omp: off · finishing scan…")}`
2050
+ : ` ${dim("[O]")} omp: ${ompEnabled ? "on" : "off"}`;
2051
+ setFooter(readFooter(`${hint}${scrollHint}${ompHint}${submitHint}`));
1997
2052
  } else if (activeTab === 1) {
1998
2053
  // Trends
1999
2054
  if (!dashData) {
@@ -2072,7 +2127,7 @@ export async function runTui({
2072
2127
  // FIX 0: progressive Dashboard load — claude first (fast), render it, THEN fill
2073
2128
  // the remaining ALL_PLATFORMS entries in the background + redraw. The user sees their cascade
2074
2129
  // within ~1 read instead of a 7s blank "loading dashboard…".
2075
- dashData = await loadDashboardData().catch((e) => {
2130
+ dashData = await loadDashboardData({ includeOmp: ompEnabled }).catch((e) => {
2076
2131
  status = `dashboard error: ${e.message}`;
2077
2132
  return null;
2078
2133
  });
@@ -2089,7 +2144,40 @@ export async function runTui({
2089
2144
  }
2090
2145
  [, boardData] = await Promise.all([
2091
2146
  dashData?._remaining
2092
- ? fillDashboardRest(dashData)
2147
+ ? fillDashboardRest(dashData, {
2148
+ shouldLoadPlatform: (platform) =>
2149
+ platform !== "omp" || ompEnabled,
2150
+ onPlatformStarted: async (platform) => {
2151
+ if (platform !== "omp") return;
2152
+ dashData._ompLoading = true;
2153
+ status =
2154
+ "omp is on · scanning history… large histories may take tens of seconds";
2155
+ await redraw();
2156
+ },
2157
+ onPlatformLoaded: async (platform, result) => {
2158
+ if (platform === "omp") {
2159
+ dashData._ompLoading = false;
2160
+ dashData._ompScanned = result.ok && ompEnabled;
2161
+ if (!ompEnabled) {
2162
+ dashData.active = dashData.active.filter(
2163
+ (d) => d.platform !== "omp",
2164
+ );
2165
+ status = "omp: off · completed scan result discarded";
2166
+ } else if (!result.ok) {
2167
+ status = "omp scan failed · switch [O] off, then on to retry";
2168
+ } else if (!result.found) {
2169
+ status = "omp: on · scan complete · no usage data found";
2170
+ } else {
2171
+ status = "omp: on · scan complete";
2172
+ }
2173
+ } else {
2174
+ status = dashData._ompLoading
2175
+ ? `${platform} ready · omp scanning…`
2176
+ : `claude ready · ${platform} ready · filling other platforms…`;
2177
+ }
2178
+ await redraw();
2179
+ },
2180
+ })
2093
2181
  : Promise.resolve(false),
2094
2182
  loadBoardData(boardWindow).catch(() => null),
2095
2183
  ]);
@@ -2349,6 +2437,64 @@ export async function runTui({
2349
2437
  return; // swallow all other keys while the preview is open
2350
2438
  }
2351
2439
 
2440
+ // Session-only omp toggle. OFF removes the row and excludes omp from later
2441
+ // refreshes; ON scans immediately and includes it until the TUI exits.
2442
+ if (activeTab === 0 && k === "o" && dashData?._ompAvailable) {
2443
+ // A filesystem traversal already in progress cannot be cancelled safely.
2444
+ // Switching OFF marks its result for discard; switching back ON waits
2445
+ // until that traversal finishes so two 10 GiB scans never overlap.
2446
+ if (dashData._ompLoading) {
2447
+ if (ompEnabled) {
2448
+ ompEnabled = false;
2449
+ dashData._ompEnabled = false;
2450
+ status = "omp: off · current scan will be discarded when it finishes";
2451
+ } else {
2452
+ status = "omp: off · waiting for the current scan to finish";
2453
+ }
2454
+ await redraw();
2455
+ return;
2456
+ }
2457
+
2458
+ ompEnabled = !ompEnabled;
2459
+ dashData._ompEnabled = ompEnabled;
2460
+ if (!ompEnabled) {
2461
+ dashData.active = dashData.active.filter((d) => d.platform !== "omp");
2462
+ dashData._ompScanned = false;
2463
+ status = "omp: off · excluded from Dashboard refreshes";
2464
+ await redraw();
2465
+ return;
2466
+ }
2467
+
2468
+ const targetDashData = dashData;
2469
+ targetDashData._ompLoading = true;
2470
+ status =
2471
+ "omp: on · scanning history… large histories may take tens of seconds";
2472
+ await redraw();
2473
+
2474
+ const result = await pullDashboardPlatform(targetDashData, "omp");
2475
+ targetDashData._ompLoading = false;
2476
+ targetDashData._ompScanned = result.ok && ompEnabled;
2477
+
2478
+ if (!ompEnabled) {
2479
+ targetDashData.active = targetDashData.active.filter(
2480
+ (d) => d.platform !== "omp",
2481
+ );
2482
+ }
2483
+
2484
+ // [R] may replace dashData while this scan is running. Do not let the
2485
+ // result or status from that stale Dashboard generation overwrite it.
2486
+ if (dashData !== targetDashData) return;
2487
+ status = !ompEnabled
2488
+ ? "omp: off · completed scan result discarded"
2489
+ : !result.ok
2490
+ ? "omp scan failed · switch [O] off, then on to retry"
2491
+ : result.found
2492
+ ? `omp: on · loaded ${new Date().toLocaleTimeString("en-US", { hour12: false })}`
2493
+ : "omp: on · scan complete · no usage data found";
2494
+ await redraw();
2495
+ return;
2496
+ }
2497
+
2352
2498
  // ESC → go back to Dashboard from any tab
2353
2499
  if (key === "\x1b" && activeTab !== 0) {
2354
2500
  activeTab = 0;
@@ -2558,6 +2704,11 @@ export async function runTui({
2558
2704
  }
2559
2705
 
2560
2706
  if (k === "r") {
2707
+ if (dashData?._ompLoading) {
2708
+ status = "omp scan already in progress · refresh is available when it finishes";
2709
+ await redraw();
2710
+ return;
2711
+ }
2561
2712
  status = "refreshing…";
2562
2713
  cascadeScroll = 0; // SCROLL-VIEW: reset scroll on refresh
2563
2714
  // #9: on the Compare tab, [R] re-runs the FRESH verifier pull for the current platform