purecontext-mcp 1.1.1 → 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 (37) hide show
  1. package/package.json +1 -1
  2. package/docs/dev/API_STABILITY.md +0 -319
  3. package/docs/dev/DECISIONS.md +0 -22
  4. package/docs/dev/DOCUMENTATION_PLAN.md +0 -113
  5. package/docs/dev/PHASE10_TASKS.md +0 -476
  6. package/docs/dev/PHASE11_TASKS.md +0 -385
  7. package/docs/dev/PHASE12_TASKS.md +0 -335
  8. package/docs/dev/PHASE13_TASKS.md +0 -381
  9. package/docs/dev/PHASE14_TASKS.md +0 -371
  10. package/docs/dev/PHASE15_TASKS.md +0 -256
  11. package/docs/dev/PHASE16_TASKS.md +0 -314
  12. package/docs/dev/PHASE17_TASKS.md +0 -321
  13. package/docs/dev/PHASE18_TASKS.md +0 -345
  14. package/docs/dev/PHASE19_TASKS.md +0 -261
  15. package/docs/dev/PHASE1_TASKS.md +0 -443
  16. package/docs/dev/PHASE20_TASKS.md +0 -280
  17. package/docs/dev/PHASE21_TASKS.md +0 -355
  18. package/docs/dev/PHASE22_TASKS.md +0 -371
  19. package/docs/dev/PHASE23_TASKS.md +0 -274
  20. package/docs/dev/PHASE24_TASKS.md +0 -326
  21. package/docs/dev/PHASE25_TASKS.md +0 -452
  22. package/docs/dev/PHASE26_TASKS.md +0 -253
  23. package/docs/dev/PHASE27_TASKS.md +0 -410
  24. package/docs/dev/PHASE2_TASKS.md +0 -328
  25. package/docs/dev/PHASE3_TASKS.md +0 -571
  26. package/docs/dev/PHASE4_TASKS.md +0 -531
  27. package/docs/dev/PHASE5_TASKS.md +0 -835
  28. package/docs/dev/PHASE6_TASKS.md +0 -347
  29. package/docs/dev/PHASE7_TASKS.md +0 -257
  30. package/docs/dev/PHASE8_TASKS.md +0 -299
  31. package/docs/dev/PHASE9_TASKS.md +0 -320
  32. package/docs/dev/PureContext_MCP_PRD_v1.0.docx +0 -0
  33. package/docs/dev/SELF_HOSTING.md +0 -142
  34. package/docs/dev/TEAM_SETUP.md +0 -316
  35. package/docs/dev/TELEMETRY.md +0 -99
  36. package/docs/dev/feature-analysis.md +0 -305
  37. package/docs/dev/phase-1-notes.md +0 -3
