carto-md 1.0.8 → 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.8",
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/cli/init.js CHANGED
@@ -40,6 +40,7 @@ async function run(projectRoot) {
40
40
  version: '1',
41
41
  framework: detection.framework,
42
42
  language: detection.language,
43
+ projectRoot: projectRoot,
43
44
  watch: {
44
45
  routeFiles: relRouteFiles,
45
46
  modelFiles: relModelFiles,
package/src/sync.js CHANGED
@@ -56,30 +56,21 @@ async function scanStructure(basePath) {
56
56
  */
57
57
  async function runFullSync(config) {
58
58
  const warnings = [];
59
+ const projectRoot = config.projectRoot || process.cwd();
59
60
 
60
61
  const allRouteFiles = config.watch.routeFiles || [];
61
62
  const allModelFiles = config.watch.modelFiles || [];
62
63
  const allFrontendFiles = config.watch.frontendFiles || [];
63
64
 
64
- // Aggregate data
65
65
  let allRoutes = [];
66
66
  let allModels = [];
67
67
  let allFetches = [];
68
68
  let allStorageKeys = [];
69
-
70
- // Functions: { filename: [{ name, params, returnType }] }
71
69
  const functionsMap = {};
72
- // Routes per file for file map
73
70
  const routeCountMap = {};
74
- // Env vars: { varName: Set([filename, ...]) }
75
71
  const envVarMap = new Map();
76
- // DB tables: [{ tableName, modelName, file }]
77
72
  const dbTableList = [];
78
-
79
- // Deduplicate files
80
73
  const processedFiles = new Set();
81
-
82
- // Process all code files (route + model files, deduplicated)
83
74
  const allCodeFiles = [...new Set([...allRouteFiles, ...allModelFiles])];
84
75
 
85
76
  for (const filePath of allCodeFiles) {
@@ -90,45 +81,35 @@ async function runFullSync(config) {
90
81
  if (!content) continue;
91
82
 
92
83
  const basename = path.basename(filePath);
93
- const relPath = path.relative(config.projectRoot, filePath);
84
+ const relPath = path.relative(projectRoot, filePath);
94
85
  const plugin = getPluginForFile(plugins, filePath);
95
86
 
96
- if (!plugin) {
97
- // No plugin for this file type — skip silently
98
- continue;
99
- }
87
+ if (!plugin) continue;
100
88
 
101
89
  const result = plugin.extract(content, relPath);
102
90
 
103
- // Routes
104
91
  allRoutes = allRoutes.concat(result.routes);
105
92
  routeCountMap[filePath] = result.routes.length;
106
93
 
107
- // Models
108
94
  allModels = allModels.concat(result.models);
109
95
 
110
- // Functions
111
96
  if (result.functions.length > 0 && basename !== '__init__.py') {
112
97
  functionsMap[basename] = result.functions;
113
98
  }
114
99
 
115
- // Env vars
116
100
  for (const varName of result.envVars) {
117
101
  if (!envVarMap.has(varName)) envVarMap.set(varName, new Set());
118
102
  envVarMap.get(varName).add(basename);
119
103
  }
120
104
 
121
- // DB tables
122
105
  for (const t of result.dbTables) {
123
106
  dbTableList.push({ tableName: t.tableName, modelName: t.modelName, file: basename });
124
107
  }
125
108
 
126
- // Fetches and storage keys (from JS/HTML plugins)
127
109
  allFetches = allFetches.concat(result.fetches);
128
110
  allStorageKeys = allStorageKeys.concat(result.storageKeys);
129
111
  }
130
112
 
131
- // Process frontend files separately (may overlap with code files)
132
113
  for (const filePath of allFrontendFiles) {
133
114
  if (processedFiles.has(filePath)) continue;
134
115
  processedFiles.add(filePath);
@@ -146,12 +127,10 @@ async function runFullSync(config) {
146
127
  allStorageKeys = allStorageKeys.concat(result.storageKeys);
147
128
  }
148
129
 
149
- // Global dedup: collapse dynamic fetches across all files into one summary row
150
130
  const staticFetches = allFetches.filter(f => f.url !== '[dynamic]' && !f.url.startsWith('dynamic calls detected'));
151
131
  let totalDynamic = 0;
152
132
  for (const f of allFetches) {
153
133
  if (f.url === '[dynamic]') totalDynamic++;
154
- // Also count already-collapsed per-file rows
155
134
  const m = f.url.match(/^dynamic calls detected \((\d+) unresolved\)$/);
156
135
  if (m) totalDynamic += parseInt(m[1], 10);
157
136
  }
@@ -160,7 +139,6 @@ async function runFullSync(config) {
160
139
  }
161
140
  allFetches = staticFetches;
162
141
 
163
- // Global dedup: storage keys across all files
164
142
  const skSeen = new Set();
165
143
  allStorageKeys = allStorageKeys.filter(({ operation, key }) => {
166
144
  const id = `${operation}::${key}`;
@@ -169,10 +147,8 @@ async function runFullSync(config) {
169
147
  return true;
170
148
  });
171
149
 
172
- // Build import graph from all processed files
173
150
  const fileContentsForImports = [];
174
151
  const allProcessedPaths = [...new Set([...allCodeFiles, ...allFrontendFiles])];
175
- // Re-read is avoided — collect during processing. Use a second pass for simplicity.
176
152
  for (const filePath of allProcessedPaths) {
177
153
  try {
178
154
  const content = fs.readFileSync(filePath, 'utf-8');
@@ -181,21 +157,17 @@ async function runFullSync(config) {
181
157
  // skip — already warned during extraction
182
158
  }
183
159
  }
184
- const importGraph = buildImportGraph(fileContentsForImports, config.projectRoot);
160
+ const importGraph = buildImportGraph(fileContentsForImports, projectRoot);
185
161
 
186
- // Detect tech stack from watched files + manifests
187
- const stackItems = buildStackLine(fileContentsForImports, config.projectRoot);
162
+ const stackItems = buildStackLine(fileContentsForImports, projectRoot);
188
163
 
189
- // Compute entry points and high impact files from import graph
190
164
  const allValues = new Set();
191
165
  for (const deps of Object.values(importGraph)) {
192
166
  for (const dep of deps) allValues.add(dep);
193
167
  }
194
- // Entry points: files that import 3+ others but nothing imports them
195
168
  const entryPoints = Object.keys(importGraph)
196
169
  .filter(f => !allValues.has(f) && importGraph[f].length >= 3)
197
170
  .sort();
198
- // High impact: files imported by 3+ others, sorted descending by count
199
171
  const depCount = {};
200
172
  for (const deps of Object.values(importGraph)) {
201
173
  for (const dep of deps) {
@@ -207,11 +179,10 @@ async function runFullSync(config) {
207
179
  .sort((a, b) => b[1] - a[1])
208
180
  .map(([file, count]) => ({ file, count }));
209
181
 
210
- // Build file map
211
182
  const fileMap = [];
212
183
  for (const filePath of allCodeFiles) {
213
184
  const basename = path.basename(filePath);
214
- const relPath = path.relative(config.projectRoot, filePath);
185
+ const relPath = path.relative(projectRoot, filePath);
215
186
  const funcCount = (functionsMap[basename] || []).length;
216
187
  const routeCount = routeCountMap[filePath] || 0;
217
188
  const responsibility = inferResponsibility(basename, funcCount, routeCount);
@@ -220,15 +191,12 @@ async function runFullSync(config) {
220
191
  }
221
192
  }
222
193
 
223
- // Aggregate env vars into sorted array
224
194
  const envVars = [...envVarMap.keys()]
225
195
  .sort()
226
196
  .map(name => ({ name, files: [...envVarMap.get(name)].sort() }));
227
197
 
228
- // Scan project structure
229
- const structure = await scanStructure(config.projectRoot);
198
+ const structure = await scanStructure(projectRoot);
230
199
 
231
- // Validate extracted data — drop anything malformed
232
200
  const validated = validateExtracted({
233
201
  routes: allRoutes,
234
202
  models: allModels,
@@ -255,8 +223,7 @@ async function runFullSync(config) {
255
223
 
256
224
  mergeIntoAgentsMd(config.output, autoContent);
257
225
 
258
- // Save graph to .carto/map.json (atomic write)
259
- const cartoDir = path.join(config.projectRoot, '.carto');
226
+ const cartoDir = path.join(projectRoot, '.carto');
260
227
  const mapData = {
261
228
  version: '1',
262
229
  generated: new Date().toISOString(),