@reposkein/mcp 0.1.1 → 0.1.2

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 (52) hide show
  1. package/README.md +56 -0
  2. package/dist/SKILL.md +31 -0
  3. package/dist/embed/cache.d.ts +51 -0
  4. package/dist/embed/cache.js +163 -0
  5. package/dist/embed/cache.js.map +1 -0
  6. package/dist/embed/hybrid.d.ts +43 -0
  7. package/dist/embed/hybrid.js +106 -0
  8. package/dist/embed/hybrid.js.map +1 -0
  9. package/dist/embed/provider.d.ts +34 -0
  10. package/dist/embed/provider.js +32 -0
  11. package/dist/embed/provider.js.map +1 -0
  12. package/dist/embed/providers/http.d.ts +35 -0
  13. package/dist/embed/providers/http.js +93 -0
  14. package/dist/embed/providers/http.js.map +1 -0
  15. package/dist/embed/providers/voyage.d.ts +32 -0
  16. package/dist/embed/providers/voyage.js +93 -0
  17. package/dist/embed/providers/voyage.js.map +1 -0
  18. package/dist/index.js +48 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/profile/impact.d.ts +43 -0
  21. package/dist/profile/impact.js +112 -0
  22. package/dist/profile/impact.js.map +1 -0
  23. package/dist/search/bm25f.d.ts +31 -0
  24. package/dist/search/bm25f.js +151 -0
  25. package/dist/search/bm25f.js.map +1 -0
  26. package/dist/store/GraphStore.d.ts +14 -0
  27. package/dist/store/GraphStore.js.map +1 -1
  28. package/dist/store/JsonlGraphStore.d.ts +2 -1
  29. package/dist/store/JsonlGraphStore.js +25 -0
  30. package/dist/store/JsonlGraphStore.js.map +1 -1
  31. package/dist/store/Neo4jGraphStore.d.ts +2 -1
  32. package/dist/store/Neo4jGraphStore.js +18 -0
  33. package/dist/store/Neo4jGraphStore.js.map +1 -1
  34. package/dist/store/UnconfiguredStore.d.ts +2 -1
  35. package/dist/store/UnconfiguredStore.js +3 -0
  36. package/dist/store/UnconfiguredStore.js.map +1 -1
  37. package/dist/temporal/gitlog.d.ts +65 -0
  38. package/dist/temporal/gitlog.js +254 -0
  39. package/dist/temporal/gitlog.js.map +1 -0
  40. package/dist/temporal/temporal.d.ts +22 -0
  41. package/dist/temporal/temporal.js +185 -0
  42. package/dist/temporal/temporal.js.map +1 -0
  43. package/dist/tools/impact.d.ts +11 -0
  44. package/dist/tools/impact.js +63 -0
  45. package/dist/tools/impact.js.map +1 -0
  46. package/dist/tools/semanticFind.d.ts +37 -0
  47. package/dist/tools/semanticFind.js +155 -0
  48. package/dist/tools/semanticFind.js.map +1 -0
  49. package/dist/tools/temporalContext.d.ts +12 -0
  50. package/dist/tools/temporalContext.js +55 -0
  51. package/dist/tools/temporalContext.js.map +1 -0
  52. package/package.json +1 -1
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Git-log mining: co-change, churn, and ownership signals.
3
+ *
4
+ * One `git log --no-merges --name-status -z --date=short` pass yields all three
5
+ * signals. Rename chains are reconstructed from R records so a file's history
6
+ * follows it across moves. Bulk commits (>K files) are excluded from co-change
7
+ * counting but still counted for churn. Min-support floor drops one-off pairs.
8
+ *
9
+ * All output ordering is by explicit sort keys — never Map iteration order.
10
+ */
11
+ // ── parseGitLog ──────────────────────────────────────────────────────────────
12
+ /**
13
+ * Parse output of:
14
+ * git log --no-merges --name-status -z --date=short
15
+ * --format='%x00%H%x1f%ad%x1f%aN%x1f%aE'
16
+ *
17
+ * The `-z` flag makes git NUL-terminate each path in the name-status block.
18
+ * We split on NUL to get tokens, then detect the commit header lines
19
+ * (which start with NUL %H %ad %aN %aE joined by FS=\x1f).
20
+ *
21
+ * Rename records arrive as e.g. `R100\toldpath\0newpath\0`.
22
+ * With -z, git emits: <status>\0<path1>\0 and for renames <status>\0<path1>\0<path2>\0.
23
+ *
24
+ * Actual git -z --name-status output format (tested):
25
+ * \0<sha>\x1f<date>\x1f<name>\x1f<email>\n\n<status>\0<path>\0...<status>\0<old>\0<new>\0
26
+ * The commit separator is "\n\n" before the header NUL. We split entire output
27
+ * on the pattern "\x00\x00" (double NUL that appears between commits in -z mode)
28
+ * combined with the leading-NUL header.
29
+ *
30
+ * Strategy: split on NUL → walk tokens; whenever we see a token matching
31
+ * /^\x00<sha>\x1f.../ we start a new commit header; otherwise collect
32
+ * name-status entries.
33
+ */
34
+ export function parseGitLog(raw) {
35
+ if (!raw.trim())
36
+ return [];
37
+ // With --format='%x00%H%x1f%ad%x1f%aN%x1f%aE' and -z, each commit header
38
+ // is emitted as a NUL-prefixed record. The name-status entries follow,
39
+ // NUL-separated, then the next commit header starts with NUL again.
40
+ // Split on NUL and walk tokens.
41
+ const tokens = raw.split("\x00");
42
+ const commits = [];
43
+ let current = null;
44
+ // raw file pairs for current commit before rename resolution: [status, path, ?renameTo]
45
+ let rawFiles = [];
46
+ const flush = () => {
47
+ if (!current)
48
+ return;
49
+ commits.push({ sha: current.sha, date: current.date, author_name: current.name, author_email: current.email, files: [] });
50
+ // attach raw files for post-processing (we store them temporarily)
51
+ commits[commits.length - 1]._raw = rawFiles;
52
+ rawFiles = [];
53
+ current = null;
54
+ };
55
+ for (let i = 0; i < tokens.length; i++) {
56
+ const tok = tokens[i];
57
+ // Commit headers start with \x00 then SHA\x1fDATE\x1fNAME\x1fEMAIL
58
+ // Since we split on \x00, the header token itself (after the leading NUL
59
+ // is consumed by split) may look like: \nSHA\x1f... or SHA\x1f...
60
+ // The very first token before any NUL may be empty or contain a newline.
61
+ // A header token contains \x1f (field separator).
62
+ if (tok.includes("\x1f")) {
63
+ // This is a commit header (possibly with leading whitespace/newlines)
64
+ flush();
65
+ const clean = tok.replace(/^\s+/, "");
66
+ const parts = clean.split("\x1f");
67
+ if (parts.length >= 4) {
68
+ current = {
69
+ sha: parts[0].trim(),
70
+ date: parts[1].trim(),
71
+ name: parts[2].trim(),
72
+ email: parts[3].trim(),
73
+ };
74
+ }
75
+ continue;
76
+ }
77
+ // Skip empty tokens / whitespace-only tokens (between commits, end of output)
78
+ const t = tok.trim();
79
+ if (!t)
80
+ continue;
81
+ if (!current)
82
+ continue; // haven't seen a commit header yet
83
+ // Name-status tokens: "M", "A", "D", "R100", "C100", ...
84
+ // In -z mode: after a status token, the next token(s) are paths.
85
+ // We handle this by peeking ahead.
86
+ if (/^[MADRCTU]\d*$/.test(t)) {
87
+ const status = t[0];
88
+ if (status === "R" || status === "C") {
89
+ // rename/copy: next two tokens are old-path and new-path
90
+ const oldPath = tokens[++i]?.trim() ?? "";
91
+ const newPath = tokens[++i]?.trim() ?? "";
92
+ if (oldPath && newPath) {
93
+ rawFiles.push({ status, path: oldPath, renameTo: newPath });
94
+ }
95
+ }
96
+ else {
97
+ const path = tokens[++i]?.trim() ?? "";
98
+ if (path) {
99
+ rawFiles.push({ status, path });
100
+ }
101
+ }
102
+ }
103
+ // If the token doesn't match a status code and isn't empty, it's likely a
104
+ // stray path token already consumed above — ignore.
105
+ }
106
+ flush();
107
+ // ── Rename chain reconstruction ───────────────────────────────────────────
108
+ // Git log outputs commits newest-first. A rename record `R old → new` in
109
+ // commit C means: at C, the file was renamed from `old` to `new`.
110
+ // Any commits OLDER than C that reference `old` should be canonicalized
111
+ // to `new` (the current name).
112
+ //
113
+ // Algorithm: process commits in git-log order (newest first).
114
+ // Maintain aliasMap: old-name → current-canonical-name.
115
+ // - For non-rename files: apply aliasMap to get canonical name.
116
+ // - For rename records (R/C): add oldPath → newPath to aliasMap (so
117
+ // older commits referencing oldPath map to newPath). Also add oldPath
118
+ // as a file in this commit (it was touched — it's being renamed).
119
+ // The newPath is already in aliasMap for future lookups.
120
+ const aliasMap = new Map();
121
+ const resolved = commits.map((c) => {
122
+ const raw = c._raw ?? [];
123
+ const canonPaths = new Set();
124
+ for (const f of raw) {
125
+ if (f.status === "R" || f.status === "C") {
126
+ // This commit touched both old and new paths (it performed the rename).
127
+ // The canonical name for oldPath is newPath (or whatever newPath maps to).
128
+ if (f.renameTo) {
129
+ const canonNew = resolveAlias(f.renameTo, aliasMap);
130
+ canonPaths.add(canonNew);
131
+ // Record: older commits using oldPath should map to canonNew
132
+ aliasMap.set(f.path, canonNew);
133
+ }
134
+ }
135
+ else {
136
+ const canon = resolveAlias(f.path, aliasMap);
137
+ canonPaths.add(canon);
138
+ }
139
+ }
140
+ return { ...c, files: [...canonPaths].sort() };
141
+ });
142
+ return resolved;
143
+ }
144
+ function resolveAlias(path, aliasMap) {
145
+ let cur = path;
146
+ const seen = new Set();
147
+ while (aliasMap.has(cur) && !seen.has(cur)) {
148
+ seen.add(cur);
149
+ cur = aliasMap.get(cur);
150
+ }
151
+ return cur;
152
+ }
153
+ // ── computeTemporal ──────────────────────────────────────────────────────────
154
+ export function computeTemporal(commits, opts) {
155
+ const K = opts.maxFilesPerCommit ?? 50;
156
+ const minSupport = opts.minSupport ?? 3;
157
+ const topCoChanged = opts.topCoChanged ?? 20;
158
+ const topAuthors = opts.topAuthors ?? 5;
159
+ // Per-file: churn counts, dates, author tallies
160
+ const changeCount = new Map();
161
+ const lastChanged = new Map();
162
+ const authorCommits = new Map(); // file → author_name → count
163
+ // Pairwise co-change counts (both directions stored): key = "a\x1fb"
164
+ const pairCount = new Map();
165
+ for (const commit of commits) {
166
+ const files = commit.files;
167
+ // ── churn (all commits including bulk) ───────────────────────────────
168
+ for (const f of files) {
169
+ changeCount.set(f, (changeCount.get(f) ?? 0) + 1);
170
+ const prev = lastChanged.get(f);
171
+ if (!prev || commit.date > prev)
172
+ lastChanged.set(f, commit.date);
173
+ // author tally
174
+ let am = authorCommits.get(f);
175
+ if (!am) {
176
+ am = new Map();
177
+ authorCommits.set(f, am);
178
+ }
179
+ am.set(commit.author_name, (am.get(commit.author_name) ?? 0) + 1);
180
+ }
181
+ // ── co-change (exclude bulk commits) ─────────────────────────────────
182
+ if (files.length < 2 || files.length > K)
183
+ continue;
184
+ for (let i = 0; i < files.length; i++) {
185
+ for (let j = i + 1; j < files.length; j++) {
186
+ const a = files[i];
187
+ const b = files[j];
188
+ const key = a < b ? `${a}\x1f${b}` : `${b}\x1f${a}`;
189
+ pairCount.set(key, (pairCount.get(key) ?? 0) + 1);
190
+ }
191
+ }
192
+ }
193
+ // ── Build cochange map ────────────────────────────────────────────────────
194
+ // For each pair with support ≥ minSupport, compute confidence(a→b) and confidence(b→a).
195
+ const cochangeRaw = new Map();
196
+ for (const [key, support] of pairCount.entries()) {
197
+ if (support < minSupport)
198
+ continue;
199
+ const [a, b] = key.split("\x1f");
200
+ const countA = changeCount.get(a) ?? 1;
201
+ const countB = changeCount.get(b) ?? 1;
202
+ const confAB = support / countA;
203
+ const confBA = support / countB;
204
+ if (!cochangeRaw.has(a))
205
+ cochangeRaw.set(a, []);
206
+ if (!cochangeRaw.has(b))
207
+ cochangeRaw.set(b, []);
208
+ cochangeRaw.get(a).push({ path: b, support, confidence: confAB });
209
+ cochangeRaw.get(b).push({ path: a, support, confidence: confBA });
210
+ }
211
+ // Sort each file's co-change list: confidence desc, support desc, path asc
212
+ const cochange = {};
213
+ // Sort files for deterministic output
214
+ const cochangeFiles = [...cochangeRaw.keys()].sort();
215
+ for (const f of cochangeFiles) {
216
+ const list = cochangeRaw.get(f);
217
+ list.sort((a, b) => {
218
+ if (b.confidence !== a.confidence)
219
+ return b.confidence - a.confidence;
220
+ if (b.support !== a.support)
221
+ return b.support - a.support;
222
+ return a.path.localeCompare(b.path);
223
+ });
224
+ cochange[f] = list.slice(0, topCoChanged);
225
+ }
226
+ // ── Build files map ───────────────────────────────────────────────────────
227
+ const files = {};
228
+ // Sort file keys for deterministic output
229
+ const fileKeys = [...changeCount.keys()].sort();
230
+ for (const f of fileKeys) {
231
+ const am = authorCommits.get(f) ?? new Map();
232
+ // Sort authors: commits desc, then name asc
233
+ const authors = [...am.entries()]
234
+ .sort(([na, ca], [nb, cb]) => {
235
+ if (cb !== ca)
236
+ return cb - ca;
237
+ return na.localeCompare(nb);
238
+ })
239
+ .slice(0, topAuthors)
240
+ .map(([name, commits]) => ({ name, commits }));
241
+ files[f] = {
242
+ change_count: changeCount.get(f) ?? 0,
243
+ last_changed: lastChanged.get(f) ?? "",
244
+ authors,
245
+ };
246
+ }
247
+ return {
248
+ head_sha: opts.headSha,
249
+ shallow: opts.shallow,
250
+ files,
251
+ cochange,
252
+ };
253
+ }
254
+ //# sourceMappingURL=gitlog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitlog.js","sourceRoot":"","sources":["../../src/temporal/gitlog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Temporal context runner + sidecar cache.
3
+ *
4
+ * Shells out to `git log` via execFile, caches computed TemporalStats to
5
+ * .reposkein/local/temporal/<head-sha>.json (gitignored). Second call with the
6
+ * same HEAD sha is a cache hit (file read only).
7
+ *
8
+ * Graceful: not-a-git-repo / git-missing / empty history →
9
+ * { unavailable: "<reason>" } (never throws into the request).
10
+ *
11
+ * Prunes old cache files (keeps newest K=3) on each successful compute.
12
+ */
13
+ import { type TemporalStats } from "./gitlog.js";
14
+ export type TemporalResult = TemporalStats | {
15
+ unavailable: string;
16
+ };
17
+ /**
18
+ * Compute (or load from cache) temporal stats for all files in the repo.
19
+ * Returns { unavailable: reason } if git is unavailable or repo has no history.
20
+ * Never throws.
21
+ */
22
+ export declare function getTemporal(repoPath: string): Promise<TemporalResult>;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Temporal context runner + sidecar cache.
3
+ *
4
+ * Shells out to `git log` via execFile, caches computed TemporalStats to
5
+ * .reposkein/local/temporal/<head-sha>.json (gitignored). Second call with the
6
+ * same HEAD sha is a cache hit (file read only).
7
+ *
8
+ * Graceful: not-a-git-repo / git-missing / empty history →
9
+ * { unavailable: "<reason>" } (never throws into the request).
10
+ *
11
+ * Prunes old cache files (keeps newest K=3) on each successful compute.
12
+ */
13
+ import { execFile as execFileCb } from "node:child_process";
14
+ import { promisify } from "node:util";
15
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, readdirSync, unlinkSync, statSync, } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { parseGitLog, computeTemporal } from "./gitlog.js";
18
+ const execFile = promisify(execFileCb);
19
+ /** Max cache files to keep per repo (prune oldest when exceeded). */
20
+ const MAX_CACHE_FILES = 3;
21
+ /** Window defaults (also used for cache key). */
22
+ const WINDOW_MONTHS = 12;
23
+ const MAX_COMMITS = 1000;
24
+ const COMMIT_SIZE_CAP = 50;
25
+ const MIN_SUPPORT = 3;
26
+ // ── git helpers ──────────────────────────────────────────────────────────────
27
+ async function gitExec(args, cwd) {
28
+ try {
29
+ const { stdout } = await execFile("git", args, { cwd, maxBuffer: 64 * 1024 * 1024 });
30
+ return { stdout, ok: true };
31
+ }
32
+ catch (err) {
33
+ const e = err;
34
+ if (e.code === "ENOENT") {
35
+ return { stdout: "", ok: false, error: "git executable not found in PATH" };
36
+ }
37
+ // git exits non-zero for "not a git repo" etc.
38
+ const msg = e.stderr ?? e.message ?? String(err);
39
+ return { stdout: e.stdout ?? "", ok: false, error: msg.trim() };
40
+ }
41
+ }
42
+ async function getHeadSha(repoPath) {
43
+ const r = await gitExec(["rev-parse", "HEAD"], repoPath);
44
+ if (!r.ok) {
45
+ // Check if it's a "not a git repo" error
46
+ if (r.error?.includes("not a git repository")) {
47
+ return { unavailable: "not a git repository" };
48
+ }
49
+ if (r.error?.includes("git executable not found")) {
50
+ return { unavailable: r.error };
51
+ }
52
+ // Could be an empty repo (no commits yet)
53
+ const check = await gitExec(["rev-parse", "--git-dir"], repoPath);
54
+ if (!check.ok)
55
+ return { unavailable: "not a git repository" };
56
+ return { unavailable: "repository has no commits" };
57
+ }
58
+ return { sha: r.stdout.trim() };
59
+ }
60
+ async function isShallow(repoPath) {
61
+ const r = await gitExec(["rev-parse", "--is-shallow-repository"], repoPath);
62
+ return r.ok && r.stdout.trim() === "true";
63
+ }
64
+ // ── cache helpers ─────────────────────────────────────────────────────────────
65
+ function cacheDir(repoPath) {
66
+ return join(repoPath, ".reposkein", "local", "temporal");
67
+ }
68
+ function cachePath(repoPath, headSha) {
69
+ return join(cacheDir(repoPath), `${headSha}.json`);
70
+ }
71
+ function readCache(path) {
72
+ if (!existsSync(path))
73
+ return null;
74
+ try {
75
+ const text = readFileSync(path, "utf8");
76
+ return JSON.parse(text);
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ /** Atomic write: write to a .tmp file then rename (best-effort on all platforms). */
83
+ function writeCache(path, stats) {
84
+ const tmp = `${path}.tmp`;
85
+ try {
86
+ mkdirSync(join(path, ".."), { recursive: true });
87
+ writeFileSync(tmp, JSON.stringify(stats));
88
+ renameSync(tmp, path);
89
+ }
90
+ catch {
91
+ // best-effort; a write failure must not break the tool call
92
+ try {
93
+ if (existsSync(tmp))
94
+ unlinkSync(tmp);
95
+ }
96
+ catch { /* ignore */ }
97
+ }
98
+ }
99
+ /** Keep only the newest MAX_CACHE_FILES files; prune the rest (best-effort). */
100
+ function pruneCache(dir) {
101
+ try {
102
+ if (!existsSync(dir))
103
+ return;
104
+ const files = readdirSync(dir)
105
+ .filter((f) => f.endsWith(".json"))
106
+ .map((f) => ({ name: f, mtime: statSync(join(dir, f)).mtimeMs }))
107
+ .sort((a, b) => b.mtime - a.mtime); // newest first
108
+ for (const f of files.slice(MAX_CACHE_FILES)) {
109
+ try {
110
+ unlinkSync(join(dir, f.name));
111
+ }
112
+ catch { /* ignore */ }
113
+ }
114
+ }
115
+ catch {
116
+ // best-effort
117
+ }
118
+ }
119
+ // ── main export ──────────────────────────────────────────────────────────────
120
+ /**
121
+ * Compute (or load from cache) temporal stats for all files in the repo.
122
+ * Returns { unavailable: reason } if git is unavailable or repo has no history.
123
+ * Never throws.
124
+ */
125
+ export async function getTemporal(repoPath) {
126
+ try {
127
+ // 1. Resolve HEAD sha
128
+ const headResult = await getHeadSha(repoPath);
129
+ if ("unavailable" in headResult)
130
+ return headResult;
131
+ const headSha = headResult.sha;
132
+ // 2. Check cache
133
+ const cPath = cachePath(repoPath, headSha);
134
+ const cached = readCache(cPath);
135
+ if (cached)
136
+ return cached;
137
+ // 3. Detect shallow clone
138
+ const shallow = await isShallow(repoPath);
139
+ // 4. Run git log
140
+ const logArgs = [
141
+ "log",
142
+ "--no-merges",
143
+ "--name-status",
144
+ "-z",
145
+ "--date=short",
146
+ `--format=%x00%H%x1f%ad%x1f%aN%x1f%aE`,
147
+ `--since=${WINDOW_MONTHS} months ago`,
148
+ `-n`,
149
+ String(MAX_COMMITS),
150
+ ];
151
+ const logResult = await gitExec(logArgs, repoPath);
152
+ if (!logResult.ok) {
153
+ return { unavailable: `git log failed: ${logResult.error ?? "unknown error"}` };
154
+ }
155
+ const raw = logResult.stdout;
156
+ if (!raw.trim()) {
157
+ // No commits in window
158
+ const stats = {
159
+ head_sha: headSha,
160
+ shallow,
161
+ files: {},
162
+ cochange: {},
163
+ };
164
+ writeCache(cPath, stats);
165
+ return stats;
166
+ }
167
+ // 5. Parse and compute
168
+ const commits = parseGitLog(raw);
169
+ const stats = computeTemporal(commits, {
170
+ headSha,
171
+ shallow,
172
+ maxFilesPerCommit: COMMIT_SIZE_CAP,
173
+ minSupport: MIN_SUPPORT,
174
+ });
175
+ // 6. Write cache and prune
176
+ writeCache(cPath, stats);
177
+ pruneCache(cacheDir(repoPath));
178
+ return stats;
179
+ }
180
+ catch (err) {
181
+ // Should not reach here — but fail safe
182
+ return { unavailable: `unexpected error: ${err.message ?? String(err)}` };
183
+ }
184
+ }
185
+ //# sourceMappingURL=temporal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal.js","sourceRoot":"","sources":["../../src/temporal/temporal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { GraphStore } from "../store/GraphStore.js";
2
+ import type { ToolResult } from "./readCypher.js";
3
+ export interface ImpactArgs {
4
+ node_id?: string;
5
+ file_path?: string;
6
+ name?: string;
7
+ depth?: number;
8
+ federated?: boolean;
9
+ }
10
+ /** Builds the impact tool handler bound to a store and repo. */
11
+ export declare function makeImpact(store: GraphStore, repoId: string): (args: ImpactArgs) => Promise<ToolResult>;
@@ -0,0 +1,63 @@
1
+ import { resolveTarget } from "../profile/resolve.js";
2
+ import { computeImpact } from "../profile/impact.js";
3
+ import { federationIds } from "../store/federation.js";
4
+ function clamp(val, min, max) {
5
+ return Math.min(Math.max(val, min), max);
6
+ }
7
+ /** Builds the impact tool handler bound to a store and repo. */
8
+ export function makeImpact(store, repoId) {
9
+ return async (args) => {
10
+ const sel = {
11
+ node_id: args.node_id,
12
+ file_path: args.file_path,
13
+ name: args.name,
14
+ };
15
+ if (!sel.node_id && !sel.name && !sel.file_path) {
16
+ return {
17
+ content: [{ type: "text", text: "provide one of: node_id, file_path+name, or name" }],
18
+ isError: true,
19
+ };
20
+ }
21
+ try {
22
+ const repoIds = args.federated ? await federationIds(store, repoId) : [repoId];
23
+ const resolved = await resolveTarget(store, repoIds, sel);
24
+ if (resolved.kind === "not_found") {
25
+ return {
26
+ content: [{ type: "text", text: JSON.stringify({ error: "target not found" }) }],
27
+ isError: true,
28
+ };
29
+ }
30
+ if (resolved.kind === "candidates") {
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: JSON.stringify({
36
+ ambiguous: true,
37
+ candidates: resolved.candidates.map((c) => ({
38
+ id: c.id,
39
+ name: c.qualified_name,
40
+ file_path: c.file_path,
41
+ repo_id: c.repo_id,
42
+ })),
43
+ }),
44
+ },
45
+ ],
46
+ };
47
+ }
48
+ const target = resolved.target;
49
+ const depth = clamp(args.depth ?? 3, 1, 5);
50
+ const result = await computeImpact(store, repoIds, target.id, target.qualified_name, target.file_path, { depth, maxNodes: 500 });
51
+ return {
52
+ content: [{ type: "text", text: JSON.stringify(result) }],
53
+ };
54
+ }
55
+ catch (e) {
56
+ return {
57
+ content: [{ type: "text", text: e.message }],
58
+ isError: true,
59
+ };
60
+ }
61
+ };
62
+ }
63
+ //# sourceMappingURL=impact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"impact.js","sourceRoot":"","sources":["../../src/tools/impact.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * semantic_find tool — lexical (BM25F) + optional hybrid embedding entry-point discovery.
3
+ *
4
+ * Cold-start seed finder: answers "where is X?" when the agent has no node_id
5
+ * or exact identifier. Returns ranked node_ids ready for get_context_profile.
6
+ *
7
+ * Default behaviour (REPOSKEIN_EMBED_PROVIDER unset/none):
8
+ * Pure-lexical BM25F — byte-identical to the pre-embeddings baseline.
9
+ *
10
+ * With a provider configured:
11
+ * Hybrid: BM25F lexical + cosine similarity, fused via RRF (k=60).
12
+ * On ANY embedding error → silently falls back to pure-lexical (never breaks the tool).
13
+ * The lexical ranking is always computed first, so fallback is free.
14
+ *
15
+ * Architecture: calls store.searchCorpus to load the eligible node set, then
16
+ * the shared BM25F scorer ranks in TypeScript — byte-identical for both JSONL
17
+ * and Neo4j backends. No Neo4j vector index used.
18
+ */
19
+ import type { GraphStore } from "../store/GraphStore.js";
20
+ import type { ToolResult } from "./readCypher.js";
21
+ import type { EmbeddingProvider } from "../embed/provider.js";
22
+ export interface SemanticFindArgs {
23
+ query: string;
24
+ limit?: number;
25
+ kind?: "Function" | "Class" | "Interface" | "Enum";
26
+ federated?: boolean;
27
+ }
28
+ /**
29
+ * Factory for the semantic_find handler.
30
+ *
31
+ * @param store The graph store.
32
+ * @param repoId The active repo id.
33
+ * @param repoPath File system path to the repo root (for the embedding cache).
34
+ * @param providerOverride Inject a provider directly (for tests — avoids env/network).
35
+ * Pass null to force pure-lexical. Pass undefined to use env config.
36
+ */
37
+ export declare function makeSemanticFind(store: GraphStore, repoId: string, repoPath?: string, providerOverride?: EmbeddingProvider | null): (args: SemanticFindArgs) => Promise<ToolResult>;