graph-indexer 1.2.0 → 2.0.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/CHANGELOG.md +412 -0
- package/README.md +216 -596
- package/SECURITY.md +68 -0
- package/TRADEMARK.md +31 -0
- package/cli-ui.mjs +78 -0
- package/config.mjs +179 -28
- package/daemon-ctl.mjs +175 -0
- package/daemon-lock.mjs +69 -0
- package/embedders/python/mlx_embed_server.py +61 -0
- package/embedders/python/requirements-mlx.txt +5 -0
- package/embedders/setup-mlx.mjs +110 -0
- package/embeddings.mjs +489 -0
- package/engine/binary.mjs +399 -0
- package/{core-engine.mjs → engine/memory.mjs} +107 -440
- package/{sqlite-store.mjs → engine/sqlite.mjs} +137 -42
- package/enrichment.mjs +111 -32
- package/git-signals.mjs +248 -0
- package/indexer.mjs +151 -42
- package/init.mjs +1603 -171
- package/layout.mjs +123 -0
- package/mcp/format.mjs +61 -0
- package/mcp/tools.mjs +1157 -0
- package/mcp/topology.mjs +551 -0
- package/mcp-server.mjs +55 -24
- package/package.json +44 -13
- package/parse/extractor.mjs +617 -0
- package/parse/imports.mjs +365 -0
- package/parse/languages.mjs +89 -0
- package/parse/metadata.mjs +655 -0
- package/parse/routes.mjs +327 -0
- package/prompts/CORE.md +86 -0
- package/prompts/DOMAIN_TEMPLATE.md +46 -0
- package/prompts/INTEGRATION.md +123 -0
- package/prompts/frameworks/ANDROID.md +17 -0
- package/prompts/frameworks/ASPNET_CORE.md +19 -0
- package/prompts/frameworks/FASTAPI_DJANGO.md +19 -0
- package/prompts/frameworks/LARAVEL_SYMFONY.md +22 -0
- package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +18 -0
- package/prompts/frameworks/RAILS.md +20 -0
- package/prompts/frameworks/REACT.md +20 -0
- package/prompts/frameworks/SPRING_BOOT.md +19 -0
- package/prompts/languages/BASH.md +22 -0
- package/prompts/languages/C.md +21 -0
- package/prompts/languages/CSHARP.md +24 -0
- package/prompts/languages/CSS_SCSS.md +22 -0
- package/prompts/languages/GO.md +23 -0
- package/prompts/languages/JAVA.md +23 -0
- package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +24 -0
- package/prompts/languages/KOTLIN.md +24 -0
- package/prompts/languages/PHP.md +23 -0
- package/prompts/languages/PYTHON.md +23 -0
- package/prompts/languages/RUBY.md +23 -0
- package/prompts/languages/RUST.md +21 -0
- package/prompts/languages/SWIFT.md +21 -0
- package/search-core.mjs +254 -43
- package/storage.mjs +28 -3
- package/watch-daemon.mjs +69 -23
- package/PROMPT.md +0 -182
- package/mcp-tools.mjs +0 -545
- package/parser-utils.mjs +0 -1170
package/watch-daemon.mjs
CHANGED
|
@@ -27,24 +27,53 @@ import fs from 'fs';
|
|
|
27
27
|
import path from 'path';
|
|
28
28
|
import chokidar from 'chokidar';
|
|
29
29
|
import { resolveConfig } from './config.mjs';
|
|
30
|
-
import {
|
|
30
|
+
import { ensureDataDir, migrateLegacyLayout } from './layout.mjs';
|
|
31
|
+
import { acquireLock, releaseLock, readPid } from './daemon-lock.mjs';
|
|
32
|
+
import { createStore, resolveBackend } from './storage.mjs';
|
|
31
33
|
import {
|
|
32
34
|
embeddingKeyFor, computePageRank, TEST_FILE_RE, EXAMPLE_DIR_RE,
|
|
33
|
-
summaryEmbeddingText, SUMMARY_VEC_SUFFIX,
|
|
35
|
+
summaryEmbeddingText, SUMMARY_VEC_SUFFIX, WINDOW_VEC_SUFFIX, embeddingWindows,
|
|
34
36
|
} from './search-core.mjs';
|
|
35
|
-
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} from './parser-utils.mjs';
|
|
37
|
+
import { MAX_FILE_SIZE_BYTES, buildIgnoreFilter, extractImportsFromAST, extractSemanticChunks } from './parse/extractor.mjs';
|
|
38
|
+
import { getParserForFile } from './parse/languages.mjs';
|
|
39
|
+
import { resolveLocalImports, buildEmbeddingPayload, fullBodyForEmbedding } from './parse/imports.mjs';
|
|
40
|
+
import { createEmbedder, readEmbedMeta } from './embeddings.mjs';
|
|
40
41
|
import {
|
|
41
42
|
loadEnrichmentCache, saveEnrichmentCache, attachEnrichment,
|
|
42
|
-
ollamaGenerate, parseEnrichResponse, buildEnrichPrompt,
|
|
43
|
+
ollamaGenerate, parseEnrichResponse, buildEnrichPrompt, resolveEnrichModel,
|
|
43
44
|
} from './enrichment.mjs';
|
|
44
45
|
|
|
45
46
|
const config = resolveConfig();
|
|
46
47
|
const PROJECT_ROOT = config.projectRoot;
|
|
47
48
|
|
|
49
|
+
// Embed incremental updates with the SAME provider/model the index was built with
|
|
50
|
+
// (stamped in the embeddings-bin sidecar), so live vectors match the bootstrap
|
|
51
|
+
// ones. Built lazily and cached for the daemon's lifetime.
|
|
52
|
+
let _embedder = null;
|
|
53
|
+
async function getEmbedder() {
|
|
54
|
+
if (_embedder) return _embedder;
|
|
55
|
+
const m = readEmbedMeta(config.embeddingPath);
|
|
56
|
+
_embedder = await createEmbedder(config, m?.provider
|
|
57
|
+
? { provider: m.provider, model: m.model }
|
|
58
|
+
: { provider: config.embeddingsEnabled ? 'ollama' : 'off', model: config.embedModel });
|
|
59
|
+
return _embedder;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Single-instance guard ──────────────────────────────────────────────────────
|
|
63
|
+
// Exactly one daemon may run per project. Acquire the PID lock BEFORE doing any
|
|
64
|
+
// work (loading the store, opening the db) so a redundant launch — via the MCP
|
|
65
|
+
// server, `idx-daemon start`, or a stray `idx-watch` — exits immediately instead
|
|
66
|
+
// of racing a second watcher onto the same index.
|
|
67
|
+
ensureDataDir(PROJECT_ROOT);
|
|
68
|
+
migrateLegacyLayout(PROJECT_ROOT);
|
|
69
|
+
if (!acquireLock(config.pidFile)) {
|
|
70
|
+
process.stderr.write(
|
|
71
|
+
`✋ A graph-indexer daemon is already running for this project `
|
|
72
|
+
+ `(PID ${readPid(config.pidFile)}). Exiting — only one runs per project.\n`
|
|
73
|
+
);
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
48
77
|
const ignoreFilter = buildIgnoreFilter(PROJECT_ROOT);
|
|
49
78
|
const db = await createStore(config, { cacheEmbeddings: true });
|
|
50
79
|
db.load();
|
|
@@ -69,6 +98,14 @@ function scheduleEnrichCacheSave() {
|
|
|
69
98
|
}, 3000);
|
|
70
99
|
}
|
|
71
100
|
|
|
101
|
+
// "auto" resolves to the strongest available local code model, same policy as the bootstrap indexer.
|
|
102
|
+
let _enrichModel = null;
|
|
103
|
+
async function getEnrichModel() {
|
|
104
|
+
if (_enrichModel) return _enrichModel;
|
|
105
|
+
_enrichModel = await resolveEnrichModel(config.enrichment.model, config.ollamaHost);
|
|
106
|
+
return _enrichModel;
|
|
107
|
+
}
|
|
108
|
+
|
|
72
109
|
let _coreFiles = null;
|
|
73
110
|
function isEnrichableFile(filename) {
|
|
74
111
|
if (TEST_FILE_RE.test(filename) || EXAMPLE_DIR_RE.test(filename)) return false;
|
|
@@ -94,10 +131,11 @@ async function enrichChunks(filename, chunks) {
|
|
|
94
131
|
// Live enrichment of changed core chunks: sequential and best-effort — a file
|
|
95
132
|
// save touches a handful of chunks, so latency stays low and failures only
|
|
96
133
|
// mean "no enrichment until the next full index run".
|
|
134
|
+
const model = await getEnrichModel();
|
|
97
135
|
for (const chunk of pending) {
|
|
98
136
|
if ((chunk.end_line - chunk.start_line) < 4) continue; // trivial stubs
|
|
99
137
|
const raw = await ollamaGenerate(buildEnrichPrompt(chunk), {
|
|
100
|
-
model
|
|
138
|
+
model, ollamaHost: config.ollamaHost, timeoutMs: 20000,
|
|
101
139
|
});
|
|
102
140
|
const parsed = parseEnrichResponse(raw);
|
|
103
141
|
if (!parsed) break; // model unreachable — stop trying for this batch
|
|
@@ -105,7 +143,7 @@ async function enrichChunks(filename, chunks) {
|
|
|
105
143
|
chunk.concepts = parsed.concepts;
|
|
106
144
|
chunk.hyde = parsed.hyde;
|
|
107
145
|
enrichCache.set(chunk.content_hash, {
|
|
108
|
-
summary: parsed.summary, concepts: parsed.concepts, model
|
|
146
|
+
summary: parsed.summary, concepts: parsed.concepts, model,
|
|
109
147
|
});
|
|
110
148
|
enrichCacheDirty = true;
|
|
111
149
|
}
|
|
@@ -118,7 +156,8 @@ async function enrichChunks(filename, chunks) {
|
|
|
118
156
|
// not modified since the index artifact was last written are already indexed —
|
|
119
157
|
// skipping them turns daemon start from O(repo) parse work into O(changed files),
|
|
120
158
|
// while still picking up edits made while the daemon was down.
|
|
121
|
-
const
|
|
159
|
+
const BACKEND = resolveBackend(config); // 'auto' → concrete backend by artifact presence.
|
|
160
|
+
const ARTIFACT_PATH = BACKEND === 'sqlite' ? config.sqlitePath : config.indexPath;
|
|
122
161
|
let indexBuiltAt = 0;
|
|
123
162
|
try { indexBuiltAt = fs.statSync(ARTIFACT_PATH).mtimeMs; } catch { /* no index yet → full scan */ }
|
|
124
163
|
let initialScan = true;
|
|
@@ -158,25 +197,29 @@ async function processFileChange(absolutePath) {
|
|
|
158
197
|
const newChunks = extractSemanticChunks(tree.rootNode, filename, content, ext);
|
|
159
198
|
_coreFiles = null; // dependency graph may change → PageRank core set is stale
|
|
160
199
|
|
|
161
|
-
// Enrichment BEFORE embedding
|
|
162
|
-
//
|
|
200
|
+
// Enrichment BEFORE embedding: summary rides the vector payload and
|
|
201
|
+
// embeddingKeyFor accounts for it, so cache lookups stay correct.
|
|
163
202
|
await enrichChunks(filename, newChunks);
|
|
164
203
|
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
// including the summary-only second vector for enriched chunks.
|
|
204
|
+
// Identical embedding payloads to indexer.mjs (shared helpers) so incremental
|
|
205
|
+
// updates match the bootstrap index, including summary-only vectors for enriched chunks.
|
|
168
206
|
const chunksToEmbed = newChunks.filter(c => !db.hasEmbedding(embeddingKeyFor(c)));
|
|
169
207
|
const embeddings = new Map();
|
|
170
208
|
if (chunksToEmbed.length > 0) {
|
|
171
209
|
const entries = [];
|
|
172
210
|
for (const c of chunksToEmbed) {
|
|
173
|
-
|
|
211
|
+
const baseKey = embeddingKeyFor(c);
|
|
212
|
+
// Oversized definitions embed their full body (windowed) while the lexical path
|
|
213
|
+
// keeps the 3000-char snippet — parity with how indexer.mjs builds payloads.
|
|
214
|
+
const payload = buildEmbeddingPayload(c, imports, fullBodyForEmbedding(c, content));
|
|
215
|
+
entries.push({ key: baseKey, text: payload });
|
|
216
|
+
const windows = embeddingWindows(payload); // tail windows for oversized chunks
|
|
217
|
+
for (let i = 1; i < windows.length; i++) entries.push({ key: baseKey + WINDOW_VEC_SUFFIX + i, text: windows[i] });
|
|
174
218
|
const sText = summaryEmbeddingText(c);
|
|
175
|
-
if (sText) entries.push({ key:
|
|
219
|
+
if (sText) entries.push({ key: baseKey + SUMMARY_VEC_SUFFIX, text: sText });
|
|
176
220
|
}
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
});
|
|
221
|
+
const embedder = await getEmbedder();
|
|
222
|
+
const embeddingsMatrix = await embedder.embedDocuments(entries.map(e => e.text));
|
|
180
223
|
if (embeddingsMatrix && embeddingsMatrix.length === entries.length) {
|
|
181
224
|
entries.forEach((entry, j) => {
|
|
182
225
|
if (embeddingsMatrix[j]) embeddings.set(entry.key, new Float32Array(embeddingsMatrix[j]));
|
|
@@ -187,7 +230,7 @@ async function processFileChange(absolutePath) {
|
|
|
187
230
|
db.applyFileUpdate(filename, { chunks: newChunks, imports, embeddings });
|
|
188
231
|
process.stderr.write(
|
|
189
232
|
`[daemon] 🔄 Synced: ${filename} (${newChunks.length} chunks, `
|
|
190
|
-
+ `${newChunks.length - chunksToEmbed.length} cached vectors, ${
|
|
233
|
+
+ `${newChunks.length - chunksToEmbed.length} cached vectors, ${BACKEND})\n`
|
|
191
234
|
);
|
|
192
235
|
} catch (err) {
|
|
193
236
|
process.stderr.write(`[daemon] ❌ Error in ${filename}: ${err.message}\n`);
|
|
@@ -201,7 +244,7 @@ function enqueue(absolutePath) {
|
|
|
201
244
|
queue = queue.then(() => processFileChange(absolutePath)).catch(() => {});
|
|
202
245
|
}
|
|
203
246
|
|
|
204
|
-
process.stderr.write(`🚀 Watcher Daemon started in: ${PROJECT_ROOT} (backend: ${
|
|
247
|
+
process.stderr.write(`🚀 Watcher Daemon started in: ${PROJECT_ROOT} (backend: ${BACKEND})\n`);
|
|
205
248
|
|
|
206
249
|
// Only watch source files we can actually index. Critically, this prevents
|
|
207
250
|
// chokidar from descending into node_modules / dist / .git / .gitignored dirs —
|
|
@@ -236,3 +279,6 @@ function shutdown() {
|
|
|
236
279
|
}
|
|
237
280
|
process.on('SIGTERM', shutdown);
|
|
238
281
|
process.on('SIGINT', shutdown);
|
|
282
|
+
// Always relinquish the single-instance lock on the way out (covers shutdown(),
|
|
283
|
+
// uncaught exits and normal termination) so the next launch can take over cleanly.
|
|
284
|
+
process.on('exit', () => releaseLock(config.pidFile));
|
package/PROMPT.md
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
# graph-indexer — Agent Instructions
|
|
2
|
-
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
## Prime directive
|
|
6
|
-
|
|
7
|
-
This codebase is **pre-indexed**. The `graph-indexer` MCP server answers code
|
|
8
|
-
questions from an AST-precise search index in tens of tokens, with cross-file
|
|
9
|
-
topology your file tools cannot see.
|
|
10
|
-
|
|
11
|
-
**Every code discovery task starts with a graph-indexer tool. Never start with
|
|
12
|
-
a native file tool** — no `read file`, `list directory`, `grep`, `codebase
|
|
13
|
-
search`, `find`, or equivalent — regardless of what your IDE calls them.
|
|
14
|
-
Reading a file to "look around" costs 5,000–15,000 tokens; the same answer via
|
|
15
|
-
the index costs 50–700. The difference compounds over every step of your
|
|
16
|
-
session.
|
|
17
|
-
|
|
18
|
-
Native tools are a **last resort**, permitted only under the fallback rules at
|
|
19
|
-
the end of this document. Editing files is unaffected — write code with your
|
|
20
|
-
normal tools; *discovery* belongs to the index.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## The five rules
|
|
25
|
-
|
|
26
|
-
1. **Search before you read.** First step of any discovery task is
|
|
27
|
-
`search_code`, `resolve_symbol`, or `get_repo_map` — never a file read.
|
|
28
|
-
2. **Know the name? Don't search it.** `resolve_symbol("validateToken")` is an
|
|
29
|
-
O(1) exact lookup with topology included. Use it whenever you know the
|
|
30
|
-
symbol's exact name. Use `search_code` only for concepts and behaviour.
|
|
31
|
-
3. **Never read a whole file to get one function.** Search results give you a
|
|
32
|
-
chunk ID → `get_chunk(id)` returns exactly that function's body.
|
|
33
|
-
4. **Never change a signature blind.** Before modifying any exported
|
|
34
|
-
function/class, `get_call_graph("name")` lists every caller repo-wide in
|
|
35
|
-
one call. Skipping this breaks code you cannot see.
|
|
36
|
-
5. **Escalate token spend stepwise.** signatures → summary → smart → full
|
|
37
|
-
body. Start cheap; pay for bodies only when you must (ladder below).
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Tool selection — decision table
|
|
42
|
-
|
|
43
|
-
| You need… | Call | ~Tokens |
|
|
44
|
-
| :--- | :--- | ---: |
|
|
45
|
-
| Orientation in an unfamiliar repo | `get_repo_map()` | 1,500 |
|
|
46
|
-
| Orientation in one subsystem | `get_repo_map(path_filter: "auth")` | 300 |
|
|
47
|
-
| A symbol you can name exactly | `resolve_symbol("UserService")` | 50 |
|
|
48
|
-
| Code for a concept/behaviour | `search_code(query, detail: "signatures")` | 100 |
|
|
49
|
-
| The interface of a found chunk | `get_chunk_summary(id)` | 50 |
|
|
50
|
-
| Interfaces of everything a chunk calls | `get_chunk_summary(id, expand_calls: true)` | 150 |
|
|
51
|
-
| The full implementation | `get_chunk(id)` | 300 |
|
|
52
|
-
| All exports of one known file | `get_file_skeleton("src/file.ts")` | 80 |
|
|
53
|
-
| Who calls a function (pre-refactor) | `get_call_graph("name")` | 100 |
|
|
54
|
-
| What a file imports / who imports it | resource `graph://dependencies/{path}` | 100 |
|
|
55
|
-
| Index health / why results look off | `list_index_stats()` | 100 |
|
|
56
|
-
| *(comparison)* reading one file directly | — | 5,000+ |
|
|
57
|
-
|
|
58
|
-
### Escalation ladder (token budget)
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
1. search_code(query, detail: "signatures", top_k: 5) ~100 tok — find candidates
|
|
62
|
-
2. get_chunk_summary(id) ~50 tok — confirm the interface
|
|
63
|
-
3. search_code(query, detail: "smart") ~750 tok — see query-relevant lines
|
|
64
|
-
4. get_chunk(id) ~300 tok — full body, last resort
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Stop climbing as soon as you can answer. Most questions die at step 1–2.
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## Writing queries that hit
|
|
72
|
-
|
|
73
|
-
`search_code` is hybrid (BM25 keywords + semantic vectors) and **detects your
|
|
74
|
-
query style automatically**. Both styles are first-class:
|
|
75
|
-
|
|
76
|
-
**Keyword style** — when you know domain words or partial names:
|
|
77
|
-
- `JWT token validation expiry middleware`
|
|
78
|
-
- `route registration path method handler`
|
|
79
|
-
- Partial name known? Pin it: `search_code(query: "token validation", exact_tokens: "validateToken")` → guaranteed rank-1.
|
|
80
|
-
- Use resolve_symbol("Name") when you ONLY need the definition of that specific symbol. Use search_code(query: "...", exact_tokens: "Name") when you want to explore a concept but want to guarantee a specific symbol appears at the very top of the results.
|
|
81
|
-
|
|
82
|
-
**Natural-language style** — when you only know the behaviour:
|
|
83
|
-
- `How does the application parse incoming JSON payloads from the client?`
|
|
84
|
-
- `the logic that decides whether a requested URL matches a registered route`
|
|
85
|
-
- `stopping an HTTP call that takes too long or is no longer needed`
|
|
86
|
-
|
|
87
|
-
Rules of thumb:
|
|
88
|
-
- Describe **behaviour, not nouns**: `parse and validate request body schema`, not `validation`.
|
|
89
|
-
- One-word queries (`auth`, `request`, `handler`) are useless — thousands of matches.
|
|
90
|
-
- Include path hints when you have them: `middleware auth route` boosts `auth/middleware.ts`.
|
|
91
|
-
- **A keyword miss is not a dead end**: rephrase the same need as a full
|
|
92
|
-
behavioural sentence — the semantic channel finds code that shares none of
|
|
93
|
-
your words. This rephrase is *mandatory* before any native-tool fallback.
|
|
94
|
-
- Slow-but-smarter option: `search_code(..., rerank: true)` has a local LLM
|
|
95
|
-
judge reorder the top results (~1–2 s). Use it when a natural-language query
|
|
96
|
-
returns plausible-but-not-quite results.
|
|
97
|
-
|
|
98
|
-
### Reading results
|
|
99
|
-
|
|
100
|
-
Every result card includes **free topology** — use it instead of new searches:
|
|
101
|
-
- `Deps:` what this file imports (with key symbols per import)
|
|
102
|
-
- `Used by:` which files import this one
|
|
103
|
-
- `Calls:` functions this chunk invokes
|
|
104
|
-
- `ID:` pass to `get_chunk` / `get_chunk_summary` — never re-search what you already found.
|
|
105
|
-
|
|
106
|
-
A result with node type `re_export` means the symbol is re-exported from a
|
|
107
|
-
dependency (e.g. `fastapi` re-exporting Starlette's `BackgroundTasks`): the
|
|
108
|
-
implementation lives outside this repo — do not hunt for it in the codebase.
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## Standard workflows
|
|
113
|
-
|
|
114
|
-
**Unfamiliar codebase** → `get_repo_map()` → pick files → `resolve_symbol` /
|
|
115
|
-
`search_code` to drill in.
|
|
116
|
-
|
|
117
|
-
**"Where is X implemented?"** → `search_code(behavioural query, detail:
|
|
118
|
-
"signatures")` → `get_chunk_summary(best id)` → `get_chunk(id)` only if you
|
|
119
|
-
must see the body.
|
|
120
|
-
|
|
121
|
-
**Safe refactor** → `get_call_graph("target")` → review callers →
|
|
122
|
-
`get_chunk(callerId)` for each call site you need to update → edit with your
|
|
123
|
-
normal tools.
|
|
124
|
-
|
|
125
|
-
**Understanding one file** → `get_file_skeleton(path)` (all exports + line
|
|
126
|
-
numbers, no bodies) → drill into specific symbols.
|
|
127
|
-
|
|
128
|
-
**Debugging a behaviour** → natural-language `search_code` describing the
|
|
129
|
-
symptom's mechanism → follow `Calls:` topology instead of re-searching.
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## Anti-patterns (each of these is a bug in your behaviour)
|
|
134
|
-
|
|
135
|
-
- ❌ Reading a file as your *first* move on any question.
|
|
136
|
-
- ❌ `search_code("validateToken")` — you know the name; that's `resolve_symbol`.
|
|
137
|
-
- ❌ Re-searching a concept you already have a chunk ID for.
|
|
138
|
-
- ❌ `detail: "full"` by default — signatures first, always.
|
|
139
|
-
- ❌ Editing an exported signature without `get_call_graph` first.
|
|
140
|
-
- ❌ Giving up after one keyword query without trying a behavioural rephrase.
|
|
141
|
-
- ❌ Ignoring `Used by:` / `Calls:` topology and issuing new searches for it.
|
|
142
|
-
- ❌ Re-indexing after edits: Do not tell the user to re-index or attempt to re-index after you modify a file. Graph Indexer has a live daemon (watch-daemon.mjs) that instantly updates the index in the background using SQLite WAL. Your next search will automatically see your changes.
|
|
143
|
-
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
## Fallback to native tools — the ONLY permitted cases
|
|
147
|
-
|
|
148
|
-
You may use native file/search tools **only** when one of these is true:
|
|
149
|
-
|
|
150
|
-
1. **The index says no.** You tried BOTH query styles (keyword + behavioural
|
|
151
|
-
sentence) and `resolve_symbol`, and the target genuinely isn't returned.
|
|
152
|
-
2. **The index is unhealthy.** `list_index_stats()` shows 0 chunks, a missing
|
|
153
|
-
index, or a clearly stale index that the daemon isn't updating — tell the
|
|
154
|
-
user to run `npm run mcp:index`, then fall back for now.
|
|
155
|
-
3. **Non-code files.** Configs, lockfiles, markdown, data files, generated
|
|
156
|
-
artifacts — the index covers source code chunks; plain files are fair game.
|
|
157
|
-
4. **You need exact current file state for an edit** you are about to make
|
|
158
|
-
(e.g. precise surrounding lines to produce a diff) — *after* the index
|
|
159
|
-
located the file and line range for you.
|
|
160
|
-
|
|
161
|
-
When you do fall back, scope it: read the one file the index pointed you to —
|
|
162
|
-
never directory-walk or repo-grep what the index already answered.
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
## Quick reference
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
search_code(query, exact_tokens?, detail?, top_k?, rerank?) hybrid search, topology included
|
|
170
|
-
resolve_symbol(symbol) O(1) exact definition lookup
|
|
171
|
-
get_chunk(chunk_id, view?) full body of one chunk
|
|
172
|
-
get_chunk_summary(chunk_id, expand_calls?) interface only, ~50 tok
|
|
173
|
-
get_file_skeleton(file_path) all exports of a file, no bodies
|
|
174
|
-
get_call_graph(target_function) every caller repo-wide
|
|
175
|
-
get_repo_map(path_filter?, max_files?) PageRank-ordered codebase map
|
|
176
|
-
list_index_stats() index health + search mode
|
|
177
|
-
graph://dependencies/{file_path} bidirectional import topology
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
**Detail levels:** `signatures` ~20 tok/result · `smart` (default) ~150 ·
|
|
181
|
-
`full` ~300. **Search modes:** hybrid (semantic + lexical) when Ollama runs;
|
|
182
|
-
lexical-only otherwise — in lexical-only mode prefer keyword-style queries.
|