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
|
@@ -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.
|