carto-md 1.1.2 → 1.1.3
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/CONTRIBUTING.md +26 -12
- package/README.md +258 -229
- package/package.json +1 -1
- package/src/extractors/languages/javascript.js +119 -0
package/CONTRIBUTING.md
CHANGED
|
@@ -10,23 +10,32 @@ Carto is free, open source, and community-maintained. The core team owns the mer
|
|
|
10
10
|
|
|
11
11
|
New language support lives in `src/extractors/languages/`. Each language is an isolated module.
|
|
12
12
|
|
|
13
|
-
Currently supported: JavaScript/TypeScript, Python, R.
|
|
13
|
+
Currently supported: JavaScript/TypeScript, Python, Go, R.
|
|
14
14
|
|
|
15
|
-
Wanted:
|
|
15
|
+
Wanted: Rust, Ruby, Java, PHP, C#, Swift, Kotlin.
|
|
16
16
|
|
|
17
17
|
### Tier 2 — Framework extractors (safe to add, easy to review)
|
|
18
18
|
|
|
19
19
|
Framework-specific route and model extraction lives in `src/extractors/`. Each framework is an isolated module.
|
|
20
20
|
|
|
21
|
-
Currently supported:
|
|
21
|
+
Currently supported:
|
|
22
|
+
- **JS/TS**: Express, Next.js (App + Pages Router), tRPC, Drizzle, Zod
|
|
23
|
+
- **Python**: FastAPI, Pydantic, SQLAlchemy, Django (models + URLs)
|
|
24
|
+
- **Go**: Gin, Echo, Chi, net/http
|
|
25
|
+
- **Schema**: Prisma
|
|
26
|
+
- **Frontend**: HTML fetch()
|
|
27
|
+
- **R**: Plumber, Shiny, R6, S7
|
|
22
28
|
|
|
23
|
-
Wanted:
|
|
29
|
+
Wanted: Rails, Laravel, NestJS, Hono, Spring, Flask, Fastify.
|
|
24
30
|
|
|
25
31
|
### Tier 3 — Core (review carefully before merging)
|
|
26
32
|
|
|
27
33
|
- `src/agents/merger.js` — merger logic. One bad merge = developer loses manual notes = project dies.
|
|
28
34
|
- `src/agents/domains.js` — graph-based domain clustering. Wrong clusters = wrong context files.
|
|
35
|
+
- `src/engine/carto.js` — programmatic module API. Breaking changes affect tools that import Carto.
|
|
29
36
|
- `src/mcp/server.js` — MCP server tools. Breaking changes affect Kiro/Cursor/Claude integration.
|
|
37
|
+
- `src/engine/incremental.js` — incremental graph update engine. Bugs here cause stale graphs.
|
|
38
|
+
- `src/cache/` — file hash + graph cache. Bugs here cause wrong re-index behavior.
|
|
30
39
|
- `src/detector/` — framework detection logic.
|
|
31
40
|
- `src/cli/` — CLI commands.
|
|
32
41
|
|
|
@@ -36,23 +45,27 @@ Wanted: Django, Rails, Laravel, NestJS, Hono, Gin, Spring.
|
|
|
36
45
|
|
|
37
46
|
1. Create `src/extractors/languages/yourlanguage.js`
|
|
38
47
|
2. Export a plugin object:
|
|
48
|
+
|
|
39
49
|
```js
|
|
40
50
|
module.exports = {
|
|
41
51
|
name: 'yourlanguage',
|
|
42
52
|
extensions: ['.ext'],
|
|
43
53
|
extract(content, relPath) {
|
|
44
54
|
return {
|
|
45
|
-
routes:
|
|
46
|
-
models:
|
|
47
|
-
functions:
|
|
48
|
-
envVars:
|
|
49
|
-
dbTables:
|
|
50
|
-
fetches:
|
|
51
|
-
storageKeys: []
|
|
55
|
+
routes: [{ method, path, functionName }],
|
|
56
|
+
models: [{ className, fields: [{ name, type }], kind: 'yourlanguage' }],
|
|
57
|
+
functions: [{ name, params, returnType }],
|
|
58
|
+
envVars: ['VAR_NAME'],
|
|
59
|
+
dbTables: [{ tableName, modelName }],
|
|
60
|
+
fetches: [],
|
|
61
|
+
storageKeys: [],
|
|
62
|
+
events: [{ type: 'listener'|'emitter', event: 'event.name' }],
|
|
63
|
+
jobs: [{ type: 'cron'|'queue'|'interval', expression?: '* * * * *', name?: 'job-name' }],
|
|
52
64
|
};
|
|
53
65
|
}
|
|
54
66
|
};
|
|
55
67
|
```
|
|
68
|
+
|
|
56
69
|
3. The loader auto-discovers it — no changes to `loader.js` needed
|
|
57
70
|
4. Test on at least 3 real open-source projects
|
|
58
71
|
5. Open a PR with before/after AGENTS.md examples
|
|
@@ -96,7 +109,7 @@ cd carto
|
|
|
96
109
|
npm install
|
|
97
110
|
node src/cli/index.js init # test in any project
|
|
98
111
|
node src/cli/index.js serve # test MCP server
|
|
99
|
-
npm test # run test suite
|
|
112
|
+
npm test # run test suite (30 tests)
|
|
100
113
|
```
|
|
101
114
|
|
|
102
115
|
---
|
|
@@ -105,6 +118,7 @@ npm test # run test suite
|
|
|
105
118
|
|
|
106
119
|
- [ ] Tested on at least 2-3 real open-source projects
|
|
107
120
|
- [ ] Before/after AGENTS.md included in PR description
|
|
121
|
+
- [ ] Plugin returns all fields including `events` and `jobs` (can be empty arrays)
|
|
108
122
|
- [ ] No changes to merger logic (unless explicitly fixing a merger bug)
|
|
109
123
|
- [ ] No network calls added
|
|
110
124
|
- [ ] `carto --version` still works
|
package/README.md
CHANGED
|
@@ -4,293 +4,332 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://www.npmjs.com/package/carto-md)
|
|
6
6
|
|
|
7
|
-
**The
|
|
7
|
+
**The structural intelligence layer for AI coding tools.**
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install -g carto-md
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Carto
|
|
13
|
+
Carto indexes your codebase (routes, models, import graph, blast radius, domain clusters) and keeps it live. Every AI tool you use gets accurate structural facts about your project instead of guessing.
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## What it does in one sentence
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
**Your file changes. Carto re-indexes in ~130ms. Every AI tool instantly knows what broke.**
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- You rebuild context manually, every time
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Proof: Supabase repo (6,335 files)
|
|
25
24
|
|
|
26
|
-
`
|
|
25
|
+
Fresh `carto init`. No prior knowledge of the repo.
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
```
|
|
28
|
+
Detected: nextjs (javascript)
|
|
29
|
+
Indexed: 310 files in 422ms
|
|
30
|
+
Routes: 104 API endpoints
|
|
31
|
+
Models: 3,598 extracted
|
|
32
|
+
Domains: AUTH · DATABASE · PAYMENTS · EVENTS · NOTIFICATIONS · TRPC · CORE
|
|
33
|
+
Import edges: 5,248
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then a file changes:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
packages/ui/src/lib/utils/cn.ts updated
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
Re-indexed in 129ms.
|
|
42
|
+
|
|
43
|
+
Risk: 🔴 HIGH
|
|
44
|
+
Directly affected: 55 files
|
|
45
|
+
Potentially affected: 83 files total
|
|
46
|
+
|
|
47
|
+
Files that depend on this:
|
|
48
|
+
→ packages/ui/src/components/Button/Button.tsx
|
|
49
|
+
→ packages/ui/src/components/Modal/Modal.tsx
|
|
50
|
+
→ packages/ui/src/components/shadcn/ui/button.tsx
|
|
51
|
+
→ ...83 more
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
One file changed. Carto told you exactly what broke. In 129ms. On a 6,335 file monorepo.
|
|
39
55
|
|
|
40
56
|
---
|
|
41
57
|
|
|
42
|
-
## Proof
|
|
58
|
+
## Proof: cal.com (800k lines)
|
|
43
59
|
|
|
44
60
|
Same task, two Claude sessions: *"Add a `notes` field to the booking model."*
|
|
45
61
|
|
|
46
62
|
**Without Carto:**
|
|
47
|
-
- Wrong API route: suggested `POST /api/bookings` → actual is `POST /v2/bookings`
|
|
48
|
-
- Wrong handler: suggested `handleNewBooking.ts` → not the creation path
|
|
49
|
-
- Wrong file paths: pointed to v1 API → v1 is legacy
|
|
50
|
-
- Wrong tRPC file: `bookings.tsx` → actual is `bookings/_router.tsx`
|
|
51
|
-
- Field list: ~15 fields guessed → missing 20+ real fields
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
| | What AI suggested | Reality |
|
|
65
|
+
|--|---|---|
|
|
66
|
+
| API route | `POST /api/bookings` | `POST /v2/bookings` |
|
|
67
|
+
| Handler | `handleNewBooking.ts` | Not the creation path |
|
|
68
|
+
| File path | v1 API files | v1 is legacy |
|
|
69
|
+
| tRPC file | `bookings.tsx` | `bookings/_router.tsx` |
|
|
70
|
+
| Fields found | ~15 guessed | 35+ actual fields |
|
|
71
|
+
|
|
72
|
+
**With Carto:** Correct route, correct handler, correct file, all 35+ fields. One shot. Zero follow-ups.
|
|
59
73
|
|
|
60
|
-
**4 wrong
|
|
74
|
+
**4 wrong paths → 0. 20 missing fields → 0.**
|
|
61
75
|
|
|
62
76
|
Not smarter AI. The same AI with accurate facts.
|
|
63
77
|
|
|
64
78
|
---
|
|
65
79
|
|
|
80
|
+
## Performance
|
|
81
|
+
|
|
82
|
+
Tested on Supabase (6,335 files, 310 watched):
|
|
83
|
+
|
|
84
|
+
| Operation | Time |
|
|
85
|
+
|-----------|------|
|
|
86
|
+
| Cold start (first `carto init`) | 422ms |
|
|
87
|
+
| Warm start (files cached) | 66ms |
|
|
88
|
+
| One file change (incremental re-index) | ~130ms |
|
|
89
|
+
| Blast radius lookup | <5ms |
|
|
90
|
+
| Route search | <1ms |
|
|
91
|
+
|
|
92
|
+
The secret: file hashes. On warm runs, Carto skips every file whose content hasn't changed. On a file save, only that one file gets re-parsed. The rest loads from disk cache in milliseconds.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
66
96
|
## How it works
|
|
67
97
|
|
|
68
98
|
```
|
|
69
99
|
carto init
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
↓
|
|
101
|
+
Hashes every file → skips unchanged on re-runs
|
|
102
|
+
Builds import graph → knows who depends on who
|
|
103
|
+
Extracts routes, models, functions, env vars
|
|
104
|
+
Clusters into domains (AUTH, PAYMENTS, DATABASE...)
|
|
105
|
+
Calculates blast radius for every file
|
|
106
|
+
Writes AGENTS.md + .carto/context/*.md
|
|
107
|
+
Auto-wires MCP into Kiro, Cursor, Claude Desktop
|
|
108
|
+
↓
|
|
109
|
+
carto watch
|
|
110
|
+
↓
|
|
111
|
+
File saved → re-parse 1 file → update graph → ~130ms
|
|
79
112
|
```
|
|
80
113
|
|
|
81
114
|
---
|
|
82
115
|
|
|
83
|
-
## MCP
|
|
116
|
+
## 12 MCP tools: AI queries your codebase live
|
|
117
|
+
|
|
118
|
+
`carto serve` exposes a local MCP server. Kiro, Cursor, and Claude query it mid-task instead of guessing.
|
|
119
|
+
|
|
120
|
+
| Tool | What it returns |
|
|
121
|
+
|------|----------------|
|
|
122
|
+
| `get_blast_radius(file)` | Risk level, all affected files, routes at risk per domain |
|
|
123
|
+
| `get_context(file)` | Everything about a file in one call: domain, blast radius, neighbors, routes, models |
|
|
124
|
+
| `get_routes()` | All API endpoints with file mapping |
|
|
125
|
+
| `get_structure()` | Import graph, entry points, high-impact files, tech stack |
|
|
126
|
+
| `get_domain(name)` | All routes, models, functions for AUTH / PAYMENTS / DATABASE / etc. |
|
|
127
|
+
| `get_neighbors(file, hops)` | Import graph neighbors: nodes and edges |
|
|
128
|
+
| `get_cross_domain()` | Import edges that cross domain boundaries |
|
|
129
|
+
| `search_routes(query)` | Search API routes by path or method |
|
|
130
|
+
| `get_models(domain?)` | All data models, optionally filtered by domain |
|
|
131
|
+
| `get_high_impact_files(n)` | Top N files by blast radius, highest-risk to change |
|
|
132
|
+
| `get_env_vars(domain?)` | All env vars with domain mapping |
|
|
133
|
+
| `get_domains_list()` | All detected domains with file, route, model counts |
|
|
84
134
|
|
|
85
|
-
|
|
135
|
+
---
|
|
86
136
|
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
Files affected:
|
|
90
|
-
→ apps/web/app/api/checkout/route.ts
|
|
91
|
-
→ apps/web/app/api/webhook/route.ts
|
|
92
|
-
→ packages/trpc/routers/billing.ts
|
|
93
|
-
|
|
94
|
-
Routes at risk:
|
|
95
|
-
→ POST /api/checkout
|
|
96
|
-
→ POST /api/webhook
|
|
97
|
-
→ POST /trpc/createSubscription
|
|
98
|
-
```
|
|
137
|
+
## What gets extracted
|
|
99
138
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
|
103
|
-
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
139
|
+
| Category | What Carto finds |
|
|
140
|
+
|----------|-----------------|
|
|
141
|
+
| **Routes** | FastAPI, Express, Next.js App/Pages Router, React Router (JSX + createBrowserRouter), tRPC procedures, Django URLs, Gin/Echo (Go) |
|
|
142
|
+
| **Models** | Prisma, Pydantic, SQLAlchemy, Django ORM, TypeScript interfaces/types, Zod schemas, Drizzle tables |
|
|
143
|
+
| **Graph** | Full import graph: who imports what, transitive dependencies up to 5 hops |
|
|
144
|
+
| **Blast radius** | Risk level (HIGH/MEDIUM/LOW) per file and per route |
|
|
145
|
+
| **Domains** | AUTH, PAYMENTS, DATABASE, EVENTS, TRPC, NOTIFICATIONS, CORE, auto-clustered from imports |
|
|
146
|
+
| **Events** | EventEmitter listeners, webhook handlers, queue jobs, cron schedules |
|
|
147
|
+
| **Env vars** | Every `process.env` / `os.Getenv` call (names only, never values) |
|
|
148
|
+
| **Functions** | Signatures with param names and return types |
|
|
110
149
|
|
|
111
|
-
|
|
112
|
-
Returns `AUTH.md` — all auth routes, session models, JWT functions, env vars.
|
|
150
|
+
---
|
|
113
151
|
|
|
114
|
-
|
|
115
|
-
Returns import graph, entry points, high impact files, tech stack.
|
|
152
|
+
## Languages and frameworks
|
|
116
153
|
|
|
117
|
-
|
|
154
|
+
| Language | Frameworks |
|
|
155
|
+
|----------|------------|
|
|
156
|
+
| TypeScript / JavaScript | Express, Next.js (App + Pages Router), React Router, tRPC, Drizzle, Zod |
|
|
157
|
+
| Python | FastAPI, Pydantic, SQLAlchemy, Django |
|
|
158
|
+
| Go | Gin, Echo, Chi, net/http |
|
|
159
|
+
| R | Plumber, Shiny, R6, S7 |
|
|
160
|
+
| Schema | Prisma |
|
|
161
|
+
| HTML | fetch() calls |
|
|
118
162
|
|
|
119
|
-
|
|
120
|
-
```json
|
|
121
|
-
{
|
|
122
|
-
"mcpServers": {
|
|
123
|
-
"carto": {
|
|
124
|
-
"command": "carto",
|
|
125
|
-
"args": ["serve"],
|
|
126
|
-
"cwd": "/path/to/your/project"
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
```
|
|
163
|
+
More via community. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
131
164
|
|
|
132
|
-
|
|
133
|
-
```json
|
|
134
|
-
{
|
|
135
|
-
"mcpServers": {
|
|
136
|
-
"carto": {
|
|
137
|
-
"command": "carto",
|
|
138
|
-
"args": ["serve"],
|
|
139
|
-
"cwd": "/path/to/your/project"
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
```
|
|
165
|
+
---
|
|
144
166
|
|
|
145
|
-
|
|
146
|
-
```json
|
|
147
|
-
{
|
|
148
|
-
"mcpServers": {
|
|
149
|
-
"carto": {
|
|
150
|
-
"command": "carto",
|
|
151
|
-
"args": ["serve"],
|
|
152
|
-
"cwd": "/path/to/your/project"
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
```
|
|
167
|
+
## Commands
|
|
157
168
|
|
|
158
|
-
|
|
169
|
+
| Command | What it does |
|
|
170
|
+
|---------|-------------|
|
|
171
|
+
| `carto init` | Detect project, index codebase, generate AGENTS.md, wire MCP |
|
|
172
|
+
| `carto watch` | Incremental live re-index on every file save (~130ms) |
|
|
173
|
+
| `carto sync` | One-time manual re-index |
|
|
174
|
+
| `carto impact <file>` | Blast radius: risk level, affected files, routes at risk |
|
|
175
|
+
| `carto check` | Cross-domain violations, high-risk uncommitted changes, domain health |
|
|
176
|
+
| `carto serve` | Start MCP server for Kiro / Cursor / Claude |
|
|
177
|
+
| `carto remove` | Remove AGENTS.md and .carto/ from project |
|
|
178
|
+
| `carto --version` | Show version |
|
|
159
179
|
|
|
160
180
|
---
|
|
161
181
|
|
|
162
|
-
##
|
|
163
|
-
|
|
164
|
-
Large codebases kill AI accuracy. A 2900-line AGENTS.md means AI reads 500 lines and guesses the rest.
|
|
182
|
+
## `carto check`
|
|
165
183
|
|
|
166
|
-
|
|
184
|
+
Run before committing. Tells you what's risky before you push.
|
|
167
185
|
|
|
168
186
|
```
|
|
169
|
-
|
|
170
|
-
.carto/context/
|
|
171
|
-
AUTH.md → auth routes, session models, JWT functions
|
|
172
|
-
PAYMENTS.md → Stripe routes, billing models
|
|
173
|
-
TRPC.md → all tRPC procedures
|
|
174
|
-
DATABASE.md → every model, schema, table
|
|
175
|
-
EVENTS.md → webhooks, queues, cron jobs
|
|
176
|
-
CORE.md → shared utilities
|
|
177
|
-
```
|
|
187
|
+
── Carto Check ───────────────────────────────────────
|
|
178
188
|
|
|
179
|
-
|
|
189
|
+
Files indexed : 310
|
|
190
|
+
Routes found : 104
|
|
191
|
+
Import edges : 5,248
|
|
192
|
+
Domains : AUTH · DATABASE · PAYMENTS · EVENTS · CORE
|
|
180
193
|
|
|
181
|
-
|
|
194
|
+
⚠️ High-risk uncommitted changes (1):
|
|
195
|
+
🔴 src/lib/auth.service.ts
|
|
196
|
+
11 files depend on this, blast risk: HIGH
|
|
182
197
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
## Know what breaks before you break it
|
|
198
|
+
✅ No cross-domain dependency violations
|
|
186
199
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
#
|
|
192
|
-
# Imported by:
|
|
193
|
-
# → apps/web/app/api/auth/signup/handlers/calcomSignupHandler.ts
|
|
194
|
-
# → apps/web/app/api/auth/signup/handlers/selfHostedHandler.ts
|
|
195
|
-
#
|
|
196
|
-
# Routes at risk:
|
|
197
|
-
# → POST /api/auth/signup
|
|
198
|
-
# → ALL /api/auth/signup/handlers
|
|
199
|
-
#
|
|
200
|
-
# Risk: MEDIUM
|
|
200
|
+
🔥 Top high-impact files:
|
|
201
|
+
83 dependents - packages/ui/src/lib/utils/cn.ts
|
|
202
|
+
35 dependents - packages/pg-meta/src/pg-format/index.ts
|
|
203
|
+
34 dependents - packages/icons/src/createSupabaseIcon.ts
|
|
201
204
|
```
|
|
202
205
|
|
|
203
|
-
No AI. No cloud. Runs in under a second. From your live import graph.
|
|
204
|
-
|
|
205
206
|
---
|
|
206
207
|
|
|
207
|
-
##
|
|
208
|
+
## `carto impact`
|
|
208
209
|
|
|
209
210
|
```bash
|
|
210
|
-
|
|
211
|
-
```
|
|
211
|
+
carto impact src/middleware.ts
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
Impact analysis: src/middleware.ts
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
Risk: 🔴 HIGH
|
|
216
|
+
Directly affected: 11 files across 2 domain(s)
|
|
217
|
+
Domains impacted: AUTH, PAYMENTS
|
|
218
|
+
|
|
219
|
+
Files that depend on this (11):
|
|
220
|
+
→ src/routes/auth.ts
|
|
221
|
+
→ src/routes/billing.ts
|
|
222
|
+
→ ...
|
|
223
|
+
|
|
224
|
+
Routes at risk (4):
|
|
225
|
+
🔴 POST /api/auth/login
|
|
226
|
+
🔴 POST /api/billing/checkout
|
|
227
|
+
🟡 GET /api/users/me
|
|
228
|
+
🟢 GET /api/health
|
|
217
229
|
```
|
|
218
230
|
|
|
219
231
|
---
|
|
220
232
|
|
|
221
|
-
##
|
|
233
|
+
## Programmatic API
|
|
222
234
|
|
|
223
|
-
|
|
224
|
-
cd your-project
|
|
225
|
-
carto init
|
|
226
|
-
```
|
|
235
|
+
Use Carto as a module, no CLI required. This is how tools embed it.
|
|
227
236
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
- Generates AGENTS.md + domain context files
|
|
231
|
-
- Auto-wires MCP into Kiro, Cursor, Claude Desktop
|
|
232
|
-
- Installs a git hook — syncs on every commit
|
|
237
|
+
```js
|
|
238
|
+
const { Carto } = require('carto-md');
|
|
233
239
|
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
const carto = new Carto();
|
|
241
|
+
await carto.index('/path/to/project');
|
|
236
242
|
|
|
237
|
-
|
|
243
|
+
// Everything about a file in one call
|
|
244
|
+
const ctx = carto.getContextForFile('src/auth/auth.service.ts');
|
|
245
|
+
// {
|
|
246
|
+
// domain: 'AUTH',
|
|
247
|
+
// routes: ['POST /api/auth/login', 'GET /api/auth/me'],
|
|
248
|
+
// models: ['User', 'Session'],
|
|
249
|
+
// blastRadius: { risk: 'HIGH', directlyAffected: { files: 8, domains: 2 } },
|
|
250
|
+
// neighbors: { nodes: [...], edges: [...] }, // React Flow compatible
|
|
251
|
+
// crossDomainDeps: [...],
|
|
252
|
+
// domainContext: '...AUTH.md content...'
|
|
253
|
+
// }
|
|
238
254
|
|
|
239
|
-
|
|
255
|
+
// Live updates
|
|
256
|
+
carto.on('updated', ({ file, blastRadius }) => {
|
|
257
|
+
console.log(`${file} changed, blast risk: ${blastRadius.risk}`);
|
|
258
|
+
});
|
|
240
259
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
260
|
+
await carto.reindex('src/auth/auth.service.ts'); // ~130ms
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Full API:**
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
carto.getBlastRadius(file) // Risk + affected files + routes
|
|
267
|
+
carto.getNeighbors(file, hops) // Import graph, React Flow nodes/edges
|
|
268
|
+
carto.getCrossDomainDeps() // Cross-boundary import edges
|
|
269
|
+
carto.getHighImpactFiles(n) // Top N by blast radius
|
|
270
|
+
carto.searchRoutes(query) // Route search
|
|
271
|
+
carto.getRoutes() // All API routes
|
|
272
|
+
carto.getDomain(name) // Domain cluster + context file
|
|
273
|
+
carto.getDomainsList() // All domains with counts
|
|
274
|
+
carto.getModels(domain?) // All models
|
|
275
|
+
carto.getEnvVars(domain?) // Env vars with domain mapping
|
|
276
|
+
carto.getMeta() // Index stats
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Events: `status` · `indexed` · `updated`
|
|
250
280
|
|
|
251
281
|
---
|
|
252
282
|
|
|
253
|
-
##
|
|
283
|
+
## Domain context files
|
|
254
284
|
|
|
255
|
-
|
|
256
|
-
|----------|------------|
|
|
257
|
-
| Python | FastAPI, Pydantic |
|
|
258
|
-
| JavaScript | Express, Next.js |
|
|
259
|
-
| TypeScript | Express, Next.js, Prisma, tRPC |
|
|
260
|
-
| R | Plumber, Shiny, R6, S7 |
|
|
261
|
-
| HTML | fetch() calls |
|
|
285
|
+
Large codebases kill AI accuracy. A 2,900-line AGENTS.md means the AI reads 500 lines and guesses the rest.
|
|
262
286
|
|
|
263
|
-
|
|
287
|
+
Carto splits context by domain automatically:
|
|
264
288
|
|
|
265
|
-
|
|
289
|
+
```
|
|
290
|
+
AGENTS.md → lean map, always loaded by every AI
|
|
291
|
+
.carto/context/
|
|
292
|
+
AUTH.md → auth routes, session models, JWT functions, middleware
|
|
293
|
+
PAYMENTS.md → Stripe routes, billing models, webhook handlers
|
|
294
|
+
DATABASE.md → every model, schema, table, migration pattern
|
|
295
|
+
EVENTS.md → webhooks, queues, cron jobs, event emitters
|
|
296
|
+
TRPC.md → all procedures with input/output schemas
|
|
297
|
+
CORE.md → shared utilities
|
|
298
|
+
```
|
|
266
299
|
|
|
267
|
-
|
|
300
|
+
AI reads AGENTS.md always. Then fetches only the domain file relevant to the task. 400 lines of exact context instead of 2,900 lines of everything.
|
|
268
301
|
|
|
269
|
-
|
|
270
|
-
- Data models — Pydantic, Prisma, TypeScript interfaces
|
|
271
|
-
- Function signatures — across all files
|
|
272
|
-
- Import graph — which files depend on which
|
|
273
|
-
- Domain clusters — AUTH, PAYMENTS, TRPC, DATABASE, EVENTS
|
|
274
|
-
- Blast radius — what breaks if you change a file
|
|
275
|
-
- Environment variable names — never values
|
|
276
|
-
- Database tables — SQLAlchemy, Django ORM, Prisma
|
|
302
|
+
Domain assignment runs on your import graph: files that import each other cluster together, regardless of folder names.
|
|
277
303
|
|
|
278
304
|
---
|
|
279
305
|
|
|
280
|
-
##
|
|
306
|
+
## MCP config (if auto-wire missed your IDE)
|
|
281
307
|
|
|
282
|
-
|
|
308
|
+
**Kiro**: `~/.kiro/settings/mcp.json`
|
|
309
|
+
```json
|
|
310
|
+
{ "mcpServers": { "carto": { "command": "carto", "args": ["serve"], "cwd": "/your/project" } } }
|
|
311
|
+
```
|
|
283
312
|
|
|
313
|
+
**Cursor**: `~/.cursor/mcp.json`
|
|
314
|
+
```json
|
|
315
|
+
{ "mcpServers": { "carto": { "command": "carto", "args": ["serve"], "cwd": "/your/project" } } }
|
|
284
316
|
```
|
|
285
|
-
<!-- CARTO:AUTO:START -->
|
|
286
|
-
... auto-generated content ...
|
|
287
|
-
<!-- CARTO:AUTO:END -->
|
|
288
317
|
|
|
289
|
-
|
|
318
|
+
**Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
319
|
+
```json
|
|
320
|
+
{ "mcpServers": { "carto": { "command": "carto", "args": ["serve"], "cwd": "/your/project" } } }
|
|
290
321
|
```
|
|
291
322
|
|
|
292
323
|
---
|
|
293
324
|
|
|
325
|
+
## AI tools that read AGENTS.md natively
|
|
326
|
+
|
|
327
|
+
Cursor · GitHub Copilot · Kiro · Claude Desktop · Claude Code · Codex · VS Code · Gemini CLI · Devin · Jules
|
|
328
|
+
|
|
329
|
+
Carto generates the file they all read. One source of truth. Every tool stays accurate.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
294
333
|
## What Carto fixes
|
|
295
334
|
|
|
296
335
|
Carto fixes **factual hallucination about your own project**:
|
|
@@ -298,66 +337,56 @@ Carto fixes **factual hallucination about your own project**:
|
|
|
298
337
|
- AI guessing wrong routes → fixed
|
|
299
338
|
- AI guessing wrong field names → fixed
|
|
300
339
|
- AI assuming wrong framework → fixed
|
|
301
|
-
- AI guessing wrong DB schema → fixed
|
|
302
340
|
- AI not knowing blast radius → fixed
|
|
341
|
+
- AI losing context between sessions → fixed
|
|
342
|
+
- Rebuilding project context every session → gone
|
|
303
343
|
|
|
304
|
-
What Carto does not fix: AI reasoning badly, wrong implementation logic, misunderstanding
|
|
344
|
+
What Carto does not fix: AI reasoning badly, wrong implementation logic, misunderstanding requirements. Carto makes AI **accurate** about your project. Not smarter. Accurate. Different thing.
|
|
305
345
|
|
|
306
346
|
---
|
|
307
347
|
|
|
308
|
-
##
|
|
348
|
+
## What Carto never does
|
|
309
349
|
|
|
310
|
-
-
|
|
311
|
-
-
|
|
312
|
-
-
|
|
313
|
-
-
|
|
314
|
-
- **Claude Code** — natively
|
|
315
|
-
- **Codex** — natively
|
|
316
|
-
- **VS Code** — via workspace context
|
|
317
|
-
- **Gemini CLI** — natively
|
|
318
|
-
- **Devin** — natively
|
|
319
|
-
- **Jules** — natively
|
|
350
|
+
- Sends your code anywhere. Local only.
|
|
351
|
+
- Writes secrets into AGENTS.md. `.cartoignore` blocks `.env` and credential files by default.
|
|
352
|
+
- Touches your manual notes. It writes only between `<!-- CARTO:AUTO:START -->` and `<!-- CARTO:AUTO:END -->`.
|
|
353
|
+
- Costs money. MIT license. Free forever.
|
|
320
354
|
|
|
321
355
|
---
|
|
322
356
|
|
|
323
|
-
##
|
|
324
|
-
|
|
325
|
-
- No cloud. No servers. No telemetry. No tracking.
|
|
326
|
-
- Your code never leaves your machine.
|
|
327
|
-
- No paid tiers. Free forever. MIT license.
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
## Security
|
|
332
|
-
|
|
333
|
-
Carto never writes secrets into AGENTS.md. `.cartoignore` blocks `.env` files, secret files, key files, and credential files by default. The sanitizer strips API key patterns from extracted code.
|
|
357
|
+
## Install
|
|
334
358
|
|
|
335
|
-
|
|
359
|
+
```bash
|
|
360
|
+
npm install -g carto-md
|
|
361
|
+
```
|
|
336
362
|
|
|
337
|
-
|
|
363
|
+
```bash
|
|
364
|
+
cd your-project
|
|
365
|
+
carto init
|
|
366
|
+
```
|
|
338
367
|
|
|
339
|
-
|
|
368
|
+
That's it.
|
|
340
369
|
|
|
341
370
|
---
|
|
342
371
|
|
|
343
372
|
## Origin
|
|
344
373
|
|
|
345
|
-
I was building
|
|
374
|
+
I was building Emfirge, a cloud security agent for AWS.
|
|
346
375
|
|
|
347
|
-
To make the AI
|
|
376
|
+
To make the AI understand infrastructure, I built a module that mapped AWS resources into a graph. The AI stopped hallucinating. It worked with facts.
|
|
348
377
|
|
|
349
|
-
|
|
378
|
+
Then I switched AI tools. New session. Had to explain the whole project again from scratch.
|
|
350
379
|
|
|
351
|
-
I thought: *I just built a cartography system
|
|
380
|
+
I thought: *I just built a cartography system for infrastructure. Why doesn't this exist for codebases?*
|
|
352
381
|
|
|
353
|
-
Carto is that.
|
|
382
|
+
Carto is that.
|
|
354
383
|
|
|
355
384
|
---
|
|
356
385
|
|
|
357
386
|
## License
|
|
358
387
|
|
|
359
|
-
MIT
|
|
388
|
+
MIT. Free forever.
|
|
360
389
|
|
|
361
390
|
---
|
|
362
391
|
|
|
363
|
-
*
|
|
392
|
+
*Your code changes. Carto knows. Every AI you use knows.*
|
package/package.json
CHANGED
|
@@ -38,6 +38,7 @@ module.exports = {
|
|
|
38
38
|
|
|
39
39
|
// Expose internals for typescript.js to reuse
|
|
40
40
|
_extractExpressRoutes: extractExpressRoutes,
|
|
41
|
+
_extractReactRouterRoutes: extractReactRouterRoutes,
|
|
41
42
|
_extractProcessEnv: extractProcessEnv,
|
|
42
43
|
_extractJSFetches: extractJSFetches,
|
|
43
44
|
_extractJSFunctions: extractJSFunctions,
|
|
@@ -77,6 +78,10 @@ function extractExpressRoutes(ast, filename) {
|
|
|
77
78
|
const nextRoutes = extractNextJSPagesRoutes(ast, filename);
|
|
78
79
|
routes.push(...nextRoutes);
|
|
79
80
|
|
|
81
|
+
// React Router (JSX <Route> + createBrowserRouter/createHashRouter/createMemoryRouter)
|
|
82
|
+
const reactRoutes = extractReactRouterRoutes(ast);
|
|
83
|
+
routes.push(...reactRoutes);
|
|
84
|
+
|
|
80
85
|
walk(ast, (node) => {
|
|
81
86
|
if (node.type !== 'CallExpression') return;
|
|
82
87
|
if (!node.callee || node.callee.type !== 'MemberExpression') return;
|
|
@@ -134,6 +139,120 @@ function extractExpressRoutes(ast, filename) {
|
|
|
134
139
|
});
|
|
135
140
|
}
|
|
136
141
|
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// React Router extraction
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
const REACT_ROUTER_CREATORS = new Set(['createBrowserRouter', 'createHashRouter', 'createMemoryRouter', 'useRoutes']);
|
|
147
|
+
|
|
148
|
+
function extractReactRouterRoutes(ast) {
|
|
149
|
+
const routes = [];
|
|
150
|
+
|
|
151
|
+
// 1. JSX <Route path="..." component={X} /> or element={<X />}
|
|
152
|
+
walk(ast, (node) => {
|
|
153
|
+
if (node.type !== 'JSXOpeningElement') return;
|
|
154
|
+
if (!node.name || node.name.name !== 'Route') return;
|
|
155
|
+
|
|
156
|
+
let routePath = null;
|
|
157
|
+
let componentName = '[anonymous]';
|
|
158
|
+
|
|
159
|
+
for (const attr of (node.attributes || [])) {
|
|
160
|
+
if (attr.type !== 'JSXAttribute' || !attr.name) continue;
|
|
161
|
+
const attrName = attr.name.name;
|
|
162
|
+
|
|
163
|
+
if (attrName === 'path' && attr.value) {
|
|
164
|
+
if (attr.value.type === 'StringLiteral') {
|
|
165
|
+
routePath = attr.value.value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if ((attrName === 'component' || attrName === 'element') && attr.value) {
|
|
170
|
+
if (attr.value.type === 'JSXExpressionContainer' && attr.value.expression) {
|
|
171
|
+
const expr = attr.value.expression;
|
|
172
|
+
if (expr.type === 'Identifier') {
|
|
173
|
+
componentName = expr.name;
|
|
174
|
+
} else if (expr.type === 'JSXElement' && expr.openingElement && expr.openingElement.name) {
|
|
175
|
+
componentName = expr.openingElement.name.name || '[anonymous]';
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (routePath !== null) {
|
|
182
|
+
routes.push({ method: 'VIEW', path: routePath, functionName: componentName });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 2. createBrowserRouter / createHashRouter / createMemoryRouter / useRoutes
|
|
187
|
+
walk(ast, (node) => {
|
|
188
|
+
if (node.type !== 'CallExpression') return;
|
|
189
|
+
const callee = node.callee;
|
|
190
|
+
if (!callee) return;
|
|
191
|
+
const calleeName = callee.type === 'Identifier' ? callee.name
|
|
192
|
+
: (callee.type === 'MemberExpression' && callee.property) ? callee.property.name
|
|
193
|
+
: null;
|
|
194
|
+
if (!calleeName || !REACT_ROUTER_CREATORS.has(calleeName)) return;
|
|
195
|
+
if (!node.arguments || node.arguments.length === 0) return;
|
|
196
|
+
|
|
197
|
+
const firstArg = node.arguments[0];
|
|
198
|
+
if (firstArg.type === 'ArrayExpression') {
|
|
199
|
+
extractRoutesFromRouteArray(firstArg, routes);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const seen = new Set();
|
|
204
|
+
return routes.filter(r => {
|
|
205
|
+
const key = `VIEW::${r.path}`;
|
|
206
|
+
if (seen.has(key)) return false;
|
|
207
|
+
seen.add(key);
|
|
208
|
+
return true;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function extractRoutesFromRouteArray(arrayNode, routes) {
|
|
213
|
+
for (const element of (arrayNode.elements || [])) {
|
|
214
|
+
if (!element || element.type !== 'ObjectExpression') continue;
|
|
215
|
+
extractRouteFromRouteObject(element, routes);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function extractRouteFromRouteObject(objNode, routes) {
|
|
220
|
+
let routePath = null;
|
|
221
|
+
let componentName = '[anonymous]';
|
|
222
|
+
let childrenNode = null;
|
|
223
|
+
|
|
224
|
+
for (const prop of (objNode.properties || [])) {
|
|
225
|
+
if (prop.type !== 'ObjectProperty' && prop.type !== 'Property') continue;
|
|
226
|
+
if (!prop.key) continue;
|
|
227
|
+
const key = prop.key.name || prop.key.value;
|
|
228
|
+
|
|
229
|
+
if (key === 'path' && prop.value && prop.value.type === 'StringLiteral') {
|
|
230
|
+
routePath = prop.value.value;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if ((key === 'element' || key === 'component') && prop.value) {
|
|
234
|
+
const val = prop.value;
|
|
235
|
+
if (val.type === 'Identifier') {
|
|
236
|
+
componentName = val.name;
|
|
237
|
+
} else if (val.type === 'JSXElement' && val.openingElement && val.openingElement.name) {
|
|
238
|
+
componentName = val.openingElement.name.name || '[anonymous]';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (key === 'children' && prop.value && prop.value.type === 'ArrayExpression') {
|
|
243
|
+
childrenNode = prop.value;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (routePath !== null) {
|
|
248
|
+
routes.push({ method: 'VIEW', path: routePath, functionName: componentName });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (childrenNode) {
|
|
252
|
+
extractRoutesFromRouteArray(childrenNode, routes);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
137
256
|
/**
|
|
138
257
|
* Detects Next.js Pages Router pattern:
|
|
139
258
|
* export default function handler(req, res) in files under pages/api/
|