purecontext-mcp 1.1.0 → 1.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 (45) hide show
  1. package/AGENT_INSTRUCTIONS.md +509 -0
  2. package/AGENT_INSTRUCTIONS_SHORT.md +97 -0
  3. package/CHANGELOG.md +212 -0
  4. package/docs/01-introduction.md +69 -0
  5. package/docs/02-installation.md +267 -0
  6. package/docs/03-quick-start.md +135 -0
  7. package/docs/04-configuration.md +214 -0
  8. package/docs/05-cli-reference.md +130 -0
  9. package/docs/06-tools-reference.md +499 -0
  10. package/docs/07-language-support.md +88 -0
  11. package/docs/08-framework-adapters.md +324 -0
  12. package/docs/09-dependency-graph.md +182 -0
  13. package/docs/10-semantic-search.md +153 -0
  14. package/docs/11-search-quality.md +110 -0
  15. package/docs/12-ai-summarization.md +106 -0
  16. package/docs/13-token-savings.md +110 -0
  17. package/docs/14-transport-modes.md +167 -0
  18. package/docs/15-team-setup.md +251 -0
  19. package/docs/16-docker.md +186 -0
  20. package/docs/17-web-ui.md +157 -0
  21. package/docs/18-git-history.md +157 -0
  22. package/docs/19-cross-repo.md +177 -0
  23. package/docs/20-architecture-analysis.md +228 -0
  24. package/docs/21-ecosystem-tools.md +189 -0
  25. package/docs/22-distribution.md +240 -0
  26. package/docs/23-performance.md +121 -0
  27. package/docs/24-security.md +144 -0
  28. package/docs/25-architecture-overview.md +240 -0
  29. package/docs/26-troubleshooting.md +234 -0
  30. package/docs/27-api-stability.md +114 -0
  31. package/docs/README.md +71 -0
  32. package/guide/README.md +57 -0
  33. package/guide/ai-summaries.md +127 -0
  34. package/guide/code-health.md +190 -0
  35. package/guide/code-history.md +149 -0
  36. package/guide/finding-code.md +157 -0
  37. package/guide/navigating-new-code.md +121 -0
  38. package/guide/safe-changes.md +156 -0
  39. package/guide/team-setup.md +191 -0
  40. package/guide/web-ui.md +154 -0
  41. package/guide/why-purecontext.md +73 -0
  42. package/guide/workflow-onboarding.md +114 -0
  43. package/guide/workflow-pr-review.md +199 -0
  44. package/guide/workflow-refactoring.md +172 -0
  45. package/package.json +9 -2
