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/PHASE4_TASKS.md
DELETED
|
@@ -1,531 +0,0 @@
|
|
|
1
|
-
# Phase 4 — Task Breakdown
|
|
2
|
-
|
|
3
|
-
**Goal**: Broaden language coverage to Rust, Java, and C#; add backend framework adapters (NestJS, Express/Fastify) and the Angular frontend adapter; harden the HTTP transport with authentication; enable remote repository indexing from GitHub; and deliver semantic search — the PRD Phase 3 item that was deferred.
|
|
4
|
-
|
|
5
|
-
**Scope rationale**: Phase 3 completed Python, Go, Next.js, HTTP/SSE transport, full-text search, layer violation analysis, and AI summarization expansion. The three items carried forward from the PRD that are still outstanding are: (1) semantic search, (2) authentication for remote HTTP deployments, and (3) extended language/framework coverage. Phase 4 closes all three.
|
|
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 33: Rust Language Handler
|
|
12
|
-
|
|
13
|
-
Add Rust as the first systems language. Rust's rich type system (structs, enums, traits, impls) maps cleanly onto existing `SymbolKind` types, and its `pub` visibility modifier provides a clean export boundary.
|
|
14
|
-
|
|
15
|
-
**Deliverables:**
|
|
16
|
-
- Download and bundle `grammars/tree-sitter-rust.wasm`
|
|
17
|
-
- `src/handlers/rust.ts` — implements `LanguageHandler`
|
|
18
|
-
- `extensions()`: `['.rs']`
|
|
19
|
-
- `grammarPath()`: path to `tree-sitter-rust.wasm`
|
|
20
|
-
- `extractSymbols(tree, source)`:
|
|
21
|
-
- `function_item` → kind `'function'`
|
|
22
|
-
- `struct_item` → kind `'class'`
|
|
23
|
-
- `enum_item` → kind `'enum'`
|
|
24
|
-
- `trait_item` → kind `'interface'`
|
|
25
|
-
- `impl_item` containing `function_item` children → kind `'method'`, name = `TypeName.method_name`
|
|
26
|
-
- `const_item` → kind `'const'`
|
|
27
|
-
- `type_item` (type alias) → kind `'type'`
|
|
28
|
-
- Only index items with a `pub` visibility_modifier — Rust's single visibility mechanism
|
|
29
|
-
- Exception: items inside `impl` blocks for public types may omit `pub` (emit them anyway if parent impl is public)
|
|
30
|
-
- `extractImports(tree, source)`:
|
|
31
|
-
- `use_declaration` → specifier = the path string (e.g. `std::collections::HashMap`)
|
|
32
|
-
- `extern_crate_declaration` → specifier = crate name
|
|
33
|
-
- `resolvedPath`: `null` for all (Cargo module system — no local file resolution without Cargo.toml parsing)
|
|
34
|
-
- `importedNames`: extract from `use_list` children (e.g. `use foo::{A, B}` → `['A', 'B']`)
|
|
35
|
-
- `extractDocstring(node)`:
|
|
36
|
-
- Look for `line_comment` nodes with `///` prefix immediately preceding the item
|
|
37
|
-
- Also check `doc_comment` nodes (block-style `/** */` is rare in Rust but supported)
|
|
38
|
-
- Strip `/// ` prefix, concatenate lines, return first paragraph
|
|
39
|
-
|
|
40
|
-
**Signature building:**
|
|
41
|
-
- Functions: `pub fn name(params) -> ReturnType`
|
|
42
|
-
- Structs: `pub struct Name { field1: Type, ... }` (show first 2 fields; `{ ... }` if more)
|
|
43
|
-
- Enums: `pub enum Name { Variant1, Variant2, ... }` (show first 3 variants)
|
|
44
|
-
- Traits: `pub trait Name { fn method1(...); }` (show first method signature)
|
|
45
|
-
- Constants: `pub const NAME: Type = value`
|
|
46
|
-
- Keep signatures under 120 chars
|
|
47
|
-
|
|
48
|
-
**Key technical notes:**
|
|
49
|
-
- `impl` blocks tie methods to types — the `impl_item` node has a `type` child naming the type
|
|
50
|
-
- Method name format: `TypeName.method_name` (consistent with Go handler)
|
|
51
|
-
- Trait implementations (`impl Trait for Type`) should use `TypeName.method_name` (not `Trait.method_name`)
|
|
52
|
-
- Macro-generated symbols (`derive`, procedural macros) are not visible to the AST parser — skip silently
|
|
53
|
-
- Rust has no classes; `struct` → `'class'` is the closest analog (same decision as Go)
|
|
54
|
-
|
|
55
|
-
**Verify:** Index `test/fixtures/rust-project/`. Verify exported structs, enums, traits, functions, and impl methods are extracted. Verify unexported items are skipped. Verify `///` docstrings are captured.
|
|
56
|
-
|
|
57
|
-
**Tests:** Fixture `.rs` files covering: `pub fn`, private `fn` (skipped), `pub struct`, `pub enum`, `pub trait`, `impl` with methods, `pub const`, type alias, `use` imports, `///` docstring.
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
## Task 34: Java Language Handler
|
|
62
|
-
|
|
63
|
-
Add Java as a statically-typed OO language. Java's structure (classes, interfaces, methods) maps directly onto existing `SymbolKind` types. Only `public` and `protected` symbols are indexed — private/package-private are navigation noise.
|
|
64
|
-
|
|
65
|
-
**Deliverables:**
|
|
66
|
-
- Download and bundle `grammars/tree-sitter-java.wasm`
|
|
67
|
-
- `src/handlers/java.ts` — implements `LanguageHandler`
|
|
68
|
-
- `extensions()`: `['.java']`
|
|
69
|
-
- `grammarPath()`: path to `tree-sitter-java.wasm`
|
|
70
|
-
- `extractSymbols(tree, source)`:
|
|
71
|
-
- `class_declaration` → kind `'class'`
|
|
72
|
-
- `interface_declaration` → kind `'interface'`
|
|
73
|
-
- `enum_declaration` → kind `'enum'`
|
|
74
|
-
- `annotation_type_declaration` → kind `'type'`
|
|
75
|
-
- `method_declaration` inside a class/interface → kind `'method'`, name = `ClassName.methodName`
|
|
76
|
-
- `constructor_declaration` → kind `'method'`, name = `ClassName.ClassName` (constructor)
|
|
77
|
-
- `field_declaration` with `static final` modifiers → kind `'const'`
|
|
78
|
-
- Visibility filter: only index nodes with `public` or `protected` modifiers
|
|
79
|
-
- Top-level class/interface only (not nested private classes)
|
|
80
|
-
- `extractImports(tree, source)`:
|
|
81
|
-
- `import_declaration` → specifier = the fully-qualified class name (strip `import ` prefix)
|
|
82
|
-
- `static` import: `import static foo.Bar.METHOD` → specifier = `foo.Bar`, importedNames = `['METHOD']`
|
|
83
|
-
- `resolvedPath`: `null` (Maven/Gradle classpath — no local resolution)
|
|
84
|
-
- `importedNames`: `[simpleClassName]` for regular imports
|
|
85
|
-
- `extractDocstring(node)`:
|
|
86
|
-
- Look for a `block_comment` node immediately preceding the declaration
|
|
87
|
-
- Must start with `/**` (Javadoc)
|
|
88
|
-
- Strip `/**`, `*/`, and leading ` * ` from each line
|
|
89
|
-
- Return first sentence (up to first `.` or 200 chars)
|
|
90
|
-
|
|
91
|
-
**Signature building:**
|
|
92
|
-
- Classes: `public class Name extends Base implements Interface`
|
|
93
|
-
- Interfaces: `public interface Name extends Parent`
|
|
94
|
-
- Methods: `public ReturnType methodName(Type param, ...)` (truncate params if > 100 chars)
|
|
95
|
-
- Constructors: `public ClassName(Type param, ...)`
|
|
96
|
-
- Constants: `public static final Type NAME = value`
|
|
97
|
-
- Keep signatures under 120 chars
|
|
98
|
-
|
|
99
|
-
**Key technical notes:**
|
|
100
|
-
- Java's `@Override`, `@Deprecated`, and other annotations precede methods — extract the first annotation if present as part of the signature context
|
|
101
|
-
- Generics (`<T extends Comparable<T>>`) can make signatures very long — truncate aggressively
|
|
102
|
-
- Inner classes: only extract `public static` inner classes; skip instance inner classes
|
|
103
|
-
- Java enums may have methods — extract them as `EnumName.methodName`
|
|
104
|
-
|
|
105
|
-
**Verify:** Index `test/fixtures/java-project/`. Verify public classes, interfaces, methods, and constants are extracted. Verify private/package-private items are skipped. Verify Javadoc comments are captured.
|
|
106
|
-
|
|
107
|
-
**Tests:** Fixture `.java` files covering: `public class`, `public interface`, `public enum`, public/private method (private skipped), `public static final` constant, Javadoc comment, `import` and `import static`, generic class.
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## Task 35: C# Language Handler
|
|
112
|
-
|
|
113
|
-
Add C# as the third statically-typed OO language. C# shares Java's class/interface/method structure but adds properties, namespaces, records, and primary constructors.
|
|
114
|
-
|
|
115
|
-
**Deliverables:**
|
|
116
|
-
- Download and bundle `grammars/tree-sitter-c-sharp.wasm`
|
|
117
|
-
- `src/handlers/csharp.ts` — implements `LanguageHandler`
|
|
118
|
-
- `extensions()`: `['.cs']`
|
|
119
|
-
- `grammarPath()`: path to `tree-sitter-c-sharp.wasm`
|
|
120
|
-
- `extractSymbols(tree, source)`:
|
|
121
|
-
- `class_declaration` → kind `'class'`
|
|
122
|
-
- `interface_declaration` → kind `'interface'`
|
|
123
|
-
- `enum_declaration` → kind `'enum'`
|
|
124
|
-
- `struct_declaration` → kind `'class'` (C# structs are value-type analogs of classes)
|
|
125
|
-
- `record_declaration` → kind `'class'`
|
|
126
|
-
- `method_declaration` inside a class/interface/struct → kind `'method'`
|
|
127
|
-
- `constructor_declaration` → kind `'method'`, name = `ClassName.ClassName`
|
|
128
|
-
- `property_declaration` → kind `'const'` (properties are named member access points)
|
|
129
|
-
- `field_declaration` with `const` or `static readonly` modifiers → kind `'const'`
|
|
130
|
-
- Visibility filter: only `public` and `protected` modifiers
|
|
131
|
-
- `extractImports(tree, source)`:
|
|
132
|
-
- `using_directive` → specifier = the namespace string (strip `using ` prefix)
|
|
133
|
-
- `using_static_directive` → specifier = the fully-qualified type
|
|
134
|
-
- `resolvedPath`: `null`
|
|
135
|
-
- `importedNames`: `[]` (C# using directives bring namespaces into scope, not individual names)
|
|
136
|
-
- `extractDocstring(node)`:
|
|
137
|
-
- Look for `single_line_doc_comment` nodes (`/// `) immediately preceding the declaration
|
|
138
|
-
- Extract the `<summary>` XML content if present, else return the raw text
|
|
139
|
-
- Strip `/// ` prefix and XML tags, return first sentence
|
|
140
|
-
|
|
141
|
-
**Signature building:**
|
|
142
|
-
- Classes: `public class Name : BaseClass, IInterface`
|
|
143
|
-
- Interfaces: `public interface IName`
|
|
144
|
-
- Methods: `public ReturnType MethodName(Type param, ...)`
|
|
145
|
-
- Properties: `public Type PropertyName { get; set; }`
|
|
146
|
-
- Constants: `public const Type NAME = value` or `public static readonly Type Name`
|
|
147
|
-
- Keep signatures under 120 chars
|
|
148
|
-
|
|
149
|
-
**Key technical notes:**
|
|
150
|
-
- C# properties look like fields syntactically but are accessors — treat as `'const'` kind (same pattern as Go struct fields)
|
|
151
|
-
- Async methods: `public async Task<T> MethodName(...)` — include `async` in signature
|
|
152
|
-
- Extension methods: `public static ReturnType MethodName(this Type param, ...)` — include `this` hint
|
|
153
|
-
- Nullable annotations (`?`): preserve in signatures (e.g. `string?`)
|
|
154
|
-
- Partial classes: multiple `partial class` declarations in different files should all be indexed independently
|
|
155
|
-
|
|
156
|
-
**Verify:** Index `test/fixtures/csharp-project/`. Verify public classes, interfaces, methods, and properties extracted. Verify `///` XML doc comments captured.
|
|
157
|
-
|
|
158
|
-
**Tests:** Fixture `.cs` files covering: `public class`, `public interface`, `public enum`, `public record`, public/private method, `public` property, `public const`, XML docstring (`/// <summary>`), `using` directive.
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## Task 36: Angular Adapter
|
|
163
|
-
|
|
164
|
-
Add Angular as the major frontend framework not yet covered. Angular's decorator-based architecture (`@Component`, `@Injectable`, `@NgModule`, `@Directive`, `@Pipe`) maps clearly onto existing symbol kinds.
|
|
165
|
-
|
|
166
|
-
**Deliverables:**
|
|
167
|
-
- `src/adapters/angular.ts` — implements `FrameworkAdapter`
|
|
168
|
-
- `name`: `'angular'`
|
|
169
|
-
- `detect(projectRoot)`:
|
|
170
|
-
- Check for `@angular/core` in `package.json` dependencies
|
|
171
|
-
- `fileFilter(filePath)`:
|
|
172
|
-
- `.component.ts`, `.service.ts`, `.module.ts`, `.directive.ts`, `.pipe.ts`, `.guard.ts`, `.resolver.ts`
|
|
173
|
-
- Also `*.ts` files that contain `@Component`, `@Injectable`, etc. (check file extension only; the AST check happens in extraction)
|
|
174
|
-
- `extractFrameworkSymbols(tree, source, filePath)`:
|
|
175
|
-
- Scan for `class_declaration` nodes with decorator children:
|
|
176
|
-
- `@Component` → kind `'component'`, extract `selector` from metadata
|
|
177
|
-
- `@Injectable` → kind `'class'` with `frameworkMeta.angular_service = true`
|
|
178
|
-
- `@NgModule` → kind `'class'` with `frameworkMeta.angular_module = true`
|
|
179
|
-
- `@Directive` → kind `'component'` with `frameworkMeta.angular_directive = true`, extract `selector`
|
|
180
|
-
- `@Pipe` → kind `'component'` with `frameworkMeta.angular_pipe = true`, extract `name` from metadata
|
|
181
|
-
- `@Guard` / class implementing `CanActivate` → kind `'middleware'`
|
|
182
|
-
- For each decorated class: set `name` = class name, `signature` = first decorator line + class declaration
|
|
183
|
-
- Extract route paths from `RouterModule.forRoot([...])` and `RouterModule.forChild([...])` call expressions → kind `'route'`
|
|
184
|
-
- `enrichMetadata(symbol)`:
|
|
185
|
-
- Add `frameworkMeta.selector` for components and directives
|
|
186
|
-
- Add `frameworkMeta.angular_injectable = true` for services
|
|
187
|
-
|
|
188
|
-
**Selector extraction:**
|
|
189
|
-
- Parse the decorator metadata object literal: `@Component({ selector: 'app-header', ... })`
|
|
190
|
-
- Extract the `selector` property value as a string
|
|
191
|
-
- For `@Pipe`, extract the `name` property
|
|
192
|
-
|
|
193
|
-
**Route extraction:**
|
|
194
|
-
- Find `RouterModule.forRoot` or `RouterModule.forChild` call expressions
|
|
195
|
-
- Walk the routes array literal: extract `path` string properties
|
|
196
|
-
- Emit one `'route'` symbol per route with `frameworkMeta.router = 'angular'`
|
|
197
|
-
- Handle lazy-loaded routes: `loadChildren: () => import(...)`
|
|
198
|
-
|
|
199
|
-
**Key technical notes:**
|
|
200
|
-
- Angular's `standalone: true` components (Angular 14+) don't require `NgModule` — detect and handle them
|
|
201
|
-
- Signals-based components (Angular 17+) look the same as class components from the AST perspective
|
|
202
|
-
- Guards may use the newer functional guard pattern (`export const canActivate = () => ...`) in Angular 15+ — emit as `'middleware'` kind with `frameworkMeta.angular_guard = true`
|
|
203
|
-
- Resolvers: `@Resolver` (GraphQL, not Angular routing) is NOT the same as `Resolve<T>` — check the interface, not a decorator
|
|
204
|
-
|
|
205
|
-
**Verify:** Index `test/fixtures/angular-project/`. Verify components (with selectors), services, modules, and route paths are extracted.
|
|
206
|
-
|
|
207
|
-
**Tests:** Component with selector, service with dependency injection, NgModule, standalone component, directive with selector, pipe with name, route path extraction from RouterModule, functional guard.
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
## Task 37: NestJS Adapter
|
|
212
|
-
|
|
213
|
-
Add NestJS as the primary Node.js backend framework adapter. NestJS's decorator-heavy architecture (`@Controller`, `@Get`, `@Post`, `@Injectable`, `@Module`) makes it ideal for structured extraction — routes, services, and modules are all explicitly declared.
|
|
214
|
-
|
|
215
|
-
**Deliverables:**
|
|
216
|
-
- `src/adapters/nestjs.ts` — implements `FrameworkAdapter`
|
|
217
|
-
- `name`: `'nestjs'`
|
|
218
|
-
- `detect(projectRoot)`:
|
|
219
|
-
- Check for `@nestjs/core` or `@nestjs/common` in `package.json` dependencies
|
|
220
|
-
- `fileFilter(filePath)`:
|
|
221
|
-
- `.controller.ts`, `.service.ts`, `.module.ts`, `.guard.ts`, `.middleware.ts`, `.interceptor.ts`, `.pipe.ts`, `.decorator.ts`
|
|
222
|
-
- `extractFrameworkSymbols(tree, source, filePath)`:
|
|
223
|
-
- `@Controller('path')` class → emit a `'route'` symbol per HTTP-method decorator inside:
|
|
224
|
-
- `@Get('/sub')` method → route symbol with `name = 'GET /path/sub'`, `frameworkMeta.http_method = 'GET'`
|
|
225
|
-
- `@Post(...)`, `@Put(...)`, `@Patch(...)`, `@Delete(...)` similarly
|
|
226
|
-
- Combine controller prefix + method path for the full route
|
|
227
|
-
- `@Injectable()` class → kind `'class'` with `frameworkMeta.nestjs_provider = true`
|
|
228
|
-
- `@Module(...)` class → kind `'class'` with `frameworkMeta.nestjs_module = true`
|
|
229
|
-
- `@Guard()` / `CanActivate` implementors → kind `'middleware'` with `frameworkMeta.nestjs_guard = true`
|
|
230
|
-
- `@Middleware()` / `NestMiddleware` → kind `'middleware'`
|
|
231
|
-
- `@Interceptor()` → kind `'middleware'` with `frameworkMeta.nestjs_interceptor = true`
|
|
232
|
-
- Route path merging:
|
|
233
|
-
- Controller prefix: `@Controller('users')` → `/users`
|
|
234
|
-
- Method decorator: `@Get(':id')` → `:id`
|
|
235
|
-
- Combined: `/users/:id`
|
|
236
|
-
- Support empty string prefix (root controller)
|
|
237
|
-
|
|
238
|
-
**Key technical notes:**
|
|
239
|
-
- NestJS uses TypeScript decorators heavily — the TypeScript handler must already parse the file before the adapter enriches it
|
|
240
|
-
- `@ApiTags`, `@ApiOperation` (Swagger decorators) should be ignored — they don't affect routing
|
|
241
|
-
- Versioning: `@Controller({ path: 'users', version: '1' })` — include version in route if present
|
|
242
|
-
- Microservice message patterns (`@MessagePattern`, `@EventPattern`) are not HTTP routes — skip them in Phase 4
|
|
243
|
-
|
|
244
|
-
**Verify:** Index `test/fixtures/nestjs-project/`. Verify GET/POST route symbols with full paths, service symbols, and module symbols.
|
|
245
|
-
|
|
246
|
-
**Tests:** Controller with base path + GET/POST methods (path combining), empty-path controller, service with `@Injectable`, module, guard, parameterized route (`:id`), nested path.
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
## Task 38: Express/Fastify Adapter
|
|
251
|
-
|
|
252
|
-
Add lightweight adapters for Express and Fastify. Unlike NestJS, these frameworks define routes procedurally — route extraction requires pattern-matching call expressions rather than decorators.
|
|
253
|
-
|
|
254
|
-
**Deliverables:**
|
|
255
|
-
- `src/adapters/express.ts` — implements `FrameworkAdapter`
|
|
256
|
-
- `name`: `'express'`
|
|
257
|
-
- `detect(projectRoot)`: check for `express` in `package.json` dependencies
|
|
258
|
-
- `fileFilter(filePath)`: `.ts`, `.js` files (broad — filter by content in extraction)
|
|
259
|
-
- `extractFrameworkSymbols(tree, source, filePath)`:
|
|
260
|
-
- Scan for `call_expression` nodes matching:
|
|
261
|
-
- `app.get(path, ...)`, `app.post(...)`, `app.put(...)`, `app.delete(...)`, `app.patch(...)` → kind `'route'`
|
|
262
|
-
- `router.get(...)`, `router.post(...)`, etc. → kind `'route'`
|
|
263
|
-
- `app.use(path, ...)` → kind `'middleware'` if path is a string literal
|
|
264
|
-
- For each matched call: extract the path string literal as `name`, set `frameworkMeta.http_method`
|
|
265
|
-
- Skip dynamic path construction (variable paths) — only extract string literal paths
|
|
266
|
-
- Skip calls where `app`/`router` is not a recognizable Express variable name (use heuristic: variable assigned from `express()` or `express.Router()`)
|
|
267
|
-
|
|
268
|
-
- `src/adapters/fastify.ts` — implements `FrameworkAdapter`
|
|
269
|
-
- `name`: `'fastify'`
|
|
270
|
-
- `detect(projectRoot)`: check for `fastify` or `@fastify/core` in `package.json` dependencies
|
|
271
|
-
- `fileFilter(filePath)`: `.ts`, `.js` files
|
|
272
|
-
- `extractFrameworkSymbols(tree, source, filePath)`:
|
|
273
|
-
- Scan for `call_expression` nodes matching:
|
|
274
|
-
- `fastify.get(path, ...)`, `fastify.post(...)`, etc. → kind `'route'`
|
|
275
|
-
- `fastify.register(plugin, { prefix: '...' })` → kind `'middleware'` with route prefix metadata
|
|
276
|
-
- Same string-literal-only constraint as Express adapter
|
|
277
|
-
|
|
278
|
-
**Key technical notes:**
|
|
279
|
-
- Express route definition order matters for middleware but not for symbol extraction
|
|
280
|
-
- Router nesting: `app.use('/api', apiRouter)` gives `apiRouter` routes a `/api` prefix — this is hard to resolve statically without full execution; emit routes without the prefix and note it in `frameworkMeta.prefix_unknown = true`
|
|
281
|
-
- Fastify plugins: `fastify.register()` with an inline function is equivalent to `router.use()` — extract the `prefix` option if present
|
|
282
|
-
- Both adapters: only extract routes where the path is a string literal — dynamic paths (template literals, variables) are too noisy
|
|
283
|
-
|
|
284
|
-
**Verify:** Index `test/fixtures/express-project/` and `test/fixtures/fastify-project/`. Verify GET/POST route symbols and middleware symbols are extracted from literal paths.
|
|
285
|
-
|
|
286
|
-
**Tests (Express):** `app.get`/`app.post`/`app.use`, `router.get`/`router.post`, dynamic path variable (skipped), non-Express `app` variable (skipped).
|
|
287
|
-
**Tests (Fastify):** `fastify.get`/`fastify.post`/`fastify.register` with prefix.
|
|
288
|
-
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
## Task 39: HTTP Transport Authentication
|
|
292
|
-
|
|
293
|
-
Add API key authentication to the HTTP transport. Required before the server can safely bind to non-loopback addresses for team/remote deployments.
|
|
294
|
-
|
|
295
|
-
**Deliverables:**
|
|
296
|
-
- Update `src/config/config-schema.ts`:
|
|
297
|
-
```typescript
|
|
298
|
-
interface HttpConfig {
|
|
299
|
-
port: number;
|
|
300
|
-
host: string;
|
|
301
|
-
corsOrigins: string[];
|
|
302
|
-
auth: {
|
|
303
|
-
/** Enable bearer token authentication. Default: false. */
|
|
304
|
-
enabled: boolean;
|
|
305
|
-
/**
|
|
306
|
-
* Bearer token to require on all /mcp requests.
|
|
307
|
-
* Supports ${ENV_VAR} notation for secret management.
|
|
308
|
-
* If omitted when enabled=true, generates a random token on startup and logs it.
|
|
309
|
-
*/
|
|
310
|
-
token: string;
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
```
|
|
314
|
-
- Update `src/server/http-server.ts`:
|
|
315
|
-
- When `auth.enabled = true`: check `Authorization: Bearer <token>` header on all `POST /mcp` requests
|
|
316
|
-
- Reject with `401 Unauthorized` + JSON body `{"error":"Unauthorized"}` if missing or wrong
|
|
317
|
-
- `/health` endpoint is always public (no auth required)
|
|
318
|
-
- `OPTIONS` preflight is always public
|
|
319
|
-
- Log a warning on startup if `host !== '127.0.0.1'` and `auth.enabled = false`
|
|
320
|
-
- Update `src/config/config-loader.ts`:
|
|
321
|
-
- If `auth.enabled = true` and `auth.token` is empty: generate a random 32-byte hex token with `crypto.randomBytes(32).toString('hex')` and log it to stderr at startup: `PureContext MCP auth token: <token>`
|
|
322
|
-
- Apply `resolveEnvVar()` to `auth.token`
|
|
323
|
-
- Update `src/server/mcp-server.ts`:
|
|
324
|
-
- Pass auth config to `startHttpServer`
|
|
325
|
-
|
|
326
|
-
**Security notes:**
|
|
327
|
-
- The generated token is logged once to stderr on first startup — users should save it and add it to config
|
|
328
|
-
- No rate limiting in Phase 4 (add in Phase 5 if remote deployments are common)
|
|
329
|
-
- Token comparison must use `crypto.timingSafeEqual()` to prevent timing attacks
|
|
330
|
-
|
|
331
|
-
**Verify:** Start server with `auth.enabled = true` and a configured token. Verify that requests without the token return 401. Verify requests with the correct token succeed. Verify `/health` always returns 200.
|
|
332
|
-
|
|
333
|
-
**Tests:** No auth header → 401, wrong token → 401, correct token → 200, health check bypasses auth, token generated when not set (logged to stderr).
|
|
334
|
-
|
|
335
|
-
---
|
|
336
|
-
|
|
337
|
-
## Task 40: Remote Repository Indexing
|
|
338
|
-
|
|
339
|
-
Add a new `index_repo` tool that clones and indexes a GitHub (or any Git) repository without requiring the user to clone it manually.
|
|
340
|
-
|
|
341
|
-
**Deliverables:**
|
|
342
|
-
- `src/server/tools/index-repo.ts`
|
|
343
|
-
- Tool name: `index_repo`
|
|
344
|
-
- Input schema:
|
|
345
|
-
```json
|
|
346
|
-
{
|
|
347
|
-
"url": { "type": "string", "description": "Git repository URL (https:// or git@)" },
|
|
348
|
-
"branch": { "type": "string", "description": "Branch or tag to index (default: default branch)" },
|
|
349
|
-
"token": { "type": "string", "description": "GitHub/GitLab personal access token for private repos (optional)" },
|
|
350
|
-
"fileLimit": { "type": "number", "default": 5000 }
|
|
351
|
-
}
|
|
352
|
-
```
|
|
353
|
-
- Implementation:
|
|
354
|
-
1. Validate the URL — reject non-http(s) and non-git@ URLs
|
|
355
|
-
2. Clone the repo to a temp directory under `~/.purecontext/clones/` using `child_process.spawn('git', ['clone', '--depth=1', '--branch', branch, url, targetDir])`
|
|
356
|
-
3. Call `indexFolder(targetDir, { fileLimit })` with the standard pipeline
|
|
357
|
-
4. Store the clone dir path in the repo metadata so the watcher and `reindexFiles` can reference it
|
|
358
|
-
5. Return the same response as `index_folder` (repoId, symbolCount, fileCount, duration)
|
|
359
|
-
- Cleanup: cloned repos are persistent (not temp) — agents can call `delete_index` to remove both the DB and the clone dir
|
|
360
|
-
|
|
361
|
-
- Add `clonePath` column to `repos` table in `src/core/db/schema.ts` (nullable — null for locally-indexed repos)
|
|
362
|
-
- Update `deleteIndex` in `src/core/index-manager.ts`: if `clonePath` is set, `rmSync(clonePath, { recursive: true })` after deleting the DB
|
|
363
|
-
|
|
364
|
-
- `src/config/config-schema.ts`: add `clonesDir: string` (default: `~/.purecontext/clones/`)
|
|
365
|
-
|
|
366
|
-
**Security notes:**
|
|
367
|
-
- Never execute arbitrary git URLs without validation — only `https://`, `http://`, and `git@` schemes allowed
|
|
368
|
-
- The `token` parameter is passed as a URL credential (`https://token@host/path`) — never log it
|
|
369
|
-
- Clone directory is isolated under `~/.purecontext/clones/` — same security boundary as the index DB
|
|
370
|
-
|
|
371
|
-
**Key technical notes:**
|
|
372
|
-
- `--depth=1` shallow clone keeps disk usage low for large repos
|
|
373
|
-
- Git must be available on `PATH` — check before executing and return a clear error if not found
|
|
374
|
-
- Private repos: inject token into URL as `https://<token>@github.com/owner/repo.git`
|
|
375
|
-
- Progress: `git clone` output goes to stderr — capture and forward to logger at debug level
|
|
376
|
-
- Timeout: kill the git process after 5 minutes to prevent hanging on large repos
|
|
377
|
-
|
|
378
|
-
**Verify:** Clone a small public GitHub repository, verify symbols are indexed correctly. Clone with an invalid URL, verify error response.
|
|
379
|
-
|
|
380
|
-
**Tests:** Valid public URL (mock `git clone` with a temp dir), invalid URL scheme rejected, git not on PATH returns clear error, `deleteIndex` removes clone dir.
|
|
381
|
-
|
|
382
|
-
---
|
|
383
|
-
|
|
384
|
-
## Task 41: Semantic Search
|
|
385
|
-
|
|
386
|
-
Implement semantic (meaning-based) search over indexed symbols. Fills the PRD Phase 3 deliverable that was deferred. Agents can ask "find functions that validate email addresses" and get relevant symbols even when no literal string matches.
|
|
387
|
-
|
|
388
|
-
**Deliverables:**
|
|
389
|
-
|
|
390
|
-
**Storage:**
|
|
391
|
-
- Add `embedding` column (`BLOB`, nullable) to the `symbols` table
|
|
392
|
-
- Add `fts_symbols` virtual table using SQLite FTS5 on `name + signature + summary` for fast keyword fallback
|
|
393
|
-
|
|
394
|
-
**Embedding generation:**
|
|
395
|
-
- `src/summarizer/embeddings.ts`:
|
|
396
|
-
```typescript
|
|
397
|
-
export interface EmbeddingProvider {
|
|
398
|
-
embed(texts: string[]): Promise<number[][]>;
|
|
399
|
-
}
|
|
400
|
-
export function createEmbeddingProvider(config: AIConfig): EmbeddingProvider | null;
|
|
401
|
-
```
|
|
402
|
-
- Anthropic: use `voyage-code-2` model via `https://api.voyageai.com/v1/embeddings` (Voyage AI is Anthropic's recommended embedding model for code)
|
|
403
|
-
- OpenAI-compatible: use `/v1/embeddings` endpoint with the configured model (e.g. `nomic-embed-text` for Ollama)
|
|
404
|
-
- Returns `null` if no embedding provider is configured
|
|
405
|
-
- Update `src/summarizer/summarizer.ts` (Stage 3.5 — after AI summarization):
|
|
406
|
-
- If embedding provider is configured: batch-generate embeddings for all symbols
|
|
407
|
-
- Store as 32-bit float array serialized to Buffer (`Float32Array → Buffer`)
|
|
408
|
-
- Max batch size: 128 texts per API call
|
|
409
|
-
|
|
410
|
-
**Search:**
|
|
411
|
-
- `src/server/tools/search-semantic.ts`
|
|
412
|
-
- Tool name: `search_semantic`
|
|
413
|
-
- Input schema:
|
|
414
|
-
```json
|
|
415
|
-
{
|
|
416
|
-
"repo": { "type": "string" },
|
|
417
|
-
"query": { "type": "string", "description": "Natural language description of what to find" },
|
|
418
|
-
"limit": { "type": "number", "default": 20 },
|
|
419
|
-
"minScore": { "type": "number", "default": 0.7, "description": "Minimum cosine similarity (0–1)" }
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
- Implementation:
|
|
423
|
-
1. Embed the query using the same provider used during indexing
|
|
424
|
-
2. Load all symbol embeddings from DB for the repo
|
|
425
|
-
3. Compute cosine similarity between query embedding and each symbol embedding
|
|
426
|
-
4. Return top-`limit` results above `minScore`, sorted by similarity descending
|
|
427
|
-
5. If no embeddings are stored (provider not configured): fall back to FTS5 keyword search
|
|
428
|
-
- Output: same structure as `search_symbols` — array of SymbolRecord with a `score` field added
|
|
429
|
-
- Update `src/config/config-schema.ts`:
|
|
430
|
-
- Add `ai.embeddingModel?: string` and `ai.embeddingProvider?: 'voyage' | 'openai-compatible'`
|
|
431
|
-
|
|
432
|
-
**Performance:**
|
|
433
|
-
- Cosine similarity is O(n) over all symbols — acceptable up to ~50k symbols (~10ms at 100ns/symbol)
|
|
434
|
-
- Beyond 50k symbols, consider chunking or approximate nearest-neighbour (HNSW) — defer to Phase 5
|
|
435
|
-
- Embeddings are generated once per index and reused — no re-embedding on queries
|
|
436
|
-
|
|
437
|
-
**Key technical notes:**
|
|
438
|
-
- `Float32Array` embedding vectors are typically 768 or 1536 dimensions — store as raw bytes (4 bytes × dims)
|
|
439
|
-
- Cosine similarity: `dot(a, b) / (|a| × |b|)` — normalize vectors at storage time to reduce query cost to a dot product
|
|
440
|
-
- FTS5 fallback: when embeddings are absent, use SQLite's built-in full-text search on `name || ' ' || signature || ' ' || summary`
|
|
441
|
-
- Voyage AI `voyage-code-2` is specifically trained on code and documentation — preferred over general-purpose models
|
|
442
|
-
|
|
443
|
-
**Verify:** Index a project with an embedding provider configured. Query for a natural language description and verify relevant symbols are returned. Query without embeddings configured — verify FTS5 fallback works.
|
|
444
|
-
|
|
445
|
-
**Tests:** Cosine similarity function (unit test), embedding provider mock, `search_semantic` returns results above minScore, FTS5 fallback when no embeddings, `minScore` filtering.
|
|
446
|
-
|
|
447
|
-
---
|
|
448
|
-
|
|
449
|
-
## Task 42: Phase 4 Test Fixtures and Integration Tests
|
|
450
|
-
|
|
451
|
-
Validate the full Phase 4 pipeline end-to-end.
|
|
452
|
-
|
|
453
|
-
**Deliverables:**
|
|
454
|
-
|
|
455
|
-
- `test/fixtures/rust-project/` — representative Rust project:
|
|
456
|
-
- `src/lib.rs` — public structs, traits, functions, `///` docstrings
|
|
457
|
-
- `src/auth.rs` — `pub struct AuthService`, impl block with methods
|
|
458
|
-
- `src/models.rs` — `pub struct User`, `pub enum Role`
|
|
459
|
-
- `Cargo.toml` — minimal manifest
|
|
460
|
-
|
|
461
|
-
- `test/fixtures/java-project/` — representative Java project:
|
|
462
|
-
- `src/AuthService.java` — public class with methods, Javadoc
|
|
463
|
-
- `src/User.java` — public class with fields and constructors
|
|
464
|
-
- `src/IAuthenticator.java` — public interface
|
|
465
|
-
- `src/Role.java` — public enum
|
|
466
|
-
|
|
467
|
-
- `test/fixtures/csharp-project/` — representative C# project:
|
|
468
|
-
- `src/AuthService.cs` — public class with methods, XML doc comments
|
|
469
|
-
- `src/User.cs` — public record/class with properties
|
|
470
|
-
- `src/IAuthenticator.cs` — public interface
|
|
471
|
-
|
|
472
|
-
- `test/fixtures/angular-project/` — minimal Angular project:
|
|
473
|
-
- `package.json` with `@angular/core` dep
|
|
474
|
-
- `src/app/app.module.ts` — `@NgModule`
|
|
475
|
-
- `src/app/app.component.ts` — `@Component` with selector
|
|
476
|
-
- `src/app/auth.service.ts` — `@Injectable` service
|
|
477
|
-
- `src/app/app-routing.module.ts` — `RouterModule.forRoot` with routes
|
|
478
|
-
|
|
479
|
-
- `test/fixtures/nestjs-project/` — minimal NestJS project:
|
|
480
|
-
- `package.json` with `@nestjs/core` dep
|
|
481
|
-
- `src/users/users.controller.ts` — `@Controller('users')` with `@Get`/`@Post`
|
|
482
|
-
- `src/users/users.service.ts` — `@Injectable` service
|
|
483
|
-
- `src/app.module.ts` — `@Module`
|
|
484
|
-
- `src/auth/auth.guard.ts` — `CanActivate` guard
|
|
485
|
-
|
|
486
|
-
- `test/fixtures/express-project/` — minimal Express project:
|
|
487
|
-
- `package.json` with `express` dep
|
|
488
|
-
- `src/routes/users.ts` — `router.get`/`router.post` with literal paths
|
|
489
|
-
- `src/app.ts` — `app.use('/users', usersRouter)`
|
|
490
|
-
|
|
491
|
-
- Integration tests `test/integration/phase4.test.ts`:
|
|
492
|
-
1. Rust: public struct, trait, function extracted; private items skipped
|
|
493
|
-
2. Rust: `///` docstring captured; impl method name includes type
|
|
494
|
-
3. Java: public class, interface, method extracted; private method skipped
|
|
495
|
-
4. Java: Javadoc `/** */` comment captured
|
|
496
|
-
5. C#: public class, property, method extracted; `/// <summary>` captured
|
|
497
|
-
6. Angular: `@Component` produces kind `'component'` with selector; `@Injectable` produces kind `'class'`
|
|
498
|
-
7. Angular: `RouterModule.forRoot` routes extracted
|
|
499
|
-
8. NestJS: `@Controller('users')` + `@Get(':id')` produces route `GET /users/:id`
|
|
500
|
-
9. NestJS: `@Injectable` service produces kind `'class'` with `nestjs_provider` metadata
|
|
501
|
-
10. Express: `router.get('/users', ...)` produces route symbol; dynamic path skipped
|
|
502
|
-
11. HTTP auth: request without token returns 401; correct token returns 200
|
|
503
|
-
12. `index_repo`: mock git clone, verify indexing runs on cloned dir
|
|
504
|
-
13. `search_semantic` FTS5 fallback: query returns results matching name/signature keywords
|
|
505
|
-
14. Full suite regression: all Phase 1–3 tests still green
|
|
506
|
-
|
|
507
|
-
**Performance benchmark** `test/benchmarks/phase4.bench.ts`:
|
|
508
|
-
- Index `rust-project` → record time and symbol count
|
|
509
|
-
- Index `java-project` → record time and symbol count
|
|
510
|
-
- `search_semantic` with FTS5 on a 500-symbol repo → verify under 50ms
|
|
511
|
-
|
|
512
|
-
---
|
|
513
|
-
|
|
514
|
-
## Order of Execution
|
|
515
|
-
|
|
516
|
-
```
|
|
517
|
-
Task 33: Rust language handler ██░░░░░░░░ Languages
|
|
518
|
-
Task 34: Java language handler ███░░░░░░░ Languages
|
|
519
|
-
Task 35: C# language handler ████░░░░░░ Languages
|
|
520
|
-
Task 36: Angular adapter █████░░░░░ Adapters
|
|
521
|
-
Task 37: NestJS adapter ██████░░░░ Adapters
|
|
522
|
-
Task 38: Express/Fastify adapter ███████░░░ Adapters
|
|
523
|
-
Task 39: HTTP auth ████████░░ Security
|
|
524
|
-
Task 40: Remote repository indexing █████████░ Tools
|
|
525
|
-
Task 41: Semantic search █████████░ Tools
|
|
526
|
-
Task 42: Fixtures + integration tests ██████████ Polish
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
Tasks 33, 34, and 35 are fully independent — they can be developed in parallel. Task 36 (Angular) depends on the TypeScript handler (Phase 1) but is independent of 33–35. Tasks 37 and 38 (NestJS, Express/Fastify) are also TypeScript-only and independent of each other. Task 39 (HTTP auth) builds directly on the Phase 3 HTTP transport. Task 40 (remote indexing) depends on the core indexing pipeline being stable but is otherwise independent. Task 41 (semantic search) requires the AI summarizer infrastructure from Phase 3 and adds embedding generation as a new stage. Task 42 requires all previous tasks.
|
|
530
|
-
|
|
531
|
-
**After Phase 4 is complete**, consider Phase 5: Ruby and PHP language handlers, approximate nearest-neighbour semantic search for large repos (>50k symbols), rate limiting and multi-tenant auth for hosted deployments, and a web UI for exploring the symbol graph.
|