carto-md 2.0.9 → 2.1.0
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 +19 -15
- package/README.md +192 -415
- package/docs/api/README.md +99 -0
- package/docs/api/did_we_discuss_this.md +34 -0
- package/docs/api/dismiss_suggestion.md +34 -0
- package/docs/api/explain_change_in_natural_language.md +33 -0
- package/docs/api/find_consumers_of_api.md +34 -0
- package/docs/api/get_action_patterns.md +32 -0
- package/docs/api/get_active_drift.md +32 -0
- package/docs/api/get_active_suggestions.md +25 -0
- package/docs/api/get_ai_cost_attribution.md +32 -0
- package/docs/api/get_arch_events.md +42 -0
- package/docs/api/get_architectural_drift.md +37 -0
- package/docs/api/get_architecture.md +25 -0
- package/docs/api/get_blast_radius.md +34 -0
- package/docs/api/get_canonical_pattern.md +39 -0
- package/docs/api/get_change_plan.md +34 -0
- package/docs/api/get_change_velocity.md +32 -0
- package/docs/api/get_churn_vs_blast_radius.md +32 -0
- package/docs/api/get_complexity_trend.md +39 -0
- package/docs/api/get_context.md +34 -0
- package/docs/api/get_conventions.md +32 -0
- package/docs/api/get_cross_domain.md +25 -0
- package/docs/api/get_cross_language_call_graph.md +25 -0
- package/docs/api/get_cross_repo_blast_radius.md +34 -0
- package/docs/api/get_cross_team_coupling.md +25 -0
- package/docs/api/get_data_flow.md +33 -0
- package/docs/api/get_dead_code_with_confidence.md +32 -0
- package/docs/api/get_decision_log.md +32 -0
- package/docs/api/get_dependency_surface.md +25 -0
- package/docs/api/get_domain.md +34 -0
- package/docs/api/get_domain_evolution.md +39 -0
- package/docs/api/get_domain_health.md +32 -0
- package/docs/api/get_domains_list.md +25 -0
- package/docs/api/get_drift_digest.md +32 -0
- package/docs/api/get_env_vars.md +32 -0
- package/docs/api/get_evolution_delta.md +36 -0
- package/docs/api/get_file_ownership.md +33 -0
- package/docs/api/get_file_summary.md +34 -0
- package/docs/api/get_high_impact_files.md +32 -0
- package/docs/api/get_hot_in_prod_no_tests.md +34 -0
- package/docs/api/get_hotspot_files.md +37 -0
- package/docs/api/get_iac_resources.md +25 -0
- package/docs/api/get_interface_contract.md +33 -0
- package/docs/api/get_intervention_history.md +32 -0
- package/docs/api/get_invariants.md +37 -0
- package/docs/api/get_llm_enrichment.md +33 -0
- package/docs/api/get_microservice_cut_points.md +32 -0
- package/docs/api/get_microservices_migration_cut_points.md +25 -0
- package/docs/api/get_minimal_context_for_intent.md +39 -0
- package/docs/api/get_models.md +32 -0
- package/docs/api/get_neighbors.md +39 -0
- package/docs/api/get_org_architecture.md +25 -0
- package/docs/api/get_org_domain_mapping.md +25 -0
- package/docs/api/get_pending_decisions.md +32 -0
- package/docs/api/get_predictive_risk.md +32 -0
- package/docs/api/get_progressive_disclosure_tree.md +25 -0
- package/docs/api/get_recent_decisions.md +37 -0
- package/docs/api/get_risk_weighted_blast_radius.md +32 -0
- package/docs/api/get_routes.md +25 -0
- package/docs/api/get_safety_checklist.md +33 -0
- package/docs/api/get_semantic_diff.md +34 -0
- package/docs/api/get_service_boundary_violations.md +25 -0
- package/docs/api/get_service_dependency_graph.md +25 -0
- package/docs/api/get_session_context.md +32 -0
- package/docs/api/get_similar_patterns.md +39 -0
- package/docs/api/get_stale_docs.md +25 -0
- package/docs/api/get_structure.md +25 -0
- package/docs/api/get_temporal_context.md +34 -0
- package/docs/api/get_test_coverage_map.md +25 -0
- package/docs/api/get_token_budget_report.md +36 -0
- package/docs/api/get_upgrade_risk.md +25 -0
- package/docs/api/get_working_memory.md +25 -0
- package/docs/api/ingest_otlp_traces.md +34 -0
- package/docs/api/scaffold_for_intent.md +34 -0
- package/docs/api/search_routes.md +34 -0
- package/docs/api/simulate_change_impact.md +37 -0
- package/docs/api/validate_change.md +39 -0
- package/docs/api/validate_diff.md +39 -0
- package/docs/concepts/anci.md +87 -0
- package/docs/concepts/blast-radius.md +66 -0
- package/docs/concepts/domains.md +91 -0
- package/docs/concepts/import-graph.md +102 -0
- package/docs/concepts/mcp-integration.md +148 -0
- package/docs/guides/adding-feature-safely.md +101 -0
- package/docs/guides/ci-integration.md +175 -0
- package/docs/guides/monorepo-setup.md +121 -0
- package/docs/guides/onboarding-new-engineer.md +121 -0
- package/docs/guides/pre-merge-review.md +139 -0
- package/docs/migration/v1-to-v2.md +110 -0
- package/docs/quickstart.md +95 -0
- package/docs/scale.md +2 -2
- package/docs/troubleshooting.md +180 -0
- package/package.json +10 -4
- package/scripts/gen-api-docs.js +170 -0
- package/src/acp/agent.js +83 -11
- package/src/acp/config.js +64 -0
- package/src/acp/persistence.js +146 -0
- package/src/acp/providers/anthropic.js +179 -27
- package/src/acp/providers/index.js +15 -2
- package/src/acp/providers/openai.js +164 -38
- package/src/acp/providers/sse.js +82 -0
- package/src/acp/safety.js +128 -0
- package/src/acp/session.js +73 -0
- package/src/adjacent/call-graph.js +170 -0
- package/src/adjacent/iac.js +167 -0
- package/src/adjacent/llm-enrich.js +35 -0
- package/src/adjacent/runtime.js +216 -0
- package/src/adjacent/semantic-diff.js +143 -0
- package/src/agents/scan-structure.js +1 -1
- package/src/ai/context-builder.js +215 -0
- package/src/ai/retrieval/lexical.js +122 -0
- package/src/ai/retrieval/rrf.js +121 -0
- package/src/ai/retrieval/semantic.js +35 -0
- package/src/ai/retrieval/structural.js +82 -0
- package/src/ai/tools.js +423 -0
- package/src/anci/emit.js +2 -2
- package/src/bitmap/index.js +1 -1
- package/src/bitmap/tools.js +2 -2
- package/src/brain/conventions/index.js +185 -0
- package/src/brain/index.js +31 -0
- package/src/brain/invariants/index.js +252 -0
- package/src/brain/procedural/index.js +181 -0
- package/src/brain/suggestions/index.js +153 -0
- package/src/brain/working/index.js +170 -0
- package/src/cli/check.js +47 -1
- package/src/cli/diff.js +83 -0
- package/src/cli/doctor.js +270 -0
- package/src/cli/explain.js +61 -0
- package/src/cli/index.js +101 -0
- package/src/cli/init.js +143 -5
- package/src/cli/org.js +172 -0
- package/src/cli/pr-impact.js +59 -2
- package/src/cli/serve.js +1 -1
- package/src/cli/status.js +211 -0
- package/src/cli/sync.js +2 -2
- package/src/cli/temporal.js +188 -0
- package/src/cli/validate.js +201 -0
- package/src/cli/watch.js +4 -4
- package/src/cli/why.js +101 -0
- package/src/extractors/frameworks.js +236 -0
- package/src/extractors/languages/dart.js +138 -0
- package/src/extractors/languages/go.js +11 -1
- package/src/extractors/languages/javascript.js +13 -3
- package/src/extractors/languages/kotlin.js +169 -0
- package/src/extractors/languages/php.js +195 -0
- package/src/extractors/languages/python.js +12 -1
- package/src/extractors/languages/swift.js +140 -0
- package/src/extractors/languages/typescript.js +15 -2
- package/src/extractors/plugin-api.js +102 -0
- package/src/mcp/files-without-tests.js +285 -0
- package/src/mcp/middleware/index.js +451 -0
- package/src/mcp/server.js +2292 -0
- package/src/mcp/validate.js +1 -1
- package/src/org/detect.js +262 -0
- package/src/org/queries.js +144 -0
- package/src/org/store.js +173 -0
- package/src/org/sync.js +106 -0
- package/src/predictive/cut-points.js +83 -0
- package/src/predictive/drift-digest.js +88 -0
- package/src/predictive/ownership.js +145 -0
- package/src/predictive/risk-score.js +121 -0
- package/src/predictive/validate-change.js +55 -0
- package/src/store/store-adapter.js +3 -3
- package/src/store/{sync-v2.js → sync.js} +53 -13
- package/src/temporal/backfill.js +211 -0
- package/src/temporal/delta.js +85 -0
- package/src/temporal/events.js +180 -0
- package/src/temporal/queries.js +358 -0
- package/src/temporal/snapshot.js +151 -0
- package/src/temporal/store.js +400 -0
- package/src/mcp/server-v2.js +0 -986
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Concept: Blast Radius
|
|
2
|
+
|
|
3
|
+
> The set of files affected by changing a given file, computed transitively over the import graph.
|
|
4
|
+
|
|
5
|
+
## The intuition
|
|
6
|
+
|
|
7
|
+
Open `src/lib/db.ts`. Other files `import` from it. Those files have other files that import *them*. And so on. Anything reachable by following reverse-import edges is in the **blast radius** of `db.ts`.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/lib/db.ts
|
|
11
|
+
↑
|
|
12
|
+
src/store/user.ts ← src/api/users.ts
|
|
13
|
+
← src/api/admin.ts
|
|
14
|
+
↑
|
|
15
|
+
src/store/order.ts ← src/api/orders.ts
|
|
16
|
+
← src/jobs/billing.ts
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If you change `db.ts`, six files might break. That's the blast radius.
|
|
20
|
+
|
|
21
|
+
## How carto computes it
|
|
22
|
+
|
|
23
|
+
Two layers:
|
|
24
|
+
|
|
25
|
+
**1. Direct dependents (1 hop).** The import graph stores every `from_file → to_file` edge. The reverse — *who imports me* — is one SQL query: `SELECT from_file_id FROM imports WHERE to_file_id = ?`.
|
|
26
|
+
|
|
27
|
+
**2. Transitive dependents (≤5 hops).** From the seed file, BFS over the reverse-import graph. Carto caps at 5 hops because diminishing returns: 99% of real change impact lives in the first 3 hops, hops 4–5 catch the outliers, and beyond that you're paying memory for noise.
|
|
28
|
+
|
|
29
|
+
The bitmap engine makes this fast. A 1M-edge graph fits in a few MB of Roaring Bitmaps; one transitive query is a chain of `bitmap_OR` operations executed in SIMD at ~1 cycle per 512 bits. On the vscode repo (7,567 files, 13,335 edges) a 5-hop blast-radius query returns in ~3 µs.
|
|
30
|
+
|
|
31
|
+
## What you do with it
|
|
32
|
+
|
|
33
|
+
Three places it lands:
|
|
34
|
+
|
|
35
|
+
- **`carto impact <file>`** prints the radius for a single file
|
|
36
|
+
- **`get_blast_radius(file)`** MCP tool gives the AI the same data
|
|
37
|
+
- **`validate_diff(patch)`** uses blast radius to assign a risk grade to a PR
|
|
38
|
+
|
|
39
|
+
For the last one, the rule of thumb is:
|
|
40
|
+
|
|
41
|
+
| Direct dependents | Risk (default thresholds) |
|
|
42
|
+
|------------------:|---------------------------|
|
|
43
|
+
| 0–2 | SAFE |
|
|
44
|
+
| 3–20 | LOW |
|
|
45
|
+
| 21–50 | MEDIUM |
|
|
46
|
+
| 51+ | HIGH |
|
|
47
|
+
|
|
48
|
+
These thresholds are tunable per project via `carto.config.json`. Cross-domain edges (e.g. AUTH importing PAYMENTS) escalate independently — see [`domains.md`](./domains.md).
|
|
49
|
+
|
|
50
|
+
## What it isn't
|
|
51
|
+
|
|
52
|
+
- **Not a behavioral analysis.** Blast radius doesn't tell you which dependents will actually break, just which ones *might*. The AI uses this as a pre-filter, not as a verdict.
|
|
53
|
+
- **Not call-graph reachability.** It's import-graph reachability. If A imports B but never calls anything from it (dead import), B is still in A's blast radius. We don't try to model dynamic dispatch or runtime imports.
|
|
54
|
+
- **Not a refactoring oracle.** A 50-dependent file isn't automatically bad. Some files (utility modules, type definitions) *should* be widely depended on. Blast radius is a signal, not a sentence.
|
|
55
|
+
|
|
56
|
+
## Why the bitmap version matters
|
|
57
|
+
|
|
58
|
+
The original SQLite-only implementation worked fine to ~10K files but slowed down rapidly past that — each transitive query is `O(hops × dependents × indexed-lookup)` which adds up. Bitmap queries are `O(hops × bitmap-width)` regardless of how many dependents each file has. On 1M-file synthetic graphs the bitmap path returns in microseconds where SQLite takes seconds.
|
|
59
|
+
|
|
60
|
+
See [`docs/scale.md`](../scale.md) for the per-tool latency table at synthetic scale and on real-world repos.
|
|
61
|
+
|
|
62
|
+
## Related
|
|
63
|
+
|
|
64
|
+
- [`get_blast_radius`](./mcp-integration.md#get_blast_radius) MCP tool reference
|
|
65
|
+
- [`simulate_change_impact`](./mcp-integration.md#simulate_change_impact) — union of the blast radius for multiple files
|
|
66
|
+
- [`validate_diff`](./mcp-integration.md#validate_diff) — diff-shaped risk grade built on blast radius
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Concept: Domains
|
|
2
|
+
|
|
3
|
+
> Auto-detected clusters of tightly-coupled files. Carto's "AUTH", "PAYMENTS", "DATABASE" — without you writing a config.
|
|
4
|
+
|
|
5
|
+
## The intuition
|
|
6
|
+
|
|
7
|
+
Files that import each other heavily belong together. Files that don't, don't. Cluster the import graph and you get a set of communities — those communities are the codebase's *domains*.
|
|
8
|
+
|
|
9
|
+
Carto runs **Leiden+CPM** (Constant Potts Model) on the import graph. It's the same family of community-detection algorithms used in social-network analysis. The output: each file gets a domain id; the bitmap engine indexes by it; everything downstream (cross-domain checks, the impact report, the validation API) reads off this.
|
|
10
|
+
|
|
11
|
+
## Why not just regex on paths?
|
|
12
|
+
|
|
13
|
+
Path-based detection works for greenfield projects with a clean `src/auth/`, `src/payments/` layout. It collapses on:
|
|
14
|
+
|
|
15
|
+
- Monorepos where every package has its own `auth/` subdir
|
|
16
|
+
- Legacy projects where the directory structure doesn't match the actual coupling (auth code mysteriously lives in `lib/middleware/`)
|
|
17
|
+
- Frameworks that hide the structure (Next.js routes spread across `pages/`, `app/`, `api/`)
|
|
18
|
+
|
|
19
|
+
Graph clustering is path-agnostic. It looks at who imports whom and produces named communities whose names are derived from the path tokens *most common in that cluster*. That's the magic — the cluster is found by structure, the *name* is found by frequency on the actual file paths in it.
|
|
20
|
+
|
|
21
|
+
## Naming heuristics
|
|
22
|
+
|
|
23
|
+
After clustering, Carto picks a name for each cluster:
|
|
24
|
+
|
|
25
|
+
1. Tokenize each file path: `src/auth/login.ts` → `[src, auth, login]`
|
|
26
|
+
2. Drop stopwords (`src`, `lib`, `app`, `index`, `utils`, `helpers`)
|
|
27
|
+
3. Cross-reference with a small built-in dictionary of well-known domain hints: `AUTH`, `PAYMENTS`, `DATABASE`, `NOTIFICATIONS`, `EVENTS`, `TRPC`, `CORE`
|
|
28
|
+
4. Most-frequent matching token wins. Ties broken by dictionary priority (security-relevant domains first).
|
|
29
|
+
5. If no token matches the dictionary, the most-common path token becomes the domain name (uppercased).
|
|
30
|
+
|
|
31
|
+
The result: real repos light up with sensible labels — vscode gets `EXTENSIONS`, `EVENTS`, `DATABASE`, `CLI`, `CORE`; zed (Rust) gets `DATABASE`, `AUTH`, `EVENTS`, `CORE`. A game engine would get `RENDERER`, `PHYSICS`, `AUDIO` automatically because those tokens dominate the path namespace.
|
|
32
|
+
|
|
33
|
+
## Adaptive gamma
|
|
34
|
+
|
|
35
|
+
The Leiden+CPM algorithm has a resolution parameter γ (gamma). High γ → many small clusters. Low γ → few big clusters. Carto picks γ based on repo size:
|
|
36
|
+
|
|
37
|
+
- **<100 files:** keyword-only fallback (graph clustering over-fragments tiny repos)
|
|
38
|
+
- **100–1,000 files:** γ = 0.04
|
|
39
|
+
- **1,000–10,000 files:** γ scales linearly to 0.02
|
|
40
|
+
- **>10,000 files:** γ = 0.015 (large repos have dense graphs; tighter resolution prevents one giant component)
|
|
41
|
+
|
|
42
|
+
These constants come from validation on 12 real repos. See `src/store/sync.js` for the exact formula.
|
|
43
|
+
|
|
44
|
+
## Custom domains via `carto.config.json`
|
|
45
|
+
|
|
46
|
+
Override the auto-detection if your repo needs it. Two shapes:
|
|
47
|
+
|
|
48
|
+
**Simple — keyword hints:**
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"domains": {
|
|
53
|
+
"EDITOR": ["editor", "monaco", "text"],
|
|
54
|
+
"WORKBENCH": ["workbench", "layout", "panel"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Files whose path tokens match the hints get pulled into the named domain even if the graph clusterer would have put them elsewhere.
|
|
60
|
+
|
|
61
|
+
**Full — keywords + anchors:**
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"domains": {
|
|
66
|
+
"AUTH": {
|
|
67
|
+
"keywords": ["auth", "login", "session"],
|
|
68
|
+
"anchor": ["src/auth/session.ts", "src/auth/middleware.ts"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Anchor files are *pinned*. They always end up in their named domain regardless of clustering. Useful when you've got a deliberately central auth file that the graph clusterer would otherwise lump into CORE.
|
|
75
|
+
|
|
76
|
+
## Stability across syncs
|
|
77
|
+
|
|
78
|
+
Re-clustering after a code change can shuffle domain assignments. Carto tracks the previous mapping and warns when >5% of files change domain between syncs (`carto check` surfaces it as "unstable clustering"). Anchor files give you a way to force stability where it matters.
|
|
79
|
+
|
|
80
|
+
## Where domains show up
|
|
81
|
+
|
|
82
|
+
- **`AGENTS.md`** lists them with file counts
|
|
83
|
+
- **`.carto/context/<DOMAIN>.md`** has per-domain context the AI reads when working in that area
|
|
84
|
+
- **`get_domain(name)`** MCP tool returns the full list of files, routes, models in a domain
|
|
85
|
+
- **`validate_diff`** uses cross-domain edges as a violation signal — adding `auth/login.ts → payments/billing.ts` is HIGH-risk because it crosses a sensitive boundary
|
|
86
|
+
|
|
87
|
+
## Related
|
|
88
|
+
|
|
89
|
+
- [`get_cross_domain`](./mcp-integration.md#get_cross_domain) — every import edge that crosses a domain
|
|
90
|
+
- [`carto check`](../../README.md#cli-commands) — flags new cross-domain violations
|
|
91
|
+
- [Leiden algorithm paper](https://www.nature.com/articles/s41598-019-41695-z) — the algorithm Carto uses
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Concept: Import Graph
|
|
2
|
+
|
|
3
|
+
> The foundation. Every other Carto feature is computed from this one structure.
|
|
4
|
+
|
|
5
|
+
## What's in it
|
|
6
|
+
|
|
7
|
+
For each source file Carto parses, it records:
|
|
8
|
+
|
|
9
|
+
| Source field | Meaning |
|
|
10
|
+
|--------------|---------|
|
|
11
|
+
| `id` | Stable integer (monotonic per project) |
|
|
12
|
+
| `path` | Project-relative, forward-slash-normalized |
|
|
13
|
+
| `language` | `javascript`, `typescript`, `python`, `go`, `rust`, `java`, `cpp`, `csharp`, `ruby`, `r`, `prisma` |
|
|
14
|
+
| `hash` | SHA-256 of content (mtime+size cache uses this) |
|
|
15
|
+
| `is_entry_point` | True for top-level CLIs, `main.*` files, Next.js page entries |
|
|
16
|
+
|
|
17
|
+
And for each import the parser finds:
|
|
18
|
+
|
|
19
|
+
| Edge field | Meaning |
|
|
20
|
+
|------------|---------|
|
|
21
|
+
| `from_file_id` | The importer |
|
|
22
|
+
| `to_file_id` | The importee (null if unresolved — e.g. a bare module name like `express`) |
|
|
23
|
+
| `to_path` | The raw import specifier |
|
|
24
|
+
| `resolved` | Boolean — did we successfully find the target in the index? |
|
|
25
|
+
|
|
26
|
+
## What parser does what
|
|
27
|
+
|
|
28
|
+
Two-layer extraction:
|
|
29
|
+
|
|
30
|
+
**1. Tree-sitter** (per-language plugin in `src/extractors/languages/`). Parses the file into a syntax tree, walks the tree, pulls out:
|
|
31
|
+
|
|
32
|
+
- import statements (`import x from`, `from x import`, `use crate::x`, `using A.B;`, `require 'x'`, `#include "x"`)
|
|
33
|
+
- exported symbols
|
|
34
|
+
- type definitions (interfaces, classes, structs)
|
|
35
|
+
|
|
36
|
+
For supported grammars, this is ~0.05–0.2 ms/file.
|
|
37
|
+
|
|
38
|
+
**2. Babel deep-parse** (TypeScript/JavaScript only, opt-in for route+model files). Used when the framework-specific extractor needs more than tree-sitter can give — e.g. tRPC procedures, React Router routes, Zod schemas, Prisma models. Slower (~5–20 ms/file) but reserved for the small subset of files where it matters.
|
|
39
|
+
|
|
40
|
+
## Import resolution
|
|
41
|
+
|
|
42
|
+
Local imports (`./foo`, `../utils`) are resolved relative to the importing file's directory, then tried against the index:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
./foo → src/lib/foo.ts ?
|
|
46
|
+
→ src/lib/foo.js ?
|
|
47
|
+
→ src/lib/foo/index.ts ?
|
|
48
|
+
→ src/lib/foo/index.js ?
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The first hit wins. TypeScript `paths` aliases (`@/components/Button`) are read from `tsconfig.json` and applied before the relative search — so `@/components/Button` resolves to `src/components/Button.tsx` if that's what the alias maps to.
|
|
52
|
+
|
|
53
|
+
Bare module names (`express`, `react`, `lodash`) are *intentionally* not resolved. They're external; Carto's job is to know the *internal* structure.
|
|
54
|
+
|
|
55
|
+
## How it's stored
|
|
56
|
+
|
|
57
|
+
SQLite tables `files` + `imports`. One row per file, one row per edge. Indexes on `(from_file_id)`, `(to_file_id)`, `(path)`.
|
|
58
|
+
|
|
59
|
+
Plus a derived bitmap layer in `.carto/bitmap.bin`:
|
|
60
|
+
|
|
61
|
+
- `forward[i]` = bitmap of files that file `i` imports (1-hop)
|
|
62
|
+
- `reverse[i]` = bitmap of files that import file `i` (1-hop)
|
|
63
|
+
- `popcountIndex` = files sorted by transitive 5-hop dependent count, desc
|
|
64
|
+
|
|
65
|
+
The bitmap layer is the read path. The SQLite tables are the write path + source of truth. The bitmap is rebuilt from SQLite on first sync after upgrade.
|
|
66
|
+
|
|
67
|
+
## Path-alias support
|
|
68
|
+
|
|
69
|
+
Carto reads `paths` from:
|
|
70
|
+
|
|
71
|
+
- `tsconfig.json` → alias resolution for `.ts/.tsx`
|
|
72
|
+
- `jsconfig.json` → alias resolution for `.js/.jsx`
|
|
73
|
+
|
|
74
|
+
It does *not* currently read:
|
|
75
|
+
|
|
76
|
+
- `webpack.config.js` `resolve.alias` (too many variants — convert to tsconfig if you need it indexed)
|
|
77
|
+
- `vite.config.{js,ts}` `resolve.alias` (same)
|
|
78
|
+
- `next.config.{js,mjs}` `experimental.transpilePackages` (only affects bundling, not module resolution)
|
|
79
|
+
|
|
80
|
+
If you have a path alias that's not being resolved, declare it in `tsconfig.json#paths` even if you don't use TypeScript. Carto reads it; your runtime ignores it.
|
|
81
|
+
|
|
82
|
+
## Edge cases we handle
|
|
83
|
+
|
|
84
|
+
- **Type-only imports** (`import type { X }`) — counted as edges. They're real dependencies; if the type changes, the importer needs to update.
|
|
85
|
+
- **Dynamic imports** (`import('./foo')`) — captured when the path argument is a string literal. Computed values are not resolved (no symbolic execution).
|
|
86
|
+
- **Conditional imports** (inside `if (process.env.X)`) — captured statically; we don't try to evaluate the condition.
|
|
87
|
+
- **Barrel re-exports** (`export * from './foo'`) — captured as edges, but the barrel itself doesn't get flagged as a "consumer" (it's just a pass-through).
|
|
88
|
+
- **CommonJS `require()`** — captured for `.js/.cjs` files.
|
|
89
|
+
- **Python `from .x import y`** — relative imports resolved against the package layout. Was a bug pre-2.0.7; fixed in Spec 7.
|
|
90
|
+
|
|
91
|
+
## Edge cases we don't handle
|
|
92
|
+
|
|
93
|
+
- **Monkey-patching / runtime mutation** — no.
|
|
94
|
+
- **Reflection-based loading** (Python `importlib`, Java `Class.forName`) — no.
|
|
95
|
+
- **Cross-language imports** (TS frontend HTTP-calling a Python backend route) — handled by route extraction, not the import graph. See [`get_change_plan`](./mcp-integration.md#get_change_plan).
|
|
96
|
+
|
|
97
|
+
## What you'd want to look at
|
|
98
|
+
|
|
99
|
+
- `carto inspect` — shows file count, edge count, schema version
|
|
100
|
+
- `get_neighbors(file, hops)` MCP tool — exact 1- or 2-hop import neighbors of a single file
|
|
101
|
+
- `get_high_impact_files(n)` MCP tool — top-N by transitive dependent count
|
|
102
|
+
- `.carto/anci.bin` — the binary serialization of the whole graph; consumable by any AI tool via the `loadAnci()` library
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Concept: MCP Integration
|
|
2
|
+
|
|
3
|
+
> The protocol layer that makes Carto's index visible to your AI tool.
|
|
4
|
+
|
|
5
|
+
## What MCP is
|
|
6
|
+
|
|
7
|
+
The [Model Context Protocol](https://modelcontextprotocol.io) is an open protocol Anthropic introduced in late 2024 for connecting LLMs to external tools and data. An MCP **server** exposes tools and resources; an MCP **client** (Cursor, Claude Desktop, Kiro, Claude Code, Cline, Continue, Windsurf, Zed) calls them on behalf of the model.
|
|
8
|
+
|
|
9
|
+
Carto runs an MCP server in stdio mode. You start it with `carto serve`, but you almost never do that by hand — your AI tool spawns it.
|
|
10
|
+
|
|
11
|
+
## What `carto init` writes
|
|
12
|
+
|
|
13
|
+
For each AI tool it detects on your machine, init writes the appropriate MCP config:
|
|
14
|
+
|
|
15
|
+
| Tool | Config file | Format |
|
|
16
|
+
|------|-------------|--------|
|
|
17
|
+
| Cursor | `~/.cursor/mcp.json` | `{ mcpServers: { carto: ... } }` |
|
|
18
|
+
| Claude Code | `<project>/.mcp.json` | same |
|
|
19
|
+
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` | same (cross-platform paths in README) |
|
|
20
|
+
| Kiro | `~/.kiro/settings/mcp.json` | same |
|
|
21
|
+
| Codex CLI | `~/.codex/config.toml` | `[mcp_servers.carto]` |
|
|
22
|
+
| VS Code Copilot | `<project>/.vscode/mcp.json` | `{ servers: { carto: { type: "stdio", ... } } }` |
|
|
23
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | `{ mcpServers: { carto: ... } }` |
|
|
24
|
+
|
|
25
|
+
Each config tells the tool: *"when the user starts a chat, run `carto serve` as a child process and route MCP traffic to it"*.
|
|
26
|
+
|
|
27
|
+
## The ~76 tools Carto exposes
|
|
28
|
+
|
|
29
|
+
Grouped by what they answer. Categories below match the [`docs/api/`](../api/) layout — that directory is the full per-tool reference (auto-generated from `src/mcp/server.js`). The picks below are the high-impact ones; your AI tool picks whichever one fits the question mid-task.
|
|
30
|
+
|
|
31
|
+
### Core graph (16) — project shape, blast radius, change planning
|
|
32
|
+
|
|
33
|
+
- **`get_architecture()`** — 500-word overview. The right *first* call for any new task.
|
|
34
|
+
- **`get_blast_radius(file)`** — every file affected by changing this one, with hop distance.
|
|
35
|
+
- **`get_change_plan(intent)`** — natural-language intent → files to touch, affected domains, similar patterns.
|
|
36
|
+
- **`simulate_change_impact(files)`** — union of the blast radius for *multiple* files at once. The bitmap engine is what makes this feasible at scale.
|
|
37
|
+
- **`get_high_impact_files(n)`** — top N files by transitive dependents.
|
|
38
|
+
- **`get_cross_domain()`** — every import edge that crosses a domain boundary.
|
|
39
|
+
|
|
40
|
+
Plus `get_structure`, `get_context`, `get_neighbors`, `get_domain`, `get_domains_list`, `get_routes`, `search_routes`, `get_models`, `get_env_vars`, `get_file_summary`, `get_similar_patterns`.
|
|
41
|
+
|
|
42
|
+
### Episodic memory (5) — what your AI decided last week
|
|
43
|
+
|
|
44
|
+
- **`validate_diff(diff)`** — given a unified diff: violations, blast radius, risk grade, suggestions. Sub-15ms p99. **Every call writes a row to the decision log** — the rest of this category reads from it.
|
|
45
|
+
- **`did_we_discuss_this(topic)`** — substring search over the decision log. Six-week recall, conversational.
|
|
46
|
+
- **`get_recent_decisions(time_range, kind?)`**, **`get_session_context(session_id?)`**, **`get_intervention_history(file?)`** — the underlying log surfaced different ways.
|
|
47
|
+
|
|
48
|
+
### Temporal (9) — how the architecture changed over time
|
|
49
|
+
|
|
50
|
+
- **`get_architectural_drift()`** — per-domain growth/shrink + event count over a window.
|
|
51
|
+
- **`get_hotspot_files()`** — top files by churn × blast_radius (the CodeHealth heuristic).
|
|
52
|
+
- **`get_domain_evolution(domain)`** — time-series of one domain's file count by snapshot.
|
|
53
|
+
|
|
54
|
+
Plus `get_arch_events`, `get_temporal_context`, `get_change_velocity`, `get_complexity_trend`, `get_churn_vs_blast_radius`, `get_domain_health`.
|
|
55
|
+
|
|
56
|
+
### Brain (10) — invariants, conventions, action patterns
|
|
57
|
+
|
|
58
|
+
Carto's "rules of the codebase" — mined from the import graph and git history, not written by hand.
|
|
59
|
+
|
|
60
|
+
- **`get_invariants(domain?, threshold?)`** — confidence-scored architectural rules: *"AUTH never imports from PAYMENTS"*.
|
|
61
|
+
- **`get_conventions(file?)`** — naming, export, and directory conventions that apply to a given location.
|
|
62
|
+
- **`get_action_patterns(intent?)`** — *"when developers add X, they also touch Y"* — procedural patterns from git history.
|
|
63
|
+
- **`scaffold_for_intent(intent)`** — anchor file + co-changed files + canonical pattern + conventions.
|
|
64
|
+
|
|
65
|
+
Plus `get_canonical_pattern`, `get_working_memory`, `get_active_suggestions`, `get_pending_decisions`, `get_active_drift`, `dismiss_suggestion`.
|
|
66
|
+
|
|
67
|
+
### Predictive (7) — P(file causes the next bug)
|
|
68
|
+
|
|
69
|
+
- **`get_predictive_risk(file?)`** — 0–1 score per file combining blast radius, churn, cross-domain coupling, prior interventions, and test coverage.
|
|
70
|
+
- **`get_safety_checklist(file)`** — per-file pre-edit safety summary.
|
|
71
|
+
- **`validate_change(file, content)`** — pre-write governance: diffs `content` vs disk then runs `validate_diff`.
|
|
72
|
+
- **`get_drift_digest(time_range)`** — weekly architectural digest in CLI-renderable markdown.
|
|
73
|
+
|
|
74
|
+
Plus `get_microservice_cut_points`, `get_file_ownership`, `get_cross_team_coupling`, `get_ai_cost_attribution`.
|
|
75
|
+
|
|
76
|
+
### AI-native retrieval (14) — context that fits the budget
|
|
77
|
+
|
|
78
|
+
- **`get_minimal_context_for_intent(intent, budget_tokens)`** — token-budgeted hybrid retrieval (structural + lexical + semantic, fused with RRF, boosted by same-domain and recent-churn). Usually returns 6–12 files instead of the usual 40+.
|
|
79
|
+
- **`get_progressive_disclosure_tree()`** — domain → top files → exports, structured for an LLM to drill into.
|
|
80
|
+
- **`get_token_budget_report(intent, budget)`** — diagnostic complement: which files would be picked and at what cost.
|
|
81
|
+
|
|
82
|
+
Plus `get_data_flow`, `get_interface_contract`, `get_dependency_surface`, `get_upgrade_risk`, `get_test_coverage_map`, `get_stale_docs`, `get_decision_log`, `get_evolution_delta`, `get_change_velocity`, `explain_change_in_natural_language`.
|
|
83
|
+
|
|
84
|
+
### Adjacent (8) — runtime, IaC, cross-language, dead code
|
|
85
|
+
|
|
86
|
+
- **`get_cross_language_call_graph()`** — match frontend `fetch()` to backend route handlers across language boundaries.
|
|
87
|
+
- **`get_iac_resources()`** — Terraform / Helm / Pulumi / CDK resources discovered in the repo.
|
|
88
|
+
- **`ingest_otlp_traces(path)`** + **`get_risk_weighted_blast_radius(otlp?)`** — combine static dependents with runtime call counts.
|
|
89
|
+
- **`get_dead_code_with_confidence(otlp?)`** — zero static dependents AND zero runtime hits → the safe-to-delete list.
|
|
90
|
+
- **`get_semantic_diff(diff)`** — rename + symbol-relocation detection beyond line-level.
|
|
91
|
+
|
|
92
|
+
Plus `get_hot_in_prod_no_tests`, `get_llm_enrichment`.
|
|
93
|
+
|
|
94
|
+
### Org / multi-repo (7) — service graphs across repos
|
|
95
|
+
|
|
96
|
+
- **`get_org_architecture()`** — org-wide summary across registered repos.
|
|
97
|
+
- **`get_cross_repo_blast_radius(repo)`** — *"if I break repo X, who notices?"*
|
|
98
|
+
- **`find_consumers_of_api(target)`** — every file importing a given npm/pypi/go/maven package across the org.
|
|
99
|
+
- **`get_service_dependency_graph()`**, **`get_service_boundary_violations()`** — service-shape views.
|
|
100
|
+
|
|
101
|
+
Plus `get_org_domain_mapping`, `get_microservices_migration_cut_points`.
|
|
102
|
+
|
|
103
|
+
> Full per-tool API reference at [`docs/api/`](../api/). You don't memorize these — your AI picks the right one mid-task.
|
|
104
|
+
|
|
105
|
+
## How the AI knows when to call which tool
|
|
106
|
+
|
|
107
|
+
The model reads the tool descriptions Carto provides in the `tools/list` response. Each description is one sentence written so the model has enough context to choose. Examples:
|
|
108
|
+
|
|
109
|
+
> *"Get a 500-word project overview: domains, entry points, stack, key patterns. **Use this first.**"*
|
|
110
|
+
|
|
111
|
+
> *"Given a unified diff: violations (cross-domain imports, high-blast files), blast radius per file, risk level (SAFE/LOW/MEDIUM/HIGH), suggestions. Each call is recorded in the episodic memory log so other tools can ask 'did we discuss this?'. Sub-15ms p99."*
|
|
112
|
+
|
|
113
|
+
The "Use this first" hint matters. The model orients itself with `get_architecture()`, then narrows to specific tools based on what the user asked.
|
|
114
|
+
|
|
115
|
+
## Lazy re-parse on stale files
|
|
116
|
+
|
|
117
|
+
When the model asks about `src/lib/db.ts` and you've edited it since the last `carto sync`, the MCP server stats the file, sees the mtime is newer than the DB row, and re-parses it inline (~50 ms). The model gets a fresh answer; you didn't think about freshness at all.
|
|
118
|
+
|
|
119
|
+
That's why Carto doesn't need a daemon. Git hooks handle the "I just committed" case; lazy re-parse handles the "I edited but didn't commit yet" case. Together they cover ~100% of real usage.
|
|
120
|
+
|
|
121
|
+
## Wiring it manually
|
|
122
|
+
|
|
123
|
+
If `carto init` didn't detect your AI tool, the README's [Manual MCP wiring](../../README.md) section (collapsed `<details>` block right after the install snippet) has copy-paste snippets for every supported client. The shape is always:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"mcpServers": {
|
|
128
|
+
"carto": {
|
|
129
|
+
"command": "carto",
|
|
130
|
+
"args": ["serve"],
|
|
131
|
+
"cwd": "/path/to/your/project"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Restart the tool, and it'll show carto in its MCP server list.
|
|
138
|
+
|
|
139
|
+
## Debugging
|
|
140
|
+
|
|
141
|
+
- `carto doctor` runs a sanity check on Node version, native modules, the index, git hooks, and the *presence* of MCP config files (it doesn't validate their content — that varies per tool).
|
|
142
|
+
- Set `CARTO_DEBUG=1` before launching your AI tool to make the MCP server log every tool call to stderr.
|
|
143
|
+
- Read [`troubleshooting.md`](../troubleshooting.md) for known gotchas.
|
|
144
|
+
|
|
145
|
+
## Related
|
|
146
|
+
|
|
147
|
+
- [Model Context Protocol spec](https://modelcontextprotocol.io)
|
|
148
|
+
- [`anci.md`](./anci.md) — the file-format Carto exports for tools that prefer reading a static file over speaking MCP
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Guide: Adding a feature safely
|
|
2
|
+
|
|
3
|
+
> The workflow Carto makes possible. Add a feature without breaking five other files you didn't know existed.
|
|
4
|
+
|
|
5
|
+
## The before-Carto pattern
|
|
6
|
+
|
|
7
|
+
You're asked to *"add rate limiting to /api/users"*. You ask Cursor. Cursor:
|
|
8
|
+
|
|
9
|
+
1. Greps for `users` — finds 47 matches across 12 files
|
|
10
|
+
2. Picks the first one that looks like a route handler
|
|
11
|
+
3. Adds rate-limiting middleware to it
|
|
12
|
+
4. Confidently announces success
|
|
13
|
+
|
|
14
|
+
What got missed:
|
|
15
|
+
|
|
16
|
+
- The middleware was already declared in `src/auth/middleware.ts` — now you have two copies
|
|
17
|
+
- The route handler is one of three for `/api/users` (GET, POST, PATCH) — the AI updated GET only
|
|
18
|
+
- Three callers in tests still pass requests through the un-rate-limited path
|
|
19
|
+
|
|
20
|
+
You merge it. CI passes (tests didn't cover the new middleware). It ships. The next time someone hits POST /api/users from production they're not rate-limited and the AbusePrevention engineer wakes up at 2 AM.
|
|
21
|
+
|
|
22
|
+
## The with-Carto pattern
|
|
23
|
+
|
|
24
|
+
Same task. Open your AI tool — which is now wired to carto MCP — and ask the same question.
|
|
25
|
+
|
|
26
|
+
The AI calls `get_change_plan("add rate limiting to /api/users")`. Carto's response:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
## Plan: add rate limiting to /api/users
|
|
30
|
+
|
|
31
|
+
### Relevant routes
|
|
32
|
+
- POST /api/users src/api/users.ts:42 (no rate limit)
|
|
33
|
+
- GET /api/users src/api/users.ts:18 (no rate limit)
|
|
34
|
+
- PATCH /api/users src/api/users.ts:67 (no rate limit)
|
|
35
|
+
|
|
36
|
+
### Files to touch
|
|
37
|
+
- src/api/users.ts — primary (modify 3 route handlers)
|
|
38
|
+
- src/auth/middleware.ts — rate-limit middleware already exists here, reuse it
|
|
39
|
+
|
|
40
|
+
### Similar patterns
|
|
41
|
+
- src/api/posts.ts — already uses rateLimit() middleware (12 callers)
|
|
42
|
+
- src/api/billing.ts — already uses rateLimit() middleware (4 callers)
|
|
43
|
+
|
|
44
|
+
### Blast radius
|
|
45
|
+
- src/api/users.ts has 8 direct dependents
|
|
46
|
+
- src/auth/middleware.ts has 23 transitive dependents — changes here
|
|
47
|
+
ripple wide; do not modify the middleware signature
|
|
48
|
+
|
|
49
|
+
### Cross-domain check
|
|
50
|
+
- No new cross-domain edges introduced by reusing existing middleware
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The AI now has every fact it needs. It writes the change correctly the first time.
|
|
54
|
+
|
|
55
|
+
## The literal step-by-step
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 1. Ask carto first
|
|
59
|
+
carto explain "add rate limiting to /api/users"
|
|
60
|
+
# (or do this implicitly via your AI tool — same call under the hood)
|
|
61
|
+
|
|
62
|
+
# 2. Read the existing pattern
|
|
63
|
+
carto why src/api/posts.ts # see how rate limiting is already used
|
|
64
|
+
|
|
65
|
+
# 3. Make the change in your AI tool
|
|
66
|
+
# (the AI references the plan from step 1)
|
|
67
|
+
|
|
68
|
+
# 4. Before committing, validate
|
|
69
|
+
git diff | carto validate --fail-on HIGH
|
|
70
|
+
# Exits 2 if the diff would be HIGH-risk → blocks the commit
|
|
71
|
+
|
|
72
|
+
# 5. Final check
|
|
73
|
+
carto diff
|
|
74
|
+
# Markdown impact report against HEAD
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If you're using the [GitHub Action](./ci-integration.md), step 4 happens automatically on the PR. The local `carto validate` step is for catching issues earlier.
|
|
78
|
+
|
|
79
|
+
## What "safely" actually means
|
|
80
|
+
|
|
81
|
+
Three signals Carto surfaces that you wouldn't see otherwise:
|
|
82
|
+
|
|
83
|
+
1. **The right files.** `get_change_plan` ranks files by relevance to intent (IDF token scoring + route anchoring + graph expansion). The model's hit rate jumps from "pick the most plausible from 47 grep hits" to "use the 2–3 files Carto identified".
|
|
84
|
+
|
|
85
|
+
2. **Existing patterns.** `get_similar_patterns` finds files that already solve the same shape of problem in your codebase. The model copies your conventions instead of inventing new ones.
|
|
86
|
+
|
|
87
|
+
3. **The boundary it's about to cross.** If reusing the middleware works, no new cross-domain edge. If the AI tries to import auth utilities into the payments domain, `validate_diff` catches it before save.
|
|
88
|
+
|
|
89
|
+
## When to override
|
|
90
|
+
|
|
91
|
+
Carto's heuristics are statistical. Sometimes the right answer involves crossing a domain boundary, or modifying a high-blast-radius file. Don't refuse — *acknowledge*.
|
|
92
|
+
|
|
93
|
+
The MCP `did_we_discuss_this("auth-to-payments coupling")` tool lets the AI check whether the decision has already been made. If yes, it proceeds with cited context. If no, it asks you, then records the decision so future sessions don't re-litigate.
|
|
94
|
+
|
|
95
|
+
This is the episodic-memory layer in action: not a gate, but a continuity.
|
|
96
|
+
|
|
97
|
+
## Related
|
|
98
|
+
|
|
99
|
+
- [`docs/concepts/blast-radius.md`](../concepts/blast-radius.md)
|
|
100
|
+
- [`docs/concepts/domains.md`](../concepts/domains.md) (the cross-domain check)
|
|
101
|
+
- [`docs/guides/pre-merge-review.md`](./pre-merge-review.md) (the CI-gated version)
|