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.
- package/package.json +1 -1
- package/docs/dev/API_STABILITY.md +0 -319
- package/docs/dev/DECISIONS.md +0 -22
- package/docs/dev/DOCUMENTATION_PLAN.md +0 -113
- package/docs/dev/PHASE10_TASKS.md +0 -476
- package/docs/dev/PHASE11_TASKS.md +0 -385
- package/docs/dev/PHASE12_TASKS.md +0 -335
- package/docs/dev/PHASE13_TASKS.md +0 -381
- package/docs/dev/PHASE14_TASKS.md +0 -371
- package/docs/dev/PHASE15_TASKS.md +0 -256
- package/docs/dev/PHASE16_TASKS.md +0 -314
- package/docs/dev/PHASE17_TASKS.md +0 -321
- package/docs/dev/PHASE18_TASKS.md +0 -345
- package/docs/dev/PHASE19_TASKS.md +0 -261
- package/docs/dev/PHASE1_TASKS.md +0 -443
- package/docs/dev/PHASE20_TASKS.md +0 -280
- package/docs/dev/PHASE21_TASKS.md +0 -355
- package/docs/dev/PHASE22_TASKS.md +0 -371
- package/docs/dev/PHASE23_TASKS.md +0 -274
- package/docs/dev/PHASE24_TASKS.md +0 -326
- package/docs/dev/PHASE25_TASKS.md +0 -452
- package/docs/dev/PHASE26_TASKS.md +0 -253
- package/docs/dev/PHASE27_TASKS.md +0 -410
- package/docs/dev/PHASE2_TASKS.md +0 -328
- package/docs/dev/PHASE3_TASKS.md +0 -571
- package/docs/dev/PHASE4_TASKS.md +0 -531
- package/docs/dev/PHASE5_TASKS.md +0 -835
- package/docs/dev/PHASE6_TASKS.md +0 -347
- package/docs/dev/PHASE7_TASKS.md +0 -257
- package/docs/dev/PHASE8_TASKS.md +0 -299
- package/docs/dev/PHASE9_TASKS.md +0 -320
- package/docs/dev/PureContext_MCP_PRD_v1.0.docx +0 -0
- package/docs/dev/SELF_HOSTING.md +0 -142
- package/docs/dev/TEAM_SETUP.md +0 -316
- package/docs/dev/TELEMETRY.md +0 -99
- package/docs/dev/feature-analysis.md +0 -305
- package/docs/dev/phase-1-notes.md +0 -3
package/docs/dev/PHASE3_TASKS.md
DELETED
|
@@ -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#).
|