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,280 +0,0 @@
1
- # Phase 20 — Tool Capability Enhancements
2
-
3
- **Goal**: Sharpen four existing rough edges: search transparency (debug mode), symbol source precision (context lines + drift detection), GitHub API indexing without cloning, and Gemini Flash as a summarization backend.
4
-
5
- **Scope rationale**: These are quality-of-life improvements that close behavioural gaps vs jCodeMunch. Search debug mode is essential for tuning and trust. `context_lines` is frequently needed by agents reading symbol boundaries. Hash drift detection catches stale caches before they cause bugs. GitHub API indexing removes the clone requirement for remote repos. Gemini support broadens the summarization options beyond Anthropic + OpenAI-compatible.
6
-
7
- ---
8
-
9
- ## Task 135: Search Debug Mode
10
-
11
- Add an optional `debug: true` parameter to `search_symbols` and `search_semantic` that returns a per-result score breakdown so users can understand and tune relevance.
12
-
13
- **Current state**: Both search tools return ranked results with no explanation of *why* a result ranked where it did. When results are surprising, there is no way to diagnose the scoring.
14
-
15
- **Deliverables**:
16
-
17
- - Update `src/server/tools/search-symbols.ts`:
18
- ```typescript
19
- interface SearchSymbolsInput {
20
- // ... existing fields ...
21
- debug?: boolean; // Default false
22
- }
23
-
24
- interface SymbolSearchResult {
25
- // ... existing fields ...
26
- _score?: { // Only present when debug: true
27
- total: number;
28
- nameExact: number;
29
- namePrefix: number;
30
- nameFuzzy: number;
31
- signatureMatch: number;
32
- summaryMatch: number;
33
- kindBoost: number;
34
- recencyBoost: number;
35
- };
36
- }
37
- ```
38
- - Update `src/server/tools/search-semantic.ts`:
39
- ```typescript
40
- interface SearchSemanticInput {
41
- // ... existing fields ...
42
- debug?: boolean;
43
- }
44
-
45
- interface SemanticSearchResult {
46
- // ... existing fields ...
47
- _score?: {
48
- total: number;
49
- vectorSimilarity: number; // Cosine similarity from HNSW
50
- keywordBoost: number; // FTS5 overlap bonus
51
- nameMatch: number; // Exact/prefix name match bonus
52
- hybridWeight: number; // α blending factor used
53
- };
54
- }
55
- ```
56
- - Thread `debug` flag through to the FTS5 scorer in `src/core/db/symbol-store.ts` and the HNSW retriever in the semantic search pipeline
57
- - Score fields should be raw (not normalised) so they are comparable across calls
58
- - When `debug: false` (default), the `_score` key is omitted entirely — no overhead
59
-
60
- **Key technical notes**:
61
- - Score components should reflect the actual weights used in the ranking algorithm, not reconstructed post-hoc
62
- - For FTS5, expose the BM25 score plus each field's contribution (name, signature, summary, keywords columns)
63
- - For HNSW, expose the raw cosine distance converted to similarity, plus the keyword re-ranking delta
64
- - Keep debug output deterministic — same query must produce same scores for the same index state
65
-
66
- **Tests**: `test/server/search-debug.test.ts`
67
- - `search_symbols` with `debug: true` returns `_score` on each result
68
- - `search_symbols` with `debug: false` omits `_score`
69
- - `search_semantic` with `debug: true` returns vector + keyword scores
70
- - Score components sum correctly to `total`
71
-
72
- **Verify**:
73
- ```bash
74
- # search_symbols { repoId: "...", query: "parseSymbol", debug: true }
75
- # Each result should include _score breakdown
76
- ```
77
-
78
- ---
79
-
80
- ## Task 136: `context_lines` and `verify` Parameters on `get_symbol_source` and `get_symbols`
81
-
82
- Extend symbol retrieval with surrounding context lines and optional content hash drift detection.
83
-
84
- **Current state**: `get_symbol_source` returns the exact bytes of a symbol — no surrounding context. There is no way to verify whether the cached symbol bytes still match the file on disk.
85
-
86
- **Deliverables**:
87
-
88
- - Update `src/server/tools/get-symbol-source.ts`:
89
- ```typescript
90
- interface GetSymbolSourceInput {
91
- repoId: string;
92
- symbolId: string;
93
- contextLines?: number; // Lines before and after symbol. Default 0, max 50
94
- verify?: boolean; // Re-hash file from disk. Default false
95
- }
96
-
97
- interface GetSymbolSourceOutput {
98
- // ... existing fields ...
99
- contextLines?: number; // Echoed back
100
- hashDrift?: boolean; // Only present when verify: true
101
- diskHash?: string; // Only present when verify: true and hashDrift: true
102
- cachedHash?: string; // Only present when verify: true and hashDrift: true
103
- }
104
- ```
105
- - `get_symbols` (Task 133) should also support both parameters — implement the shared logic once in a helper
106
-
107
- - Create `src/core/symbol-source-helper.ts`:
108
- ```typescript
109
- export function expandWithContextLines(
110
- fileContent: Buffer,
111
- startByte: number,
112
- endByte: number,
113
- contextLines: number
114
- ): { expandedContent: string; expandedStartLine: number; expandedEndLine: number }
115
-
116
- export async function verifyContentHash(
117
- filePath: string,
118
- cachedHash: string
119
- ): Promise<{ drift: boolean; diskHash: string }>
120
- ```
121
- - `expandWithContextLines`: split file content on `\n`, find lines containing `startByte`/`endByte`, expand by N lines in each direction, re-join
122
- - `verifyContentHash`: read file from disk, SHA-256 hash it, compare to `cachedHash` from `files` table
123
-
124
- **Key technical notes**:
125
- - `contextLines` max 50 to prevent accidentally returning entire files
126
- - When `contextLines > 0`, include line numbers in the output (same format as `get_file_content`)
127
- - `verify` reads the file from disk — only use when content freshness matters. Document the performance cost in the tool description
128
- - Hash drift does NOT auto-trigger re-indexing — it only reports. Let the caller decide (e.g. call `index_folder` again)
129
-
130
- **Tests**: `test/server/symbol-context.test.ts`
131
- - `contextLines: 3` returns 3 lines before and after symbol
132
- - `contextLines: 0` returns bare symbol (backward compatible)
133
- - `contextLines > 50` → clamped to 50 with warning in `_meta`
134
- - `verify: true` with matching hash → `hashDrift: false`
135
- - `verify: true` with modified file → `hashDrift: true` + both hashes returned
136
- - `verify: false` → no `hashDrift` field
137
-
138
- **Verify**:
139
- ```bash
140
- # get_symbol_source { repoId: "...", symbolId: "abc123", contextLines: 5, verify: true }
141
- ```
142
-
143
- ---
144
-
145
- ## Task 137: GitHub API Indexing
146
-
147
- Extend `index_repo` to support indexing a GitHub repository via the GitHub Contents API — no local clone required.
148
-
149
- **Current state**: `index_repo` clones the repository using git and indexes the local clone. This requires git to be installed, adequate disk space, and works poorly for large repos or CI environments without persistent storage.
150
-
151
- **Deliverables**:
152
-
153
- - Update `src/server/tools/index-repo.ts` to detect GitHub URLs and route to the API path:
154
- ```typescript
155
- interface IndexRepoInput {
156
- url: string; // git clone URL or https://github.com/owner/repo
157
- branch?: string; // Default: repo's default branch
158
- useGitHubApi?: boolean; // Force GitHub API mode (auto-detected for github.com URLs)
159
- githubToken?: string; // Override GITHUB_TOKEN env var
160
- }
161
- ```
162
- - Create `src/core/github-api-fetcher.ts`:
163
- ```typescript
164
- interface GitHubApiFetcher {
165
- listFiles(owner: string, repo: string, branch: string): Promise<GitHubTreeEntry[]>
166
- fetchFile(owner: string, repo: string, sha: string): Promise<Buffer>
167
- getDefaultBranch(owner: string, repo: string): Promise<string>
168
- getCommitSha(owner: string, repo: string, branch: string): Promise<string>
169
- }
170
- ```
171
- - GitHub API strategy:
172
- 1. `GET /repos/{owner}/{repo}` — get default branch + metadata
173
- 2. `GET /repos/{owner}/{repo}/git/trees/{branch}?recursive=1` — get full file tree
174
- 3. Filter to indexable extensions using existing `file-discovery.ts` logic
175
- 4. Fetch file content via `GET /repos/{owner}/{repo}/contents/{path}` (base64 encoded) or blob SHA
176
- 5. Incremental re-index: store `git_tree_sha` in `repos` table; on next call, compare to avoid re-fetching unchanged files (use blob SHA from tree API)
177
- - Auto-detect GitHub API mode: if `url` matches `github.com/{owner}/{repo}`, use API. Otherwise use git clone
178
- - Respect `GITHUB_TOKEN` env var for authentication (higher rate limits: 5000 req/hr vs 60)
179
- - Rate limit handling: detect 403/429, retry with exponential backoff + jitter
180
-
181
- **Key technical notes**:
182
- - GitHub tree API returns the full recursive file list in one call — use this for file discovery, not individual directory listings
183
- - Blob SHA from the tree response is the incremental key — store in `files` table as `remote_sha` alongside the content hash
184
- - File size limit: skip files > 500KB (same as local indexing default)
185
- - Store fetched file content in `file_store` — same path as local indexing so all other tools work identically
186
- - Private repos require a `GITHUB_TOKEN` — surface a clear error if the token is missing and the repo returns 404/403
187
-
188
- **Tests**: `test/core/github-api-fetcher.test.ts` (mocked HTTP)
189
- - Full file list fetched from tree API
190
- - Incremental: unchanged blob SHA skips re-fetch
191
- - Rate limit retry with backoff
192
- - Missing token on private repo → clear error
193
- - URL parsing: `github.com/owner/repo`, `https://github.com/owner/repo`, `git@github.com:owner/repo.git`
194
-
195
- **Verify**:
196
- ```bash
197
- # index_repo { url: "https://github.com/expressjs/express", useGitHubApi: true }
198
- ```
199
-
200
- ---
201
-
202
- ## Task 138: Gemini Flash Summarization Backend
203
-
204
- Add Google Gemini Flash as a summarization backend option alongside the existing Anthropic and OpenAI-compatible providers.
205
-
206
- **Current state**: `src/summarizer/ai-summarizer.ts` supports Anthropic (Messages API) and OpenAI-compatible endpoints (covers Ollama, LM Studio, etc.). Gemini requires a different SDK/API call structure.
207
-
208
- **Deliverables**:
209
-
210
- - Update `src/summarizer/ai-summarizer.ts` to support a `gemini` provider:
211
- ```typescript
212
- type SummarizerProvider = 'anthropic' | 'openai-compatible' | 'gemini';
213
-
214
- interface SummarizerConfig {
215
- provider: SummarizerProvider;
216
- model?: string; // Default per provider: claude-haiku-4-5 / gpt-4o-mini / gemini-2.0-flash
217
- apiKey?: string;
218
- baseUrl?: string; // For openai-compatible only
219
- maxConcurrency?: number;
220
- }
221
- ```
222
- - Create `src/summarizer/providers/gemini-provider.ts`:
223
- - Use the Gemini REST API directly (no SDK dependency): `POST https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent`
224
- - Auth: `?key={GOOGLE_API_KEY}` query parameter
225
- - Request format: `{ contents: [{ parts: [{ text: prompt }] }] }`
226
- - Response: `candidates[0].content.parts[0].text`
227
- - Default model: `gemini-2.0-flash`
228
- - Config schema update (`src/config/config-schema.ts`):
229
- ```json
230
- {
231
- "summarizer": {
232
- "provider": "gemini",
233
- "model": "gemini-2.0-flash",
234
- "apiKey": "${GOOGLE_API_KEY}"
235
- }
236
- }
237
- ```
238
- - Auto-detection: if `GOOGLE_API_KEY` is set and no other provider is configured, use Gemini
239
- - Same batch processing, concurrency controls, and fallback logic as existing providers
240
-
241
- **Key technical notes**:
242
- - Use `https.request` (same pattern as the Anthropic provider) — no new dependencies
243
- - Gemini Flash is priced similarly to Claude Haiku — document the cost trade-offs in config comments
244
- - The prompt template is identical across providers — Gemini receives the same symbol signature + docstring input
245
- - Error mapping: Gemini quota errors (429) should trigger the same backoff as Anthropic overload errors
246
-
247
- **Tests**: `test/summarizer/gemini-provider.test.ts` (mocked HTTP)
248
- - Successful summarization call
249
- - Auth via `GOOGLE_API_KEY` env var
250
- - 429 → retry with backoff
251
- - Malformed response → falls back to docstring
252
- - Auto-detection when `GOOGLE_API_KEY` set
253
-
254
- **Verify**:
255
- ```bash
256
- GOOGLE_API_KEY=... npx purecontext-mcp config --check
257
- # Should report: summarizer provider: gemini (gemini-2.0-flash)
258
- ```
259
-
260
- ---
261
-
262
- ## Order of Execution
263
-
264
- ```
265
- 135 (search debug) ── independent
266
- 136 (context_lines/verify) ── implement helper first, then wire into get_symbol_source
267
- Task 133 (get_symbols) from Phase 19 should also use this helper
268
- 137 (GitHub API indexing) ── independent
269
- 138 (Gemini backend) ── independent
270
- ```
271
-
272
- Task 136's `symbol-source-helper.ts` should be shared with Task 133 (`get_symbols`) — implement 136 first if doing sequentially.
273
-
274
- ---
275
-
276
- ## Phase 20 Completion
277
-
278
- **Before**: Search is a black box; symbol source is exact bytes only; indexing requires git clone; summarization limited to Anthropic + OpenAI-compatible.
279
-
280
- **After**: Search results carry score breakdowns for tuning and trust. Agents can request surrounding context lines and detect cache drift. GitHub repos can be indexed via API without cloning. Gemini Flash is a first-class summarization backend. PureContext reaches behavioural parity with jCodeMunch on all tool-level capability gaps.
@@ -1,355 +0,0 @@
1
- # Phase 21 — Ecosystem & Data Tools
2
-
3
- **Goal**: Add a context provider plugin framework, a dbt provider, a `search_columns` tool, an OpenAPI/Swagger handler, and a SQL language handler with dbt Jinja preprocessing — bringing PureContext to full parity with jCodeMunch's ecosystem and data-engineering capabilities.
4
-
5
- **Scope rationale**: Data engineering is a high-value niche where jCodeMunch has meaningful differentiation. The context provider framework is the architectural foundation — everything else in this phase plugs into it. OpenAPI/Swagger and SQL are common in real backends and data pipelines; not supporting them is a conspicuous gap.
6
-
7
- ---
8
-
9
- ## Task 139: Context Provider Framework
10
-
11
- Build the plugin architecture that allows ecosystem-specific metadata (dbt, Terraform, database catalogs, etc.) to enrich indexed symbols automatically after the core indexing pipeline completes.
12
-
13
- **Current state**: Framework adapters (`src/adapters/`) handle AST-level symbol extraction. There is no post-indexing enrichment layer for ecosystem metadata that lives outside the source files themselves (e.g. schema.yml descriptions, Terraform variable docs, OpenAPI tags).
14
-
15
- **Deliverables**:
16
-
17
- - `src/providers/provider-registry.ts` — auto-discover and activate providers
18
- - `src/core/types.ts` — add `ContextProvider` interface:
19
- ```typescript
20
- interface ContextProvider {
21
- name: string;
22
- description: string;
23
-
24
- /** Return true if this provider applies to the given project root */
25
- detect(projectRoot: string): Promise<boolean>;
26
-
27
- /** Called once after full indexing completes for a repo */
28
- enrich(
29
- repoId: string,
30
- projectRoot: string,
31
- db: Database
32
- ): Promise<EnrichmentResult>;
33
- }
34
-
35
- interface EnrichmentResult {
36
- symbolsEnriched: number;
37
- metadataAdded: Record<string, unknown>; // Provider-specific stats
38
- }
39
- ```
40
- - `src/core/db/schema.ts` — add `provider_metadata` table:
41
- ```sql
42
- CREATE TABLE IF NOT EXISTS provider_metadata (
43
- repo_id TEXT NOT NULL,
44
- provider_name TEXT NOT NULL,
45
- entity_key TEXT NOT NULL, -- e.g. model name, file path
46
- metadata TEXT NOT NULL, -- JSON blob
47
- updated_at INTEGER NOT NULL,
48
- PRIMARY KEY (repo_id, provider_name, entity_key)
49
- );
50
- ```
51
- - `src/core/index-manager.ts` — after core indexing completes, run all activated providers:
52
- ```typescript
53
- // After symbol extraction and summarization:
54
- const providers = await providerRegistry.detectAll(projectRoot);
55
- for (const provider of providers) {
56
- const result = await provider.enrich(repoId, projectRoot, db);
57
- logger.info(`Provider ${provider.name}: enriched ${result.symbolsEnriched} symbols`);
58
- }
59
- ```
60
- - Provider enrichment updates `framework_meta` on affected `symbols` rows and writes to `provider_metadata`
61
- - `src/config/config-schema.ts` — add optional `providers` config block:
62
- ```json
63
- {
64
- "providers": {
65
- "dbt": { "enabled": true, "schemaDir": "models" },
66
- "terraform": { "enabled": false }
67
- }
68
- }
69
- ```
70
- - Default: all providers are auto-detected (no config needed). Config allows disabling or overriding paths
71
-
72
- **Key technical notes**:
73
- - Providers run in series after indexing, not during AST parsing — they read the already-indexed symbols from the DB and annotate them
74
- - Providers must be idempotent: re-running enrichment on an already-enriched repo should produce the same result
75
- - Provider errors must not fail the indexing operation — log at `warn` and continue
76
- - The `provider_metadata` table is the storage layer; `symbols.framework_meta` is the search-visible enrichment
77
-
78
- **Tests**: `test/providers/provider-registry.test.ts`
79
- - `detect()` returns false for non-matching project
80
- - Provider runs after indexing
81
- - Provider failure does not abort indexing
82
- - Idempotent re-enrichment
83
-
84
- **Verify**:
85
- ```bash
86
- # index_folder { path: "/path/to/dbt-project" }
87
- # Should log: "Provider dbt: enriched N symbols"
88
- ```
89
-
90
- ---
91
-
92
- ## Task 140: dbt Context Provider
93
-
94
- Implement the dbt context provider that auto-detects dbt projects, parses `schema.yml` column metadata and doc blocks, and enriches indexed SQL symbols with descriptions, tags, and column-level metadata.
95
-
96
- **Current state**: No dbt awareness exists. dbt SQL models, when indexed (after Task 143), will have symbol records but no descriptions, column metadata, or tags from schema.yml.
97
-
98
- **Deliverables**:
99
-
100
- - `src/providers/dbt-provider.ts` — implements `ContextProvider`:
101
- ```typescript
102
- class DbtProvider implements ContextProvider {
103
- name = 'dbt';
104
- description = 'Enriches dbt models with schema.yml descriptions and column metadata';
105
-
106
- async detect(projectRoot: string): Promise<boolean> {
107
- // Check for dbt_project.yml
108
- }
109
-
110
- async enrich(repoId: string, projectRoot: string, db: Database): Promise<EnrichmentResult>
111
- }
112
- ```
113
- - Detection: presence of `dbt_project.yml` in `projectRoot`
114
- - Schema parsing: recursively find all `schema.yml` / `schema.yaml` files under `models/` directory
115
- - Parse YAML (use `js-yaml` — already a common dependency; add if not present)
116
- - Extract per-model: `description`, `tags`, `meta`, `columns[]` (name + description + tags)
117
- - Extract per-source: `description`, `tables[]` with column metadata
118
- - Doc block parsing: find `{% docs model_name %}...{% enddocs %}` in `.md` files
119
- - Enrichment logic:
120
- - Match schema.yml model names to `symbols` rows where `name = model_name AND kind = 'function'` (SQL models are indexed as functions)
121
- - Update `symbols.summary` with model description if currently empty or auto-generated
122
- - Update `symbols.framework_meta` with `{ columns: [...], tags: [...], meta: {...} }`
123
- - Write full column metadata to `provider_metadata` table keyed by `(repoId, 'dbt', modelName)`
124
- - `src/core/db/symbol-store.ts` — add `updateSymbolMeta(id, summary, frameworkMeta)` if not present
125
-
126
- **Key technical notes**:
127
- - YAML parsing: `js-yaml` handles the dbt schema.yml format. Watch for multi-document YAML files
128
- - Column metadata lives in `provider_metadata`, not `symbols` — it's too wide for a symbol row
129
- - Only overwrite `summary` if it was auto-generated (check for presence of a sentinel prefix) — never overwrite a human-written docstring
130
- - Tags from schema.yml should be added to the FTS5 `keywords` column for searchability
131
-
132
- **Tests**: `test/providers/dbt-provider.test.ts`
133
- - Detection: `dbt_project.yml` present → true; absent → false
134
- - Schema.yml with models, columns, tags parsed correctly
135
- - Doc block extraction
136
- - Symbol summary updated with model description
137
- - `provider_metadata` rows written correctly
138
- - Uses fixture: `test/fixtures/dbt-project/`
139
-
140
- **Verify**:
141
- ```bash
142
- # After indexing a dbt project, search_symbols should return models with descriptions
143
- # provider_metadata should have column metadata rows
144
- ```
145
-
146
- ---
147
-
148
- ## Task 141: `search_columns` Tool
149
-
150
- Search column metadata across dbt/SQLMesh models — find columns by name, description, or tag without reading entire schema files.
151
-
152
- **Current state**: No column-level search exists. Users/agents must read entire `schema.yml` files to find column information. jCodeMunch benchmarks this tool at 77% fewer tokens than grep.
153
-
154
- **Deliverables**:
155
-
156
- - `src/server/tools/search-columns.ts` — new MCP tool:
157
- ```typescript
158
- interface SearchColumnsInput {
159
- repoId: string;
160
- query: string; // Search column names and descriptions
161
- modelFilter?: string; // Optional: only search within this model name
162
- tagFilter?: string[]; // Optional: only columns with these tags
163
- limit?: number; // Default 20, max 100
164
- }
165
-
166
- interface ColumnResult {
167
- modelName: string;
168
- modelFilePath: string;
169
- columnName: string;
170
- columnDescription: string;
171
- tags: string[];
172
- dataType?: string; // If present in schema.yml
173
- meta?: Record<string, unknown>;
174
- }
175
-
176
- interface SearchColumnsOutput {
177
- query: string;
178
- totalResults: number;
179
- columns: ColumnResult[];
180
- _meta: ResponseMeta;
181
- }
182
- ```
183
- - Query logic: search `provider_metadata` table where `provider_name = 'dbt'`, parse JSON metadata, filter columns by name/description match
184
- - Use SQLite `json_each` to query within the JSON metadata blob efficiently:
185
- ```sql
186
- SELECT pm.entity_key as model_name, s.file_path, col.value as col_data
187
- FROM provider_metadata pm, json_each(pm.metadata, '$.columns') col
188
- WHERE pm.repo_id = ? AND pm.provider_name = 'dbt'
189
- AND (col.value->>'name' LIKE ? OR col.value->>'description' LIKE ?)
190
- ```
191
- - `modelFilter`: add `AND pm.entity_key = ?`
192
- - `tagFilter`: filter post-query in application code (JSON array membership check)
193
- - Token savings: compare returned column metadata size vs full schema.yml files
194
-
195
- **Key technical notes**:
196
- - This tool returns useful results only when the dbt provider (Task 140) has been run — return a clear hint if `provider_metadata` has no dbt rows for the repo
197
- - SQLite JSON functions (`json_each`, `->>`): available since SQLite 3.38 — check minimum version and document
198
- - The search is substring-based (LIKE), not FTS5. This is intentional — column names are short and precision matters more than recall
199
-
200
- **Tests**: `test/server/search-columns.test.ts`
201
- - Query matching column name
202
- - Query matching column description
203
- - `modelFilter` narrows to one model
204
- - `tagFilter` narrows by tag
205
- - No dbt metadata → helpful error with hint to run dbt provider
206
- - Returns `_meta` with token savings
207
-
208
- **Verify**:
209
- ```bash
210
- # search_columns { repoId: "...", query: "customer_id" }
211
- # Should return all models with a column named/described as customer_id
212
- ```
213
-
214
- ---
215
-
216
- ## Task 142: OpenAPI/Swagger Language Handler
217
-
218
- Index OpenAPI and Swagger specification files, extracting API paths as function symbols and schema definitions as class symbols.
219
-
220
- **Current state**: `.openapi.yaml`, `.swagger.json`, and similar API spec files are skipped by the indexer. They are common in modern backends and contain critical API surface information.
221
-
222
- **Deliverables**:
223
-
224
- - `src/handlers/openapi.ts` — implements `LanguageHandler`:
225
- ```typescript
226
- class OpenApiHandler implements LanguageHandler {
227
- extensions() { return ['.yaml', '.yml', '.json']; }
228
- // Detection: only activate when file content contains 'openapi:' or 'swagger:' key
229
- }
230
- ```
231
- - Since OpenAPI/Swagger files are structured data (YAML/JSON), not code, this handler does **not** use tree-sitter. Instead, parse with `js-yaml` / `JSON.parse` and extract symbols directly:
232
- ```typescript
233
- interface OpenApiExtractor {
234
- extractPaths(spec: OpenApiSpec): SymbolRecord[] // paths → kind: 'function'
235
- extractSchemas(spec: OpenApiSpec): SymbolRecord[] // components.schemas → kind: 'class'
236
- extractTags(spec: OpenApiSpec): string[]
237
- }
238
- ```
239
- - Path symbol extraction:
240
- - Each `paths['/endpoint'][method]` becomes one `SymbolRecord`
241
- - `name`: `METHOD /endpoint` (e.g. `GET /users/{id}`)
242
- - `kind`: `'function'`
243
- - `signature`: `operationId?: string` or auto-generated from method+path
244
- - `summary`: `operation.summary` or `operation.description`
245
- - `frameworkMeta`: `{ method, path, tags, parameters: [...], requestBody: {...} }`
246
- - Schema symbol extraction:
247
- - Each `components.schemas[Name]` → `SymbolRecord` with `kind: 'class'`
248
- - `name`: schema name
249
- - `signature`: `interface SchemaName` with top-level properties listed
250
- - `summary`: schema `description`
251
- - `frameworkMeta`: `{ properties: [...], required: [...] }`
252
- - File detection: the handler's `extensions()` returns `.yaml/.yml/.json` which overlap with other files. Add a `detect(content: Buffer): boolean` method that checks for `openapi:` or `swagger:` at root level before committing to parse
253
- - `grammarPath()` returns `null` for this handler (no tree-sitter grammar needed)
254
- - `extractImports()` returns `[]` (OpenAPI files have no imports)
255
-
256
- **Key technical notes**:
257
- - OpenAPI 3.x and Swagger 2.x are both supported — detect via `openapi: 3.x.x` vs `swagger: '2.0'`
258
- - Large specs with 200+ paths should still complete quickly — JSON/YAML parsing is fast
259
- - `startByte`/`endByte` for each symbol: find the key in the raw file content using string search (since we're not using tree-sitter byte offsets). Use `Buffer.indexOf` to find the path/schema name key in the serialized file
260
- - Register in `src/handlers/handler-registry.ts` with lower priority than exact extension matches (YAML handler should not conflict with Vue SFC handler)
261
-
262
- **Tests**: `test/handlers/openapi.test.ts`
263
- - OpenAPI 3.x path extraction (GET, POST, DELETE)
264
- - Swagger 2.0 path extraction
265
- - Schema component extraction
266
- - Non-OpenAPI YAML file → `detect()` returns false
267
- - Summary from `operation.summary`
268
- - `frameworkMeta` contains method, path, tags
269
-
270
- **Verify**:
271
- ```bash
272
- # index_folder on a project with openapi.yaml
273
- # search_symbols { query: "GET /users" } should return the path symbol
274
- ```
275
-
276
- ---
277
-
278
- ## Task 143: SQL Language Handler with dbt Jinja Preprocessing
279
-
280
- Add a SQL language handler that indexes `.sql` files, with dbt Jinja template preprocessing to strip/replace template blocks before parsing.
281
-
282
- **Current state**: `.sql` files are not indexed. dbt projects contain hundreds of SQL model files that are currently invisible to PureContext.
283
-
284
- **Deliverables**:
285
-
286
- - `src/handlers/sql.ts` — implements `LanguageHandler`:
287
- ```typescript
288
- class SqlHandler implements LanguageHandler {
289
- extensions() { return ['.sql']; }
290
- grammarPath() { return 'grammars/tree-sitter-sql.wasm'; }
291
- }
292
- ```
293
- - Download and bundle `tree-sitter-sql.wasm` (from `@matthijs/tree-sitter-sql` or equivalent) in `grammars/`
294
- - dbt Jinja preprocessing — create `src/handlers/sql-jinja-preprocessor.ts`:
295
- ```typescript
296
- function preprocessJinja(source: Buffer): Buffer {
297
- // Replace {{ ref('model') }} → 'model'
298
- // Replace {{ source('schema', 'table') }} → 'schema.table'
299
- // Replace {% if ... %}...{% endif %} → (empty or stub)
300
- // Replace {% set var = ... %} → (empty)
301
- // Replace {{ var }} → 'var'
302
- // Result must be valid SQL parseable by tree-sitter-sql
303
- }
304
- ```
305
- - Symbol extraction from SQL AST:
306
- - `CREATE TABLE / VIEW / MATERIALIZED VIEW name` → `kind: 'class'`
307
- - `CREATE FUNCTION / PROCEDURE name` → `kind: 'function'`
308
- - `WITH cte_name AS (...)` CTEs → `kind: 'const'`
309
- - For dbt model files (no explicit CREATE statement), the file itself is the model → emit one symbol: `kind: 'function'`, `name: stem(filePath)`
310
- - Macro extraction from dbt macro files:
311
- - `{% macro macro_name(...) %}` → `kind: 'function'`
312
- - `{% test test_name(...) %}` → `kind: 'function'`
313
- - `{% snapshot snapshot_name %}` → `kind: 'class'`
314
- - Import extraction: `{{ ref('model_name') }}` and `{{ source('schema', 'table') }}` become `ImportRecord` entries for the dependency graph
315
-
316
- **Key technical notes**:
317
- - dbt files often have no SQL DDL — just a SELECT. The handler must detect this pattern and emit the model-file symbol
318
- - Jinja preprocessing must be applied before passing source to tree-sitter — tree-sitter-sql cannot parse Jinja syntax
319
- - After preprocessing, store the *original* source in `file_store` (not the preprocessed version) so `get_file_content` returns the real file
320
- - Regex-based Jinja stripping is sufficient — a full Jinja parser is not needed for symbol extraction purposes
321
- - The dbt provider (Task 140) runs *after* this handler and enriches the extracted symbols with schema.yml metadata
322
-
323
- **Tests**: `test/handlers/sql.test.ts`
324
- - Plain SQL: `CREATE TABLE`, `CREATE FUNCTION`, CTEs extracted
325
- - dbt model file (SELECT only): model symbol emitted with filename as name
326
- - dbt macro file: macro symbols extracted
327
- - Jinja preprocessing: `ref()` and `source()` replaced, resulting SQL is valid
328
- - Import records from `ref()` calls
329
-
330
- **Verify**:
331
- ```bash
332
- # index_folder on a dbt project
333
- # get_repo_outline should show SQL files indexed
334
- # search_symbols { query: "orders" } should return the orders.sql model
335
- ```
336
-
337
- ---
338
-
339
- ## Order of Execution
340
-
341
- ```
342
- 139 (Context Provider Framework) ──▶ 140 (dbt Provider) ──▶ 141 (search_columns)
343
- 142 (OpenAPI Handler) ── independent
344
- 143 (SQL Handler) ── independent (but dbt Provider enriches SQL symbols, so 143 before 140 in practice)
345
- ```
346
-
347
- Recommended sequence: 139 → 143 → 140 → 141 → 142
348
-
349
- ---
350
-
351
- ## Phase 21 Completion
352
-
353
- **Before**: No ecosystem metadata, no column search, no SQL or OpenAPI indexing, no context enrichment plugins.
354
-
355
- **After**: A pluggable context provider framework that any data tool can hook into. dbt projects are fully indexed — SQL models with descriptions, tags, and column metadata from schema.yml. API spec files are indexed with paths and schemas as searchable symbols. `search_columns` closes the last major workflow gap vs jCodeMunch. PureContext reaches full feature parity.