carto-md 2.0.8 → 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.
Files changed (182) hide show
  1. package/CONTRIBUTING.md +19 -15
  2. package/README.md +203 -286
  3. package/docs/anci/v0.1-DRAFT.md +420 -0
  4. package/docs/api/README.md +99 -0
  5. package/docs/api/did_we_discuss_this.md +34 -0
  6. package/docs/api/dismiss_suggestion.md +34 -0
  7. package/docs/api/explain_change_in_natural_language.md +33 -0
  8. package/docs/api/find_consumers_of_api.md +34 -0
  9. package/docs/api/get_action_patterns.md +32 -0
  10. package/docs/api/get_active_drift.md +32 -0
  11. package/docs/api/get_active_suggestions.md +25 -0
  12. package/docs/api/get_ai_cost_attribution.md +32 -0
  13. package/docs/api/get_arch_events.md +42 -0
  14. package/docs/api/get_architectural_drift.md +37 -0
  15. package/docs/api/get_architecture.md +25 -0
  16. package/docs/api/get_blast_radius.md +34 -0
  17. package/docs/api/get_canonical_pattern.md +39 -0
  18. package/docs/api/get_change_plan.md +34 -0
  19. package/docs/api/get_change_velocity.md +32 -0
  20. package/docs/api/get_churn_vs_blast_radius.md +32 -0
  21. package/docs/api/get_complexity_trend.md +39 -0
  22. package/docs/api/get_context.md +34 -0
  23. package/docs/api/get_conventions.md +32 -0
  24. package/docs/api/get_cross_domain.md +25 -0
  25. package/docs/api/get_cross_language_call_graph.md +25 -0
  26. package/docs/api/get_cross_repo_blast_radius.md +34 -0
  27. package/docs/api/get_cross_team_coupling.md +25 -0
  28. package/docs/api/get_data_flow.md +33 -0
  29. package/docs/api/get_dead_code_with_confidence.md +32 -0
  30. package/docs/api/get_decision_log.md +32 -0
  31. package/docs/api/get_dependency_surface.md +25 -0
  32. package/docs/api/get_domain.md +34 -0
  33. package/docs/api/get_domain_evolution.md +39 -0
  34. package/docs/api/get_domain_health.md +32 -0
  35. package/docs/api/get_domains_list.md +25 -0
  36. package/docs/api/get_drift_digest.md +32 -0
  37. package/docs/api/get_env_vars.md +32 -0
  38. package/docs/api/get_evolution_delta.md +36 -0
  39. package/docs/api/get_file_ownership.md +33 -0
  40. package/docs/api/get_file_summary.md +34 -0
  41. package/docs/api/get_high_impact_files.md +32 -0
  42. package/docs/api/get_hot_in_prod_no_tests.md +34 -0
  43. package/docs/api/get_hotspot_files.md +37 -0
  44. package/docs/api/get_iac_resources.md +25 -0
  45. package/docs/api/get_interface_contract.md +33 -0
  46. package/docs/api/get_intervention_history.md +32 -0
  47. package/docs/api/get_invariants.md +37 -0
  48. package/docs/api/get_llm_enrichment.md +33 -0
  49. package/docs/api/get_microservice_cut_points.md +32 -0
  50. package/docs/api/get_microservices_migration_cut_points.md +25 -0
  51. package/docs/api/get_minimal_context_for_intent.md +39 -0
  52. package/docs/api/get_models.md +32 -0
  53. package/docs/api/get_neighbors.md +39 -0
  54. package/docs/api/get_org_architecture.md +25 -0
  55. package/docs/api/get_org_domain_mapping.md +25 -0
  56. package/docs/api/get_pending_decisions.md +32 -0
  57. package/docs/api/get_predictive_risk.md +32 -0
  58. package/docs/api/get_progressive_disclosure_tree.md +25 -0
  59. package/docs/api/get_recent_decisions.md +37 -0
  60. package/docs/api/get_risk_weighted_blast_radius.md +32 -0
  61. package/docs/api/get_routes.md +25 -0
  62. package/docs/api/get_safety_checklist.md +33 -0
  63. package/docs/api/get_semantic_diff.md +34 -0
  64. package/docs/api/get_service_boundary_violations.md +25 -0
  65. package/docs/api/get_service_dependency_graph.md +25 -0
  66. package/docs/api/get_session_context.md +32 -0
  67. package/docs/api/get_similar_patterns.md +39 -0
  68. package/docs/api/get_stale_docs.md +25 -0
  69. package/docs/api/get_structure.md +25 -0
  70. package/docs/api/get_temporal_context.md +34 -0
  71. package/docs/api/get_test_coverage_map.md +25 -0
  72. package/docs/api/get_token_budget_report.md +36 -0
  73. package/docs/api/get_upgrade_risk.md +25 -0
  74. package/docs/api/get_working_memory.md +25 -0
  75. package/docs/api/ingest_otlp_traces.md +34 -0
  76. package/docs/api/scaffold_for_intent.md +34 -0
  77. package/docs/api/search_routes.md +34 -0
  78. package/docs/api/simulate_change_impact.md +37 -0
  79. package/docs/api/validate_change.md +39 -0
  80. package/docs/api/validate_diff.md +39 -0
  81. package/docs/concepts/anci.md +87 -0
  82. package/docs/concepts/blast-radius.md +66 -0
  83. package/docs/concepts/domains.md +91 -0
  84. package/docs/concepts/import-graph.md +102 -0
  85. package/docs/concepts/mcp-integration.md +148 -0
  86. package/docs/guides/adding-feature-safely.md +101 -0
  87. package/docs/guides/ci-integration.md +175 -0
  88. package/docs/guides/monorepo-setup.md +121 -0
  89. package/docs/guides/onboarding-new-engineer.md +121 -0
  90. package/docs/guides/pre-merge-review.md +139 -0
  91. package/docs/migration/v1-to-v2.md +110 -0
  92. package/docs/quickstart.md +95 -0
  93. package/docs/scale.md +129 -0
  94. package/docs/troubleshooting.md +180 -0
  95. package/package.json +12 -5
  96. package/scripts/gen-api-docs.js +170 -0
  97. package/scripts/postinstall.js +391 -24
  98. package/src/acp/agent.js +83 -11
  99. package/src/acp/config.js +64 -0
  100. package/src/acp/persistence.js +146 -0
  101. package/src/acp/providers/anthropic.js +179 -27
  102. package/src/acp/providers/index.js +15 -2
  103. package/src/acp/providers/openai.js +164 -38
  104. package/src/acp/providers/sse.js +82 -0
  105. package/src/acp/safety.js +128 -0
  106. package/src/acp/session.js +73 -0
  107. package/src/adjacent/call-graph.js +170 -0
  108. package/src/adjacent/iac.js +167 -0
  109. package/src/adjacent/llm-enrich.js +35 -0
  110. package/src/adjacent/runtime.js +216 -0
  111. package/src/adjacent/semantic-diff.js +143 -0
  112. package/src/agents/leiden.js +4 -4
  113. package/src/agents/scan-structure.js +2 -2
  114. package/src/ai/context-builder.js +215 -0
  115. package/src/ai/retrieval/lexical.js +122 -0
  116. package/src/ai/retrieval/rrf.js +121 -0
  117. package/src/ai/retrieval/semantic.js +35 -0
  118. package/src/ai/retrieval/structural.js +82 -0
  119. package/src/ai/tools.js +423 -0
  120. package/src/anci/consumer.js +305 -0
  121. package/src/anci/deserialize.js +160 -0
  122. package/src/anci/emit.js +85 -0
  123. package/src/anci/serialize.js +264 -0
  124. package/src/anci/yaml.js +401 -0
  125. package/src/bitmap/index.js +1 -1
  126. package/src/bitmap/tools.js +2 -2
  127. package/src/brain/conventions/index.js +185 -0
  128. package/src/brain/index.js +31 -0
  129. package/src/brain/invariants/index.js +252 -0
  130. package/src/brain/procedural/index.js +181 -0
  131. package/src/brain/suggestions/index.js +153 -0
  132. package/src/brain/working/index.js +170 -0
  133. package/src/cli/anci.js +237 -0
  134. package/src/cli/check.js +47 -1
  135. package/src/cli/diff.js +83 -0
  136. package/src/cli/doctor.js +270 -0
  137. package/src/cli/explain.js +61 -0
  138. package/src/cli/index.js +115 -0
  139. package/src/cli/init.js +144 -6
  140. package/src/cli/inspect.js +1 -1
  141. package/src/cli/org.js +172 -0
  142. package/src/cli/pr-impact.js +554 -0
  143. package/src/cli/serve.js +1 -1
  144. package/src/cli/status.js +211 -0
  145. package/src/cli/sync.js +2 -2
  146. package/src/cli/temporal.js +188 -0
  147. package/src/cli/validate.js +201 -0
  148. package/src/cli/watch.js +4 -4
  149. package/src/cli/why.js +101 -0
  150. package/src/extractors/frameworks.js +236 -0
  151. package/src/extractors/languages/dart.js +138 -0
  152. package/src/extractors/languages/go.js +11 -1
  153. package/src/extractors/languages/javascript.js +16 -6
  154. package/src/extractors/languages/kotlin.js +169 -0
  155. package/src/extractors/languages/php.js +195 -0
  156. package/src/extractors/languages/python.js +12 -1
  157. package/src/extractors/languages/swift.js +140 -0
  158. package/src/extractors/languages/typescript.js +15 -2
  159. package/src/extractors/plugin-api.js +102 -0
  160. package/src/mcp/change-plan.js +8 -8
  161. package/src/mcp/files-without-tests.js +285 -0
  162. package/src/mcp/middleware/index.js +451 -0
  163. package/src/mcp/server.js +2292 -0
  164. package/src/mcp/validate.js +1 -1
  165. package/src/org/detect.js +262 -0
  166. package/src/org/queries.js +144 -0
  167. package/src/org/store.js +173 -0
  168. package/src/org/sync.js +106 -0
  169. package/src/predictive/cut-points.js +83 -0
  170. package/src/predictive/drift-digest.js +88 -0
  171. package/src/predictive/ownership.js +145 -0
  172. package/src/predictive/risk-score.js +121 -0
  173. package/src/predictive/validate-change.js +55 -0
  174. package/src/store/store-adapter.js +3 -3
  175. package/src/store/{sync-v2.js → sync.js} +105 -16
  176. package/src/temporal/backfill.js +211 -0
  177. package/src/temporal/delta.js +85 -0
  178. package/src/temporal/events.js +180 -0
  179. package/src/temporal/queries.js +358 -0
  180. package/src/temporal/snapshot.js +151 -0
  181. package/src/temporal/store.js +400 -0
  182. package/src/mcp/server-v2.js +0 -986