@@ -1,571 +0,0 @@
1
- # Phase 3 — Task Breakdown
2
-
3
- **Goal**: Expand language coverage to Python and Go, add the Next.js adapter, deliver HTTP/SSE transport, harden security, and round out the tool set with text search, layer violation analysis, and a more capable AI summarizer.
4
-
5
- **Scope rationale**: Phase 2 moved React into scope but deferred HTTP/SSE transport. The PRD Phase 3 deliverables (React + Next.js, layer violations, semantic search) are partially satisfied — React is done, Next.js is not. The Phase 2 end note called out Python, Go, HTTP/SSE, and AI expansion as Phase 3 targets. This document covers all of it.
6
-
7
- **Approach**: Tasks are sequenced so each one builds on the previous. Complete them in order. Each task should be independently testable before moving to the next.
8
-
9
- ---
10
-
11
- ## Task 24: Python Language Handler
12
-
13
- Add Python as the first non-JS/TS language. This validates that the `LanguageHandler` interface generalises cleanly and is the foundation for Go (Task 25) and any future language.
14
-
15
- **Deliverables:**
16
- - Download and bundle `grammars/tree-sitter-python.wasm`
17
- - `src/handlers/python.ts` — implements `LanguageHandler`
18
- - `extensions()`: `['.py']`
19
- - `grammarPath()`: path to `tree-sitter-python.wasm`
20
- - `extractSymbols(tree, source)`:
21
- - `function_definition` → kind `'function'`
22
- - `class_definition` → kind `'class'`
23
- - `decorated_definition` wrapping a `function_definition` → kind `'function'` (pull name from inner node)
24
- - `decorated_definition` wrapping a `class_definition` → kind `'class'`
25
- - Method: `function_definition` whose parent chain includes a `class_definition` → kind `'method'`
26
- - Module-level `expression_statement` / `assignment` with a type annotation → kind `'const'`
27
- - Only extract top-level and class-level symbols (not nested function bodies)
28
- - Capture name, byte offsets (`startIndex` / `endIndex`), build signature
29
- - `extractImports(tree, source)`:
30
- - `import_statement`: `import foo`, `import foo as bar` → specifier = module name
31
- - `import_from_statement`: `from foo import bar, baz` → specifier = module path, importedNames = [bar, baz]
32
- - Relative imports: `from . import x`, `from ..utils import y` → specifier = relative path, resolvedPath = null (leave for resolver to handle)
33
- - `isTypeOnly`: always `false` for Python (no type-only imports)
34
- - `extractDocstring(node)`:
35
- - For function/class nodes: check if first child of the body is a `string` node (triple-quoted docstring)
36
- - Extract text, strip quotes and leading whitespace, return first paragraph (up to first blank line)
37
-
38
- **Signature building:**
39
- - Functions: `def name(params) -> returnType:` (truncate params if > 100 chars)
40
- - Classes: `class Name(bases):` or `class Name:`
41
- - Constants: `NAME: Type = ...` or `NAME = ...` (truncate value)
42
- - Keep signatures under 120 chars
43
-
44
- **Key technical notes:**
45
- - Python uses `startIndex` / `endIndex` on nodes (byte offsets), same as TypeScript — no special handling needed
46
- - `decorated_definition` wraps the actual declaration; extract name from the inner `function_definition` or `class_definition` child
47
- - Method detection: walk `parent` chain up to find a `class_definition` before hitting module root
48
- - Python has no explicit `export` concept — index all top-level and class-level definitions
49
-
50
- **Verify:** Index `test/fixtures/python-project/` (created in Task 32). Verify functions, classes, methods, and decorated functions are extracted with correct byte offsets. Verify docstrings are captured.
51
-
52
- **Tests:** Fixture `.py` files covering: simple function, class with methods, `@decorator` on function, `@decorator` on class, module-level typed constant, `from x import y` / `import x` / relative import, function with docstring, function without docstring, nested function (should NOT be extracted).
53
-
54
- ---
55
-
56
- ## Task 25: Go Language Handler
57
-
58
- Add Go as the second non-JS/TS language. Go's type system (structs, interfaces, methods with receivers) maps cleanly onto the existing `SymbolKind` types.
59
-
60
- **Deliverables:**
61
- - Download and bundle `grammars/tree-sitter-go.wasm`
62
- - `src/handlers/go.ts` — implements `LanguageHandler`
63
- - `extensions()`: `['.go']`
64
- - `grammarPath()`: path to `tree-sitter-go.wasm`
65
- - `extractSymbols(tree, source)`:
66
- - `function_declaration` → kind `'function'`
67
- - `method_declaration` (has a receiver) → kind `'method'`
68
- - `type_declaration` containing a `type_spec`:
69
- - `struct_type` → kind `'class'` (closest analog; no true class in Go)
70
- - `interface_type` → kind `'interface'`
71
- - all others (type aliases, custom types) → kind `'type'`
72
- - `const_declaration` → kind `'const'`
73
- - Extract both single-value (`const X = ...`) and grouped (`const ( X = ...\n Y = ... )`) const declarations
74
- - Skip unexported symbols (names that start with a lowercase letter) — these are package-private and typically noise for navigation
75
- - `extractImports(tree, source)`:
76
- - `import_declaration` with one or more `import_spec` children
77
- - For each `import_spec`: specifier = the quoted path string (strip quotes), alias = the local name if present
78
- - All Go imports are package paths (e.g., `"fmt"`, `"github.com/user/pkg"`)
79
- - `resolvedPath`: `null` for all Go imports (external module system — no local resolution without go.mod parsing)
80
- - `importedNames`: `[]` (Go uses package-level identifiers, not named imports)
81
- - `extractDocstring(node)`:
82
- - Look for a `comment` node (or sequence of `comment` nodes) immediately preceding the symbol node in the parent's children list
83
- - Strip `//` prefix and leading spaces from each line
84
- - Concatenate lines, return first sentence (up to first `.` or 100 chars)
85
-
86
- **Signature building:**
87
- - Functions: `func Name(params) returnType`
88
- - Methods: `func (recv ReceiverType) Name(params) returnType`
89
- - Structs: `type Name struct { ... }` (show first 2 fields if body is short, else `{ ... }`)
90
- - Interfaces: `type Name interface { ... }` (show first 2 methods if short)
91
- - Type aliases: `type Name = BaseType`
92
- - Constants: `const Name Type = value`
93
- - Keep signatures under 120 chars
94
-
95
- **Key technical notes:**
96
- - Go method receivers are part of the `method_declaration` node — extract receiver type to qualify the method name (`ReceiverType.MethodName`)
97
- - Exported symbol rule: Go's only visibility mechanism is first-letter case. Names starting with uppercase are exported.
98
- - Go has no decorator/annotation system — skip decorator handling entirely
99
- - `const` groups: a single `const_declaration` may contain multiple `const_spec` children — emit one `SymbolRecord` per spec
100
-
101
- **Verify:** Index `test/fixtures/go-project/` (created in Task 32). Verify exported functions, methods (with receiver type in name), struct types, interface types, and constants are extracted. Verify unexported symbols are skipped.
102
-
103
- **Tests:** Fixture `.go` files covering: exported function, unexported function (skipped), struct with fields, interface with methods, method with receiver, const single/grouped, import single/grouped, docstring comment preceding function.
104
-
105
- ---
106
-
107
- ## Task 26: Next.js Adapter
108
-
109
- The Next.js adapter extends the React adapter (Phase 2) with route extraction — both the Pages Router and the App Router. This mirrors the relationship between the Vue and Nuxt adapters.
110
-
111
- **Deliverables:**
112
- - `src/adapters/nextjs.ts` — implements `FrameworkAdapter`
113
- - `name`: `'nextjs'`
114
- - `extensions()`: `[]` — Next.js uses `.tsx`/`.jsx`/`.ts`/`.js` already handled by the TS/JS handler
115
- - `detect(projectRoot)`:
116
- - Check for `next.config.js`, `next.config.ts`, `next.config.mjs`, or `next.config.cjs` in project root
117
- - OR check `package.json` for `next` in `dependencies`
118
- - `fileFilter(filePath)`:
119
- - **Pages Router**: `pages/**/*.{tsx,jsx,ts,js}` → `true`
120
- - **App Router**: `app/**/page.{tsx,jsx}`, `app/**/layout.{tsx,jsx}`, `app/**/loading.{tsx,jsx}`, `app/**/error.{tsx,jsx}`, `app/**/not-found.{tsx,jsx}` → `true`
121
- - **API routes (Pages Router)**: `pages/api/**/*.{ts,js}` → `true`
122
- - **API routes (App Router)**: `app/**/route.{ts,js}` → `true`
123
- - **Middleware**: `middleware.{ts,js}` at project root → `true`
124
- - `src/` prefix variants: repeat all patterns with `src/` prepended (Next.js supports `src/app/` and `src/pages/`)
125
- - All other files → `false`
126
- - `extractFrameworkSymbols(tree, source, filePath)`:
127
-
128
- **Pages Router page** (`pages/**`, not `pages/api/**`):
129
- - Emit one `route` symbol per file
130
- - Name: filename without extension
131
- - Route path: derive from file path using Pages Router conventions
132
- - `pages/index.tsx` → `/`
133
- - `pages/about.tsx` → `/about`
134
- - `pages/blog/[slug].tsx` → `/blog/:slug`
135
- - `pages/[...catchAll].tsx` → `/*catchAll`
136
- - `pages/[[...optionalCatchAll]].tsx` → `/*optionalCatchAll?`
137
- - Scan AST for exported `getServerSideProps` → `frameworkMeta.ssr: true`
138
- - Scan AST for exported `getStaticProps` → `frameworkMeta.ssg: true`
139
- - Scan AST for exported `getStaticPaths` → `frameworkMeta.dynamic_ssg: true`
140
- - `frameworkMeta.router: 'pages'`
141
-
142
- **App Router page** (`app/**/page.{tsx,jsx}`, and other special files):
143
- - Emit one `route` symbol per `page.tsx` file
144
- - Route path: derive from directory path above `page.tsx`
145
- - `app/page.tsx` → `/`
146
- - `app/about/page.tsx` → `/about`
147
- - `app/blog/[slug]/page.tsx` → `/blog/:slug`
148
- - `app/(marketing)/about/page.tsx` → `/about` (route groups in parens are stripped)
149
- - Detect `'use client'` directive (first string literal in file) → `frameworkMeta.client_component: true`; absence implies `frameworkMeta.server_component: true`
150
- - `layout.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx` → emit with kind `'component'` and appropriate `frameworkMeta.nextjs_special: 'layout' | 'loading' | 'error' | 'not-found'`
151
- - `frameworkMeta.router: 'app'`
152
-
153
- **API Routes (Pages Router)** (`pages/api/**`):
154
- - Emit one `route` symbol per file
155
- - Route path: `/api/...` prefix + Pages Router path derivation
156
- - No HTTP method on the symbol (Pages Router uses request object, not filename)
157
- - `frameworkMeta.api_route: true`, `frameworkMeta.router: 'pages'`
158
-
159
- **API Routes (App Router)** (`app/**/route.{ts,js}`):
160
- - Scan AST for exported functions named `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`
161
- - Emit one `route` symbol per HTTP method export found
162
- - Route path: derive from directory of `route.ts`
163
- - `frameworkMeta.http_method`, `frameworkMeta.api_route: true`, `frameworkMeta.router: 'app'`
164
-
165
- **Middleware** (`middleware.ts` at root):
166
- - Emit one `middleware` symbol
167
- - Scan for `config.matcher` export → `frameworkMeta.matcher`
168
- - `frameworkMeta.nextjs_middleware: true`
169
-
170
- - `enrichMetadata(symbol)`:
171
- - For kind `'function'` or `'const'` in Next.js files: check if name starts with uppercase and returns JSX → upgrade to `'component'` (same as React adapter)
172
- - Preserve all existing `frameworkMeta` fields
173
- - Register via `registerAdapter(nextjsAdapter)` at module level
174
-
175
- **Key decisions:**
176
- - Next.js and React adapters can be active simultaneously — React adapter handles components and hooks, Next.js adapter handles routes and middleware
177
- - App Router route groups (directories wrapped in `()`) are stripped from route paths
178
- - Parallel routes (`@slot` directories) are not extracted in Phase 3 — too complex, low value
179
- - `.tsx` in `app/` without `page.tsx` name is ignored by Next.js adapter (React adapter handles it normally)
180
-
181
- **Verify:** Index `test/fixtures/nextjs-project/` (created in Task 32). Verify Pages Router produces route symbols with correct paths. Verify App Router produces routes with `router: 'app'`. Verify `pages/api/users.ts` produces an API route. Verify `app/api/users/route.ts` with exported `GET` and `POST` produces two route symbols.
182
-
183
- **Tests:** Route path derivation (pure functions, unit-testable): index, nested, dynamic, catch-all, optional catch-all, route group stripping. App Router client component detection. API route method extraction (Pages + App). Middleware symbol with matcher.
184
-
185
- ---
186
-
187
- ## Task 27: Security Layer
188
-
189
- Before shipping HTTP transport (Task 28) — which exposes the server over a network socket — harden the file access layer against path traversal, symlink escape, and accidental credential exposure.
190
-
191
- **Deliverables:**
192
- - `src/core/security.ts`
193
- - `SecurityError` extends `PureContextError` — already in `errors.ts`, add if missing
194
- - `validatePath(filePath: string, projectRoot: string): string`
195
- - Resolve both paths to absolute with `path.resolve()`
196
- - Verify resolved path starts with `projectRoot + path.sep`
197
- - Throw `SecurityError('path_traversal')` if not
198
- - Return the resolved absolute path
199
- - `checkSymlinkEscape(resolvedPath: string, projectRoot: string): void`
200
- - Use `fs.realpathSync()` to resolve all symlinks
201
- - Verify the real path is still within `projectRoot`
202
- - Throw `SecurityError('symlink_escape')` if not
203
- - `isSecretFile(filePath: string): boolean`
204
- - Match against these patterns (case-insensitive):
205
- - `.env`, `.env.*` (`.env.local`, `.env.production`, etc.)
206
- - `*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.crt`, `*.cer`
207
- - `id_rsa`, `id_ed25519`, `id_ecdsa`, `id_dsa` (and `*.pub`)
208
- - `credentials.json`, `credentials.yaml`, `credentials.yml`
209
- - `secrets.json`, `secrets.yaml`, `secrets.yml`
210
- - `serviceAccountKey*.json`, `*-service-account.json`
211
- - `*.token`, `*.secret`
212
- - Returns `true` if the file should be excluded from indexing
213
- - `isBinaryFile(content: Buffer): boolean`
214
- - Check first 8192 bytes for null bytes (`\x00`) — reliable binary indicator
215
- - Return `true` if null bytes found
216
- - `checkFileSize(size: number, maxBytes: number): void`
217
- - Throw `SecurityError('file_too_large')` if `size > maxBytes`
218
- - Default max: 1 MB (1_048_576 bytes), configurable via `config.maxFileSizeBytes`
219
-
220
- - Update `src/core/file-discovery.ts`:
221
- - Apply `isSecretFile()` during file scanning — skip matching files with a debug log
222
- - Apply `isBinaryFile()` after reading file content — skip with debug log
223
- - Apply `checkFileSize()` — skip oversized files with a warning log
224
-
225
- - Update `src/server/tools/get-symbol-source.ts`:
226
- - Call `validatePath()` on every file path before reading from the files table
227
- - This prevents crafted symbol IDs from escaping the project root
228
-
229
- - Update `src/config/config-schema.ts`:
230
- - Add `maxFileSizeBytes: number` (default: 1_048_576)
231
- - Add `allowSymlinks: boolean` (default: `false`) — when false, symlink escape check is enforced
232
-
233
- **Key decisions:**
234
- - Security checks are synchronous and cheap — no performance concern on the hot path
235
- - Secret file exclusion is a safelist of patterns, not a blocklist — conservative by design
236
- - Binary detection by null-byte check is sufficient; MIME types add complexity without meaningful benefit
237
- - `allowSymlinks: false` by default — symlinks inside the project root are fine, but symlinks that resolve outside are blocked
238
-
239
- **Verify:** Attempt to read a path like `../../etc/passwd` via `get-symbol-source` — verify `SecurityError` is thrown. Index a project with a `.env` file — verify it does not appear in the index. Index a project with a binary file — verify it is skipped cleanly.
240
-
241
- **Tests:** `validatePath`: valid path, path traversal with `../`, path traversal with encoded chars. `isSecretFile`: `.env`, `.env.local`, `id_rsa`, `credentials.json`, normal `.ts` file (false). `isBinaryFile`: Buffer with null byte (true), Buffer of plain text (false). `checkFileSize`: oversized buffer throws, normal buffer passes.
242
-
243
- ---
244
-
245
- ## Task 28: HTTP/SSE Transport
246
-
247
- Add HTTP + Server-Sent Events transport so PureContext can be used from web-based MCP clients, remote development environments, and multi-client setups. The security layer from Task 27 must be in place before deploying this.
248
-
249
- **Deliverables:**
250
- - `src/server/transport.ts` — transport abstraction
251
- - `TransportMode`: `'stdio' | 'http' | 'both'`
252
- - `createTransport(mode: TransportMode, options: TransportOptions): Transport`
253
- - `TransportOptions`: `{ port?: number, host?: string, corsOrigins?: string[] }`
254
- - For `'stdio'`: wraps `StdioServerTransport` from the MCP SDK (existing behavior)
255
- - For `'http'`: wraps `SSEServerTransport` from the MCP SDK
256
- - For `'both'`: starts both transports simultaneously
257
-
258
- - `src/server/http-server.ts`
259
- - `startHttpServer(mcpServer: McpServer, options: TransportOptions): HttpServer`
260
- - Use Node.js `http.createServer()` (no Express dependency)
261
- - Routes:
262
- - `GET /health` → `{ status: 'ok', version, repoCount, uptime }`
263
- - `GET /sse` → SSE stream (MCP SSE transport endpoint)
264
- - `POST /message` → MCP message handler (SSE transport companion)
265
- - All other paths → 404
266
- - CORS: set `Access-Control-Allow-Origin` from configured `corsOrigins` (default: `['localhost']`)
267
- - Request size limit: reject POST bodies > 1 MB (guard against malformed requests)
268
- - Log each incoming request at debug level (method, path, client IP)
269
-
270
- - Update `src/config/config-schema.ts`:
271
- - Add `transport`: `'stdio' | 'http' | 'both'` (default: `'stdio'`)
272
- - Add `http.port`: number (default: `3000`)
273
- - Add `http.host`: string (default: `'127.0.0.1'` — loopback only by default, not `0.0.0.0`)
274
- - Add `http.corsOrigins`: string[] (default: `['http://localhost:*']`)
275
-
276
- - Update `src/config/cli.ts`:
277
- - Add `--transport` flag: `purecontext-mcp --transport http`
278
- - Add `--port` flag: `purecontext-mcp --port 3001`
279
- - CLI flags override config.json values
280
-
281
- - Update `src/index.ts`:
282
- - Read transport config, start appropriate transport(s)
283
- - On HTTP mode: log `PureContext MCP listening on http://{host}:{port}` to stderr
284
-
285
- - Update `README.md`:
286
- - Add section: HTTP/SSE transport setup
287
- - Claude Code / VS Code MCP config snippet for HTTP mode
288
-
289
- **Key decisions:**
290
- - Default `host: '127.0.0.1'` is intentional — binding to `0.0.0.0` exposes the server on all interfaces and requires explicit opt-in
291
- - The MCP SDK's `SSEServerTransport` handles protocol-level SSE framing — we only need to wire the HTTP server to it
292
- - No authentication in Phase 3 (loopback-only default mitigates risk; auth is Phase 4 if remote access is needed)
293
- - `'both'` mode is for development — stdio for Claude Code, HTTP for browser-based clients simultaneously
294
-
295
- **Verify:** Start with `--transport http`, connect using an MCP HTTP client (or curl the `/health` endpoint). Index a project via HTTP transport. Start with `--transport both` and verify stdio and HTTP tools both work simultaneously.
296
-
297
- **Tests:** HTTP server unit tests with `node:http` requests: health check returns correct fields, unknown path returns 404, CORS headers present on OPTIONS, oversized POST rejected. Transport factory: stdio mode creates StdioServerTransport, http mode starts HTTP server, both mode starts both.
298
-
299
- ---
300
-
301
- ## Task 29: Full-Text Search Tool
302
-
303
- Add `search-text` — full-text search across raw file content. This complements `search-symbols` (which searches symbol metadata) by letting agents grep for literal strings, patterns, or keywords that aren't symbol names.
304
-
305
- **Deliverables:**
306
- - `src/server/tools/search-text.ts`
307
- - Tool name: `search-text`
308
- - Input schema:
309
- ```json
310
- {
311
- "repo": { "type": "string", "description": "Repo ID or path" },
312
- "query": { "type": "string", "description": "Literal string or regex pattern to search for" },
313
- "is_regex": { "type": "boolean", "default": false },
314
- "file_pattern": { "type": "string", "description": "Glob pattern to filter files (e.g. '**/*.ts')" },
315
- "context_lines": { "type": "number", "default": 2, "description": "Lines of context before and after each match" },
316
- "max_results": { "type": "number", "default": 50 }
317
- }
318
- ```
319
- - Implementation:
320
- 1. Load repo from DB — verify it exists
321
- 2. Query `files` table for all files in repo (with `raw_content`)
322
- 3. If `file_pattern` is provided, filter file paths using minimatch
323
- 4. For each matching file: split `raw_content` into lines, scan for `query` (literal or regex)
324
- 5. For each match: record `{ file, line, column, matchText, contextBefore, contextAfter }`
325
- 6. Stop accumulating after `max_results` matches are found
326
- - Output:
327
- ```json
328
- {
329
- "matches": [
330
- {
331
- "file": "src/auth/login.ts",
332
- "line": 42,
333
- "column": 8,
334
- "match": "validateToken(",
335
- "context": " // Validate the JWT\n validateToken(token, secret);\n return decoded;"
336
- }
337
- ],
338
- "total_matches": 7,
339
- "files_searched": 143,
340
- "truncated": false,
341
- "_tokenEstimate": 820
342
- }
343
- ```
344
- - Security: apply `validatePath()` from the security layer on every file path before reading content
345
- - Register in `src/server/mcp-server.ts`
346
-
347
- **Key decisions:**
348
- - Search runs against cached `raw_content` in SQLite — no live file reads needed, keeps search fast
349
- - Regex errors are caught and returned as a tool error (not a server crash)
350
- - `context_lines` default of 2 gives enough context to understand the match without flooding the response
351
- - `_tokenEstimate` calculation: sum of all match + context line lengths / 4 (rough token estimate)
352
-
353
- **Verify:** Index a project, search for a string that appears in multiple files. Verify matches include correct line numbers and context. Search with an invalid regex — verify graceful error response. Search with `file_pattern` — verify only matching files are searched.
354
-
355
- **Tests:** Literal search with single result, literal search with multiple results, regex search, file_pattern filtering, max_results truncation (verify `truncated: true`), invalid regex returns error, empty result set.
356
-
357
- ---
358
-
359
- ## Task 30: Layer Violation Analyzer
360
-
361
- Implement the `get-layer-violations` tool called out in the PRD. This lets agents (and developers) verify that the declared architectural dependency rules are being followed — no core importing handlers, no handlers importing adapters, etc.
362
-
363
- **Deliverables:**
364
- - `src/server/tools/get-layer-violations.ts`
365
- - Tool name: `get-layer-violations`
366
- - Input schema:
367
- ```json
368
- {
369
- "repo": { "type": "string" },
370
- "layers": {
371
- "type": "array",
372
- "description": "Layer definitions (optional — falls back to config.json). If omitted, reads from project config.",
373
- "items": {
374
- "type": "object",
375
- "properties": {
376
- "name": { "type": "string" },
377
- "paths": { "type": "array", "items": { "type": "string" } }
378
- }
379
- }
380
- }
381
- }
382
- ```
383
- - Implementation:
384
- 1. Resolve layer config: prefer tool input, fall back to `config.json` `layers` field, fall back to error if neither present
385
- 2. Build a map: for each file in the repo, determine which layer it belongs to (first match wins; files matching no layer are `'unclassified'`)
386
- 3. Load all `dep_edges` for the repo from `dep_edges` table
387
- 4. For each edge `(sourceFile → targetFile)`:
388
- - Look up `sourceLayer` and `targetLayer`
389
- - Check if `sourceLayer → targetLayer` is a declared allowed direction
390
- - If not, record as a violation
391
- 5. Return violations grouped by layer pair
392
-
393
- - Output:
394
- ```json
395
- {
396
- "violations": [
397
- {
398
- "from_layer": "core",
399
- "to_layer": "handlers",
400
- "from_file": "src/core/index-manager.ts",
401
- "to_file": "src/handlers/typescript.ts",
402
- "import_spec": "../handlers/typescript.js"
403
- }
404
- ],
405
- "summary": {
406
- "total_violations": 1,
407
- "by_layer_pair": { "core → handlers": 1 }
408
- },
409
- "layers_analyzed": ["core", "handlers", "adapters"],
410
- "_tokenEstimate": 240
411
- }
412
- ```
413
-
414
- - Update `src/config/config-schema.ts`:
415
- - Add `layers` config key:
416
- ```json
417
- {
418
- "layers": {
419
- "definitions": [
420
- { "name": "core", "paths": ["src/core/**"] },
421
- { "name": "handlers", "paths": ["src/handlers/**"] },
422
- { "name": "adapters", "paths": ["src/adapters/**"] },
423
- { "name": "server", "paths": ["src/server/**"] }
424
- ],
425
- "rules": [
426
- { "from": "adapters", "to": "handlers", "allowed": true },
427
- { "from": "handlers", "to": "core", "allowed": true },
428
- { "from": "core", "to": "handlers", "allowed": false },
429
- { "from": "core", "to": "adapters", "allowed": false },
430
- { "from": "handlers", "to": "adapters", "allowed": false }
431
- ]
432
- }
433
- }
434
- ```
435
- - Any `from → to` pair not listed in `rules` is treated as `allowed: true` (opt-in violation detection)
436
- - Register in `src/server/mcp-server.ts`
437
-
438
- **Key decisions:**
439
- - Layer assignment is based on file path glob matching — the same glob patterns used for file discovery
440
- - `unclassified` files are excluded from violation checks (they'd generate too much noise)
441
- - Violations are directional: `core → handlers` is a violation, `handlers → core` is allowed (in the default config)
442
- - The default layer config in `config.json` mirrors PureContext's own architecture — useful for dogfooding
443
-
444
- **Verify:** Index this repo (PureContext itself). Run `get-layer-violations` with the default layer config. Verify the known-good architecture produces zero violations. Introduce a deliberate violation in a temp file — verify it is detected.
445
-
446
- **Tests:** Layer assignment (unit-testable): file in `src/core/` → `'core'`, file in `src/adapters/` → `'adapters'`, file outside any layer → `'unclassified'`. Violation detection: edge from allowed direction (no violation), edge from disallowed direction (violation recorded). Config fallback: no layers in input → reads from config.
447
-
448
- ---
449
-
450
- ## Task 31: AI Summarization Expansion
451
-
452
- Extend the AI summarizer from Phase 2 to support OpenAI-compatible endpoints (enabling self-hosted models, Ollama, LM Studio) and improve batch prompt quality by including symbol context. Also add re-summarization on incremental re-index so symbols that change get fresh summaries.
453
-
454
- **Deliverables:**
455
- - Update `src/summarizer/ai-summarizer.ts`:
456
- - Add `OpenAICompatibleSummarizer` alongside the existing Anthropic implementation
457
- - Any endpoint that speaks the OpenAI `/v1/chat/completions` API (Ollama, LM Studio, Azure OpenAI, OpenRouter, etc.)
458
- - Configured via `ai.endpoint` (the base URL) + `ai.apiKey` + `ai.model`
459
- - Uses `fetch` directly (no SDK dependency — the OpenAI API is simple enough)
460
- - Improve batch prompt quality:
461
- - Current: just symbol signature + 100 chars of source
462
- - New: signature + file path + parent class name (if method) + first 3 import specifiers (context about what the file does) + 150 chars of source
463
- - Prompt template: `"Summarize each symbol in one sentence. Context: [file_path]. [imports_hint].\n\nSymbols:\n1. [signature]\n[source_snippet]\n..."`
464
- - Add `summarizeBatchWithRetry()`: if the provider returns a malformed or incomplete response, retry once with a simpler prompt (signature only) before falling back to signature fallback
465
- - No change to graceful degradation behavior — network/API errors still return an empty map
466
-
467
- - Update `src/config/config-schema.ts`:
468
- - `ai.provider`: `'anthropic' | 'openai-compatible' | 'none'` (was `'anthropic' | 'openai' | 'none'`)
469
- - `ai.endpoint`: `string | null` — required for `'openai-compatible'`, ignored for `'anthropic'`
470
- - `ai.model`: `string` — default `'claude-haiku-4-5-20251001'` for Anthropic, `'gpt-4o-mini'` for OpenAI-compatible
471
- - `ai.apiKey`: `string` — can be a literal key or `'${ENV_VAR}'` notation (resolved from environment at load time)
472
- - `ai.batchSize`: `number` — default `50`, controls how many symbols per API call
473
-
474
- - Update `src/core/index-manager.ts`:
475
- - After incremental re-index (`reindexFiles()`): collect re-indexed symbol IDs
476
- - For each re-indexed symbol that previously had an AI-generated summary (detectable by absence of JSDoc/docstring): clear the summary (reset to signature fallback), then re-queue for AI summarization
477
- - This ensures stale AI summaries don't persist when code changes
478
-
479
- - Update `src/config/config-loader.ts`:
480
- - Resolve `'${ENV_VAR}'` references in `ai.apiKey` during load
481
- - Warn (do not throw) if `ai.allowRemoteAI` is `true` but `ai.apiKey` is missing
482
-
483
- **Key decisions:**
484
- - OpenAI-compatible support with `fetch` keeps the dependency count low — no `openai` npm package needed
485
- - Re-summarization on incremental re-index is conservative: only re-summarize symbols that actually changed (content hash comparison), not all symbols in the file
486
- - `ai.apiKey` env var resolution at load time (not at call time) keeps the hot path simple
487
- - `ai.batchSize` is configurable because self-hosted models (Ollama) have much lower context limits than cloud APIs
488
-
489
- **Verify:** Configure `ai.provider: 'openai-compatible'` with a local Ollama endpoint. Index a small project. Verify AI summaries are generated. Modify a file, trigger re-index — verify the changed symbol's summary is refreshed. Verify `ai.allowRemoteAI: false` still skips AI calls entirely.
490
-
491
- **Tests:** OpenAI-compatible provider: mock `fetch`, verify correct payload format, verify response parsing. Retry logic: mock a malformed first response, verify retry with simpler prompt. Re-summarization: stub index manager, verify changed symbols are re-queued but unchanged symbols are not. API key env var resolution.
492
-
493
- ---
494
-
495
- ## Task 32: Phase 3 Test Fixtures and Integration Tests
496
-
497
- Validate the full Phase 3 pipeline end-to-end across all new handlers, adapters, tools, and transports.
498
-
499
- **Deliverables:**
500
-
501
- - `test/fixtures/python-project/` — representative Python project:
502
- - `src/auth/auth.py` — class with methods, decorators, docstrings
503
- - `src/utils/format.py` — module-level functions, constants
504
- - `src/models/user.py` — dataclass / typed class
505
- - `src/api/__init__.py` — empty package file (should produce no symbols)
506
- - `tests/test_auth.py` — test file (should be indexed, lower priority)
507
- - Imports between files (relative and absolute)
508
-
509
- - `test/fixtures/go-project/` — representative Go project:
510
- - `main.go` — main package with exported function
511
- - `pkg/auth/auth.go` — struct, interface, methods, docstrings
512
- - `pkg/utils/format.go` — exported and unexported functions (unexported should be skipped)
513
- - `pkg/models/user.go` — struct with exported fields
514
- - Import declarations (standard library + internal)
515
-
516
- - `test/fixtures/nextjs-project/` — minimal Next.js project:
517
- - `package.json` with `"next": "^14.x"` dep
518
- - `next.config.js` (triggers detection)
519
- - `pages/index.tsx` — home page
520
- - `pages/blog/[slug].tsx` — dynamic route
521
- - `pages/api/users.ts` — Pages Router API route
522
- - `app/page.tsx` — App Router home page
523
- - `app/dashboard/page.tsx` — App Router nested page
524
- - `app/api/users/route.ts` — App Router API route with `GET` and `POST`
525
- - `middleware.ts` — root middleware
526
- - `src/components/Button.tsx` — React component (not a Next.js route)
527
-
528
- - Integration tests `test/integration/phase3.test.ts`:
529
- 1. Index `python-project` → verify class, methods, functions extracted with correct byte offsets
530
- 2. Verify Python docstring captured from `auth.py`
531
- 3. Verify Python `@decorator` functions extracted with correct name (not the `decorated_definition` node name)
532
- 4. Index `go-project` → verify exported functions/structs/interfaces extracted, unexported functions NOT extracted
533
- 5. Verify Go method name includes receiver type (e.g., `AuthService.Login`)
534
- 6. Verify Go docstring captured from preceding `//` comments
535
- 7. Index `nextjs-project` → verify Pages Router route `/blog/:slug` produced
536
- 8. Verify App Router route `/dashboard` produced with `router: 'app'`
537
- 9. Verify `app/api/users/route.ts` produces two route symbols (GET and POST)
538
- 10. Verify `src/components/Button.tsx` produces kind `'component'` (React adapter, not Next.js)
539
- 11. Run `search-text` on `python-project` → verify literal match returns correct line number and context
540
- 12. Run `search-text` with invalid regex → verify error response, no crash
541
- 13. Run `get-layer-violations` on PureContext itself with default layer config → verify zero violations
542
- 14. Start server with `--transport http` → send MCP message via HTTP → verify response
543
- 15. Phase 1 + Phase 2 tests: run full suite, verify zero regressions
544
-
545
- - **Performance benchmark** `test/benchmarks/phase3.bench.ts`:
546
- - Index `python-project` → record time and symbol count
547
- - Index `go-project` → record time and symbol count
548
- - `search-text` on a 200+ file project → verify response under 200ms
549
- - Compare Phase 3 symbol counts vs Phase 1 (JS/TS only) on a multi-language project
550
-
551
- **Verify:** `npm run test` passes entirely — all Phase 1, 2, and 3 tests green. No regressions. Performance targets from PRD Section 8.2 still met for existing tools.
552
-
553
- ---
554
-
555
- ## Order of Execution
556
-
557
- ```
558
- Task 24: Python language handler ██░░░░░░░░ Languages
559
- Task 25: Go language handler ███░░░░░░░ Languages
560
- Task 26: Next.js adapter ████░░░░░░ Adapters
561
- Task 27: Security layer █████░░░░░ Security
562
- Task 28: HTTP/SSE transport ██████░░░░ Transport
563
- Task 29: Full-text search tool ███████░░░ Tools
564
- Task 30: Layer violation analyzer ████████░░ Tools
565
- Task 31: AI summarization expansion █████████░ Summarization
566
- Task 32: Fixtures + integration tests ██████████ Polish
567
- ```
568
-
569
- Tasks 24 and 25 are fully independent — they can be developed in parallel. Task 26 (Next.js) depends on Phase 2's React adapter being complete but is otherwise independent of Tasks 24–25. Task 27 (security) should be complete before Task 28 (HTTP transport). Tasks 29 and 30 are independent of transport and can start once the core from Phase 1 is stable.
570
-
571
- **After Phase 3 is complete**, consider Phase 4: Angular adapter, Express/Fastify/NestJS backend adapters, GitHub repository indexing (`index-repo` tool), and extended language coverage (Rust, Java, C#).