@reposkein/mcp 0.3.0 → 0.4.0

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 (98) hide show
  1. package/README.md +73 -1
  2. package/binary-digests.json +4 -4
  3. package/dist/SKILL.md +19 -0
  4. package/dist/cli/agentAdapters.d.ts +68 -0
  5. package/dist/cli/agentAdapters.js +223 -0
  6. package/dist/cli/agentAdapters.js.map +1 -0
  7. package/dist/cli/ansi.d.ts +24 -0
  8. package/dist/cli/ansi.js +40 -0
  9. package/dist/cli/ansi.js.map +1 -0
  10. package/dist/cli/doctor.d.ts +34 -2
  11. package/dist/cli/doctor.js +51 -6
  12. package/dist/cli/doctor.js.map +1 -1
  13. package/dist/cli/doctorFreshness.d.ts +43 -0
  14. package/dist/cli/doctorFreshness.js +111 -0
  15. package/dist/cli/doctorFreshness.js.map +1 -0
  16. package/dist/cli/doctorSummaries.d.ts +2 -0
  17. package/dist/cli/doctorSummaries.js +95 -0
  18. package/dist/cli/doctorSummaries.js.map +1 -0
  19. package/dist/cli/federatedDiscovery.d.ts +16 -0
  20. package/dist/cli/federatedDiscovery.js +75 -0
  21. package/dist/cli/federatedDiscovery.js.map +1 -0
  22. package/dist/cli/init.d.ts +69 -6
  23. package/dist/cli/init.js +194 -28
  24. package/dist/cli/init.js.map +1 -1
  25. package/dist/cli/sourceSlices.d.ts +17 -0
  26. package/dist/cli/sourceSlices.js +59 -0
  27. package/dist/cli/sourceSlices.js.map +1 -0
  28. package/dist/cli/stats.d.ts +29 -0
  29. package/dist/cli/stats.js +180 -0
  30. package/dist/cli/stats.js.map +1 -0
  31. package/dist/cli/view.d.ts +71 -3
  32. package/dist/cli/view.js +147 -12
  33. package/dist/cli/view.js.map +1 -1
  34. package/dist/index.d.ts +8 -0
  35. package/dist/index.js +286 -123
  36. package/dist/index.js.map +1 -1
  37. package/dist/indexer/fetchBinary.d.ts +14 -0
  38. package/dist/indexer/fetchBinary.js +81 -40
  39. package/dist/indexer/fetchBinary.js.map +1 -1
  40. package/dist/indexer/runIndexer.d.ts +0 -2
  41. package/dist/indexer/runIndexer.js +8 -4
  42. package/dist/indexer/runIndexer.js.map +1 -1
  43. package/dist/store/JsonlGraphStore.d.ts +13 -3
  44. package/dist/store/JsonlGraphStore.js +55 -17
  45. package/dist/store/JsonlGraphStore.js.map +1 -1
  46. package/dist/store/decisions.d.ts +20 -2
  47. package/dist/store/decisions.js +35 -2
  48. package/dist/store/decisions.js.map +1 -1
  49. package/dist/store/indexedAt.d.ts +18 -0
  50. package/dist/store/indexedAt.js +56 -0
  51. package/dist/store/indexedAt.js.map +1 -0
  52. package/dist/store/instrumentTool.d.ts +40 -0
  53. package/dist/store/instrumentTool.js +82 -0
  54. package/dist/store/instrumentTool.js.map +1 -0
  55. package/dist/store/repoContextCache.d.ts +15 -0
  56. package/dist/store/repoContextCache.js +26 -0
  57. package/dist/store/repoContextCache.js.map +1 -0
  58. package/dist/store/repoSession.d.ts +63 -0
  59. package/dist/store/repoSession.js +123 -0
  60. package/dist/store/repoSession.js.map +1 -0
  61. package/dist/store/resolveRepoPath.d.ts +31 -0
  62. package/dist/store/resolveRepoPath.js +85 -0
  63. package/dist/store/resolveRepoPath.js.map +1 -0
  64. package/dist/store/sessionLog.d.ts +98 -0
  65. package/dist/store/sessionLog.js +216 -0
  66. package/dist/store/sessionLog.js.map +1 -0
  67. package/dist/store/sessionStats.d.ts +55 -0
  68. package/dist/store/sessionStats.js +140 -0
  69. package/dist/store/sessionStats.js.map +1 -0
  70. package/dist/store/sidecar.d.ts +39 -3
  71. package/dist/store/sidecar.js +89 -5
  72. package/dist/store/sidecar.js.map +1 -1
  73. package/dist/store/summaryShards.d.ts +117 -0
  74. package/dist/store/summaryShards.js +291 -0
  75. package/dist/store/summaryShards.js.map +1 -0
  76. package/dist/store/teamConfig.d.ts +13 -0
  77. package/dist/store/teamConfig.js +49 -0
  78. package/dist/store/teamConfig.js.map +1 -0
  79. package/dist/tools/indexerTools.d.ts +14 -2
  80. package/dist/tools/indexerTools.js +17 -5
  81. package/dist/tools/indexerTools.js.map +1 -1
  82. package/dist/tools/recordDecision.js +6 -1
  83. package/dist/tools/recordDecision.js.map +1 -1
  84. package/dist/viz/assets/{graph.worker-CC7BeE-8.js → graph.worker-DkH6-lMU.js} +1 -1
  85. package/dist/viz/assets/index-CxqeGuRS.css +1 -0
  86. package/dist/viz/assets/index-DiNZyrXx.js +9 -0
  87. package/dist/viz/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  88. package/dist/viz/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  89. package/dist/viz/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
  90. package/dist/viz/assets/r3f-BZY3QR6_.js +336 -0
  91. package/dist/viz/assets/tanstack-CjXr-L-s.js +62 -0
  92. package/dist/viz/assets/three-Cpu5RUEM.js +4116 -0
  93. package/dist/viz/index.html +5 -4
  94. package/package.json +5 -4
  95. package/dist/viz/assets/index-C_8UkiJ8.js +0 -9
  96. package/dist/viz/assets/r3f-Cku1OhB-.js +0 -277
  97. package/dist/viz/assets/tanstack-DyXcNRWk.js +0 -77
  98. package/dist/viz/assets/three-Z8XaLGmf.js +0 -3839
