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.
- package/CONTRIBUTING.md +19 -15
- package/README.md +203 -286
- package/docs/anci/v0.1-DRAFT.md +420 -0
- 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 +129 -0
- package/docs/troubleshooting.md +180 -0
- package/package.json +12 -5
- package/scripts/gen-api-docs.js +170 -0
- package/scripts/postinstall.js +391 -24
- 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/leiden.js +4 -4
- package/src/agents/scan-structure.js +2 -2
- 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/consumer.js +305 -0
- package/src/anci/deserialize.js +160 -0
- package/src/anci/emit.js +85 -0
- package/src/anci/serialize.js +264 -0
- package/src/anci/yaml.js +401 -0
- 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/anci.js +237 -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 +115 -0
- package/src/cli/init.js +144 -6
- package/src/cli/inspect.js +1 -1
- package/src/cli/org.js +172 -0
- package/src/cli/pr-impact.js +554 -0
- 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 +16 -6
- 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/change-plan.js +8 -8
- 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} +105 -16
- 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,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
|