@@ -0,0 +1,324 @@
1
+ # Framework Adapters
2
+
3
+
4
+ Framework adapters layer domain-specific symbol extraction on top of language handlers. They are auto-detected from project config files.
5
+
6
+ ## How adapters work
7
+
8
+ Each adapter implements the `FrameworkAdapter` interface:
9
+
10
+ - `detect(projectRoot)` — returns `true` if this framework is present (checks `package.json`, `go.mod`, etc.)
11
+ - `fileFilter(filePath)` — returns `true` for files this adapter should process
12
+ - `preProcess(source, filePath)` — splits multi-language files into parseable blocks (e.g., Vue SFCs)
13
+ - `extractFrameworkSymbols(tree, source, filePath)` — extracts framework-specific symbols from the AST
14
+ - `enrichMetadata(symbol)` — adds `frameworkMeta` to existing symbols
15
+
16
+ Multiple adapters can be active simultaneously. Adapters compose — a Vue + Nuxt project activates both.
17
+
18
+ ## Configuring adapters
19
+
20
+ ```json
21
+ {
22
+ "adapters": "auto" // auto-detect from project files (default)
23
+ "adapters": "none" // disable all adapters
24
+ "adapters": ["vue", "nuxt"] // explicit list
25
+ }
26
+ ```
27
+
28
+ ---
29
+
30
+ ## JavaScript / TypeScript frameworks
31
+
32
+ ### Vue 3
33
+
34
+ **Detected by:** `vue` in `package.json`, or any `.vue` file present.
35
+
36
+ Extracts from `.vue` Single File Components:
37
+ - `component` — the component itself (from filename or `defineComponent`)
38
+ - `composable` — exported `use*` functions
39
+
40
+ `frameworkMeta` includes: `vue_component: true`, `vue_composable: true`.
41
+
42
+ ### Nuxt
43
+
44
+ **Detected by:** `nuxt.config.ts` (or `.js`, `.mts`, `.mjs`) in project root.
45
+
46
+ Extracts:
47
+ - `route` from `pages/**/*.vue` (derives route path: `pages/blog/[slug].vue` → `/blog/:slug`)
48
+ - `route` from `server/api/**/*.ts` (HTTP method from filename suffix)
49
+ - `middleware` from `plugins/**` and `middleware/**`
50
+ - Enriches composables in `composables/**` with `nuxt_auto_import: true`
51
+
52
+ ### React
53
+
54
+ **Detected by:** `react` in `package.json` dependencies.
55
+
56
+ Enriches TypeScript handler symbols:
57
+ - PascalCase functions returning JSX → `component`
58
+ - `use*` functions → `hook`
59
+
60
+ ### Next.js
61
+
62
+ **Detected by:** `next.config.*` or `next` in `package.json`.
63
+
64
+ Extracts:
65
+ - **Pages Router** (`pages/**`): `route` symbols with path derivation
66
+ - `pages/blog/[slug].tsx` → `/blog/:slug`
67
+ - Detects `getServerSideProps` (SSR), `getStaticProps` (SSG)
68
+ - **App Router** (`app/**/page.tsx`): `route` symbols
69
+ - `app/(marketing)/about/page.tsx` → `/about` (route groups stripped)
70
+ - Detects `'use client'` directive
71
+ - **API routes**: `pages/api/**` and `app/**/route.ts` with HTTP method exports
72
+ - **Middleware** (`middleware.ts`): `middleware` symbol with matcher config
73
+
74
+ ### Angular
75
+
76
+ **Detected by:** `@angular/core` in `package.json`.
77
+
78
+ Extracts from decorated classes:
79
+ - `@Component` → `component` (with `selector`)
80
+ - `@Injectable` → `class` (service)
81
+ - `@NgModule` → `class` (module)
82
+ - `@Directive` → `component` (with `selector`)
83
+ - `@Pipe` → `component` (with pipe name)
84
+ - `RouterModule.forRoot/forChild` → `route` symbols
85
+
86
+ ### NestJS
87
+
88
+ **Detected by:** `@nestjs/core` in `package.json`.
89
+
90
+ Extracts from decorated controllers:
91
+ - `@Controller('prefix')` + `@Get(':id')` → `route` with combined path (`GET /prefix/:id`)
92
+ - `@Injectable` → `class` with `nestjs_provider: true`
93
+ - `@Module` → `class` with `nestjs_module: true`
94
+ - `@Guard` / `CanActivate` → `middleware`
95
+
96
+ ### Express
97
+
98
+ **Detected by:** `express` in `package.json`.
99
+
100
+ Extracts string-literal route registrations:
101
+ - `app.get('/path', ...)` → `route`
102
+ - `router.post('/path', ...)` → `route`
103
+ - `app.use('/path', ...)` → `middleware`
104
+
105
+ Dynamic paths (variables, template literals) are skipped.
106
+
107
+ ### Fastify
108
+
109
+ **Detected by:** `fastify` in `package.json`.
110
+
111
+ Same pattern as Express: `fastify.get(path, ...)` → `route`.
112
+
113
+ ---
114
+
115
+ ## Python frameworks
116
+
117
+ ### Flask
118
+
119
+ **Detected by:** `Flask` in `requirements.txt` or `pyproject.toml`.
120
+
121
+ Extracts:
122
+ - `@app.route('/path')` → `route`
123
+ - `@app.get('/path')`, `@app.post(...)` → `route`
124
+ - Blueprint routes: `@bp.route(...)` → `route`
125
+ - Class-based views (inheriting `MethodView`) → `view`
126
+
127
+ Flask path parameters (`<int:user_id>`) are preserved as-is.
128
+
129
+ ### FastAPI
130
+
131
+ **Detected by:** `fastapi` in `requirements.txt` or `pyproject.toml`.
132
+
133
+ Extracts:
134
+ - `@app.get('/items/{id}')` → `route`
135
+ - `@router.post(...)` (APIRouter) → `route`
136
+
137
+ FastAPI path parameters (`{param}`) are preserved.
138
+
139
+ ### Django
140
+
141
+ **Detected by:** `manage.py` at project root, or `django` in requirements.
142
+
143
+ Extracts:
144
+ - `models.Model` subclasses → `model`
145
+ - Class-based views (`APIView`, `ViewSet`, etc.) → `view`
146
+ - Function-based views (with `@login_required`, `@api_view`) → `view`
147
+ - `path(...)` / `re_path(...)` in `urls.py` → `route`
148
+ - `@receiver(post_save)` → `signal`
149
+
150
+ ---
151
+
152
+ ## Go frameworks
153
+
154
+ ### Gin
155
+
156
+ **Detected by:** `github.com/gin-gonic/gin` in `go.mod`.
157
+
158
+ Extracts: `r.GET("/path", handler)`, `r.POST(...)`, etc. → `route`. Groups: `r.Group("/api")` prefix applied to child routes.
159
+
160
+ ### Echo
161
+
162
+ **Detected by:** `github.com/labstack/echo` in `go.mod`.
163
+
164
+ Same pattern as Gin: `e.GET("/path", handler)` → `route`.
165
+
166
+ ### Fiber
167
+
168
+ **Detected by:** `github.com/gofiber/fiber` in `go.mod`.
169
+
170
+ Title-case methods: `app.Get("/path", handler)` → `route`.
171
+
172
+ ---
173
+
174
+ ## PHP frameworks
175
+
176
+ ### Laravel
177
+
178
+ **Detected by:** `laravel/framework` in `composer.json`.
179
+
180
+ Extracts:
181
+ - `Route::get('/path', ...)` → `route`
182
+ - `Route::resource('users', Controller::class)` → multiple CRUD routes
183
+ - `Route::group(['prefix' => '/api'], ...)` → prefix applied to children
184
+ - `User extends Model` → `model`
185
+ - Controller public methods → `view`
186
+ - Middleware classes → `middleware`
187
+
188
+ ### Symfony
189
+
190
+ **Detected by:** `symfony/framework-bundle` in `composer.json`.
191
+
192
+ Extracts:
193
+ - `#[Route('/path', methods: ['GET'])]` → `route` (PHP 8 attributes)
194
+ - `@Route('/path')` in docblock → `route` (annotation style)
195
+
196
+ ---
197
+
198
+ ## Ruby frameworks
199
+
200
+ ### Rails
201
+
202
+ **Detected by:** `gem 'rails'` in `Gemfile`.
203
+
204
+ Extracts:
205
+ - `ApplicationRecord` subclasses → `model` (with `has_many` associations)
206
+ - Controller public methods → `view` (action)
207
+ - `get '/path'`, `resources :users` in `routes.rb` → `route`
208
+
209
+ ### Sinatra
210
+
211
+ **Detected by:** `gem 'sinatra'` in `Gemfile` or `require 'sinatra'` in source.
212
+
213
+ Extracts: `get '/path' do ... end` → `route`.
214
+
215
+ ---
216
+
217
+ ## Kotlin frameworks
218
+
219
+ ### Ktor
220
+
221
+ **Detected by:** `io.ktor` in `build.gradle` / `pom.xml`.
222
+
223
+ Extracts from routing DSL:
224
+ - `get("/path") { ... }` → `route`
225
+ - `route("/api") { get("/users") }` → combined path `/api/users`
226
+ - `authenticate { ... }` → `frameworkMeta.authenticated: true`
227
+
228
+ ### Spring (Kotlin)
229
+
230
+ **Detected by:** `org.springframework.boot` in `build.gradle` / `pom.xml`.
231
+
232
+ Extracts: `@RestController` + `@GetMapping("/path")` → `route`; `@Service`, `@Component`, `@Repository` → class with metadata.
233
+
234
+ ---
235
+
236
+ ## Rust frameworks
237
+
238
+ ### Axum
239
+
240
+ **Detected by:** `axum` in `Cargo.toml`.
241
+
242
+ Extracts: `.route("/path", get(handler))` chains → `route`. Layers: `.layer(...)` → `middleware`.
243
+
244
+ ### Actix-web
245
+
246
+ **Detected by:** `actix-web` in `Cargo.toml`.
247
+
248
+ Extracts: `#[get("/path")]` attribute macro → `route`; `.wrap(...)` → `middleware`.
249
+
250
+ ### Rocket
251
+
252
+ **Detected by:** `rocket` in `Cargo.toml`.
253
+
254
+ Extracts: `#[get("/path")]` → `route`; `#[catch(404)]` → error catcher; `Fairing` implementations → `middleware`.
255
+
256
+ ---
257
+
258
+ ## Java frameworks
259
+
260
+ ### Spring Boot
261
+
262
+ **Detected by:** `spring-boot-starter` in `pom.xml` / `build.gradle`.
263
+
264
+ Extracts: `@GetMapping`, `@PostMapping` (combined with `@RequestMapping` prefix) → `route`; `@Service`, `@Component`, `@Repository` → beans; `@Bean` methods; `@Scheduled` methods → scheduled tasks.
265
+
266
+ ### Micronaut
267
+
268
+ **Detected by:** `io.micronaut` in build files.
269
+
270
+ Extracts: `@Get("/path")`, `@Post(...)` → `route`; `@Client` interfaces.
271
+
272
+ ### Quarkus
273
+
274
+ **Detected by:** `io.quarkus` in build files.
275
+
276
+ Extracts: JAX-RS `@GET` / `@Path` → `route`; `@ApplicationScoped` → bean.
277
+
278
+ ---
279
+
280
+ ## ORM adapters
281
+
282
+ ### Hibernate (Java)
283
+
284
+ **Detected by:** `hibernate-core` or `jakarta.persistence` in dependencies.
285
+
286
+ Extracts from `@Entity` classes: table name, columns with types and nullability, relationships (`@OneToMany`, etc.), named queries.
287
+
288
+ ### SQLAlchemy (Python)
289
+
290
+ **Detected by:** `sqlalchemy` in requirements.
291
+
292
+ Extracts from `Base` / `DeclarativeBase` subclasses: `__tablename__`, `Column(Type)` fields, `relationship()` associations, `@hybrid_property` methods. Supports both SQLAlchemy 1.x and 2.0 (`mapped_column`) styles.
293
+
294
+ ### Django ORM
295
+
296
+ **Detected by:** Django project with `manage.py`.
297
+
298
+ Extracts `models.Model` subclasses with field types: `CharField`, `IntegerField`, `ForeignKey`, `ManyToManyField`, etc.
299
+
300
+ ### Prisma
301
+
302
+ **Detected by:** `prisma` in `package.json` or `schema.prisma` present.
303
+
304
+ Extracts model definitions and relations from `schema.prisma`.
305
+
306
+ ### TypeORM
307
+
308
+ **Detected by:** `typeorm` in `package.json`.
309
+
310
+ Extracts `@Entity` classes and `@Column` / `@Relation` fields.
311
+
312
+ ---
313
+
314
+ ## Mobile frameworks
315
+
316
+ ### Flutter
317
+
318
+ **Detected by:** `sdk: flutter` in `pubspec.yaml`.
319
+
320
+ Extracts from `.dart` files:
321
+ - `StatelessWidget` subclasses → `widget`
322
+ - `StatefulWidget` subclasses → `widget` (with linked State class)
323
+ - `ChangeNotifier` subclasses → `class` with `flutter_notifier: true`
324
+
@@ -0,0 +1,182 @@
1
+ # Dependency Graph Tools
2
+
3
+
4
+ The dependency graph tracks import relationships between files. Four tools let agents query it at different granularities — from a single hop to a full transitive walk.
5
+
6
+ ---
7
+
8
+ ## Concepts
9
+
10
+ During indexing, each `import` / `require` / `use` statement is resolved to a dependency edge:
11
+
12
+ ```
13
+ dep_edge: sourceFile → resolvedTargetFile (via import specifier)
14
+ ```
15
+
16
+ Edges are stored in the `dep_edges` SQLite table. External packages (e.g., `from 'react'`) produce edges with `resolvedPath: null` and are excluded from graph traversal.
17
+
18
+ Two directions of traversal:
19
+ - **Forward walk** — "what does X depend on?" (imports, transitively)
20
+ - **Reverse walk** — "what depends on X?" (importers, transitively)
21
+
22
+ ---
23
+
24
+ ## `get_context_bundle`
25
+
26
+ **Purpose:** Forward-walk from a symbol — returns everything an agent needs to understand it (the symbol itself plus its transitive imports).
27
+
28
+ **When to use:** Before modifying a function — understand its full context without reading whole files.
29
+
30
+ **Parameters:**
31
+
32
+ | Parameter | Type | Default | Description |
33
+ |-----------|------|---------|-------------|
34
+ | `repoId` | `string` | required | Target repository |
35
+ | `symbolId` | `string` | required | Starting symbol |
36
+ | `maxDepth` | `number` | `3` | Traversal depth |
37
+ | `maxTokens` | `number` | — | Stop collecting when estimate exceeds this |
38
+
39
+ **Example:**
40
+
41
+ ```
42
+ "Give me everything needed to understand the processOrder function."
43
+
44
+ → get_context_bundle({ symbolId: "processOrder-id", maxDepth: 2 })
45
+ → Returns: processOrder + validateCart + calculateTax + formatPrice
46
+ _tokenEstimate: 820
47
+ ```
48
+
49
+ **Response:**
50
+
51
+ ```json
52
+ {
53
+ "symbols": [
54
+ { "id": "...", "name": "processOrder", "signature": "...", "source": "..." },
55
+ { "id": "...", "name": "validateCart", "signature": "...", "source": "..." }
56
+ ],
57
+ "files": ["src/orders/processor.ts", "src/cart/validator.ts"],
58
+ "_tokenEstimate": 820
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ## `get_blast_radius`
65
+
66
+ **Purpose:** Reverse-walk — all files that (transitively) import a given symbol. Tells you what would break if you change or delete it.
67
+
68
+ **When to use:** Before modifying or deleting a symbol.
69
+
70
+ **Parameters:**
71
+
72
+ | Parameter | Type | Default | Description |
73
+ |-----------|------|---------|-------------|
74
+ | `repoId` | `string` | required | Target repository |
75
+ | `symbolId` | `string` | required | Symbol to analyze |
76
+ | `maxDepth` | `number` | `5` | Traversal depth |
77
+
78
+ **Example:**
79
+
80
+ ```
81
+ "What breaks if I change UserService.authenticate?"
82
+
83
+ → get_blast_radius({ symbolId: "UserService.authenticate-id" })
84
+ → Returns: 14 files at depth 1–3
85
+ (AuthController, LoginPage, SessionMiddleware, tests/...)
86
+ ```
87
+
88
+ **Response:**
89
+
90
+ ```json
91
+ {
92
+ "importers": [
93
+ "src/controllers/auth.ts",
94
+ "src/middleware/session.ts",
95
+ "src/pages/Login.tsx",
96
+ "test/auth.test.ts"
97
+ ],
98
+ "count": 14,
99
+ "_tokenEstimate": 120
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## `find_importers`
106
+
107
+ **Purpose:** Direct (one-hop) importers of a file — faster and narrower than `get_blast_radius`.
108
+
109
+ **When to use:** Quick check — "who imports this module directly?"
110
+
111
+ **Parameters:** `{ repoId, filePath }` — `filePath` is relative to repo root.
112
+
113
+ **Response:**
114
+
115
+ ```json
116
+ {
117
+ "importers": [
118
+ {
119
+ "filePath": "src/controllers/auth.ts",
120
+ "importedNames": ["UserService", "AuthToken"]
121
+ }
122
+ ],
123
+ "_tokenEstimate": 80
124
+ }
125
+ ```
126
+
127
+ ---
128
+
129
+ ## `find_dead_code`
130
+
131
+ **Purpose:** Exported symbols in files that nothing else imports — potential dead code.
132
+
133
+ **When to use:** Cleanup sprints, pre-refactor audits.
134
+
135
+ **Parameters:**
136
+
137
+ | Parameter | Type | Default | Description |
138
+ |-----------|------|---------|-------------|
139
+ | `repoId` | `string` | required | Target repository |
140
+ | `limit` | `number` | `50` | Max results |
141
+
142
+ **Response:**
143
+
144
+ ```json
145
+ {
146
+ "symbols": [
147
+ {
148
+ "id": "...",
149
+ "name": "legacyFormatDate",
150
+ "kind": "function",
151
+ "filePath": "src/utils/date-old.ts",
152
+ "signature": "function legacyFormatDate(d: Date): string"
153
+ }
154
+ ],
155
+ "_tokenEstimate": 240
156
+ }
157
+ ```
158
+
159
+ **False positive sources:**
160
+ - **Dynamic imports** — `import('./module')` are not tracked by the static graph
161
+ - **Side-effect imports** — `import './setup'` (no names imported) create edges but no `importedNames`
162
+ - **External consumers** — if this repo is itself an npm package, external consumers won't appear in the index
163
+ - **Test files** — test imports are included in the graph; symbols only used by tests are not dead
164
+
165
+ ---
166
+
167
+ ## Combining graph tools
168
+
169
+ A typical refactoring workflow:
170
+
171
+ ```
172
+ 1. get_blast_radius(symbolId)
173
+ → See the full impact scope before touching anything
174
+
175
+ 2. get_context_bundle(symbolId, maxDepth: 2)
176
+ → Understand the symbol and its immediate dependencies
177
+
178
+ 3. Make the change
179
+
180
+ 4. find_dead_code(repoId)
181
+ → Verify no orphaned exports were left behind
182
+ ```
@@ -0,0 +1,153 @@
1
+ # Semantic Search
2
+
3
+
4
+ Semantic search finds symbols by **meaning** rather than exact name. It uses an HNSW (Hierarchical Navigable Small World) vector index built from symbol summaries and signatures.
5
+
6
+ ---
7
+
8
+ ## How it works
9
+
10
+ 1. During indexing, each symbol's summary + signature is sent to an embedding model (e.g., OpenAI `text-embedding-3-small`)
11
+ 2. The resulting vector is stored in an HNSW index persisted at `~/.purecontext/indexes/{repoId}/hnsw.idx`
12
+ 3. At search time, the query is embedded using the same model
13
+ 4. HNSW performs an approximate nearest-neighbor search by cosine similarity
14
+ 5. Results are ranked by similarity score and returned
15
+
16
+ ---
17
+
18
+ ## When the HNSW index is built
19
+
20
+ The HNSW index is only built when **all three** conditions are met:
21
+
22
+ 1. `semantic.enabled: true` in config
23
+ 2. An embedding provider is configured (`semantic.provider`)
24
+ 3. The repo has more symbols than `semantic.threshold` (default: 50,000)
25
+
26
+ **For small or medium repos**, set `threshold` low to enable HNSW early:
27
+
28
+ ```json
29
+ {
30
+ "semantic": {
31
+ "enabled": true,
32
+ "provider": "openai",
33
+ "threshold": 100
34
+ }
35
+ }
36
+ ```
37
+
38
+ Below the threshold, all search falls back to FTS5 keyword search — which is fast and accurate for name-based queries.
39
+
40
+ ---
41
+
42
+ ## Enabling semantic search
43
+
44
+ ### Using OpenAI embeddings
45
+
46
+ ```json
47
+ {
48
+ "semantic": {
49
+ "enabled": true,
50
+ "provider": "openai",
51
+ "threshold": 50000
52
+ },
53
+ "ai": {
54
+ "openaiApiKey": "${OPENAI_API_KEY}"
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### Using local Ollama embeddings
60
+
61
+ ```json
62
+ {
63
+ "semantic": {
64
+ "enabled": true,
65
+ "provider": "local",
66
+ "localEmbeddingEndpoint": "http://localhost:11434",
67
+ "threshold": 1000
68
+ }
69
+ }
70
+ ```
71
+
72
+ Set the model by passing it in the endpoint config or using Ollama's default embedding model.
73
+
74
+ ---
75
+
76
+ ## Using `search_semantic`
77
+
78
+ ```json
79
+ {
80
+ "repo": "my-project",
81
+ "query": "function that validates user credentials",
82
+ "mode": "hybrid",
83
+ "semantic_weight": 0.6,
84
+ "keyword_weight": 0.4,
85
+ "max_results": 10
86
+ }
87
+ ```
88
+
89
+ **Response:**
90
+
91
+ ```json
92
+ {
93
+ "results": [
94
+ {
95
+ "id": "8f3a...",
96
+ "name": "authenticateUser",
97
+ "kind": "function",
98
+ "filePath": "src/auth/validator.ts",
99
+ "signature": "function authenticateUser(creds: Credentials): Promise<User>",
100
+ "summary": "Validates credentials against the database and returns a session token.",
101
+ "scores": {
102
+ "keyword": 0.72,
103
+ "semantic": 0.89,
104
+ "combined": 0.82
105
+ }
106
+ }
107
+ ]
108
+ }
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Keyword vs semantic — when to use which
114
+
115
+ | Situation | Best tool |
116
+ |-----------|-----------|
117
+ | You know the name or a fragment of it | `search_symbols` (keyword) |
118
+ | You know what it does, not what it's called | `search_semantic` |
119
+ | You want maximum recall | `search_semantic` with `mode: "hybrid"` |
120
+ | Semantic index not available (small repo) | `search_symbols` always works |
121
+
122
+ The `search_symbols` tool also supports `mode: "hybrid"` — it will use HNSW automatically if the index exists.
123
+
124
+ ---
125
+
126
+ ## How hybrid search works
127
+
128
+ Hybrid mode runs both searches and merges results using **Reciprocal Rank Fusion (RRF)**:
129
+
130
+ ```
131
+ score = keyword_weight × (1 / (60 + keyword_rank))
132
+ + semantic_weight × (1 / (60 + semantic_rank))
133
+ ```
134
+
135
+ This combines the precision of exact-name matching with the recall of semantic matching. Adjust weights to bias toward one or the other.
136
+
137
+ ---
138
+
139
+ ## Performance
140
+
141
+ - HNSW search: < 10ms for k=10 in a 100k vector index
142
+ - Embedding generation: batched at index time, ~50ms per symbol (API latency)
143
+ - Indexes are persisted — no rebuild on each startup
144
+
145
+ ## Rebuilding the vector index
146
+
147
+ The HNSW index is rebuilt automatically on full re-index. To force a rebuild without re-parsing all files:
148
+
149
+ ```
150
+ Use invalidate_cache with force: true, then index_folder again.
151
+ ```
152
+
153
+ Or call `index_folder` with `force: true` — this re-embeds all symbols even if their source hasn't changed.