@@ -0,0 +1,25 @@
1
+ # `get_working_memory`
2
+
3
+ Live state snapshot: branch, HEAD, uncommitted files, recent decision count, open HIGH-severity warnings, recent drift. Read this at the start of every AI session.
4
+
5
+ ## Input schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {},
11
+ "required": []
12
+ }
13
+ ```
14
+
15
+ ## Required arguments
16
+
17
+ _None._
18
+
19
+ ## Properties
20
+
21
+ _None._
22
+
23
+ ## See also
24
+
25
+ - [Index of all MCP tools](./README.md)
@@ -0,0 +1,34 @@
1
+ # `ingest_otlp_traces`
2
+
3
+ Parse an OpenTelemetry OTLP JSON/JSONL trace file and aggregate per-route hit counts. Use the resulting counts with get_risk_weighted_blast_radius.
4
+
5
+ ## Input schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "path": {
12
+ "type": "string",
13
+ "description": "Path to OTLP file"
14
+ }
15
+ },
16
+ "required": [
17
+ "path"
18
+ ]
19
+ }
20
+ ```
21
+
22
+ ## Required arguments
23
+
24
+ - `path`
25
+
26
+ ## Properties
27
+
28
+ | Name | Type | Description |
29
+ |------|------|-------------|
30
+ | `path` | string | Path to OTLP file |
31
+
32
+ ## See also
33
+
34
+ - [Index of all MCP tools](./README.md)
@@ -0,0 +1,34 @@
1
+ # `scaffold_for_intent`
2
+
3
+ For a natural-language intent ("add a payment route"), returns: anchor file + co-changed files + canonical pattern + conventions to follow. Combines invariants, conventions, and procedural memory.
4
+
5
+ ## Input schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "intent": {
12
+ "type": "string",
13
+ "description": "Natural-language description of the change."
14
+ }
15
+ },
16
+ "required": [
17
+ "intent"
18
+ ]
19
+ }
20
+ ```
21
+
22
+ ## Required arguments
23
+
24
+ - `intent`
25
+
26
+ ## Properties
27
+
28
+ | Name | Type | Description |
29
+ |------|------|-------------|
30
+ | `intent` | string | Natural-language description of the change. |
31
+
32
+ ## See also
33
+
34
+ - [Index of all MCP tools](./README.md)
@@ -0,0 +1,34 @@
1
+ # `search_routes`
2
+
3
+ Search API routes by path or method. Case-insensitive.
4
+
5
+ ## Input schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "query": {
12
+ "type": "string",
13
+ "description": "Search query e.g. \"auth\", \"POST\", \"/api/users\""
14
+ }
15
+ },
16
+ "required": [
17
+ "query"
18
+ ]
19
+ }
20
+ ```
21
+
22
+ ## Required arguments
23
+
24
+ - `query`
25
+
26
+ ## Properties
27
+
28
+ | Name | Type | Description |
29
+ |------|------|-------------|
30
+ | `query` | string | Search query e.g. "auth", "POST", "/api/users" |
31
+
32
+ ## See also
33
+
34
+ - [Index of all MCP tools](./README.md)
@@ -0,0 +1,37 @@
1
+ # `simulate_change_impact`
2
+
3
+ Given a list of files, returns all files transitively affected by changing them simultaneously, with hop distance. Powered by the bitmap engine — only feasible at this speed (sub-millisecond) with bitmap OR-aggregation. Use when planning a refactor that touches multiple files.
4
+
5
+ ## Input schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "files": {
12
+ "type": "array",
13
+ "items": {
14
+ "type": "string"
15
+ },
16
+ "description": "Array of relative file paths from project root"
17
+ }
18
+ },
19
+ "required": [
20
+ "files"
21
+ ]
22
+ }
23
+ ```
24
+
25
+ ## Required arguments
26
+
27
+ - `files`
28
+
29
+ ## Properties
30
+
31
+ | Name | Type | Description |
32
+ |------|------|-------------|
33
+ | `files` | array | Array of relative file paths from project root |
34
+
35
+ ## See also
36
+
37
+ - [Index of all MCP tools](./README.md)
@@ -0,0 +1,39 @@
1
+ # `validate_change`
2
+
3
+ Pre-write governance: given a file + proposed full content, synthesizes a diff vs disk and runs validate_diff. Use in IDE onWillSaveTextDocument hooks.
4
+
5
+ ## Input schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "file": {
12
+ "type": "string"
13
+ },
14
+ "content": {
15
+ "type": "string"
16
+ }
17
+ },
18
+ "required": [
19
+ "file",
20
+ "content"
21
+ ]
22
+ }
23
+ ```
24
+
25
+ ## Required arguments
26
+
27
+ - `file`
28
+ - `content`
29
+
30
+ ## Properties
31
+
32
+ | Name | Type | Description |
33
+ |------|------|-------------|
34
+ | `file` | string | |
35
+ | `content` | string | |
36
+
37
+ ## See also
38
+
39
+ - [Index of all MCP tools](./README.md)
@@ -0,0 +1,39 @@
1
+ # `validate_diff`
2
+
3
+ Given a unified diff, returns: violations (cross-domain imports, high-blast files), blast radius per file, risk level (SAFE/LOW/MEDIUM/HIGH), and suggestions. Sub-15ms p99 on a 7K-file repo. Each call is recorded in the episodic memory log so other tools can ask "did we discuss this?".
4
+
5
+ ## Input schema
6
+
7
+ ```json
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "diff": {
12
+ "type": "string",
13
+ "description": "Unified diff text (output of `git diff` / GitHub PR patch)."
14
+ },
15
+ "session_id": {
16
+ "type": "number",
17
+ "description": "Optional session id. Defaults to the most recent active session, or a fresh one."
18
+ }
19
+ },
20
+ "required": [
21
+ "diff"
22
+ ]
23
+ }
24
+ ```
25
+
26
+ ## Required arguments
27
+
28
+ - `diff`
29
+
30
+ ## Properties
31
+
32
+ | Name | Type | Description |
33
+ |------|------|-------------|
34
+ | `diff` | string | Unified diff text (output of `git diff` / GitHub PR patch). |
35
+ | `session_id` | number | Optional session id. Defaults to the most recent active session, or a fresh one. |
36
+
37
+ ## See also
38
+
39
+ - [Index of all MCP tools](./README.md)
@@ -0,0 +1,87 @@
1
+ # Concept: ANCI
2
+
3
+ > **Architecturally Normalized Code Index** — the open file format for any codebase to describe its architecture to AI tools.
4
+
5
+ ## The problem
6
+
7
+ Every AI coding tool today re-discovers a codebase's architecture from scratch on every session. Cursor builds its own embedding index. Cline builds its own. Continue builds its own. Same parsing, every tool, every session, every time. Wasted CPU; wasted disk; inconsistent results.
8
+
9
+ What's missing is a *format*. OpenAPI did this for REST APIs — once any API has an `openapi.yaml`, every tool consumes it the same way. ANCI is the equivalent for codebases.
10
+
11
+ ## What it is
12
+
13
+ Two files at `.carto/anci.{yaml,bin}`. Together they describe:
14
+
15
+ - the domain partition (which files belong to which cluster)
16
+ - the import graph (forward + reverse adjacency)
17
+ - per-file metadata (path, language, exports)
18
+ - popcount index (files ranked by transitive dependents)
19
+
20
+ The split is hybrid by design:
21
+
22
+ - **`anci.yaml`** — strict-subset YAML 1.2, human-readable, grep-able. Holds the schema version, generator info, domain names, and counts. ~1–4 KB.
23
+ - **`anci.bin`** — binary body. Magic `0x49434E41` ("ANCI"), version byte, six sections (forward graph, reverse graph, popcount, paths, file→domain, domain names) packed via Roaring-bitmap-like compression. ~200 KB to ~5 MB depending on repo size.
24
+
25
+ Why split: a 100K-file repo as pure YAML is 50–500 MB — too large for an AI tool to consume per-session. The binary section solves the size problem without sacrificing the human-readable header.
26
+
27
+ ## Carto is the reference implementation
28
+
29
+ `carto sync` writes both files automatically on every full sync. The same data the MCP server hands the AI is what ANCI captures on disk. Any tool can consume it without speaking MCP:
30
+
31
+ ```js
32
+ const { loadAnci } = require('carto-md/src/anci/consumer');
33
+
34
+ const reader = loadAnci('./.carto');
35
+ console.log(reader.domains); // [{name, file_count}, ...]
36
+ console.log(reader.getHighImpactFiles(5)); // top 5 by transitive dependents
37
+ console.log(reader.blastRadius('src/auth/session.ts')); // { count, hops, files }
38
+ console.log(reader.simulateChangeImpact([ // multi-file change blast radius
39
+ 'src/auth/session.ts',
40
+ 'src/db/connection.ts',
41
+ ]));
42
+ ```
43
+
44
+ `loadAnci()` has zero dependencies. Anyone shipping an AI tool can drop it in and consume Carto's index without taking on Carto's whole tarball.
45
+
46
+ ## The CLI surface
47
+
48
+ ```bash
49
+ carto anci publish # re-emit anci.{yaml,bin} from the current index
50
+ carto anci show # human-readable summary
51
+ carto anci validate ./.carto # validate a published pair
52
+ ```
53
+
54
+ Publish runs implicitly on `carto sync`, so you only need to invoke these manually if you're debugging.
55
+
56
+ ## Versioning
57
+
58
+ The spec is at `docs/anci/v0.1-DRAFT.md`. It's a draft; the wire format may break between v0.1.x patches. v1.0+ will follow semver and consumers refusing unsupported major versions is the contract.
59
+
60
+ The header version is what consumers look at first:
61
+
62
+ ```yaml
63
+ anci:
64
+ version: "0.1.0-DRAFT"
65
+ generator: "carto-md@2.0.9"
66
+ generated_at: "2026-06-26T12:34:56Z"
67
+ ```
68
+
69
+ A consumer should reject anything outside its supported major.minor prefix.
70
+
71
+ ## Why publish a spec at all
72
+
73
+ If only Carto consumes Carto's binary format, it's just an internal optimization. If three AI tools agree on a format, it becomes the standard. The OpenAPI precedent: Swagger shipped as a proprietary format in 2011, became de-facto by 2014, formalized as the OpenAPI Initiative by 2016.
74
+
75
+ Carto's bet is the same: publish the binary spec early, support it as the reference implementation, and let the ecosystem coalesce. Once 3+ AI tools read ANCI, every AI tool has to.
76
+
77
+ ## Status
78
+
79
+ - Spec: v0.1 DRAFT, published in [`docs/anci/v0.1-DRAFT.md`](../anci/v0.1-DRAFT.md)
80
+ - Carto generates the files on every `carto sync`
81
+ - Consumer library shipped: `src/anci/consumer.js`
82
+ - Partner integration: TBD — first one is the unblocker
83
+
84
+ ## Related
85
+
86
+ - [`docs/anci/v0.1-DRAFT.md`](../anci/v0.1-DRAFT.md) — the spec itself
87
+ - [`mcp-integration.md`](./mcp-integration.md) — the alternative consumption path (live, slightly more flexible, but every consumer pays the MCP-protocol surface tax)
@@ -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