purecontext-mcp 1.1.0 → 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/AGENT_INSTRUCTIONS.md +509 -0
- package/AGENT_INSTRUCTIONS_SHORT.md +97 -0
- package/CHANGELOG.md +212 -0
- package/docs/01-introduction.md +69 -0
- package/docs/02-installation.md +267 -0
- package/docs/03-quick-start.md +135 -0
- package/docs/04-configuration.md +214 -0
- package/docs/05-cli-reference.md +130 -0
- package/docs/06-tools-reference.md +499 -0
- package/docs/07-language-support.md +88 -0
- package/docs/08-framework-adapters.md +324 -0
- package/docs/09-dependency-graph.md +182 -0
- package/docs/10-semantic-search.md +153 -0
- package/docs/11-search-quality.md +110 -0
- package/docs/12-ai-summarization.md +106 -0
- package/docs/13-token-savings.md +110 -0
- package/docs/14-transport-modes.md +167 -0
- package/docs/15-team-setup.md +251 -0
- package/docs/16-docker.md +186 -0
- package/docs/17-web-ui.md +157 -0
- package/docs/18-git-history.md +157 -0
- package/docs/19-cross-repo.md +177 -0
- package/docs/20-architecture-analysis.md +228 -0
- package/docs/21-ecosystem-tools.md +189 -0
- package/docs/22-distribution.md +240 -0
- package/docs/23-performance.md +121 -0
- package/docs/24-security.md +144 -0
- package/docs/25-architecture-overview.md +240 -0
- package/docs/26-troubleshooting.md +234 -0
- package/docs/27-api-stability.md +114 -0
- package/docs/README.md +71 -0
- package/guide/README.md +57 -0
- package/guide/ai-summaries.md +127 -0
- package/guide/code-health.md +190 -0
- package/guide/code-history.md +149 -0
- package/guide/finding-code.md +157 -0
- package/guide/navigating-new-code.md +121 -0
- package/guide/safe-changes.md +156 -0
- package/guide/team-setup.md +191 -0
- package/guide/web-ui.md +154 -0
- package/guide/why-purecontext.md +73 -0
- package/guide/workflow-onboarding.md +114 -0
- package/guide/workflow-pr-review.md +199 -0
- package/guide/workflow-refactoring.md +172 -0
- package/package.json +9 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Why PureContext
|
|
2
|
+
|
|
3
|
+
## The problem is not tokens. It's accuracy.
|
|
4
|
+
|
|
5
|
+
When an AI agent works with your code, the quality of its answers depends entirely on what you put in front of it. Give it an entire 800-line file to find one function and two things happen: you burn thousands of tokens getting there, and the AI spends most of that context on code that has nothing to do with your question.
|
|
6
|
+
|
|
7
|
+
Token savings are the measurable side effect of a more fundamental improvement: **AI gets better answers from precise context than from bulk context.**
|
|
8
|
+
|
|
9
|
+
A 45-line function retrieved by name gives Claude exactly what it needs. An 800-line file gives Claude the function plus seven unrelated utilities, three deprecated helpers, a wall of imports, and a pile of comments about things that were fixed in 2019. All of that crowds out the signal.
|
|
10
|
+
|
|
11
|
+
PureContext fixes this by giving AI agents a way to navigate code the way experienced engineers do — by name, by meaning, by dependency — rather than by reading everything and hoping.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What changes in practice
|
|
16
|
+
|
|
17
|
+
### Your AI assistant gets fewer hallucinations
|
|
18
|
+
|
|
19
|
+
Hallucinations in coding tasks most often happen when the AI is working from incomplete or outdated context. If Claude has to read a file from two weeks ago that you haven't reindexed, or guess at function signatures from imports rather than seeing the actual definition, it will make things up convincingly.
|
|
20
|
+
|
|
21
|
+
PureContext indexes your codebase on demand and re-indexes incrementally as you work. When Claude asks for `validatePaymentMethod`, it gets the current definition — not a guess, not a stale version, the actual code as it exists right now.
|
|
22
|
+
|
|
23
|
+
### You stop copy-pasting code into the chat
|
|
24
|
+
|
|
25
|
+
Without PureContext, a typical conversation goes:
|
|
26
|
+
|
|
27
|
+
> "I need help understanding how the order processing pipeline works."
|
|
28
|
+
> *[You open five files, copy the relevant parts, paste them into the chat]*
|
|
29
|
+
> "Here's the OrderProcessor class, here's the CartValidator, here's..."
|
|
30
|
+
|
|
31
|
+
With PureContext, Claude navigates the codebase itself. You describe what you want to understand and Claude fetches the relevant symbols, follows the dependency chain, and builds its own picture. You stay in the conversation; you're not the file fetcher.
|
|
32
|
+
|
|
33
|
+
### Large codebases become navigable
|
|
34
|
+
|
|
35
|
+
A solo developer working on a 500-file TypeScript monorepo, and an enterprise team working on a 40,000-file Java platform, face the same structural problem: the codebase is too large to hold in any context window. PureContext makes both tractable by turning "read these files" into "retrieve these symbols."
|
|
36
|
+
|
|
37
|
+
The difference matters most in enterprise environments where no single person knows the whole codebase, onboarding takes months, and getting AI to help requires giving it enough context to be useful without hitting token limits.
|
|
38
|
+
|
|
39
|
+
### AI agents can plan changes safely
|
|
40
|
+
|
|
41
|
+
Before PureContext, asking an AI to help you change a core function was risky. The AI didn't know what depended on it. It couldn't see what would break.
|
|
42
|
+
|
|
43
|
+
With the dependency graph tools, Claude can check the blast radius of any change before touching it — see what imports the function, follow the transitive dependency chain, and tell you "this change will affect 14 files across 3 services." That's the difference between AI assistance and AI guesswork.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Who this is for
|
|
48
|
+
|
|
49
|
+
**Solo developers** get a faster inner loop. Index your project once, then navigate it with natural language instead of file browsing. The AI remembers the structure so you don't have to keep re-explaining it in every conversation.
|
|
50
|
+
|
|
51
|
+
**Teams** get a shared understanding of the codebase. When one developer indexes the repository on a shared server, everyone on the team can search it. New developers get a pre-built picture of the codebase on day one. Senior engineers don't spend their week explaining architecture.
|
|
52
|
+
|
|
53
|
+
**Enterprise environments** get the audit trails, access controls, rate limiting, and Docker-based deployment that make AI-assisted development compatible with security requirements. PureContext doesn't read your code and send it to a third party — it indexes locally and serves over your own network.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## What PureContext is not
|
|
58
|
+
|
|
59
|
+
It is not a replacement for reading code. There will always be times when you need to read a file carefully, understand edge cases, or review logic line by line. PureContext makes those moments targeted — you know which file, which function, which 45 lines matter — instead of exploratory.
|
|
60
|
+
|
|
61
|
+
It is not a code editor or language server. It does not type-check, lint, or autocomplete. Those tools solve different problems.
|
|
62
|
+
|
|
63
|
+
It is not magic. The quality of its output depends on the quality of your codebase structure, documentation, and naming. Well-named functions with docstrings are searchable from the first index. Undocumented spaghetti becomes searchable with AI summarization enabled — but meaningful naming still wins.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## The compounding effect
|
|
68
|
+
|
|
69
|
+
The value of PureContext grows with use. The index improves as your codebase improves. The dependency graph becomes more useful as you add more framework adapters. Git history integration becomes richer as the project ages. AI summaries mean that even undocumented code becomes discoverable.
|
|
70
|
+
|
|
71
|
+
On a fresh solo project: **useful from day one** for navigation and symbol retrieval.
|
|
72
|
+
|
|
73
|
+
On a two-year-old enterprise codebase: **transformative** — because that's where the navigation problem is most acute and where accurate AI assistance has the most value.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Workflow: Onboarding to a New Codebase
|
|
2
|
+
|
|
3
|
+
**Scenario:** You've just joined a fintech startup. The codebase is a Node.js/TypeScript monorepo with eight microservices — payments, accounts, notifications, reporting, identity, audit, gateway, and shared-lib. Total: ~6,000 files, ~80,000 symbols. Your first task is to fix a bug in the payment processing flow. You don't know the codebase at all.
|
|
4
|
+
|
|
5
|
+
Your tech lead has indexed the shared PureContext server and given you an API key. This is your first day.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The session
|
|
10
|
+
|
|
11
|
+
**You:** "I'm new to this codebase. I need to understand how the payment processing flow works — from when a user submits a payment to when it's confirmed. Can you walk me through it?"
|
|
12
|
+
|
|
13
|
+
**Claude** calls `get_file_tree` and `search_symbols(query: "payment", kind: "route")`:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Payment-related routes:
|
|
17
|
+
POST /v1/payments services/gateway/src/routes/payments.ts
|
|
18
|
+
POST /v1/payments/:id/capture services/gateway/src/routes/payments.ts
|
|
19
|
+
GET /v1/payments/:id services/gateway/src/routes/payments.ts
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Entry point found: the gateway service handles incoming payment requests. Claude calls `get_file_outline("services/gateway/src/routes/payments.ts")`:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
PaymentRoutes
|
|
26
|
+
handleCreatePayment() POST handler, validates request, forwards to payment service
|
|
27
|
+
handleCapturePayment() Captures a previously authorized payment
|
|
28
|
+
handleGetPayment() Returns payment status
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Claude:** "Payment requests enter through the gateway service at `handleCreatePayment`. Let me trace what happens from there."
|
|
32
|
+
|
|
33
|
+
Claude calls `get_context_bundle(symbolId: "handleCreatePayment", maxDepth: 2)`:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Context bundle (depth 2, ~2,400 tokens):
|
|
37
|
+
|
|
38
|
+
handleCreatePayment() gateway/routes/payments.ts — the entry handler
|
|
39
|
+
PaymentServiceClient gateway/clients/payment.ts — gRPC client to payment service
|
|
40
|
+
createPayment() services/payment/src/core.ts — the actual business logic
|
|
41
|
+
validatePaymentRequest() services/payment/src/validators.ts
|
|
42
|
+
PaymentRepository services/payment/src/db/repo.ts
|
|
43
|
+
ChargeProvider services/payment/src/providers/stripe.ts
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Claude:** "The flow is: gateway receives the HTTP request → validates it → calls the payment service via gRPC → the payment service validates the request, saves a record, and calls the Stripe provider. Let me get the source for `createPayment` so you can see the core logic."
|
|
47
|
+
|
|
48
|
+
You now understand the service topology and the call chain — from an HTTP POST to a Stripe API call — in under 2 minutes, without reading a single file manually.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Finding the bug
|
|
53
|
+
|
|
54
|
+
**You:** "The bug I'm looking at is that payments are being marked as 'confirmed' even when Stripe returns a 'requires_action' status. Where would that status handling be?"
|
|
55
|
+
|
|
56
|
+
**Claude** calls `search_symbols(query: "requires_action")`:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
No exact match. Trying semantic search...
|
|
60
|
+
|
|
61
|
+
search_semantic(query: "handle stripe payment status requires action") →
|
|
62
|
+
|
|
63
|
+
handlePaymentStatus() services/payment/src/status-handler.ts similarity: 0.92
|
|
64
|
+
processStripeWebhook() services/payment/src/webhooks/stripe.ts similarity: 0.87
|
|
65
|
+
updatePaymentState() services/payment/src/state-machine.ts similarity: 0.84
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Claude:** "`handlePaymentStatus` looks like the most likely location. Let me retrieve it."
|
|
69
|
+
|
|
70
|
+
`get_symbol_source(symbolId: "handlePaymentStatus")` returns 67 lines. Claude reads it and finds:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
case 'requires_action':
|
|
74
|
+
case 'requires_payment_method':
|
|
75
|
+
// TODO: implement 3DS handling
|
|
76
|
+
return updatePaymentState(paymentId, 'confirmed'); // ← the bug
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Found in 4 minutes. Without PureContext, you'd have been reading files in the payment service hoping to find the status handling code.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Understanding the impact before fixing
|
|
84
|
+
|
|
85
|
+
**You:** "Before I fix this, I need to understand what else depends on `updatePaymentState`. If I change how it works, what breaks?"
|
|
86
|
+
|
|
87
|
+
**Claude** calls `get_blast_radius(symbolId: "updatePaymentState")`:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
updatePaymentState() is imported by 8 files:
|
|
91
|
+
|
|
92
|
+
services/payment/src/status-handler.ts (the file we're fixing)
|
|
93
|
+
services/payment/src/webhooks/stripe.ts (webhook handler)
|
|
94
|
+
services/payment/src/retry.ts (retry logic)
|
|
95
|
+
services/payment/test/status.test.ts (tests)
|
|
96
|
+
services/payment/test/integration.test.ts
|
|
97
|
+
services/reporting/src/sync.ts (cross-service! reporting reads payment state)
|
|
98
|
+
services/audit/src/listeners.ts (cross-service! audit logs state changes)
|
|
99
|
+
services/notifications/src/triggers.ts (cross-service! triggers on state changes)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Claude:** "Changing `updatePaymentState` affects three other services: reporting, audit, and notifications. The fix to handle `requires_action` correctly shouldn't break these — they're just listening for state changes — but you should verify the test coverage in each service."
|
|
103
|
+
|
|
104
|
+
You now know the blast radius of your fix before writing a line. You know which tests to run and which service owners to notify. This conversation — from "I'm new" to "I understand the fix and its impact" — took about 15 minutes.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## What made this possible
|
|
109
|
+
|
|
110
|
+
- **No file browsing.** You never opened a file explorer or searched GitHub. Claude navigated the codebase and retrieved exactly the relevant code.
|
|
111
|
+
- **No onboarding sessions needed.** A senior engineer's time was not required. The shared index gave Claude enough context to explain the architecture accurately.
|
|
112
|
+
- **Bug found by meaning, not name.** `search_semantic` found `handlePaymentStatus` even though it didn't contain the exact search terms.
|
|
113
|
+
- **Impact known before the fix.** `get_blast_radius` revealed the cross-service dependencies that aren't obvious from reading the payment service alone.
|
|
114
|
+
- **Token efficient.** The entire session — architecture overview, bug location, blast radius analysis — used roughly 8,000 tokens. Reading the relevant files directly would have cost 50,000+ tokens and still required knowing which files to read.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Workflow: Reviewing a Pull Request
|
|
2
|
+
|
|
3
|
+
**Scenario:** It's Tuesday morning. You open GitHub and find a pull request sitting in your queue: "Migrate user authentication to OAuth2 — 40 files changed." You're the designated reviewer. The author is a colleague you trust, but this is a large, cross-cutting change affecting the core authentication flow. You have about 45 minutes before your next meeting.
|
|
4
|
+
|
|
5
|
+
Without context, you'd start reading diffs top to bottom, hoping to understand the intent as you go. With PureContext, you start by understanding the impact, then read strategically.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Step 1: Understand the scope before reading anything
|
|
10
|
+
|
|
11
|
+
**You:** "I have a PR to review — 40 files changed in the authentication migration. Before I read any diffs, give me a symbol-level analysis of what actually changed."
|
|
12
|
+
|
|
13
|
+
**Claude** calls `analyze_diff(base: "main", head: "feature/oauth2-migration")`:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Symbols modified (signature changed — potential breaking changes):
|
|
17
|
+
authenticateUser() apps/auth/validator.py signature changed
|
|
18
|
+
require_auth apps/auth/decorators.py implementation changed
|
|
19
|
+
UserPermissions apps/core/types.py fields added
|
|
20
|
+
|
|
21
|
+
Symbols modified (implementation only — no signature change):
|
|
22
|
+
JWTMiddleware apps/auth/middleware.py replaced internally
|
|
23
|
+
createSession() apps/auth/session.py updated token format
|
|
24
|
+
... (12 more)
|
|
25
|
+
|
|
26
|
+
Symbols added (new in this PR):
|
|
27
|
+
OAuthClient apps/auth/oauth2.py new class
|
|
28
|
+
exchangeOAuthCode() apps/auth/oauth2.py new
|
|
29
|
+
refreshOAuthToken() apps/auth/oauth2.py new
|
|
30
|
+
OAuthCallbackView apps/auth/views.py new
|
|
31
|
+
... (8 more)
|
|
32
|
+
|
|
33
|
+
Symbols deleted:
|
|
34
|
+
generateLegacyToken() apps/auth/legacy.py removed
|
|
35
|
+
OldSessionManager apps/auth/sessions.py removed
|
|
36
|
+
validateBasicAuth() apps/auth/basic.py removed
|
|
37
|
+
|
|
38
|
+
Blast radius of modified symbols: 47 files
|
|
39
|
+
Review priority: HIGH (signature changes to authenticateUser and require_auth)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
In 30 seconds you know:
|
|
43
|
+
- The signature of `authenticateUser` changed — everything that calls it may need to be checked
|
|
44
|
+
- `require_auth` was modified — it wraps 134 view functions, so behavior changes here affect the entire application
|
|
45
|
+
- Three symbols were deleted — are they truly dead, or are there call sites the PR missed?
|
|
46
|
+
- The real blast radius is 47 files, not 40 (some impact is indirect)
|
|
47
|
+
|
|
48
|
+
This frames your entire review before you've read a diff.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Step 2: Verify the deleted symbols were actually unused
|
|
53
|
+
|
|
54
|
+
The deleted symbols are the first concern. Deleting code that's still called causes a runtime error.
|
|
55
|
+
|
|
56
|
+
**You:** "Three symbols were deleted. Verify that they had no importers in main — confirm they were actually dead code before this PR."
|
|
57
|
+
|
|
58
|
+
**Claude** calls `get_blast_radius` on each deleted symbol against the main branch index:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
generateLegacyToken() — 0 importers in main (confirmed dead)
|
|
62
|
+
OldSessionManager — 0 importers in main (confirmed dead)
|
|
63
|
+
validateBasicAuth() — 0 importers in main (confirmed dead)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
All three confirmed dead. The author cleaned up dead code correctly. This concern is closed.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Step 3: Check the signature change for `authenticateUser`
|
|
71
|
+
|
|
72
|
+
A signature change to a function is the highest-risk change in any PR. It means every call site needs to handle the new interface.
|
|
73
|
+
|
|
74
|
+
**You:** "authenticateUser changed its signature. Show me the old signature, the new one, and all the places that call it."
|
|
75
|
+
|
|
76
|
+
**Claude** retrieves the symbol history for `authenticateUser` and the current source:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Old signature (main):
|
|
80
|
+
def authenticate_user(username: str, password: str) -> Optional[User]
|
|
81
|
+
|
|
82
|
+
New signature (this PR):
|
|
83
|
+
def authenticate_user(credentials: OAuthCredentials | PasswordCredentials) -> AuthResult
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The parameter type changed from two positional strings to a union type, and the return type changed from `Optional[User]` to `AuthResult`. This is a significant interface change.
|
|
87
|
+
|
|
88
|
+
**Claude** calls `find_references(symbolId: "authenticateUser")` on the current branch:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
authenticate_user() is called in 6 places:
|
|
92
|
+
|
|
93
|
+
apps/auth/views.py:45 LoginView.post() ← using new signature ✓
|
|
94
|
+
apps/users/tests/test_auth.py:23 test_login() ← updated ✓
|
|
95
|
+
apps/users/tests/test_auth.py:67 test_lockout() ← updated ✓
|
|
96
|
+
apps/api/auth.py:89 api_login() ← using new signature ✓
|
|
97
|
+
apps/admin/views.py:134 admin_login() ← still using old signature ✗
|
|
98
|
+
apps/integrations/sso.py:67 sso_authenticate() ← still using old signature ✗
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Two call sites weren't updated. `admin_login` and `sso_authenticate` are still passing two positional string arguments. This is a bug in the PR — it would pass type checking if the types weren't enforced strictly, but would fail at runtime.
|
|
102
|
+
|
|
103
|
+
You've found the blocking issue in the PR. Two minutes into the review.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Step 4: Verify the critical path
|
|
108
|
+
|
|
109
|
+
`require_auth` is the decorator used by 134 views. It was modified. You need to know the modification is backward compatible.
|
|
110
|
+
|
|
111
|
+
**You:** "require_auth was modified. Show me what changed and verify that all 134 views that use it still work with the new implementation."
|
|
112
|
+
|
|
113
|
+
**Claude** retrieves the new source for `require_auth`:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
def require_auth(view_func):
|
|
117
|
+
@wraps(view_func)
|
|
118
|
+
def wrapper(request, *args, **kwargs):
|
|
119
|
+
# NEW: Accept both JWT (legacy) and OAuth2 Bearer tokens
|
|
120
|
+
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
|
|
121
|
+
if auth_header.startswith('Bearer '):
|
|
122
|
+
user = authenticate_oauth_token(auth_header[7:])
|
|
123
|
+
else:
|
|
124
|
+
user = authenticate_jwt_token(request) # legacy path preserved
|
|
125
|
+
if user is None:
|
|
126
|
+
return HttpResponse(status=401)
|
|
127
|
+
request.user = user
|
|
128
|
+
return view_func(request, *args, **kwargs)
|
|
129
|
+
return wrapper
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The implementation accepts both token formats — OAuth2 Bearer tokens and the legacy JWT. The 134 decorated views don't need to be updated because the decorator handles the transition transparently. This is a good implementation decision, and it's verifiable from the code.
|
|
133
|
+
|
|
134
|
+
**You:** "This looks correct — it's a compatible shim. But check: is there any test that specifically covers the JWT fallback path in the new implementation?"
|
|
135
|
+
|
|
136
|
+
**Claude** calls `search_symbols(query: "authenticate_jwt_token", filePath: "tests/**")`:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
authenticate_jwt_token referenced in tests:
|
|
140
|
+
tests/auth/test_middleware.py test_jwt_fallback() ← exists ✓
|
|
141
|
+
tests/auth/test_decorators.py test_require_auth() ← exists ✓
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The fallback path is tested. The implementation is sound.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Step 5: Check for any missed text-level dependencies
|
|
149
|
+
|
|
150
|
+
Symbol analysis catches structured dependencies. But JWT-specific string patterns — cache keys, token format strings, configuration keys — might be missed.
|
|
151
|
+
|
|
152
|
+
**You:** "Search for any remaining references to the old JWT configuration key or the old session format that might have been missed."
|
|
153
|
+
|
|
154
|
+
**Claude** calls `search_text(query: "JWT_SECRET", filePath: "**/*.py")`:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
Remaining JWT_SECRET references:
|
|
158
|
+
apps/auth/oauth2.py:156 settings.JWT_SECRET ← why does the new OAuth module reference this?
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Unexpected. The new OAuth2 module references `JWT_SECRET`. You open the source:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# Temporary: use JWT_SECRET as the OAuth state parameter signing key
|
|
165
|
+
# TODO: Replace with dedicated OAUTH_STATE_SECRET before merge
|
|
166
|
+
state_sig = hmac.new(settings.JWT_SECRET.encode(), ...)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The author left a TODO comment but didn't create a separate configuration key. This is a code smell — the OAuth implementation is reusing the JWT secret for a different purpose, which violates key isolation. Flag it in the review.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## The review summary
|
|
174
|
+
|
|
175
|
+
45 minutes, structured review. Findings:
|
|
176
|
+
|
|
177
|
+
| Finding | Severity | Location |
|
|
178
|
+
|---------|----------|----------|
|
|
179
|
+
| `authenticate_user` called with old signature | **Blocking** | `admin/views.py:134`, `integrations/sso.py:67` |
|
|
180
|
+
| JWT secret reused as OAuth state signing key | **Major** — flag before merge | `auth/oauth2.py:156` |
|
|
181
|
+
| Deleted symbols confirmed dead | **Pass** | legacy.py, sessions.py, basic.py |
|
|
182
|
+
| `require_auth` shim correctly backward compatible | **Pass** | auth/decorators.py |
|
|
183
|
+
| Legacy fallback path covered by tests | **Pass** | tests/auth/ |
|
|
184
|
+
|
|
185
|
+
You found two real issues — one blocking, one major — before reading most of the diff. The blocking issue (two call sites using the wrong signature) would have been a runtime error in production. The major issue (key reuse) is a security design problem.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## What made this review effective
|
|
190
|
+
|
|
191
|
+
**Start with impact, not diffs.** `analyze_diff` gave you the symbol-level map before you read anything. You knew where to focus.
|
|
192
|
+
|
|
193
|
+
**Verify deletions first.** Deleted code that's still called is the most dangerous class of PR error. Checking blast radius for deleted symbols takes 30 seconds and catches the worst case.
|
|
194
|
+
|
|
195
|
+
**Find all call sites for signature changes.** `find_references` across the branch revealed the two missed call sites that static analysis alone might have missed in a dynamically typed codebase.
|
|
196
|
+
|
|
197
|
+
**Use text search for string-level dependencies.** Symbol analysis doesn't catch JWT_SECRET usage in string context. Text search does.
|
|
198
|
+
|
|
199
|
+
**Know when to stop.** With the two blocking issues identified and the critical path verified, you have what you need to write a meaningful review. You don't need to read all 40 diffs to give a quality review — you need to read the right ones.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Workflow: Refactoring Legacy Code
|
|
2
|
+
|
|
3
|
+
**Scenario:** Your team inherited a legacy Python web application — a Django monolith that's been running for six years. The authentication system uses a custom JWT implementation that predates the team's tenure. You've been asked to replace it with a standard OAuth2 flow. The problem: nobody on the current team fully understands how the old authentication system is wired together. It grew organically, it's partially documented, and there are integration tests that haven't run in eight months.
|
|
4
|
+
|
|
5
|
+
This is not a rare scenario. It is the normal state of software that has lived long enough.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Phase 1: Understand what exists
|
|
10
|
+
|
|
11
|
+
**You:** "I need to replace the legacy JWT authentication with OAuth2. Before touching anything, help me understand how the current authentication system works — what it's made of, where it's used, and what depends on it."
|
|
12
|
+
|
|
13
|
+
**Claude** calls `search_symbols(query: "authenticate", filePath: "apps/auth/**")`:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
apps/auth/jwt.py authenticate_token() function
|
|
17
|
+
apps/auth/jwt.py generate_token() function
|
|
18
|
+
apps/auth/jwt.py validate_claims() function
|
|
19
|
+
apps/auth/middleware.py JWTMiddleware class
|
|
20
|
+
apps/auth/decorators.py require_auth function (decorator)
|
|
21
|
+
apps/auth/views.py LoginView class (view)
|
|
22
|
+
apps/auth/views.py LogoutView class (view)
|
|
23
|
+
apps/auth/views.py RefreshTokenView class (view)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Eight symbols in the auth module. Claude retrieves the outline for each file and builds a mental model. Then you ask the critical question:
|
|
27
|
+
|
|
28
|
+
**You:** "How widely is `require_auth` used across the application? This is the decorator — everything using it will need to work differently after the migration."
|
|
29
|
+
|
|
30
|
+
**Claude** calls `get_blast_radius(symbolId: "require_auth")`:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
require_auth is used in 47 files across 12 Django apps:
|
|
34
|
+
|
|
35
|
+
apps/users/views.py 18 decorated views
|
|
36
|
+
apps/billing/views.py 12 decorated views
|
|
37
|
+
apps/orders/views.py 9 decorated views
|
|
38
|
+
apps/admin_panel/views.py 6 decorated views
|
|
39
|
+
apps/api/views.py 31 decorated views
|
|
40
|
+
... (7 more apps)
|
|
41
|
+
|
|
42
|
+
Total decorated views: 134
|
|
43
|
+
Also used in: 3 custom middleware files
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
134 views. The scope is clear before you've planned a single line of migration. You're not doing a small change — you're touching the entire application's access control layer.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Phase 2: Understand the current implementation
|
|
51
|
+
|
|
52
|
+
**You:** "Let me see the full context bundle for authenticate_token — I need to understand exactly how it works before designing the replacement."
|
|
53
|
+
|
|
54
|
+
`get_context_bundle(symbolId: "authenticate_token", maxDepth: 3)` returns:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
authenticate_token() apps/auth/jwt.py — main function, 45 lines
|
|
58
|
+
validate_claims() apps/auth/jwt.py — claim validation, 28 lines
|
|
59
|
+
AuthUser apps/users/models.py — the user model it returns
|
|
60
|
+
UserPermissions apps/auth/permissions.py — permission model
|
|
61
|
+
get_user_from_cache() apps/core/cache.py — Redis user caching, 22 lines
|
|
62
|
+
settings.JWT_SECRET apps/core/settings.py — the signing key location
|
|
63
|
+
|
|
64
|
+
Token estimate: 1,840 tokens
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
With the context bundle loaded, Claude explains how the current implementation works: JWT is decoded, the `sub` claim is extracted, the user is looked up from Redis cache first, then the database, and a combined `AuthUser` object is returned with permissions. The cache key format is `auth:user:{user_id}`.
|
|
68
|
+
|
|
69
|
+
This is knowledge that should be in documentation but isn't. Now it is — extracted directly from the code.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Phase 3: Find the hidden dependencies
|
|
74
|
+
|
|
75
|
+
**You:** "Are there any places outside the auth module that interact directly with the JWT format, tokens, or the user cache? I don't want to miss something that breaks silently."
|
|
76
|
+
|
|
77
|
+
**Claude** calls `search_text(query: "JWT_SECRET")` and `search_text(query: "auth:user:", is_regex: false)`:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
JWT_SECRET references:
|
|
81
|
+
apps/auth/jwt.py:34 settings.JWT_SECRET (expected)
|
|
82
|
+
apps/integrations/webhooks.py:89 settings.JWT_SECRET (unexpected!)
|
|
83
|
+
scripts/generate_test_tokens.py:12 settings.JWT_SECRET
|
|
84
|
+
|
|
85
|
+
auth:user: cache key references:
|
|
86
|
+
apps/auth/jwt.py:67 (expected)
|
|
87
|
+
apps/core/cache.py:45 (expected)
|
|
88
|
+
apps/analytics/sessions.py:128 (unexpected!)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Two surprises: `webhooks.py` is generating JWTs independently (a parallel implementation you didn't know existed), and `analytics/sessions.py` is reading directly from the user cache using the key format. Both would silently break after the migration if you hadn't found them.
|
|
92
|
+
|
|
93
|
+
This is the difference between a migration that works and a migration that works until three months later when a webhook or analytics edge case surfaces.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Phase 4: Check for dead code before migrating
|
|
98
|
+
|
|
99
|
+
**You:** "Are there any auth-related functions that are already dead — unused exports we shouldn't bother migrating?"
|
|
100
|
+
|
|
101
|
+
**Claude** calls `find_dead_code(repoId)` filtered to auth-related files:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Dead code in auth module:
|
|
105
|
+
|
|
106
|
+
generate_legacy_token() apps/auth/legacy.py — not imported anywhere
|
|
107
|
+
OldSessionManager apps/auth/sessions.py — class with no instantiations
|
|
108
|
+
validate_basic_auth() apps/auth/basic.py — imported nowhere
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Three functions that can be deleted immediately without migration. The legacy session manager dates from before the JWT implementation. Migrating it would be wasted effort.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Phase 5: Plan the migration path
|
|
116
|
+
|
|
117
|
+
With a complete picture of the system, you can now plan the migration confidently:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Migration plan (informed by PureContext analysis):
|
|
121
|
+
|
|
122
|
+
1. Create OAuth2 implementation alongside existing JWT:
|
|
123
|
+
- New: apps/auth/oauth2.py
|
|
124
|
+
- Preserve: existing JWT system during transition
|
|
125
|
+
|
|
126
|
+
2. Create a compatibility shim in require_auth:
|
|
127
|
+
- Accept both JWT (existing) and OAuth2 Bearer tokens
|
|
128
|
+
- No changes to 134 view files — they use the decorator, not the auth logic
|
|
129
|
+
|
|
130
|
+
3. Fix the hidden dependencies first:
|
|
131
|
+
- apps/integrations/webhooks.py — replace with OAuth2 client credentials
|
|
132
|
+
- apps/analytics/sessions.py — update cache key reading logic
|
|
133
|
+
|
|
134
|
+
4. Migrate clients gradually:
|
|
135
|
+
- Internal services first, external API clients last
|
|
136
|
+
|
|
137
|
+
5. Delete dead code before merging:
|
|
138
|
+
- apps/auth/legacy.py, sessions.py, basic.py
|
|
139
|
+
|
|
140
|
+
6. Remove JWT system when all clients confirmed migrated
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The plan addresses all the blast radius, the hidden dependencies, and the dead code — in the right order. A migration plan shaped by actual analysis rather than assumptions.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Phase 6: After the migration
|
|
148
|
+
|
|
149
|
+
Once the migration is complete, re-index and verify:
|
|
150
|
+
|
|
151
|
+
**You:** "The migration is done. Check for any dead code that the old JWT implementation left behind, and verify there are no remaining JWT references."
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
search_text(query: "JWT_SECRET") → 0 results
|
|
155
|
+
search_text(query: "jwt.decode") → 0 results
|
|
156
|
+
find_dead_code() →
|
|
157
|
+
apps/auth/jwt.py (authenticate_token, generate_token, validate_claims) — now dead
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The old JWT module is now entirely dead code. Safe to delete.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## What this workflow demonstrates
|
|
165
|
+
|
|
166
|
+
**Discovery before commitment.** Understanding the full scope — 134 decorated views, 2 hidden dependencies, 3 dead functions — happened before a single line of migration code was written. The plan emerged from evidence, not guesswork.
|
|
167
|
+
|
|
168
|
+
**Hidden dependencies surfaced.** `search_text` for string literals and cache key patterns found things that symbol search alone wouldn't catch. Text search and symbol search complement each other.
|
|
169
|
+
|
|
170
|
+
**Migration sequencing informed by data.** The decision to use a compatibility shim in `require_auth` (avoiding changes to 134 files) came directly from the blast radius analysis. Without that number, you might have tried to update each view individually.
|
|
171
|
+
|
|
172
|
+
**Verification at the end.** Re-indexing after the migration and checking for remaining references gives you a definitive answer: the migration is complete, not "probably complete."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "purecontext-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Token-efficient source code navigation MCP server for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,14 @@
|
|
|
30
30
|
"grammars/",
|
|
31
31
|
"prebuilds/",
|
|
32
32
|
"src/ui/dist/",
|
|
33
|
-
"scripts/check-sqlite.js"
|
|
33
|
+
"scripts/check-sqlite.js",
|
|
34
|
+
"README.md",
|
|
35
|
+
"AGENT_INSTRUCTIONS.md",
|
|
36
|
+
"AGENT_INSTRUCTIONS_SHORT.md",
|
|
37
|
+
"CHANGELOG.md",
|
|
38
|
+
"LICENSE",
|
|
39
|
+
"docs/",
|
|
40
|
+
"guide/"
|
|
34
41
|
],
|
|
35
42
|
"main": "./dist/index.js",
|
|
36
43
|
"exports": {
|