carto-md 1.0.9 → 1.0.10

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.
@@ -0,0 +1,108 @@
1
+ # Contributing to Carto
2
+
3
+ Carto is free, open source, and community-maintained. The core team owns the merger logic, AST engine, and CLI. The community owns language and framework extractors.
4
+
5
+ ---
6
+
7
+ ## What to contribute
8
+
9
+ ### Tier 1 — Languages (safe to add, easy to review)
10
+
11
+ New language support lives in `src/ast/languages/`. Each language is an isolated module.
12
+
13
+ Currently supported: JavaScript/TypeScript, Python.
14
+
15
+ Wanted: Go, Rust, Ruby, Java, PHP, C#.
16
+
17
+ ### Tier 2 — Framework extractors (safe to add, easy to review)
18
+
19
+ Framework-specific route and model extraction lives in `src/extractors/`. Each framework is an isolated module.
20
+
21
+ Currently supported: FastAPI, Express, Next.js App Router, Prisma, HTML fetch().
22
+
23
+ Wanted: Django, Rails, Laravel, NestJS, Hono, Gin, Spring.
24
+
25
+ ### Tier 3 — Core (review carefully before merging)
26
+
27
+ - `src/agents/merger.js` — merger logic. One bad merge = developer loses manual notes = project dies. Changes here need strong justification and full test coverage.
28
+ - `src/ast/` — AST engine. Wrong extraction = wrong AGENTS.md = AI gets confident with wrong facts. Worse than no AGENTS.md.
29
+ - `src/detector/` — framework detection logic.
30
+ - `src/cli/` — CLI commands.
31
+
32
+ ---
33
+
34
+ ## How to add a language
35
+
36
+ 1. Create `src/ast/languages/yourlanguage.js`
37
+ 2. Export a single function: `extractFromFile(filePath, fileContent)`
38
+ 3. Return:
39
+ ```js
40
+ {
41
+ functions: [{ name, params, returns }],
42
+ classes: [{ name, fields }],
43
+ imports: [{ from, symbols }],
44
+ exports: [{ name }]
45
+ }
46
+ ```
47
+ 4. Add it to `src/ast/parser.js` language map
48
+ 5. Test on at least 3 real open-source projects
49
+ 6. Open a PR with before/after AGENTS.md examples
50
+
51
+ ---
52
+
53
+ ## How to add a framework extractor
54
+
55
+ 1. Create `src/extractors/yourframework.js`
56
+ 2. Export:
57
+ ```js
58
+ {
59
+ detect(projectRoot, files) → boolean,
60
+ extractRoutes(filePath, fileContent) → [{ method, path, functionName }],
61
+ extractModels(filePath, fileContent) → [{ name, fields: [{ name, type }] }]
62
+ }
63
+ ```
64
+ 3. Add detection logic to `src/detector/framework.js`
65
+ 4. Test on at least 2 real projects using that framework
66
+ 5. Open a PR with before/after AGENTS.md examples
67
+
68
+ ---
69
+
70
+ ## Ground rules
71
+
72
+ - **Never break the merger.** Manual sections in AGENTS.md are sacred. If your change could corrupt them, it needs a full merger test suite pass.
73
+ - **Wrong output is worse than no output.** If your extractor produces incorrect routes or models, AI gets confident with wrong facts. Only ship when accurate on real projects.
74
+ - **Test on unknown repos.** Don't just test on projects you wrote. Find a real open-source repo using the framework and verify the output is correct.
75
+ - **No cloud, no telemetry, no tracking.** Carto is local only. Forever. Don't add any network calls.
76
+ - **No paid features.** Free forever. MIT. Don't propose monetization.
77
+
78
+ ---
79
+
80
+ ## Development setup
81
+
82
+ ```bash
83
+ git clone https://github.com/anshsonkar/carto-ansh
84
+ cd carto-ansh
85
+ npm install
86
+ node src/cli/index.js init # test in any project
87
+ ```
88
+
89
+ ---
90
+
91
+ ## PR checklist
92
+
93
+ - [ ] Tested on at least 2-3 real open-source projects
94
+ - [ ] Before/after AGENTS.md included in PR description
95
+ - [ ] No changes to merger logic (unless explicitly fixing a merger bug)
96
+ - [ ] No network calls added
97
+ - [ ] `carto --version` still works
98
+ - [ ] Existing tests pass
99
+
100
+ ---
101
+
102
+ ## Issues
103
+
104
+ - **Bug**: Open an issue with the project type, command run, and what AGENTS.md produced vs what you expected.
105
+ - **Language request**: Open an issue titled "Language: [name]" — someone from the community will pick it up.
106
+ - **Framework request**: Open an issue titled "Framework: [name]".
107
+
108
+ All issues acknowledged within 24 hours.
package/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # carto
2
+
3
+ [![npm version](https://img.shields.io/npm/v/carto-md)](https://www.npmjs.com/package/carto-md)
4
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
5
+ [![npm downloads](https://img.shields.io/npm/dm/carto-md)](https://www.npmjs.com/package/carto-md)
6
+
7
+ **Your code changes. AGENTS.md updates. Every AI always knows.**
8
+
9
+ Carto auto-generates and auto-maintains your `AGENTS.md` file. Every time you save, your routes, models, functions, and dependencies are extracted and written into the standard file every AI coding tool already reads.
10
+
11
+ ---
12
+
13
+ ## Origin
14
+
15
+ I was building [Emfirge](https://emfirge.com) — a cloud security advisor for AWS.
16
+
17
+ To make the AI inside Emfirge understand infrastructure, I wrote a module called `cartography.py`. It scanned AWS resources, built a graph of how they connected, and wrote it into a structured map. The AI stopped hallucinating. It worked with accurate facts about the actual infrastructure — not guesses.
18
+
19
+ Halfway through, I switched AI tools. Opened a new session. Had to explain everything again from scratch.
20
+
21
+ I thought: *I just built a cartography system so AI can understand infrastructure. Why doesn't this exist for codebases?*
22
+
23
+ Carto is that thing. Same insight, different domain. Map your codebase once. Every AI session starts with accurate facts. You never explain your project again.
24
+
25
+ ---
26
+
27
+ ## The problem
28
+
29
+ AI coding tools are blind to your actual project. Every session starts from zero.
30
+
31
+ - Claude hallucinates your schema
32
+ - Copilot suggests the wrong field names
33
+ - Kiro asks what framework you're using
34
+ - You rebuild context manually, every time
35
+
36
+ `AGENTS.md` is the standard that fixes this — a file in your project root that every AI tool reads for project context. But it's static. You write it manually. It gets stale the moment your code changes.
37
+
38
+ **Carto makes it live.**
39
+
40
+ ---
41
+
42
+ ## Why not just paste your code?
43
+
44
+ Context windows are large now. But pasting code means:
45
+
46
+ - You decide what's relevant — you're often wrong
47
+ - AI sees a snapshot, not your live state
48
+ - Bigger context ≠ better context
49
+
50
+ Carto gives AI the map. You give AI the problem. Different jobs.
51
+
52
+ ---
53
+
54
+ ## How it works
55
+
56
+ ```
57
+ You save a file
58
+
59
+ Carto extracts routes, models, functions, env vars
60
+
61
+ AGENTS.md updated in 300ms
62
+
63
+ Cursor, Copilot, Kiro, Codex, Claude — all read current truth
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Install
69
+
70
+ ```bash
71
+ npm install -g carto-md
72
+ ```
73
+
74
+ Or run without installing:
75
+
76
+ ```bash
77
+ npx carto-md init
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Usage
83
+
84
+ ```bash
85
+ # 1. Go to your project
86
+ cd your-project
87
+
88
+ # 2. Generate AGENTS.md (run once)
89
+ carto init
90
+
91
+ # 3. Keep it live while you work
92
+ carto watch
93
+ ```
94
+
95
+ Leave `carto watch` running in a background terminal. Every file save updates AGENTS.md automatically.
96
+
97
+ ---
98
+
99
+ ## Commands
100
+
101
+ | Command | What it does |
102
+ |---------|-------------|
103
+ | `carto init` | Detect stack, generate AGENTS.md, install git hook |
104
+ | `carto watch` | Watch files, update AGENTS.md on every save |
105
+ | `carto sync` | One-time refresh, no watcher |
106
+ | `carto impact <file>` | Show blast radius before touching a file |
107
+ | `carto --version` | Show version |
108
+
109
+ **When to use each:**
110
+ - `init` — once, when you add Carto to a project
111
+ - `watch` — every work session, leave it running
112
+ - `sync` — if you skipped watch and just want a fresh snapshot
113
+ - `impact` — before editing anything critical
114
+
115
+ ---
116
+
117
+ ## What gets extracted automatically
118
+
119
+ - API routes — FastAPI, Express, Next.js App Router
120
+ - Data models — Pydantic, Prisma
121
+ - Function signatures — across all files
122
+ - Dependencies — from `package.json` / `requirements.txt`
123
+ - Environment variable names — never values
124
+ - Frontend API calls — from `fetch()` patterns
125
+ - Import graph — which files depend on which
126
+ - Database tables
127
+
128
+ ---
129
+
130
+ ## What Carto never touches
131
+
132
+ The manual sections you write directly into `AGENTS.md` — architecture decisions, active bugs, business rules, coding conventions — stay yours forever. Carto only rewrites content between its own markers:
133
+
134
+ ```
135
+ <!-- CARTO:AUTO:START -->
136
+ ... auto-generated content ...
137
+ <!-- CARTO:AUTO:END -->
138
+
139
+ Your manual notes here. Never touched.
140
+ ```
141
+
142
+ ---
143
+
144
+ ## carto impact
145
+
146
+ Before touching any file, know the blast radius:
147
+
148
+ ```bash
149
+ carto impact app/models.py
150
+
151
+ # Impact analysis: app/models.py
152
+ #
153
+ # Imported by:
154
+ # → app/main.py
155
+ # → app/rules.py
156
+ # → app/scoring.py
157
+ # → app/aws_collector.py
158
+ # → tests/conftest.py
159
+ #
160
+ # Routes affected:
161
+ # → POST /analyze
162
+ # → GET /history
163
+ # → POST /simulate
164
+ # → ... 12 more
165
+ #
166
+ # Risk: HIGH — 5 files depend on this
167
+ ```
168
+
169
+ Most production bugs aren't logic errors. They're *"I didn't know X depended on Y."* Carto makes that invisible knowledge visible before you break something.
170
+
171
+ ---
172
+
173
+ ## What Carto fixes
174
+
175
+ Carto fixes **factual hallucination about your own project**:
176
+
177
+ - AI guessing wrong routes → fixed
178
+ - AI guessing wrong field names → fixed
179
+ - AI assuming wrong framework → fixed
180
+ - AI guessing wrong DB schema → fixed
181
+
182
+ What Carto does not fix: AI reasoning badly, wrong implementation logic, misunderstanding what you want. Carto makes AI **accurate** about your project. Not smarter. Accurate. Different thing.
183
+
184
+ ---
185
+
186
+ ## Real test — cal.com (800k lines)
187
+
188
+ We ran the same task in two Claude sessions: *"Add a `notes` field to the booking model."*
189
+
190
+ **Without AGENTS.md:**
191
+ - Wrong API route: suggested `POST /api/bookings` → actual is `POST /v2/bookings`
192
+ - Wrong handler: suggested `handleNewBooking.ts` → not the creation path
193
+ - Wrong file paths: pointed to v1 API (`apps/api/v1/...`) → v1 is legacy
194
+ - Wrong tRPC file: `bookings.tsx` → actual is `bookings/_router.tsx`
195
+ - Field list: ~15 fields guessed → missing 20+ real fields
196
+ - Couldn't proceed without follow-up: *"Want me to write the exact diffs once you confirm the codebase location?"*
197
+
198
+ **With AGENTS.md (generated by Carto):**
199
+ - Correct API route: `POST /v2/bookings` ✅
200
+ - Correct controller path ✅
201
+ - Correct tRPC file ✅
202
+ - All 35+ booking fields returned accurately ✅
203
+ - Answered in one shot. No follow-up needed.
204
+
205
+ **4 wrong file paths → 0. 20 missing fields → 0. Zero follow-up clarifications.**
206
+
207
+ This is what Carto does. Not smarter AI. The same AI with accurate facts.
208
+
209
+ ---
210
+
211
+ ## AI tools that read AGENTS.md
212
+
213
+ Drop the file in your project root. Each tool picks it up via its own context config:
214
+
215
+ - **Cursor** — via context rules
216
+ - **GitHub Copilot** — via workspace instructions
217
+ - **Kiro** — natively
218
+ - **Codex** — natively
219
+ - **VS Code** — via workspace context
220
+ - **Gemini CLI** — natively
221
+ - **Devin** — natively
222
+ - **Jules** — natively
223
+
224
+ ---
225
+
226
+ ## Tested on
227
+
228
+ - FastAPI + Python projects
229
+ - Next.js App Router
230
+ - Next.js + Prisma
231
+ - React + FastAPI monorepos
232
+ - Large monorepos (5000+ files — tested on Supabase and cal.com for stability, caps at 50 most important files on projects this scale)
233
+
234
+ ---
235
+
236
+ ## What it does NOT do
237
+
238
+ - No cloud. No servers. No telemetry. No tracking.
239
+ - Your code never leaves your machine.
240
+ - No paid tiers. Free forever. MIT license.
241
+
242
+ ---
243
+
244
+ ## Security
245
+
246
+ 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.
247
+
248
+ ---
249
+
250
+ ## Contributing
251
+
252
+ Python and JS/TS are supported today. Community adds more.
253
+
254
+ - **Add a language**: `src/ast/languages/`
255
+ - **Add a framework**: `src/extractors/`
256
+
257
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
258
+
259
+ ---
260
+
261
+ ## License
262
+
263
+ MIT — free forever.
264
+
265
+ ---
266
+
267
+ *Built because AGENTS.md won. Someone had to keep it alive.*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carto-md",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "The context layer for AI-native development.",
5
5
  "bin": {
6
6
  "carto": "src/cli/index.js"
package/src/sync.js CHANGED
@@ -62,25 +62,15 @@ async function runFullSync(config) {
62
62
  const allModelFiles = config.watch.modelFiles || [];
63
63
  const allFrontendFiles = config.watch.frontendFiles || [];
64
64
 
65
- // Aggregate data
66
65
  let allRoutes = [];
67
66
  let allModels = [];
68
67
  let allFetches = [];
69
68
  let allStorageKeys = [];
70
-
71
- // Functions: { filename: [{ name, params, returnType }] }
72
69
  const functionsMap = {};
73
- // Routes per file for file map
74
70
  const routeCountMap = {};
75
- // Env vars: { varName: Set([filename, ...]) }
76
71
  const envVarMap = new Map();
77
- // DB tables: [{ tableName, modelName, file }]
78
72
  const dbTableList = [];
79
-
80
- // Deduplicate files
81
73
  const processedFiles = new Set();
82
-
83
- // Process all code files (route + model files, deduplicated)
84
74
  const allCodeFiles = [...new Set([...allRouteFiles, ...allModelFiles])];
85
75
 
86
76
  for (const filePath of allCodeFiles) {
@@ -94,42 +84,32 @@ async function runFullSync(config) {
94
84
  const relPath = path.relative(projectRoot, filePath);
95
85
  const plugin = getPluginForFile(plugins, filePath);
96
86
 
97
- if (!plugin) {
98
- // No plugin for this file type — skip silently
99
- continue;
100
- }
87
+ if (!plugin) continue;
101
88
 
102
89
  const result = plugin.extract(content, relPath);
103
90
 
104
- // Routes
105
91
  allRoutes = allRoutes.concat(result.routes);
106
92
  routeCountMap[filePath] = result.routes.length;
107
93
 
108
- // Models
109
94
  allModels = allModels.concat(result.models);
110
95
 
111
- // Functions
112
96
  if (result.functions.length > 0 && basename !== '__init__.py') {
113
97
  functionsMap[basename] = result.functions;
114
98
  }
115
99
 
116
- // Env vars
117
100
  for (const varName of result.envVars) {
118
101
  if (!envVarMap.has(varName)) envVarMap.set(varName, new Set());
119
102
  envVarMap.get(varName).add(basename);
120
103
  }
121
104
 
122
- // DB tables
123
105
  for (const t of result.dbTables) {
124
106
  dbTableList.push({ tableName: t.tableName, modelName: t.modelName, file: basename });
125
107
  }
126
108
 
127
- // Fetches and storage keys (from JS/HTML plugins)
128
109
  allFetches = allFetches.concat(result.fetches);
129
110
  allStorageKeys = allStorageKeys.concat(result.storageKeys);
130
111
  }
131
112
 
132
- // Process frontend files separately (may overlap with code files)
133
113
  for (const filePath of allFrontendFiles) {
134
114
  if (processedFiles.has(filePath)) continue;
135
115
  processedFiles.add(filePath);
@@ -147,12 +127,10 @@ async function runFullSync(config) {
147
127
  allStorageKeys = allStorageKeys.concat(result.storageKeys);
148
128
  }
149
129
 
150
- // Global dedup: collapse dynamic fetches across all files into one summary row
151
130
  const staticFetches = allFetches.filter(f => f.url !== '[dynamic]' && !f.url.startsWith('dynamic calls detected'));
152
131
  let totalDynamic = 0;
153
132
  for (const f of allFetches) {
154
133
  if (f.url === '[dynamic]') totalDynamic++;
155
- // Also count already-collapsed per-file rows
156
134
  const m = f.url.match(/^dynamic calls detected \((\d+) unresolved\)$/);
157
135
  if (m) totalDynamic += parseInt(m[1], 10);
158
136
  }
@@ -161,7 +139,6 @@ async function runFullSync(config) {
161
139
  }
162
140
  allFetches = staticFetches;
163
141
 
164
- // Global dedup: storage keys across all files
165
142
  const skSeen = new Set();
166
143
  allStorageKeys = allStorageKeys.filter(({ operation, key }) => {
167
144
  const id = `${operation}::${key}`;
@@ -170,10 +147,8 @@ async function runFullSync(config) {
170
147
  return true;
171
148
  });
172
149
 
173
- // Build import graph from all processed files
174
150
  const fileContentsForImports = [];
175
151
  const allProcessedPaths = [...new Set([...allCodeFiles, ...allFrontendFiles])];
176
- // Re-read is avoided — collect during processing. Use a second pass for simplicity.
177
152
  for (const filePath of allProcessedPaths) {
178
153
  try {
179
154
  const content = fs.readFileSync(filePath, 'utf-8');
@@ -184,19 +159,15 @@ async function runFullSync(config) {
184
159
  }
185
160
  const importGraph = buildImportGraph(fileContentsForImports, projectRoot);
186
161
 
187
- // Detect tech stack from watched files + manifests
188
162
  const stackItems = buildStackLine(fileContentsForImports, projectRoot);
189
163
 
190
- // Compute entry points and high impact files from import graph
191
164
  const allValues = new Set();
192
165
  for (const deps of Object.values(importGraph)) {
193
166
  for (const dep of deps) allValues.add(dep);
194
167
  }
195
- // Entry points: files that import 3+ others but nothing imports them
196
168
  const entryPoints = Object.keys(importGraph)
197
169
  .filter(f => !allValues.has(f) && importGraph[f].length >= 3)
198
170
  .sort();
199
- // High impact: files imported by 3+ others, sorted descending by count
200
171
  const depCount = {};
201
172
  for (const deps of Object.values(importGraph)) {
202
173
  for (const dep of deps) {
@@ -208,7 +179,6 @@ async function runFullSync(config) {
208
179
  .sort((a, b) => b[1] - a[1])
209
180
  .map(([file, count]) => ({ file, count }));
210
181
 
211
- // Build file map
212
182
  const fileMap = [];
213
183
  for (const filePath of allCodeFiles) {
214
184
  const basename = path.basename(filePath);
@@ -221,15 +191,12 @@ async function runFullSync(config) {
221
191
  }
222
192
  }
223
193
 
224
- // Aggregate env vars into sorted array
225
194
  const envVars = [...envVarMap.keys()]
226
195
  .sort()
227
196
  .map(name => ({ name, files: [...envVarMap.get(name)].sort() }));
228
197
 
229
- // Scan project structure
230
198
  const structure = await scanStructure(projectRoot);
231
199
 
232
- // Validate extracted data — drop anything malformed
233
200
  const validated = validateExtracted({
234
201
  routes: allRoutes,
235
202
  models: allModels,
@@ -256,7 +223,6 @@ async function runFullSync(config) {
256
223
 
257
224
  mergeIntoAgentsMd(config.output, autoContent);
258
225
 
259
- // Save graph to .carto/map.json (atomic write)
260
226
  const cartoDir = path.join(projectRoot, '.carto');
261
227
  const mapData = {
262
228
  version: '1',