@reposkein/mcp 0.2.7 → 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 (140) hide show
  1. package/README.md +73 -1
  2. package/binary-digests.json +4 -4
  3. package/dist/SKILL.md +63 -1
  4. package/dist/cli/adr.d.ts +37 -0
  5. package/dist/cli/adr.js +231 -0
  6. package/dist/cli/adr.js.map +1 -0
  7. package/dist/cli/agentAdapters.d.ts +68 -0
  8. package/dist/cli/agentAdapters.js +223 -0
  9. package/dist/cli/agentAdapters.js.map +1 -0
  10. package/dist/cli/ansi.d.ts +24 -0
  11. package/dist/cli/ansi.js +40 -0
  12. package/dist/cli/ansi.js.map +1 -0
  13. package/dist/cli/doctor.d.ts +34 -2
  14. package/dist/cli/doctor.js +53 -5
  15. package/dist/cli/doctor.js.map +1 -1
  16. package/dist/cli/doctorDecisions.d.ts +5 -0
  17. package/dist/cli/doctorDecisions.js +83 -0
  18. package/dist/cli/doctorDecisions.js.map +1 -0
  19. package/dist/cli/doctorFreshness.d.ts +43 -0
  20. package/dist/cli/doctorFreshness.js +111 -0
  21. package/dist/cli/doctorFreshness.js.map +1 -0
  22. package/dist/cli/doctorSummaries.d.ts +2 -0
  23. package/dist/cli/doctorSummaries.js +95 -0
  24. package/dist/cli/doctorSummaries.js.map +1 -0
  25. package/dist/cli/federatedDiscovery.d.ts +16 -0
  26. package/dist/cli/federatedDiscovery.js +75 -0
  27. package/dist/cli/federatedDiscovery.js.map +1 -0
  28. package/dist/cli/init.d.ts +69 -6
  29. package/dist/cli/init.js +194 -28
  30. package/dist/cli/init.js.map +1 -1
  31. package/dist/cli/sourceSlices.d.ts +17 -0
  32. package/dist/cli/sourceSlices.js +59 -0
  33. package/dist/cli/sourceSlices.js.map +1 -0
  34. package/dist/cli/stats.d.ts +29 -0
  35. package/dist/cli/stats.js +180 -0
  36. package/dist/cli/stats.js.map +1 -0
  37. package/dist/cli/view.d.ts +71 -3
  38. package/dist/cli/view.js +147 -12
  39. package/dist/cli/view.js.map +1 -1
  40. package/dist/guard/decisionValidation.d.ts +29 -0
  41. package/dist/guard/decisionValidation.js +51 -0
  42. package/dist/guard/decisionValidation.js.map +1 -0
  43. package/dist/guard/summaryValidation.d.ts +3 -0
  44. package/dist/guard/summaryValidation.js +5 -3
  45. package/dist/guard/summaryValidation.js.map +1 -1
  46. package/dist/index.d.ts +48 -0
  47. package/dist/index.js +355 -78
  48. package/dist/index.js.map +1 -1
  49. package/dist/indexer/decisionsAffected.d.ts +42 -0
  50. package/dist/indexer/decisionsAffected.js +149 -0
  51. package/dist/indexer/decisionsAffected.js.map +1 -0
  52. package/dist/indexer/fetchBinary.d.ts +14 -0
  53. package/dist/indexer/fetchBinary.js +81 -40
  54. package/dist/indexer/fetchBinary.js.map +1 -1
  55. package/dist/indexer/runIndexer.d.ts +3 -2
  56. package/dist/indexer/runIndexer.js +8 -4
  57. package/dist/indexer/runIndexer.js.map +1 -1
  58. package/dist/profile/decisions.d.ts +37 -0
  59. package/dist/profile/decisions.js +112 -0
  60. package/dist/profile/decisions.js.map +1 -0
  61. package/dist/profile/types.d.ts +8 -0
  62. package/dist/store/GraphStore.d.ts +5 -0
  63. package/dist/store/JsonlGraphStore.d.ts +14 -3
  64. package/dist/store/JsonlGraphStore.js +66 -19
  65. package/dist/store/JsonlGraphStore.js.map +1 -1
  66. package/dist/store/decisions.d.ts +131 -0
  67. package/dist/store/decisions.js +321 -0
  68. package/dist/store/decisions.js.map +1 -0
  69. package/dist/store/indexedAt.d.ts +18 -0
  70. package/dist/store/indexedAt.js +56 -0
  71. package/dist/store/indexedAt.js.map +1 -0
  72. package/dist/store/instrumentTool.d.ts +40 -0
  73. package/dist/store/instrumentTool.js +82 -0
  74. package/dist/store/instrumentTool.js.map +1 -0
  75. package/dist/store/repoContextCache.d.ts +15 -0
  76. package/dist/store/repoContextCache.js +26 -0
  77. package/dist/store/repoContextCache.js.map +1 -0
  78. package/dist/store/repoSession.d.ts +63 -0
  79. package/dist/store/repoSession.js +123 -0
  80. package/dist/store/repoSession.js.map +1 -0
  81. package/dist/store/resolveRepoPath.d.ts +31 -0
  82. package/dist/store/resolveRepoPath.js +85 -0
  83. package/dist/store/resolveRepoPath.js.map +1 -0
  84. package/dist/store/sessionLog.d.ts +98 -0
  85. package/dist/store/sessionLog.js +216 -0
  86. package/dist/store/sessionLog.js.map +1 -0
  87. package/dist/store/sessionStats.d.ts +55 -0
  88. package/dist/store/sessionStats.js +140 -0
  89. package/dist/store/sessionStats.js.map +1 -0
  90. package/dist/store/sidecar.d.ts +39 -3
  91. package/dist/store/sidecar.js +89 -5
  92. package/dist/store/sidecar.js.map +1 -1
  93. package/dist/store/summaryShards.d.ts +117 -0
  94. package/dist/store/summaryShards.js +291 -0
  95. package/dist/store/summaryShards.js.map +1 -0
  96. package/dist/store/teamConfig.d.ts +13 -0
  97. package/dist/store/teamConfig.js +49 -0
  98. package/dist/store/teamConfig.js.map +1 -0
  99. package/dist/tools/getContextProfile.d.ts +1 -1
  100. package/dist/tools/getContextProfile.js +9 -1
  101. package/dist/tools/getContextProfile.js.map +1 -1
  102. package/dist/tools/getDecision.d.ts +6 -0
  103. package/dist/tools/getDecision.js +59 -0
  104. package/dist/tools/getDecision.js.map +1 -0
  105. package/dist/tools/impact.d.ts +1 -1
  106. package/dist/tools/impact.js +13 -1
  107. package/dist/tools/impact.js.map +1 -1
  108. package/dist/tools/indexerTools.d.ts +16 -2
  109. package/dist/tools/indexerTools.js +44 -5
  110. package/dist/tools/indexerTools.js.map +1 -1
  111. package/dist/tools/listDecisions.d.ts +19 -0
  112. package/dist/tools/listDecisions.js +93 -0
  113. package/dist/tools/listDecisions.js.map +1 -0
  114. package/dist/tools/reaffirmDecision.d.ts +11 -0
  115. package/dist/tools/reaffirmDecision.js +55 -0
  116. package/dist/tools/reaffirmDecision.js.map +1 -0
  117. package/dist/tools/recordDecision.d.ts +24 -0
  118. package/dist/tools/recordDecision.js +135 -0
  119. package/dist/tools/recordDecision.js.map +1 -0
  120. package/dist/tools/semanticFind.d.ts +9 -2
  121. package/dist/tools/semanticFind.js +44 -2
  122. package/dist/tools/semanticFind.js.map +1 -1
  123. package/dist/tools/setDecisionStatus.d.ts +8 -0
  124. package/dist/tools/setDecisionStatus.js +34 -0
  125. package/dist/tools/setDecisionStatus.js.map +1 -0
  126. package/dist/viz/assets/{graph.worker-CC7BeE-8.js → graph.worker-DkH6-lMU.js} +1 -1
  127. package/dist/viz/assets/index-CxqeGuRS.css +1 -0
  128. package/dist/viz/assets/index-DiNZyrXx.js +9 -0
  129. package/dist/viz/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  130. package/dist/viz/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  131. package/dist/viz/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
  132. package/dist/viz/assets/r3f-BZY3QR6_.js +336 -0
  133. package/dist/viz/assets/tanstack-CjXr-L-s.js +62 -0
  134. package/dist/viz/assets/three-Cpu5RUEM.js +4116 -0
  135. package/dist/viz/index.html +5 -4
  136. package/package.json +5 -4
  137. package/dist/viz/assets/index-C_8UkiJ8.js +0 -9
  138. package/dist/viz/assets/r3f-Cku1OhB-.js +0 -277
  139. package/dist/viz/assets/tanstack-DyXcNRWk.js +0 -77
  140. package/dist/viz/assets/three-Z8XaLGmf.js +0 -3839