@@ -0,0 +1,98 @@
1
+ /** One JSONL record per tool invocation, appended to the active repo's
2
+ * `.reposkein/local/sessions/<session-id>.jsonl` (REP-20). Never contains
3
+ * argument or result VALUES — only shapes/sizes — so it's safe to leave
4
+ * lying around on disk (though it's git-ignored regardless, see
5
+ * `.reposkein/.gitignore` written by the indexer: `local/`). */
6
+ export interface SessionLogRecord {
7
+ /** ISO-8601 timestamp of the call. */
8
+ ts: string;
9
+ /** MCP tool name, e.g. "get_context_profile". */
10
+ tool: string;
11
+ /** Argument KEY NAMES only, sorted — never values (may contain code/user text). */
12
+ argsShape: string[];
13
+ /** Byte size of the text content returned to the agent. */
14
+ resultBytes: number;
15
+ /** Node ids / file paths the call touched, when the result exposes them
16
+ * (best-effort — see `extractTouchedIds`). Omitted when none found. */
17
+ nodeIds?: string[];
18
+ /** False when the handler threw or returned `isError: true`. */
19
+ ok: boolean;
20
+ }
21
+ export interface ToolResultLike {
22
+ content?: Array<{
23
+ type: string;
24
+ text?: string;
25
+ }>;
26
+ isError?: boolean;
27
+ }
28
+ /** `<repoPath>/.reposkein/local/sessions` — where per-session JSONL logs
29
+ * live for this repo. Git-ignored via `.reposkein/.gitignore`'s `local/`
30
+ * entry (written by the indexer at index time). */
31
+ export declare function sessionsDir(repoPath: string): string;
32
+ export declare function sessionLogPath(repoPath: string, sessionId: string): string;
33
+ /** Session id = server process lifetime: start-timestamp + pid. Filesystem-
34
+ * safe (no colons) and lexicographically sortable = chronological. */
35
+ export declare function defaultSessionId(now?: Date, pid?: number): string;
36
+ /** Resolves this process's session id: `REPOSKEIN_SESSION_ID` override, else
37
+ * `defaultSessionId()`. Computed once per server process and reused for
38
+ * every call and every repo it touches during the connection's lifetime. */
39
+ export declare function resolveSessionId(env?: NodeJS.ProcessEnv, now?: Date, pid?: number): string;
40
+ /** Appends one record. Best-effort: a write failure (unwritable dir, full
41
+ * disk, permission error) is swallowed — logging must never fail or slow a
42
+ * tool call. Creates `local/sessions/` if needed. */
43
+ export declare function appendSessionLog(repoPath: string, sessionId: string, record: SessionLogRecord): void;
44
+ export interface PruneOptions {
45
+ /** Keep at most this many session files (newest first, by mtime). */
46
+ keep: number;
47
+ /** Drop any session file older than this many days, even if under `keep`. */
48
+ maxAgeDays: number;
49
+ }
50
+ export declare const DEFAULT_RETENTION: PruneOptions;
51
+ /** Prunes `local/sessions/` for one repo: a file survives only if it is both
52
+ * among the `keep` most-recently-modified files AND newer than
53
+ * `maxAgeDays`. Deleting one file's read/stat failure never aborts the
54
+ * sweep. Silently no-ops if the dir doesn't exist or isn't readable —
55
+ * pruning must never fail server startup. */
56
+ export declare function pruneSessions(repoPath: string, opts?: PruneOptions, now?: number): void;
57
+ /** Best-effort extraction of node ids / file paths a tool call touched, from
58
+ * its JSON-text result content — used for the "top queried nodes/files"
59
+ * stat. Generic (one implementation for all tools, not per-tool logic):
60
+ * parses each text block as JSON and walks it for a small allowlist of
61
+ * id-shaped keys (`node_id`, `id`, `decision_id`, `node_ids`,
62
+ * `anchor_node_ids`, `file_path`, `path`). Non-JSON or unparseable content
63
+ * contributes nothing — never throws. */
64
+ export declare function extractTouchedIds(result: ToolResultLike | undefined): string[];
65
+ /** Byte size of the text an agent would actually read from a tool result
66
+ * (sum of `content[].text`), the basis for "context tokens saved". Falls
67
+ * back to the full JSON envelope size only when there's no text content AT
68
+ * ALL (no `content` array, or no block of type "text") — a result whose
69
+ * text blocks exist but are empty strings correctly reports 0, it does not
70
+ * fall back to stringifying the envelope. */
71
+ export declare function resultByteSize(result: ToolResultLike | undefined): number;
72
+ /** Argument KEY NAMES only, sorted, never values. */
73
+ export declare function argsShapeOf(args: unknown): string[];
74
+ /** The single per-process instrumentation sink: one instance is created in
75
+ * `main()` and shared by the tool-dispatch wrapper for every registered
76
+ * tool (see `createToolLogger`/`withLog` in instrumentTool.ts) — the one
77
+ * place tool calls get logged, so individual tool handlers never contain
78
+ * logging code.
79
+ *
80
+ * Retention is enforced lazily, per repo, the first time this process logs
81
+ * a call for that repo — equivalent to "prune on server start" for the
82
+ * common single-repo case, and the only sound definition of "start" once a
83
+ * session can touch more than one repo (zero-config workspace mode). */
84
+ export declare class SessionLogger {
85
+ private readonly sessionId;
86
+ private readonly retention;
87
+ private readonly prunedRepos;
88
+ constructor(sessionId: string, retention?: PruneOptions);
89
+ getSessionId(): string;
90
+ log(repoPath: string, entry: Omit<SessionLogRecord, "ts">): void;
91
+ /** Same as `log`, but with `ts` supplied by the caller instead of stamped
92
+ * here — for the deferred-write path (see `instrumentTool.ts`), where the
93
+ * call's completion time is captured synchronously on the hot path and
94
+ * the actual write happens later (setImmediate); using that captured
95
+ * `ts` keeps the record's timestamp accurate to when the call finished,
96
+ * not to whenever the deferred write got scheduled. */
97
+ logAt(ts: string, repoPath: string, entry: Omit<SessionLogRecord, "ts">): void;
98
+ }
@@ -0,0 +1,216 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /** `<repoPath>/.reposkein/local/sessions` — where per-session JSONL logs
4
+ * live for this repo. Git-ignored via `.reposkein/.gitignore`'s `local/`
5
+ * entry (written by the indexer at index time). */
6
+ export function sessionsDir(repoPath) {
7
+ return join(repoPath, ".reposkein", "local", "sessions");
8
+ }
9
+ export function sessionLogPath(repoPath, sessionId) {
10
+ return join(sessionsDir(repoPath), `${sessionId}.jsonl`);
11
+ }
12
+ function pad(n, len = 2) {
13
+ return String(n).padStart(len, "0");
14
+ }
15
+ /** Session id = server process lifetime: start-timestamp + pid. Filesystem-
16
+ * safe (no colons) and lexicographically sortable = chronological. */
17
+ export function defaultSessionId(now = new Date(), pid = process.pid) {
18
+ const stamp = `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}` +
19
+ `T${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}Z`;
20
+ return `${stamp}-${pid}`;
21
+ }
22
+ /** Resolves this process's session id: `REPOSKEIN_SESSION_ID` override, else
23
+ * `defaultSessionId()`. Computed once per server process and reused for
24
+ * every call and every repo it touches during the connection's lifetime. */
25
+ export function resolveSessionId(env = process.env, now = new Date(), pid = process.pid) {
26
+ const override = env.REPOSKEIN_SESSION_ID?.trim();
27
+ return override ? override : defaultSessionId(now, pid);
28
+ }
29
+ /** Appends one record. Best-effort: a write failure (unwritable dir, full
30
+ * disk, permission error) is swallowed — logging must never fail or slow a
31
+ * tool call. Creates `local/sessions/` if needed. */
32
+ export function appendSessionLog(repoPath, sessionId, record) {
33
+ try {
34
+ mkdirSync(sessionsDir(repoPath), { recursive: true });
35
+ appendFileSync(sessionLogPath(repoPath, sessionId), JSON.stringify(record) + "\n", "utf8");
36
+ }
37
+ catch {
38
+ // best-effort — see doc comment
39
+ }
40
+ }
41
+ export const DEFAULT_RETENTION = { keep: 50, maxAgeDays: 30 };
42
+ /** Prunes `local/sessions/` for one repo: a file survives only if it is both
43
+ * among the `keep` most-recently-modified files AND newer than
44
+ * `maxAgeDays`. Deleting one file's read/stat failure never aborts the
45
+ * sweep. Silently no-ops if the dir doesn't exist or isn't readable —
46
+ * pruning must never fail server startup. */
47
+ export function pruneSessions(repoPath, opts = DEFAULT_RETENTION, now = Date.now()) {
48
+ try {
49
+ const dir = sessionsDir(repoPath);
50
+ if (!existsSync(dir))
51
+ return;
52
+ const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
53
+ const withStats = files
54
+ .map((f) => {
55
+ const path = join(dir, f);
56
+ try {
57
+ return { path, mtimeMs: statSync(path).mtimeMs };
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ })
63
+ .filter((x) => x !== null)
64
+ .sort((a, b) => b.mtimeMs - a.mtimeMs); // newest first
65
+ const maxAgeMs = opts.maxAgeDays * 24 * 60 * 60 * 1000;
66
+ withStats.forEach((entry, idx) => {
67
+ const tooOld = now - entry.mtimeMs > maxAgeMs;
68
+ const overCount = idx >= opts.keep;
69
+ if (tooOld || overCount) {
70
+ try {
71
+ unlinkSync(entry.path);
72
+ }
73
+ catch {
74
+ // best-effort
75
+ }
76
+ }
77
+ });
78
+ }
79
+ catch {
80
+ // best-effort — see doc comment
81
+ }
82
+ }
83
+ /** Keys whose STRING value is treated as an id/path a tool call touched. */
84
+ const ID_KEYS = new Set(["node_id", "id", "decision_id"]);
85
+ /** Keys whose ARRAY value holds ids/paths (each string element collected). */
86
+ const ID_ARRAY_KEYS = new Set(["node_ids", "anchor_node_ids"]);
87
+ /** Keys whose STRING value is a file path a tool call touched. */
88
+ const PATH_KEYS = new Set(["file_path", "path"]);
89
+ const MAX_TOUCHED_IDS = 25;
90
+ const MAX_WALK_DEPTH = 6;
91
+ function walkForIds(value, out, depth) {
92
+ if (out.size >= MAX_TOUCHED_IDS || depth > MAX_WALK_DEPTH || value == null)
93
+ return;
94
+ if (Array.isArray(value)) {
95
+ for (const v of value)
96
+ walkForIds(v, out, depth + 1);
97
+ return;
98
+ }
99
+ if (typeof value !== "object")
100
+ return;
101
+ for (const [key, v] of Object.entries(value)) {
102
+ if (out.size >= MAX_TOUCHED_IDS)
103
+ return;
104
+ if ((ID_KEYS.has(key) || PATH_KEYS.has(key)) && typeof v === "string") {
105
+ out.add(v);
106
+ }
107
+ else if (ID_ARRAY_KEYS.has(key) && Array.isArray(v)) {
108
+ for (const item of v) {
109
+ if (out.size >= MAX_TOUCHED_IDS)
110
+ break;
111
+ if (typeof item === "string")
112
+ out.add(item);
113
+ }
114
+ }
115
+ else {
116
+ walkForIds(v, out, depth + 1);
117
+ }
118
+ }
119
+ }
120
+ /** Best-effort extraction of node ids / file paths a tool call touched, from
121
+ * its JSON-text result content — used for the "top queried nodes/files"
122
+ * stat. Generic (one implementation for all tools, not per-tool logic):
123
+ * parses each text block as JSON and walks it for a small allowlist of
124
+ * id-shaped keys (`node_id`, `id`, `decision_id`, `node_ids`,
125
+ * `anchor_node_ids`, `file_path`, `path`). Non-JSON or unparseable content
126
+ * contributes nothing — never throws. */
127
+ export function extractTouchedIds(result) {
128
+ if (!result)
129
+ return [];
130
+ const found = new Set();
131
+ for (const block of result.content ?? []) {
132
+ if (found.size >= MAX_TOUCHED_IDS)
133
+ break;
134
+ if (block.type !== "text" || typeof block.text !== "string")
135
+ continue;
136
+ let parsed;
137
+ try {
138
+ parsed = JSON.parse(block.text);
139
+ }
140
+ catch {
141
+ continue;
142
+ }
143
+ walkForIds(parsed, found, 0);
144
+ }
145
+ return [...found];
146
+ }
147
+ /** Byte size of the text an agent would actually read from a tool result
148
+ * (sum of `content[].text`), the basis for "context tokens saved". Falls
149
+ * back to the full JSON envelope size only when there's no text content AT
150
+ * ALL (no `content` array, or no block of type "text") — a result whose
151
+ * text blocks exist but are empty strings correctly reports 0, it does not
152
+ * fall back to stringifying the envelope. */
153
+ export function resultByteSize(result) {
154
+ if (!result)
155
+ return 0;
156
+ const blocks = result.content ?? [];
157
+ let bytes = 0;
158
+ let hasTextBlock = false;
159
+ for (const block of blocks) {
160
+ if (block.type === "text" && typeof block.text === "string") {
161
+ hasTextBlock = true;
162
+ bytes += Buffer.byteLength(block.text, "utf8");
163
+ }
164
+ }
165
+ return hasTextBlock ? bytes : Buffer.byteLength(JSON.stringify(result), "utf8");
166
+ }
167
+ /** Argument KEY NAMES only, sorted, never values. */
168
+ export function argsShapeOf(args) {
169
+ if (!args || typeof args !== "object")
170
+ return [];
171
+ return Object.keys(args).sort();
172
+ }
173
+ /** The single per-process instrumentation sink: one instance is created in
174
+ * `main()` and shared by the tool-dispatch wrapper for every registered
175
+ * tool (see `createToolLogger`/`withLog` in instrumentTool.ts) — the one
176
+ * place tool calls get logged, so individual tool handlers never contain
177
+ * logging code.
178
+ *
179
+ * Retention is enforced lazily, per repo, the first time this process logs
180
+ * a call for that repo — equivalent to "prune on server start" for the
181
+ * common single-repo case, and the only sound definition of "start" once a
182
+ * session can touch more than one repo (zero-config workspace mode). */
183
+ export class SessionLogger {
184
+ sessionId;
185
+ retention;
186
+ prunedRepos = new Set();
187
+ constructor(sessionId, retention = DEFAULT_RETENTION) {
188
+ this.sessionId = sessionId;
189
+ this.retention = retention;
190
+ }
191
+ getSessionId() {
192
+ return this.sessionId;
193
+ }
194
+ log(repoPath, entry) {
195
+ this.logAt(new Date().toISOString(), repoPath, entry);
196
+ }
197
+ /** Same as `log`, but with `ts` supplied by the caller instead of stamped
198
+ * here — for the deferred-write path (see `instrumentTool.ts`), where the
199
+ * call's completion time is captured synchronously on the hot path and
200
+ * the actual write happens later (setImmediate); using that captured
201
+ * `ts` keeps the record's timestamp accurate to when the call finished,
202
+ * not to whenever the deferred write got scheduled. */
203
+ logAt(ts, repoPath, entry) {
204
+ try {
205
+ if (!this.prunedRepos.has(repoPath)) {
206
+ this.prunedRepos.add(repoPath);
207
+ pruneSessions(repoPath, this.retention);
208
+ }
209
+ appendSessionLog(repoPath, this.sessionId, { ts, ...entry });
210
+ }
211
+ catch {
212
+ // never fail/slow the tool call — see appendSessionLog's own try/catch too
213
+ }
214
+ }
215
+ }
216
+ //# sourceMappingURL=sessionLog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionLog.js","sourceRoot":"","sources":["../../src/store/sessionLog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { type SessionLogRecord } from "./sessionLog.js";
2
+ /** grep-baseline token-ratio multiplier used for the "estimated context
3
+ * tokens saved" stat. Sourced from `mcp/bench/README.md`'s Track 1 retrieval
4
+ * benchmark ("Mean token ratio on structural tasks: 8.4x (grep / RepoSkein,
5
+ * generous-to-grep)") — a measured, repo-local number, not invented here.
6
+ * Always LABEL output built from this as an estimate: it's one repo's
7
+ * 5-task mean, generalized to every call in a session. */
8
+ export declare const GREP_BASELINE_MULTIPLIER = 8.4;
9
+ export interface SessionSummary {
10
+ sessionId: string;
11
+ /** Absolute path to the session's .jsonl file. */
12
+ file: string;
13
+ recordCount: number;
14
+ callsByTool: Record<string, number>;
15
+ /** Top node ids / file paths touched, most-referenced first. */
16
+ topTouched: Array<{
17
+ id: string;
18
+ count: number;
19
+ }>;
20
+ /** Count of ADRs recorded this session (record_decision calls). */
21
+ decisionsWritten: number;
22
+ /** Count of semantic summaries written this session (write_semantic_summary calls). */
23
+ summariesWritten: number;
24
+ /** ms between the first and last record's `ts`. 0 for a single-record session. */
25
+ durationMs: number;
26
+ totalResultBytes: number;
27
+ /** GREP_BASELINE_MULTIPLIER * totalResultBytes / 4 (≈4 bytes/token), i.e.
28
+ * the estimated token cost an equivalent grep-based approach would have
29
+ * paid for what RepoSkein returned in `totalResultBytes`. Labeled an
30
+ * estimate everywhere it's rendered — see GREP_BASELINE_MULTIPLIER. */
31
+ estimatedGrepTokens: number;
32
+ estimatedRepoSkeinTokens: number;
33
+ estimatedTokensSaved: number;
34
+ failedCalls: number;
35
+ }
36
+ /** Reads and parses one session's JSONL file. Malformed lines are skipped
37
+ * (never thrown) — a session log is best-effort telemetry, not a source of
38
+ * truth that must be pristine. */
39
+ export declare function readSessionLog(file: string): SessionLogRecord[];
40
+ /** Every session file for a repo, sorted newest-first (by mtime). */
41
+ export declare function listSessionFiles(repoPath: string): Array<{
42
+ sessionId: string;
43
+ file: string;
44
+ mtimeMs: number;
45
+ }>;
46
+ /** Aggregates one session's records into the report shape rendered by
47
+ * `reposkein-mcp stats`. Pure function over already-parsed records so it's
48
+ * cheap to unit test independent of the filesystem. */
49
+ export declare function summarizeSession(sessionId: string, file: string, records: SessionLogRecord[]): SessionSummary;
50
+ /** Merges several sessions' records into one combined summary (`--all`) —
51
+ * same aggregation, over the union of records, with a synthetic id. */
52
+ export declare function summarizeSessions(sessions: Array<{
53
+ sessionId: string;
54
+ file: string;
55
+ }>): SessionSummary;
@@ -0,0 +1,140 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { sessionsDir } from "./sessionLog.js";
4
+ /** grep-baseline token-ratio multiplier used for the "estimated context
5
+ * tokens saved" stat. Sourced from `mcp/bench/README.md`'s Track 1 retrieval
6
+ * benchmark ("Mean token ratio on structural tasks: 8.4x (grep / RepoSkein,
7
+ * generous-to-grep)") — a measured, repo-local number, not invented here.
8
+ * Always LABEL output built from this as an estimate: it's one repo's
9
+ * 5-task mean, generalized to every call in a session. */
10
+ export const GREP_BASELINE_MULTIPLIER = 8.4;
11
+ /** Reads and parses one session's JSONL file. Malformed lines are skipped
12
+ * (never thrown) — a session log is best-effort telemetry, not a source of
13
+ * truth that must be pristine. */
14
+ export function readSessionLog(file) {
15
+ if (!existsSync(file))
16
+ return [];
17
+ let text;
18
+ try {
19
+ text = readFileSync(file, "utf8");
20
+ }
21
+ catch {
22
+ return [];
23
+ }
24
+ const out = [];
25
+ for (const line of text.split("\n")) {
26
+ if (line.trim() === "")
27
+ continue;
28
+ try {
29
+ const rec = JSON.parse(line);
30
+ if (typeof rec.tool === "string" && typeof rec.ts === "string") {
31
+ out.push({
32
+ ts: rec.ts,
33
+ tool: rec.tool,
34
+ argsShape: Array.isArray(rec.argsShape) ? rec.argsShape : [],
35
+ resultBytes: typeof rec.resultBytes === "number" ? rec.resultBytes : 0,
36
+ ...(Array.isArray(rec.nodeIds) && rec.nodeIds.length ? { nodeIds: rec.nodeIds } : {}),
37
+ ok: rec.ok !== false,
38
+ });
39
+ }
40
+ }
41
+ catch {
42
+ // skip malformed line
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+ /** Every session file for a repo, sorted newest-first (by mtime). */
48
+ export function listSessionFiles(repoPath) {
49
+ const dir = sessionsDir(repoPath);
50
+ if (!existsSync(dir))
51
+ return [];
52
+ let names;
53
+ try {
54
+ names = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
55
+ }
56
+ catch {
57
+ return [];
58
+ }
59
+ return names
60
+ .map((name) => {
61
+ const file = join(dir, name);
62
+ let mtimeMs = 0;
63
+ try {
64
+ mtimeMs = statSync(file).mtimeMs;
65
+ }
66
+ catch {
67
+ /* keep 0 — still listed, just sorts last */
68
+ }
69
+ return { sessionId: name.replace(/\.jsonl$/, ""), file, mtimeMs };
70
+ })
71
+ .sort((a, b) => b.mtimeMs - a.mtimeMs);
72
+ }
73
+ /** Tools whose calls count as "summaries/ADRs written this session". */
74
+ const DECISION_TOOLS = new Set(["record_decision"]);
75
+ const SUMMARY_TOOLS = new Set(["write_semantic_summary"]);
76
+ /** Aggregates one session's records into the report shape rendered by
77
+ * `reposkein-mcp stats`. Pure function over already-parsed records so it's
78
+ * cheap to unit test independent of the filesystem. */
79
+ export function summarizeSession(sessionId, file, records) {
80
+ const callsByTool = {};
81
+ const touchedCounts = new Map();
82
+ let decisionsWritten = 0;
83
+ let summariesWritten = 0;
84
+ let totalResultBytes = 0;
85
+ let failedCalls = 0;
86
+ let minTs = Infinity;
87
+ let maxTs = -Infinity;
88
+ for (const rec of records) {
89
+ callsByTool[rec.tool] = (callsByTool[rec.tool] ?? 0) + 1;
90
+ totalResultBytes += rec.resultBytes;
91
+ if (!rec.ok)
92
+ failedCalls++;
93
+ if (DECISION_TOOLS.has(rec.tool))
94
+ decisionsWritten++;
95
+ if (SUMMARY_TOOLS.has(rec.tool))
96
+ summariesWritten++;
97
+ for (const id of rec.nodeIds ?? [])
98
+ touchedCounts.set(id, (touchedCounts.get(id) ?? 0) + 1);
99
+ const t = Date.parse(rec.ts);
100
+ if (!Number.isNaN(t)) {
101
+ if (t < minTs)
102
+ minTs = t;
103
+ if (t > maxTs)
104
+ maxTs = t;
105
+ }
106
+ }
107
+ const topTouched = [...touchedCounts.entries()]
108
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
109
+ .slice(0, 10)
110
+ .map(([id, count]) => ({ id, count }));
111
+ const durationMs = Number.isFinite(minTs) && Number.isFinite(maxTs) && maxTs > minTs ? maxTs - minTs : 0;
112
+ // ~4 bytes/token is the common rough estimator (also used by mcp/bench —
113
+ // see its token cost model) — deliberately simple and labeled an estimate.
114
+ const estimatedRepoSkeinTokens = Math.round(totalResultBytes / 4);
115
+ const estimatedGrepTokens = Math.round(estimatedRepoSkeinTokens * GREP_BASELINE_MULTIPLIER);
116
+ const estimatedTokensSaved = estimatedGrepTokens - estimatedRepoSkeinTokens;
117
+ return {
118
+ sessionId,
119
+ file,
120
+ recordCount: records.length,
121
+ callsByTool,
122
+ topTouched,
123
+ decisionsWritten,
124
+ summariesWritten,
125
+ durationMs,
126
+ totalResultBytes,
127
+ estimatedGrepTokens,
128
+ estimatedRepoSkeinTokens,
129
+ estimatedTokensSaved,
130
+ failedCalls,
131
+ };
132
+ }
133
+ /** Merges several sessions' records into one combined summary (`--all`) —
134
+ * same aggregation, over the union of records, with a synthetic id. */
135
+ export function summarizeSessions(sessions) {
136
+ const allRecords = sessions.flatMap((s) => readSessionLog(s.file));
137
+ const label = sessions.length === 1 ? sessions[0].sessionId : `${sessions.length} sessions`;
138
+ return summarizeSession(label, sessions.map((s) => s.file).join(","), allRecords);
139
+ }
140
+ //# sourceMappingURL=sessionStats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionStats.js","sourceRoot":"","sources":["../../src/store/sessionStats.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- /** A persisted summary record (git-ignored .reposkein/local/summaries.jsonl). */
1
+ import { type SummaryAccumulator } from "./summaryShards.js";
2
+ /** A persisted summary record (git-ignored .reposkein/local/summaries-<agent>.jsonl). */
2
3
  export interface SidecarSummary {
3
4
  id: string;
4
5
  semantic_summary: string;
@@ -7,10 +8,45 @@ export interface SidecarSummary {
7
8
  summary_at: string;
8
9
  summary_by: string;
9
10
  }
10
- export declare function sidecarPath(repoPath: string): string;
11
+ /** Directory holding every agent's sidecar. Git-ignored in full. */
12
+ export declare function sidecarDir(repoPath: string): string;
13
+ /** Filesystem-safe slug for the writing agent.
14
+ *
15
+ * Untrusted input: REPOSKEIN_AGENT comes from whatever launched the server, so
16
+ * it is reduced to `[a-z0-9._-]` and capped. A value that reduces to nothing
17
+ * (or is absent) falls back to "agent", which is also the pre-multi-agent
18
+ * name — so a single-agent setup keeps one stable file. */
19
+ export declare function agentSlug(agent?: string | undefined): string;
20
+ /** THIS process's sidecar: one file per agent.
21
+ *
22
+ * A single shared `local/summaries.jsonl` meant two agents running against the
23
+ * same checkout silently overwrote each other — `upsertSidecar` rewrites the
24
+ * whole file, so the slower writer's prose vanished with no error anywhere.
25
+ * One file per writer removes the shared mutable state; the indexer folds
26
+ * every `local/summaries*.jsonl` into the committed shards. */
27
+ export declare function sidecarPath(repoPath: string, agent?: string): string;
28
+ /** Every agent's sidecar in this repo, sorted, so reads are deterministic and
29
+ * independent of directory order. Includes the pre-split
30
+ * `local/summaries.jsonl` an older client may still be writing. */
31
+ export declare function sidecarPaths(repoPath: string): string[];
11
32
  /** Reads the sidecar into a map keyed by node id. Missing file → empty map.
12
33
  * Best-effort: malformed lines / read errors are skipped. */
13
34
  export declare function readSidecar(path: string): Map<string, SidecarSummary>;
35
+ /** Every agent's sidecar records, folded through the SAME rules the Rust
36
+ * indexer applies — `beats` within the sidecar set, losers preserved — so the
37
+ * server serves exactly the record the next `index` is going to write.
38
+ *
39
+ * This used to be a plain last-file-wins merge, which disagreed with the
40
+ * indexer whenever two agents summarised the same node, and dropped the loser
41
+ * with no trace. Callers should hand `conflicts` to `recordSummaryConflicts`. */
42
+ export declare function loadAllSidecars(repoPath: string): SummaryAccumulator;
43
+ /** Convenience view of `loadAllSidecars` for callers that only want the
44
+ * winning record per node id. Divergence losers are dropped on the floor
45
+ * here, so prefer `loadAllSidecars` anywhere they can be recorded. */
46
+ export declare function readAllSidecars(repoPath: string): Map<string, SidecarSummary>;
14
47
  /** Upserts one record and rewrites the sidecar sorted by id (deterministic, no
15
- * duplicate lines). Creates the local/ dir if needed. Best-effort. */
48
+ * duplicate lines). Creates the local/ dir if needed. Best-effort.
49
+ *
50
+ * Rewriting the whole file is safe only because `path` belongs to one agent:
51
+ * see `sidecarPath`. */
16
52
  export declare function upsertSidecar(path: string, rec: SidecarSummary): void;