localscope-mcp 0.1.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.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +39 -0
- package/dist/constants.js.map +1 -0
- package/dist/http.d.ts +3 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +43 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/tools.d.ts +62 -0
- package/dist/schemas/tools.d.ts.map +1 -0
- package/dist/schemas/tools.js +76 -0
- package/dist/schemas/tools.js.map +1 -0
- package/dist/services/embedder.d.ts +18 -0
- package/dist/services/embedder.d.ts.map +1 -0
- package/dist/services/embedder.js +135 -0
- package/dist/services/embedder.js.map +1 -0
- package/dist/services/extractor.d.ts +9 -0
- package/dist/services/extractor.d.ts.map +1 -0
- package/dist/services/extractor.js +232 -0
- package/dist/services/extractor.js.map +1 -0
- package/dist/services/impact-types.d.ts +38 -0
- package/dist/services/impact-types.d.ts.map +1 -0
- package/dist/services/impact-types.js +2 -0
- package/dist/services/impact-types.js.map +1 -0
- package/dist/services/impact.d.ts +6 -0
- package/dist/services/impact.d.ts.map +1 -0
- package/dist/services/impact.js +98 -0
- package/dist/services/impact.js.map +1 -0
- package/dist/services/indexer.d.ts +27 -0
- package/dist/services/indexer.d.ts.map +1 -0
- package/dist/services/indexer.js +241 -0
- package/dist/services/indexer.js.map +1 -0
- package/dist/services/language.d.ts +4 -0
- package/dist/services/language.d.ts.map +1 -0
- package/dist/services/language.js +54 -0
- package/dist/services/language.js.map +1 -0
- package/dist/services/repo-manager.d.ts +16 -0
- package/dist/services/repo-manager.d.ts.map +1 -0
- package/dist/services/repo-manager.js +90 -0
- package/dist/services/repo-manager.js.map +1 -0
- package/dist/services/walker.d.ts +31 -0
- package/dist/services/walker.d.ts.map +1 -0
- package/dist/services/walker.js +228 -0
- package/dist/services/walker.js.map +1 -0
- package/dist/tools/register.d.ts +6 -0
- package/dist/tools/register.d.ts.map +1 -0
- package/dist/tools/register.js +308 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 localscope contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# localscope
|
|
2
|
+
|
|
3
|
+
**A local code analyst for your AI assistant.**
|
|
4
|
+
|
|
5
|
+
localscope is an [MCP](https://modelcontextprotocol.io) server that indexes your repository on your machine — files, symbols, imports, optional embeddings — and lets Claude, Cursor, Codex, Windsurf, or any MCP client answer questions like:
|
|
6
|
+
|
|
7
|
+
> "Where does X break if I change Y?"
|
|
8
|
+
|
|
9
|
+
without a single byte of your code leaving your machine.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
$ claude mcp add localscope -- npx localscope-mcp
|
|
13
|
+
|
|
14
|
+
User: what breaks if I rename parseConfig?
|
|
15
|
+
Claude: → localscope_impact(parseConfig)
|
|
16
|
+
Direct dependents (break first):
|
|
17
|
+
- src/main.ts
|
|
18
|
+
- src/services/server.ts
|
|
19
|
+
Symbols at risk: parseConfig (exported function) …
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Why
|
|
23
|
+
|
|
24
|
+
Cloud code-search tools are great — until the repo is under NDA, on an air-gapped machine, or you simply don't want your code on someone else's servers. localscope gives assistants *codebase vision* with a hard guarantee: **zero network calls, zero telemetry, zero config.**
|
|
25
|
+
|
|
26
|
+
| | localscope | cloud tools |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Code leaves machine | never | yes |
|
|
29
|
+
| Setup | `npx localscope-mcp` | API key, upload |
|
|
30
|
+
| Works offline | yes | no |
|
|
31
|
+
| Impact analysis | import graph + symbols | varies |
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
Requirements: Node 18+.
|
|
36
|
+
|
|
37
|
+
**Claude Code:**
|
|
38
|
+
```bash
|
|
39
|
+
claude mcp add localscope -- npx localscope-mcp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Cursor** (`.cursor/mcp.json`):
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"localscope": { "command": "npx", "args": ["localscope-mcp"] }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Any MCP client** — stdio server, one command:
|
|
52
|
+
```bash
|
|
53
|
+
npx localscope-mcp
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Optional: semantic search with local ONNX embeddings (still offline — the model runs on your CPU):
|
|
57
|
+
```bash
|
|
58
|
+
npm install -g @huggingface/transformers
|
|
59
|
+
```
|
|
60
|
+
Not installed? localscope automatically falls back to lexical + symbol search. No error, no setup step.
|
|
61
|
+
|
|
62
|
+
## Tools
|
|
63
|
+
|
|
64
|
+
### `localscope_index`
|
|
65
|
+
Build the local index: files, symbols, import graph, embeddings if available. Respects `.gitignore` and skips `node_modules`, `dist`, lockfiles, binaries. Typical repo indexes in well under a second.
|
|
66
|
+
|
|
67
|
+
### `localscope_search`
|
|
68
|
+
Find code by meaning ("retry with backoff"), by symbol name ("parseConfig"), or by fragment. Each hit shows file, lines, symbol, score, and *how* it matched — semantic, lexical, or symbol. Identifiers are split camelCase-aware, so "parse config" finds `parseConfig`.
|
|
69
|
+
|
|
70
|
+
### `localscope_impact`
|
|
71
|
+
The headline tool. Give it a file path or a symbol name and it walks the reverse dependency graph:
|
|
72
|
+
|
|
73
|
+
- **Direct dependents** — files importing the target; these break first
|
|
74
|
+
- **Transitive dependents** — everything downstream, up to `max_depth` hops
|
|
75
|
+
- **Symbols at risk** — exported functions/classes in the target and why each is fragile
|
|
76
|
+
- **Fuzzy suggestions** — typo in the name? It suggests what you meant
|
|
77
|
+
|
|
78
|
+
### `localscope_status`
|
|
79
|
+
Index stats: files, chunks, symbols, embedder mode, timestamp.
|
|
80
|
+
|
|
81
|
+
## Privacy guarantee
|
|
82
|
+
|
|
83
|
+
localscope makes **no outbound network calls** — not for search, not for models, not for updates. The ONNX embedder (if you install it) downloads its model once from Hugging Face into your local cache, then runs fully offline. You can verify it yourself: [src/services/embedder.ts](src/services/embedder.ts) is the only module that touches `@huggingface/transformers`, and only when you've installed it.
|
|
84
|
+
|
|
85
|
+
Air-gap friendly. NDA friendly. Paranoia friendly.
|
|
86
|
+
|
|
87
|
+
## How impact analysis works
|
|
88
|
+
|
|
89
|
+
1. **Walk** the repo, respecting `.gitignore` (compiled from real gitignore semantics: negation, directory rules, globstars).
|
|
90
|
+
2. **Extract symbols** per language — functions, classes, interfaces, types, methods, constants — with regex grammars for TypeScript/JavaScript, Python, Go, Rust, Java, and Ruby.
|
|
91
|
+
3. **Resolve imports** into a file graph. TypeScript-style `.js` → `.ts` mapping included (ESM-style imports resolve correctly).
|
|
92
|
+
4. **Answer** "who breaks?" by traversing the reverse graph and cross-referencing the symbol table.
|
|
93
|
+
|
|
94
|
+
No LSP server. No language server per language. No daemon. Just reading files fast and getting the graph right.
|
|
95
|
+
|
|
96
|
+
## HTTP mode (optional)
|
|
97
|
+
|
|
98
|
+
stdio is the default and right for local use. If you need HTTP (e.g. a shared dev-machine setup):
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
LOCALSCOPE_TRANSPORT=http LOCALSCOPE_PORT=3000 npx localscope-mcp
|
|
102
|
+
# MCP endpoint: http://127.0.0.1:3000/mcp
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Binds to `127.0.0.1` only, rejects non-local origins. Do not expose it to the network.
|
|
106
|
+
|
|
107
|
+
## Configuration
|
|
108
|
+
|
|
109
|
+
Zero required. Everything is optional:
|
|
110
|
+
|
|
111
|
+
| Env var | Default | Purpose |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `LOCALSCOPE_TRANSPORT` | `stdio` | `stdio` or `http` |
|
|
114
|
+
| `LOCALSCOPE_PORT` | `3000` | HTTP port |
|
|
115
|
+
| `LOCALSCOPE_RG_PATH` | auto-detect | Path to ripgrep binary, if you have one |
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git clone <repo> && cd localscope
|
|
121
|
+
npm install
|
|
122
|
+
npm test # 31 tests
|
|
123
|
+
npm run build
|
|
124
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
CI runs typecheck, lint, and tests on Node 18/20/22 across Linux, macOS, and Windows.
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const CHARACTER_LIMIT = 25000;
|
|
2
|
+
export declare const DEFAULT_MAX_FILE_BYTES = 1000000;
|
|
3
|
+
export declare const DEFAULT_CHUNK_LINES = 60;
|
|
4
|
+
export declare const DEFAULT_MAX_FILES = 50000;
|
|
5
|
+
export declare const DEFAULT_SEARCH_LIMIT = 20;
|
|
6
|
+
export declare const DEFAULT_MAX_IMPACT_DEPTH = 5;
|
|
7
|
+
export declare const IGNORED_DIRS: readonly string[];
|
|
8
|
+
export declare const IGNORED_FILE_PATTERNS: readonly RegExp[];
|
|
9
|
+
export declare const DEFAULT_SEMANTIC_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
10
|
+
export declare const SEMANTIC_ACTIVATION_HINT = "Semantic search disabled. For better results: npm install -g @huggingface/transformers or add it as a dependency, then restart. Falling back to lexical search.";
|
|
11
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC,eAAO,MAAM,sBAAsB,UAAY,CAAC;AAEhD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAsBzC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAMlD,CAAC;AAEF,eAAO,MAAM,sBAAsB,4BAA4B,CAAC;AAEhE,eAAO,MAAM,wBAAwB,oKAC8H,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const CHARACTER_LIMIT = 25_000;
|
|
2
|
+
export const DEFAULT_MAX_FILE_BYTES = 1_000_000;
|
|
3
|
+
export const DEFAULT_CHUNK_LINES = 60;
|
|
4
|
+
export const DEFAULT_MAX_FILES = 50_000;
|
|
5
|
+
export const DEFAULT_SEARCH_LIMIT = 20;
|
|
6
|
+
export const DEFAULT_MAX_IMPACT_DEPTH = 5;
|
|
7
|
+
export const IGNORED_DIRS = [
|
|
8
|
+
"node_modules",
|
|
9
|
+
".git",
|
|
10
|
+
"dist",
|
|
11
|
+
"build",
|
|
12
|
+
"out",
|
|
13
|
+
".next",
|
|
14
|
+
".nuxt",
|
|
15
|
+
".turbo",
|
|
16
|
+
".vercel",
|
|
17
|
+
"target",
|
|
18
|
+
"vendor",
|
|
19
|
+
"__pycache__",
|
|
20
|
+
".venv",
|
|
21
|
+
"venv",
|
|
22
|
+
".mypy_cache",
|
|
23
|
+
".pytest_cache",
|
|
24
|
+
"coverage",
|
|
25
|
+
".idea",
|
|
26
|
+
".vscode",
|
|
27
|
+
".cache",
|
|
28
|
+
"tmp",
|
|
29
|
+
];
|
|
30
|
+
export const IGNORED_FILE_PATTERNS = [
|
|
31
|
+
/\.(lock|log|min\.js|min\.map)$/i,
|
|
32
|
+
/package-lock\.json$/i,
|
|
33
|
+
/pnpm-lock\.yaml$/i,
|
|
34
|
+
/yarn\.lock$/i,
|
|
35
|
+
/\.env(\..+)?$/i,
|
|
36
|
+
];
|
|
37
|
+
export const DEFAULT_SEMANTIC_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
38
|
+
export const SEMANTIC_ACTIVATION_HINT = 'Semantic search disabled. For better results: npm install -g @huggingface/transformers or add it as a dependency, then restart. Falling back to lexical search.';
|
|
39
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AAEtC,MAAM,CAAC,MAAM,sBAAsB,GAAG,SAAS,CAAC;AAEhD,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAExC,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAE1C,MAAM,CAAC,MAAM,YAAY,GAAsB;IAC7C,cAAc;IACd,MAAM;IACN,MAAM;IACN,OAAO;IACP,KAAK;IACL,OAAO;IACP,OAAO;IACP,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,aAAa;IACb,OAAO;IACP,MAAM;IACN,aAAa;IACb,eAAe;IACf,UAAU;IACV,OAAO;IACP,SAAS;IACT,QAAQ;IACR,KAAK;CACN,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAsB;IACtD,iCAAiC;IACjC,sBAAsB;IACtB,mBAAmB;IACnB,cAAc;IACd,gBAAgB;CACjB,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAEhE,MAAM,CAAC,MAAM,wBAAwB,GACnC,iKAAiK,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAazE,wBAAsB,SAAS,CAC7B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAsCf"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
|
+
async function readBody(req) {
|
|
4
|
+
const chunks = [];
|
|
5
|
+
for await (const chunk of req) {
|
|
6
|
+
chunks.push(chunk);
|
|
7
|
+
}
|
|
8
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
9
|
+
}
|
|
10
|
+
export async function startHttp(server, port) {
|
|
11
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
12
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
13
|
+
if (url.pathname !== "/mcp") {
|
|
14
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
15
|
+
res.end(JSON.stringify({ error: "not found" }));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const origin = req.headers.origin;
|
|
19
|
+
if (origin &&
|
|
20
|
+
!/^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/.test(origin)) {
|
|
21
|
+
res.writeHead(403, { "content-type": "application/json" });
|
|
22
|
+
res.end(JSON.stringify({ error: "origin not allowed (local-only server)" }));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (req.method !== "POST") {
|
|
26
|
+
res.writeHead(405, { "content-type": "application/json" });
|
|
27
|
+
res.end(JSON.stringify({ error: "method not allowed; POST JSON-RPC only" }));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const body = await readBody(req);
|
|
31
|
+
const transport = new StreamableHTTPServerTransport({
|
|
32
|
+
sessionIdGenerator: undefined,
|
|
33
|
+
enableJsonResponse: true,
|
|
34
|
+
});
|
|
35
|
+
res.on("close", () => transport.close());
|
|
36
|
+
await server.connect(transport);
|
|
37
|
+
await transport.handleRequest(req, res, body);
|
|
38
|
+
});
|
|
39
|
+
await new Promise((resolve) => {
|
|
40
|
+
httpServer.listen(port, "127.0.0.1", () => resolve());
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAEnG,KAAK,UAAU,QAAQ,CACrB,GAAyB;IAEzB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAiB,EACjB,IAAY;IAEZ,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACtD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAC5B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;QAClC,IACE,MAAM;YACN,CAAC,sDAAsD,CAAC,IAAI,CAAC,MAAM,CAAC,EACpE,CAAC;YACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,wCAAwC,EAAE,CAAC,CAAC,CAAC;YAC7E,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,wCAAwC,EAAE,CAAC,CAAC,CAAC;YAC7E,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC;YAClD,kBAAkB,EAAE,SAAS;YAC7B,kBAAkB,EAAE,IAAI;SACzB,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;QACzC,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { RepoManager } from "./services/repo-manager.js";
|
|
5
|
+
import { registerTools } from "./tools/register.js";
|
|
6
|
+
const VERSION = "0.1.0";
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: "localscope-mcp",
|
|
9
|
+
version: VERSION,
|
|
10
|
+
});
|
|
11
|
+
const manager = new RepoManager();
|
|
12
|
+
registerTools(server, manager);
|
|
13
|
+
async function main() {
|
|
14
|
+
const transportMode = process.env.LOCALSCOPE_TRANSPORT ?? "stdio";
|
|
15
|
+
if (transportMode === "http") {
|
|
16
|
+
const { startHttp } = await import("./http.js");
|
|
17
|
+
const port = Number(process.env.LOCALSCOPE_PORT ?? 3000);
|
|
18
|
+
await startHttp(server, port);
|
|
19
|
+
console.error(`localscope-mcp ${VERSION} listening on http://127.0.0.1:${port}/mcp`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const transport = new StdioServerTransport();
|
|
23
|
+
await server.connect(transport);
|
|
24
|
+
console.error(`localscope-mcp ${VERSION} ready (stdio, local-only)`);
|
|
25
|
+
}
|
|
26
|
+
main().catch((error) => {
|
|
27
|
+
console.error("fatal:", error instanceof Error ? error.message : error);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,gBAAgB;IACtB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAClC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/B,KAAK,UAAU,IAAI;IACjB,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC;IAElE,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;QAC7B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,CAAC;QACzD,MAAM,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,kBAAkB,OAAO,kCAAkC,IAAI,MAAM,CAAC,CAAC;QACrF,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,kBAAkB,OAAO,4BAA4B,CAAC,CAAC;AACvE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const ResponseFormatSchema: z.ZodDefault<z.ZodEnum<["markdown", "json"]>>;
|
|
3
|
+
export declare const IndexToolSchema: z.ZodObject<{
|
|
4
|
+
path: z.ZodDefault<z.ZodString>;
|
|
5
|
+
max_files: z.ZodDefault<z.ZodNumber>;
|
|
6
|
+
response_format: z.ZodDefault<z.ZodEnum<["markdown", "json"]>>;
|
|
7
|
+
}, "strict", z.ZodTypeAny, {
|
|
8
|
+
path: string;
|
|
9
|
+
max_files: number;
|
|
10
|
+
response_format: "markdown" | "json";
|
|
11
|
+
}, {
|
|
12
|
+
path?: string | undefined;
|
|
13
|
+
max_files?: number | undefined;
|
|
14
|
+
response_format?: "markdown" | "json" | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
export declare const SearchToolSchema: z.ZodObject<{
|
|
17
|
+
query: z.ZodString;
|
|
18
|
+
path: z.ZodDefault<z.ZodString>;
|
|
19
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
20
|
+
response_format: z.ZodDefault<z.ZodEnum<["markdown", "json"]>>;
|
|
21
|
+
}, "strict", z.ZodTypeAny, {
|
|
22
|
+
query: string;
|
|
23
|
+
path: string;
|
|
24
|
+
response_format: "markdown" | "json";
|
|
25
|
+
limit: number;
|
|
26
|
+
}, {
|
|
27
|
+
query: string;
|
|
28
|
+
path?: string | undefined;
|
|
29
|
+
response_format?: "markdown" | "json" | undefined;
|
|
30
|
+
limit?: number | undefined;
|
|
31
|
+
}>;
|
|
32
|
+
export declare const ImpactToolSchema: z.ZodObject<{
|
|
33
|
+
target: z.ZodString;
|
|
34
|
+
path: z.ZodDefault<z.ZodString>;
|
|
35
|
+
max_depth: z.ZodDefault<z.ZodNumber>;
|
|
36
|
+
response_format: z.ZodDefault<z.ZodEnum<["markdown", "json"]>>;
|
|
37
|
+
}, "strict", z.ZodTypeAny, {
|
|
38
|
+
target: string;
|
|
39
|
+
path: string;
|
|
40
|
+
response_format: "markdown" | "json";
|
|
41
|
+
max_depth: number;
|
|
42
|
+
}, {
|
|
43
|
+
target: string;
|
|
44
|
+
path?: string | undefined;
|
|
45
|
+
response_format?: "markdown" | "json" | undefined;
|
|
46
|
+
max_depth?: number | undefined;
|
|
47
|
+
}>;
|
|
48
|
+
export declare const StatusToolSchema: z.ZodObject<{
|
|
49
|
+
path: z.ZodDefault<z.ZodString>;
|
|
50
|
+
response_format: z.ZodDefault<z.ZodEnum<["markdown", "json"]>>;
|
|
51
|
+
}, "strict", z.ZodTypeAny, {
|
|
52
|
+
path: string;
|
|
53
|
+
response_format: "markdown" | "json";
|
|
54
|
+
}, {
|
|
55
|
+
path?: string | undefined;
|
|
56
|
+
response_format?: "markdown" | "json" | undefined;
|
|
57
|
+
}>;
|
|
58
|
+
export type IndexToolInput = z.infer<typeof IndexToolSchema>;
|
|
59
|
+
export type SearchToolInput = z.infer<typeof SearchToolSchema>;
|
|
60
|
+
export type ImpactToolInput = z.infer<typeof ImpactToolSchema>;
|
|
61
|
+
export type StatusToolInput = z.infer<typeof StatusToolSchema>;
|
|
62
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/schemas/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,oBAAoB,+CAGyD,CAAC;AAE3F,eAAO,MAAM,eAAe;;;;;;;;;;;;EAiBjB,CAAC;AAEZ,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;EAiBlB,CAAC;AAEZ,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;EAsBlB,CAAC;AAEZ,eAAO,MAAM,gBAAgB;;;;;;;;;EAUlB,CAAC;AAEZ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAC7D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC/D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC/D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const ResponseFormatSchema = z
|
|
3
|
+
.enum(["markdown", "json"])
|
|
4
|
+
.default("markdown")
|
|
5
|
+
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable");
|
|
6
|
+
export const IndexToolSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
path: z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1)
|
|
11
|
+
.max(4096)
|
|
12
|
+
.default(".")
|
|
13
|
+
.describe("Repository path to index (absolute, or relative to the directory the server was started in)"),
|
|
14
|
+
max_files: z
|
|
15
|
+
.number()
|
|
16
|
+
.int()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(200_000)
|
|
19
|
+
.default(50_000)
|
|
20
|
+
.describe("Safety cap on number of files to index"),
|
|
21
|
+
response_format: ResponseFormatSchema,
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export const SearchToolSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
query: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(2, "Query must be at least 2 characters")
|
|
29
|
+
.max(500, "Query must not exceed 500 characters")
|
|
30
|
+
.describe("Natural-language query, symbol name, or code fragment to find"),
|
|
31
|
+
path: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(4096)
|
|
35
|
+
.default(".")
|
|
36
|
+
.describe("Repository path (must match a previously indexed root)"),
|
|
37
|
+
limit: z.number().int().min(1).max(100).default(20)
|
|
38
|
+
.describe("Maximum results to return"),
|
|
39
|
+
response_format: ResponseFormatSchema,
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
export const ImpactToolSchema = z
|
|
43
|
+
.object({
|
|
44
|
+
target: z
|
|
45
|
+
.string()
|
|
46
|
+
.min(1)
|
|
47
|
+
.max(1024)
|
|
48
|
+
.describe("File path (e.g. 'src/utils/parse.ts') OR symbol name (e.g. 'parseConfig', 'UserService')"),
|
|
49
|
+
path: z
|
|
50
|
+
.string()
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(4096)
|
|
53
|
+
.default(".")
|
|
54
|
+
.describe("Repository path (must match a previously indexed root)"),
|
|
55
|
+
max_depth: z
|
|
56
|
+
.number()
|
|
57
|
+
.int()
|
|
58
|
+
.min(1)
|
|
59
|
+
.max(10)
|
|
60
|
+
.default(5)
|
|
61
|
+
.describe("How deep to walk the reverse dependency graph"),
|
|
62
|
+
response_format: ResponseFormatSchema,
|
|
63
|
+
})
|
|
64
|
+
.strict();
|
|
65
|
+
export const StatusToolSchema = z
|
|
66
|
+
.object({
|
|
67
|
+
path: z
|
|
68
|
+
.string()
|
|
69
|
+
.min(1)
|
|
70
|
+
.max(4096)
|
|
71
|
+
.default(".")
|
|
72
|
+
.describe("Repository path to check"),
|
|
73
|
+
response_format: ResponseFormatSchema,
|
|
74
|
+
})
|
|
75
|
+
.strict();
|
|
76
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/schemas/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,IAAI,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;KAC1B,OAAO,CAAC,UAAU,CAAC;KACnB,QAAQ,CAAC,6EAA6E,CAAC,CAAC;AAE3F,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC;KAC7B,MAAM,CAAC;IACN,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,OAAO,CAAC,GAAG,CAAC;SACZ,QAAQ,CAAC,6FAA6F,CAAC;IAC1G,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,OAAO,CAAC;SACZ,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CAAC,wCAAwC,CAAC;IACrD,eAAe,EAAE,oBAAoB;CACtC,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC;KAC9B,MAAM,CAAC;IACN,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,EAAE,qCAAqC,CAAC;SAC7C,GAAG,CAAC,GAAG,EAAE,sCAAsC,CAAC;SAChD,QAAQ,CAAC,+DAA+D,CAAC;IAC5E,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,OAAO,CAAC,GAAG,CAAC;SACZ,QAAQ,CAAC,wDAAwD,CAAC;IACrE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;SAChD,QAAQ,CAAC,2BAA2B,CAAC;IACxC,eAAe,EAAE,oBAAoB;CACtC,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC;KAC9B,MAAM,CAAC;IACN,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,QAAQ,CAAC,0FAA0F,CAAC;IACvG,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,OAAO,CAAC,GAAG,CAAC;SACZ,QAAQ,CAAC,wDAAwD,CAAC;IACrE,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CAAC,+CAA+C,CAAC;IAC5D,eAAe,EAAE,oBAAoB;CACtC,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC;KAC9B,MAAM,CAAC;IACN,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,OAAO,CAAC,GAAG,CAAC;SACZ,QAAQ,CAAC,0BAA0B,CAAC;IACvC,eAAe,EAAE,oBAAoB;CACtC,CAAC;KACD,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EmbedderKind } from "../types.js";
|
|
2
|
+
export interface Embedder {
|
|
3
|
+
readonly kind: EmbedderKind;
|
|
4
|
+
readonly embed: (texts: readonly string[]) => Promise<readonly number[][]>;
|
|
5
|
+
readonly query: (text: string) => Promise<readonly number[]>;
|
|
6
|
+
}
|
|
7
|
+
export declare function getOnnxEmbedder(modelId?: string): Promise<Embedder | null>;
|
|
8
|
+
export declare function tokenize(text: string): string[];
|
|
9
|
+
export declare const lexicalEmbedder: Embedder;
|
|
10
|
+
export declare function splitIdentifier(name: string): string[];
|
|
11
|
+
export declare function cosineSimilarity(a: readonly number[], b: readonly number[]): number;
|
|
12
|
+
export interface LexicalIndexEntry {
|
|
13
|
+
readonly chunkId: string;
|
|
14
|
+
readonly tf: ReadonlyMap<string, number>;
|
|
15
|
+
}
|
|
16
|
+
export declare function buildLexicalTf(chunkId: string, content: string, symbolName: string | null): LexicalIndexEntry;
|
|
17
|
+
export declare function lexicalScore(queryTokens: readonly string[], entry: LexicalIndexEntry): number;
|
|
18
|
+
//# sourceMappingURL=embedder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedder.d.ts","sourceRoot":"","sources":["../../src/services/embedder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,EAAE,CAAC,CAAC;IAC3E,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;CAC9D;AAgDD,wBAAgB,eAAe,CAC7B,OAAO,GAAE,MAA+B,GACvC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAK1B;AAgCD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAK/C;AAKD,eAAO,MAAM,eAAe,EAAE,QAI7B,CAAC;AAYF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQtD;AAED,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,SAAS,MAAM,EAAE,EACpB,CAAC,EAAE,SAAS,MAAM,EAAE,GACnB,MAAM,CAYR;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,iBAAiB,CAcnB;AAED,wBAAgB,YAAY,CAC1B,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,KAAK,EAAE,iBAAiB,GACvB,MAAM,CAOR"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { DEFAULT_SEMANTIC_MODEL } from "../constants.js";
|
|
2
|
+
let cachedOnnx = null;
|
|
3
|
+
async function createOnnxEmbedder(modelId) {
|
|
4
|
+
try {
|
|
5
|
+
const mod = (await import("@huggingface/transformers"));
|
|
6
|
+
const extractor = await mod.pipeline("feature-extraction", modelId, {
|
|
7
|
+
dtype: "q8",
|
|
8
|
+
});
|
|
9
|
+
const embedOne = async (text) => {
|
|
10
|
+
const out = await extractor(text);
|
|
11
|
+
return normalize(meanPool(out.data));
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
kind: { type: "onnx", model: modelId },
|
|
15
|
+
embed: async (texts) => {
|
|
16
|
+
if (texts.length === 0)
|
|
17
|
+
return [];
|
|
18
|
+
const results = [];
|
|
19
|
+
const batchSize = 16;
|
|
20
|
+
for (let i = 0; i < texts.length; i += batchSize) {
|
|
21
|
+
const batch = texts.slice(i, i + batchSize);
|
|
22
|
+
const batchResults = await Promise.all(batch.map(embedOne));
|
|
23
|
+
results.push(...batchResults);
|
|
24
|
+
}
|
|
25
|
+
return results;
|
|
26
|
+
},
|
|
27
|
+
query: (text) => embedOne(text),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function getOnnxEmbedder(modelId = DEFAULT_SEMANTIC_MODEL) {
|
|
35
|
+
if (!cachedOnnx) {
|
|
36
|
+
cachedOnnx = createOnnxEmbedder(modelId).catch(() => null);
|
|
37
|
+
}
|
|
38
|
+
return cachedOnnx;
|
|
39
|
+
}
|
|
40
|
+
const STOP_WORDS = new Set([
|
|
41
|
+
"the",
|
|
42
|
+
"a",
|
|
43
|
+
"an",
|
|
44
|
+
"and",
|
|
45
|
+
"or",
|
|
46
|
+
"if",
|
|
47
|
+
"then",
|
|
48
|
+
"else",
|
|
49
|
+
"for",
|
|
50
|
+
"of",
|
|
51
|
+
"to",
|
|
52
|
+
"in",
|
|
53
|
+
"on",
|
|
54
|
+
"at",
|
|
55
|
+
"by",
|
|
56
|
+
"is",
|
|
57
|
+
"it",
|
|
58
|
+
"as",
|
|
59
|
+
"with",
|
|
60
|
+
"this",
|
|
61
|
+
"that",
|
|
62
|
+
"be",
|
|
63
|
+
"from",
|
|
64
|
+
"are",
|
|
65
|
+
"we",
|
|
66
|
+
"you",
|
|
67
|
+
"i",
|
|
68
|
+
]);
|
|
69
|
+
export function tokenize(text) {
|
|
70
|
+
return text
|
|
71
|
+
.toLowerCase()
|
|
72
|
+
.split(/[^a-z0-9_$]+/)
|
|
73
|
+
.filter((t) => t.length > 1 && !STOP_WORDS.has(t));
|
|
74
|
+
}
|
|
75
|
+
const LEXICAL_NOTE = "lexical TF scoring over identifier-split tokens (no model installed)";
|
|
76
|
+
export const lexicalEmbedder = {
|
|
77
|
+
kind: { type: "lexical", note: LEXICAL_NOTE },
|
|
78
|
+
embed: async (texts) => texts.map(() => []),
|
|
79
|
+
query: async () => [],
|
|
80
|
+
};
|
|
81
|
+
function meanPool(data) {
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
function normalize(vec) {
|
|
85
|
+
const norm = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
|
|
86
|
+
if (norm === 0)
|
|
87
|
+
return vec;
|
|
88
|
+
return vec.map((v) => v / norm);
|
|
89
|
+
}
|
|
90
|
+
export function splitIdentifier(name) {
|
|
91
|
+
return name
|
|
92
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
93
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
94
|
+
.replace(/[_$-]+/g, " ")
|
|
95
|
+
.toLowerCase()
|
|
96
|
+
.split(/\s+/)
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
}
|
|
99
|
+
export function cosineSimilarity(a, b) {
|
|
100
|
+
let dot = 0;
|
|
101
|
+
let normA = 0;
|
|
102
|
+
let normB = 0;
|
|
103
|
+
const len = Math.min(a.length, b.length);
|
|
104
|
+
for (let i = 0; i < len; i++) {
|
|
105
|
+
dot += a[i] * b[i];
|
|
106
|
+
normA += a[i] * a[i];
|
|
107
|
+
normB += b[i] * b[i];
|
|
108
|
+
}
|
|
109
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
110
|
+
return denom === 0 ? 0 : dot / denom;
|
|
111
|
+
}
|
|
112
|
+
export function buildLexicalTf(chunkId, content, symbolName) {
|
|
113
|
+
const tokens = [
|
|
114
|
+
...tokenize(content),
|
|
115
|
+
...(symbolName ? splitIdentifier(symbolName) : []),
|
|
116
|
+
];
|
|
117
|
+
const tf = new Map();
|
|
118
|
+
for (const token of tokens) {
|
|
119
|
+
tf.set(token, (tf.get(token) ?? 0) + 1);
|
|
120
|
+
}
|
|
121
|
+
const total = tokens.length || 1;
|
|
122
|
+
for (const [k, v] of tf) {
|
|
123
|
+
tf.set(k, v / total);
|
|
124
|
+
}
|
|
125
|
+
return { chunkId, tf };
|
|
126
|
+
}
|
|
127
|
+
export function lexicalScore(queryTokens, entry) {
|
|
128
|
+
let score = 0;
|
|
129
|
+
for (const token of queryTokens) {
|
|
130
|
+
score += entry.tf.get(token) ?? 0;
|
|
131
|
+
}
|
|
132
|
+
const norm = Math.sqrt(queryTokens.length) || 1;
|
|
133
|
+
return score / norm;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=embedder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedder.js","sourceRoot":"","sources":["../../src/services/embedder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CodeChunk, Language, SourceSymbol } from "../types.js";
|
|
2
|
+
import { isSourceFile } from "./language.js";
|
|
3
|
+
export interface ExtractionResult {
|
|
4
|
+
readonly symbols: readonly SourceSymbol[];
|
|
5
|
+
readonly chunks: readonly CodeChunk[];
|
|
6
|
+
}
|
|
7
|
+
export declare function extractFromFile(content: string, filePath: string, language: Language): ExtractionResult;
|
|
8
|
+
export { isSourceFile };
|
|
9
|
+
//# sourceMappingURL=extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/services/extractor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EACR,YAAY,EAEb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AA4G7C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;CACvC;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,GACjB,gBAAgB,CAuFlB;AA2DD,OAAO,EAAE,YAAY,EAAE,CAAC"}
|