@@ -0,0 +1,63 @@
1
+ import { type RepoResolution } from "./resolveRepoPath.js";
2
+ export interface RepoInfo {
3
+ path: string;
4
+ /** From <path>/.reposkein/meta.json, when present. */
5
+ repo_id?: string;
6
+ /** Display name: repo_id if meta.json resolved one, else the directory's
7
+ * basename — always present so `select_repo` has something to match on
8
+ * even before the repo has been indexed (no meta.json yet). */
9
+ name: string;
10
+ /** Cheap line counts (not full parses) — omitted where the file is absent. */
11
+ nodes?: number;
12
+ edges?: number;
13
+ }
14
+ export declare function describeRepo(path: string): RepoInfo;
15
+ /** Every repo `list_repos` should report. Deliberately broader than
16
+ * `resolveRepoPath`'s own short-circuiting precedence: discovery always
17
+ * walks the filesystem (nearest ancestor with `.reposkein/`, else
18
+ * subdirectories) so a workspace's sibling repos are visible — and thus
19
+ * selectable via `select_repo` — even in a session where REPOSKEIN_REPO_PATH
20
+ * happens to pin one of them. That env pin is unioned in (deduped) so it's
21
+ * never missing from the list, but it does not suppress the others; only
22
+ * `resolveRepoPath`'s actual *default* pick (used by `resolve()` below)
23
+ * gives it priority over the walk. Sorted for deterministic output. */
24
+ export declare function discoverRepoPaths(opts: {
25
+ cwd: string;
26
+ envRepoPath?: string;
27
+ }): string[];
28
+ /** Tracks the MCP session's active repo across tool calls.
29
+ *
30
+ * Resolution precedence: `select_repo` (session-only; sticky for the rest of
31
+ * the connection) > REPOSKEIN_REPO_PATH > walk-up > walk-down. `select_repo`
32
+ * is deliberately allowed to override REPOSKEIN_REPO_PATH once called — it's
33
+ * the more specific, more recent signal, and it's how a workspace-mode
34
+ * session (multiple sibling repos, walk-down ambiguous) picks one to work
35
+ * with instead of restarting the server with a different env var. */
36
+ export declare class RepoSession {
37
+ private readonly cwd;
38
+ private readonly envRepoPath;
39
+ private selectedPath;
40
+ constructor(opts: {
41
+ cwd: string;
42
+ envRepoPath: string | undefined;
43
+ });
44
+ /** Current resolution, honoring an active `select_repo` pick first. */
45
+ resolve(): RepoResolution;
46
+ /** Every candidate repo for this session — see `discoverRepoPaths`. Each
47
+ * entry is flagged `selected` for whichever one `resolve()` currently
48
+ * returns, so an agent can see the effect of a prior `select_repo` call. */
49
+ list(): (RepoInfo & {
50
+ selected: boolean;
51
+ })[];
52
+ /** Sets the session-active repo by path, by its meta.json `repo_id`, or by
53
+ * display `name` (as returned by `list()`/`list_repos`) — never an
54
+ * arbitrary filesystem path, so this can't be used to point outside what
55
+ * resolution actually discovered. */
56
+ select(pathOrName: string): {
57
+ ok: true;
58
+ repo: RepoInfo;
59
+ } | {
60
+ ok: false;
61
+ error: string;
62
+ };
63
+ }
@@ -0,0 +1,123 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+ import { resolveRepoPath, walkUp, walkDown } from "./resolveRepoPath.js";
4
+ function countLines(text) {
5
+ let n = 0;
6
+ for (const line of text.split("\n"))
7
+ if (line.trim())
8
+ n++;
9
+ return n;
10
+ }
11
+ function readMetaRepoId(path) {
12
+ try {
13
+ const meta = JSON.parse(readFileSync(join(path, ".reposkein", "meta.json"), "utf8"));
14
+ return typeof meta.repo_id === "string" ? meta.repo_id : undefined;
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ }
20
+ /** Line counts for nodes.jsonl / edges.jsonl if they exist — the same cheap
21
+ * "count non-empty lines" approach `cli/doctor.ts` uses, not a full parse. */
22
+ function cheapCounts(path) {
23
+ const out = {};
24
+ const nodesFile = join(path, ".reposkein", "nodes.jsonl");
25
+ const edgesFile = join(path, ".reposkein", "edges.jsonl");
26
+ try {
27
+ if (existsSync(nodesFile))
28
+ out.nodes = countLines(readFileSync(nodesFile, "utf8"));
29
+ }
30
+ catch {
31
+ /* unreadable — omit rather than fail list_repos over one repo */
32
+ }
33
+ try {
34
+ if (existsSync(edgesFile))
35
+ out.edges = countLines(readFileSync(edgesFile, "utf8"));
36
+ }
37
+ catch {
38
+ /* unreadable — omit */
39
+ }
40
+ return out;
41
+ }
42
+ export function describeRepo(path) {
43
+ const repo_id = readMetaRepoId(path);
44
+ return {
45
+ path,
46
+ ...(repo_id ? { repo_id } : {}),
47
+ name: repo_id ?? basename(path),
48
+ ...cheapCounts(path),
49
+ };
50
+ }
51
+ /** Every repo `list_repos` should report. Deliberately broader than
52
+ * `resolveRepoPath`'s own short-circuiting precedence: discovery always
53
+ * walks the filesystem (nearest ancestor with `.reposkein/`, else
54
+ * subdirectories) so a workspace's sibling repos are visible — and thus
55
+ * selectable via `select_repo` — even in a session where REPOSKEIN_REPO_PATH
56
+ * happens to pin one of them. That env pin is unioned in (deduped) so it's
57
+ * never missing from the list, but it does not suppress the others; only
58
+ * `resolveRepoPath`'s actual *default* pick (used by `resolve()` below)
59
+ * gives it priority over the walk. Sorted for deterministic output. */
60
+ export function discoverRepoPaths(opts) {
61
+ const up = walkUp(opts.cwd);
62
+ const physical = up ? [up] : walkDown(opts.cwd);
63
+ if (opts.envRepoPath && !physical.includes(opts.envRepoPath)) {
64
+ return [...physical, opts.envRepoPath].sort();
65
+ }
66
+ return physical;
67
+ }
68
+ /** Tracks the MCP session's active repo across tool calls.
69
+ *
70
+ * Resolution precedence: `select_repo` (session-only; sticky for the rest of
71
+ * the connection) > REPOSKEIN_REPO_PATH > walk-up > walk-down. `select_repo`
72
+ * is deliberately allowed to override REPOSKEIN_REPO_PATH once called — it's
73
+ * the more specific, more recent signal, and it's how a workspace-mode
74
+ * session (multiple sibling repos, walk-down ambiguous) picks one to work
75
+ * with instead of restarting the server with a different env var. */
76
+ export class RepoSession {
77
+ cwd;
78
+ envRepoPath;
79
+ selectedPath;
80
+ constructor(opts) {
81
+ this.cwd = opts.cwd;
82
+ this.envRepoPath = opts.envRepoPath;
83
+ }
84
+ /** Current resolution, honoring an active `select_repo` pick first. */
85
+ resolve() {
86
+ if (this.selectedPath)
87
+ return { repoPath: this.selectedPath, source: "explicit" };
88
+ return resolveRepoPath({ cwd: this.cwd, envRepoPath: this.envRepoPath });
89
+ }
90
+ /** Every candidate repo for this session — see `discoverRepoPaths`. Each
91
+ * entry is flagged `selected` for whichever one `resolve()` currently
92
+ * returns, so an agent can see the effect of a prior `select_repo` call. */
93
+ list() {
94
+ const active = this.resolve().repoPath;
95
+ return discoverRepoPaths({ cwd: this.cwd, envRepoPath: this.envRepoPath }).map((path) => ({
96
+ ...describeRepo(path),
97
+ selected: path === active,
98
+ }));
99
+ }
100
+ /** Sets the session-active repo by path, by its meta.json `repo_id`, or by
101
+ * display `name` (as returned by `list()`/`list_repos`) — never an
102
+ * arbitrary filesystem path, so this can't be used to point outside what
103
+ * resolution actually discovered. */
104
+ select(pathOrName) {
105
+ const candidates = discoverRepoPaths({ cwd: this.cwd, envRepoPath: this.envRepoPath }).map(describeRepo);
106
+ const hit = candidates.find((c) => c.path === pathOrName) ??
107
+ candidates.find((c) => c.repo_id === pathOrName) ??
108
+ candidates.find((c) => c.name === pathOrName);
109
+ if (!hit) {
110
+ return {
111
+ ok: false,
112
+ error: `No repo matching "${pathOrName}" among the discovered candidates` +
113
+ (candidates.length > 0
114
+ ? `: ${candidates.map((c) => c.path).join(", ")}.`
115
+ : " (none found).") +
116
+ " Call list_repos first and pass one of its `path` or `name` values.",
117
+ };
118
+ }
119
+ this.selectedPath = hit.path;
120
+ return { ok: true, repo: hit };
121
+ }
122
+ }
123
+ //# sourceMappingURL=repoSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repoSession.js","sourceRoot":"","sources":["../../src/store/repoSession.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ export type RepoResolutionSource = "explicit" | "env" | "walk-up" | "walk-down" | "none";
2
+ export interface RepoResolution {
3
+ /** Resolved repo root, or undefined if none could be determined. */
4
+ repoPath: string | undefined;
5
+ /** How repoPath was determined ("none" when nothing resolved). */
6
+ source: RepoResolutionSource;
7
+ /** Populated only when source is "none" because walk-down found 2+ hits
8
+ * (workspace mode, ambiguous) — name them so the caller can pick one. */
9
+ candidates?: string[];
10
+ }
11
+ /** Nearest ancestor of `cwd` (inclusive) containing `.reposkein/`. */
12
+ export declare function walkUp(cwd: string): string | undefined;
13
+ /** Scans descendants of `cwd` (bounded to `maxDepth` levels, skipping
14
+ * build/VCS/tool directories) for directories containing `.reposkein/`.
15
+ * Does not recurse further into a hit — nested repos are a graph-federation
16
+ * concern (see store/federation.ts), not a workspace-candidate one here.
17
+ * Returns hits sorted for deterministic ambiguity-error output. */
18
+ export declare function walkDown(cwd: string, maxDepth?: number): string[];
19
+ /** Zero-config repo resolution.
20
+ *
21
+ * Precedence: explicit tool arg > REPOSKEIN_REPO_PATH > walk-up > walk-down.
22
+ * - Walk-up: nearest ancestor of `cwd` (inclusive) containing `.reposkein/`.
23
+ * - Walk-down (workspace mode, only tried when no ancestor has one): scans
24
+ * `cwd`'s children/grandchildren for `.reposkein/` dirs. One hit auto-
25
+ * selects it. Zero or 2+ hits leave `repoPath` undefined ("none"); 2+
26
+ * populates `candidates` so the caller can build an actionable error. */
27
+ export declare function resolveRepoPath(opts: {
28
+ cwd: string;
29
+ envRepoPath?: string;
30
+ explicit?: string;
31
+ }): RepoResolution;
@@ -0,0 +1,85 @@
1
+ import { readdirSync, statSync } from "node:fs";
2
+ import { dirname, join, resolve as resolvePath } from "node:path";
3
+ const REPOSKEIN_DIR = ".reposkein";
4
+ /** Directories never treated as repo candidates while walking down: build
5
+ * output, VCS internals, other worktrees/tool state — never real repo roots. */
6
+ const SKIP_DIR_NAMES = new Set(["node_modules", ".git", "target", "dist", ".worktrees", ".claude"]);
7
+ function hasReposkeinDir(dir) {
8
+ try {
9
+ return statSync(join(dir, REPOSKEIN_DIR)).isDirectory();
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ /** Nearest ancestor of `cwd` (inclusive) containing `.reposkein/`. */
16
+ export function walkUp(cwd) {
17
+ let dir = resolvePath(cwd);
18
+ for (;;) {
19
+ if (hasReposkeinDir(dir))
20
+ return dir;
21
+ const parent = dirname(dir);
22
+ if (parent === dir)
23
+ return undefined; // reached filesystem root
24
+ dir = parent;
25
+ }
26
+ }
27
+ /** Scans descendants of `cwd` (bounded to `maxDepth` levels, skipping
28
+ * build/VCS/tool directories) for directories containing `.reposkein/`.
29
+ * Does not recurse further into a hit — nested repos are a graph-federation
30
+ * concern (see store/federation.ts), not a workspace-candidate one here.
31
+ * Returns hits sorted for deterministic ambiguity-error output. */
32
+ export function walkDown(cwd, maxDepth = 2) {
33
+ const root = resolvePath(cwd);
34
+ const hits = [];
35
+ function recurse(dir, depth) {
36
+ let entries;
37
+ try {
38
+ entries = readdirSync(dir, { withFileTypes: true });
39
+ }
40
+ catch {
41
+ return; // unreadable dir (permissions, race) — skip, don't fail resolution
42
+ }
43
+ for (const entry of entries) {
44
+ if (!entry.isDirectory())
45
+ continue;
46
+ if (entry.name === REPOSKEIN_DIR)
47
+ continue;
48
+ if (SKIP_DIR_NAMES.has(entry.name))
49
+ continue;
50
+ const full = join(dir, entry.name);
51
+ if (hasReposkeinDir(full)) {
52
+ hits.push(full);
53
+ continue;
54
+ }
55
+ if (depth < maxDepth)
56
+ recurse(full, depth + 1);
57
+ }
58
+ }
59
+ recurse(root, 1);
60
+ return hits.sort();
61
+ }
62
+ /** Zero-config repo resolution.
63
+ *
64
+ * Precedence: explicit tool arg > REPOSKEIN_REPO_PATH > walk-up > walk-down.
65
+ * - Walk-up: nearest ancestor of `cwd` (inclusive) containing `.reposkein/`.
66
+ * - Walk-down (workspace mode, only tried when no ancestor has one): scans
67
+ * `cwd`'s children/grandchildren for `.reposkein/` dirs. One hit auto-
68
+ * selects it. Zero or 2+ hits leave `repoPath` undefined ("none"); 2+
69
+ * populates `candidates` so the caller can build an actionable error. */
70
+ export function resolveRepoPath(opts) {
71
+ if (opts.explicit)
72
+ return { repoPath: opts.explicit, source: "explicit" };
73
+ if (opts.envRepoPath)
74
+ return { repoPath: opts.envRepoPath, source: "env" };
75
+ const up = walkUp(opts.cwd);
76
+ if (up)
77
+ return { repoPath: up, source: "walk-up" };
78
+ const down = walkDown(opts.cwd);
79
+ if (down.length === 1)
80
+ return { repoPath: down[0], source: "walk-down" };
81
+ if (down.length > 1)
82
+ return { repoPath: undefined, source: "none", candidates: down };
83
+ return { repoPath: undefined, source: "none" };
84
+ }
85
+ //# sourceMappingURL=resolveRepoPath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveRepoPath.js","sourceRoot":"","sources":["../../src/store/resolveRepoPath.ts"],"names":[],"mappings":"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"}
@@ -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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"}