sigrank 0.0.194 → 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.
Files changed (2) hide show
  1. package/adapters/index.mjs +92 -26
  2. package/package.json +1 -1
@@ -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.194",
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",