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,95 @@
|
|
|
1
|
+
# Quickstart — 10 minutes from install to first MCP query
|
|
2
|
+
|
|
3
|
+
Get carto wired into your AI tool and answering questions about your codebase in under ten minutes.
|
|
4
|
+
|
|
5
|
+
## 1. Install (30 seconds)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g carto-md
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Carto ships prebuilt native binaries for macOS arm64/x64, Linux x64 (glibc + musl), and Windows x64. If you're on a platform without prebuilds you'll fall back to compiling from source — see [troubleshooting.md](./troubleshooting.md#native-build-fails) if that breaks.
|
|
12
|
+
|
|
13
|
+
Verify the install:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
carto --version
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 2. Index your project (1 minute)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd ~/your-project
|
|
23
|
+
carto init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You'll see something like:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
[CARTO] Detecting project...
|
|
30
|
+
[CARTO] Detected: nextjs (typescript)
|
|
31
|
+
[CARTO] Found 847 JS/TS files (847 total)
|
|
32
|
+
[CARTO] Discovered 847 source files
|
|
33
|
+
[CARTO] Computing reverse dependencies...
|
|
34
|
+
[CARTO] Indexed 847 files in 1.8s
|
|
35
|
+
[CARTO] MCP auto-wired into: Cursor, Claude Code, Kiro
|
|
36
|
+
[CARTO] AGENTS.md generated.
|
|
37
|
+
|
|
38
|
+
┌─ Carto · indexed ─────────────────────────────────────────
|
|
39
|
+
│ 847 files · 5 domains · 23 routes · 1,204 import edges
|
|
40
|
+
│
|
|
41
|
+
│ Top domains:
|
|
42
|
+
│ CORE (677 files)
|
|
43
|
+
│ AUTH (42 files)
|
|
44
|
+
│ PAYMENTS (38 files)
|
|
45
|
+
│ DATABASE (67 files)
|
|
46
|
+
│
|
|
47
|
+
│ 💡 Highest-risk file: src/lib/db.ts
|
|
48
|
+
│ (34 files depend on it — try `carto why src/lib/db.ts`)
|
|
49
|
+
└───────────────────────────────────────────────────────────
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
What just happened:
|
|
53
|
+
|
|
54
|
+
- Carto walked the project, parsed every source file with tree-sitter
|
|
55
|
+
- Built an import graph, clustered files into domains (Leiden+CPM), extracted routes + models
|
|
56
|
+
- Wrote `.carto/` containing the SQLite index, the bitmap sidecar, the per-domain context files
|
|
57
|
+
- Wrote `AGENTS.md` at the project root
|
|
58
|
+
- Installed 4 git hooks so the index re-syncs on commit/checkout/merge/rebase
|
|
59
|
+
- Detected your installed AI tools and wrote MCP config files for each
|
|
60
|
+
|
|
61
|
+
## 3. Restart your AI tool
|
|
62
|
+
|
|
63
|
+
The MCP wiring lands when the AI tool starts fresh. Kill it and reopen, then verify carto loaded — most tools log MCP server connections.
|
|
64
|
+
|
|
65
|
+
## 4. Ask your first question
|
|
66
|
+
|
|
67
|
+
Open any prompt your AI tool exposes and try one of:
|
|
68
|
+
|
|
69
|
+
> *"What's the blast radius of `src/lib/db.ts`?"*
|
|
70
|
+
|
|
71
|
+
> *"Show me the architecture overview."*
|
|
72
|
+
|
|
73
|
+
> *"What files would I need to change to add rate limiting to /api/users?"*
|
|
74
|
+
|
|
75
|
+
The AI calls Carto's MCP tools (`get_blast_radius`, `get_architecture`, `get_change_plan`) and answers with structural facts instead of grepping.
|
|
76
|
+
|
|
77
|
+
## 5. Try the CLI too
|
|
78
|
+
|
|
79
|
+
While you're here:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
carto status # one-screen health view
|
|
83
|
+
carto why src/lib/db.ts # 3-line summary of a file
|
|
84
|
+
carto explain "add rate limiting" # natural-language → plan
|
|
85
|
+
carto check # cross-domain violations
|
|
86
|
+
carto doctor # diagnose any setup issues
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## What's next
|
|
90
|
+
|
|
91
|
+
- [`docs/concepts/blast-radius.md`](./concepts/blast-radius.md) — what "blast radius" means and how it's computed
|
|
92
|
+
- [`docs/concepts/domains.md`](./concepts/domains.md) — how the Leiden clusterer infers AUTH/PAYMENTS/etc. without config
|
|
93
|
+
- [`docs/concepts/mcp-integration.md`](./concepts/mcp-integration.md) — every MCP tool with an example call
|
|
94
|
+
- [`docs/guides/adding-feature-safely.md`](./guides/adding-feature-safely.md) — the workflow this whole thing enables
|
|
95
|
+
- [`docs/guides/ci-integration.md`](./guides/ci-integration.md) — drop the GitHub Action onto every PR
|
package/docs/scale.md
CHANGED
|
@@ -8,8 +8,8 @@ The bitmap-backed graph engine is designed to handle 100K-1M files cleanly. This
|
|
|
8
8
|
|
|
9
9
|
For each run we capture:
|
|
10
10
|
|
|
11
|
-
- **Init time** — full `
|
|
12
|
-
- **Sync time** — second `
|
|
11
|
+
- **Init time** — full `runSync` from a wiped `.carto/` directory.
|
|
12
|
+
- **Sync time** — second `runSync` immediately after, no files changed (mtime+hash skip path).
|
|
13
13
|
- **DB size** — `.carto/carto.db` bytes on disk.
|
|
14
14
|
- **bitmap.bin size** — the sidecar bytes on disk.
|
|
15
15
|
- **Sidecar (RAM)** — sum of all bitmap word-array byte lengths held in memory.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
> Real failure modes, with fixes.
|
|
4
|
+
|
|
5
|
+
## Install / native modules
|
|
6
|
+
|
|
7
|
+
### `node-gyp rebuild` fails on install
|
|
8
|
+
|
|
9
|
+
Symptom: `npm install -g carto-md` exits with C++ errors mentioning `tree-sitter` or `better-sqlite3`.
|
|
10
|
+
|
|
11
|
+
Why: no prebuilt binary for your platform → falling back to compile-from-source → no C++ toolchain present.
|
|
12
|
+
|
|
13
|
+
Fix: install build tools, then retry.
|
|
14
|
+
|
|
15
|
+
| OS | Command |
|
|
16
|
+
|---|---|
|
|
17
|
+
| macOS | `xcode-select --install` |
|
|
18
|
+
| Ubuntu / Debian | `sudo apt-get install build-essential python3` |
|
|
19
|
+
| RHEL / Fedora | `sudo dnf groupinstall "Development Tools"` |
|
|
20
|
+
| Windows | `npm install --global windows-build-tools` (run from elevated PowerShell) |
|
|
21
|
+
| Alpine | `apk add --no-cache python3 make g++` |
|
|
22
|
+
|
|
23
|
+
If you're on a supported platform (macOS arm64/x64, Linux x64 glibc + musl, Windows x64), you *shouldn't* be hitting this. Run `carto doctor` — the output will tell you which native module is the problem.
|
|
24
|
+
|
|
25
|
+
### Node version refused at install time
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
[CARTO] Node v16.x.x is too old. carto-md requires Node ≥ v18.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Upgrade Node. https://nodejs.org/ — even-numbered LTS releases (18, 20, 22) are best.
|
|
32
|
+
|
|
33
|
+
### Missing optional grammars
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
⚠ Tree-sitter grammars (2 of 8 missing: tree-sitter-rust, tree-sitter-java)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The optional language grammars failed to prebuild on your platform. Files in those languages still get indexed — Carto falls back to regex extraction (less accurate but functional).
|
|
40
|
+
|
|
41
|
+
To force a grammar to install:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g tree-sitter-rust
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If that fails too, the platform doesn't have a prebuild and won't compile. That's fine — regex fallback works.
|
|
48
|
+
|
|
49
|
+
## Index / sync
|
|
50
|
+
|
|
51
|
+
### `carto sync` says "0 files re-parsed" but I changed files
|
|
52
|
+
|
|
53
|
+
Cache hit. Carto stats each file's mtime+size and skips ones that haven't changed. If you genuinely changed a file but mtime didn't move (rare — usually copying a same-content file), force a re-parse:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
touch src/the/file.ts
|
|
57
|
+
carto sync
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For a full rebuild from scratch:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
carto remove
|
|
64
|
+
carto init
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `carto check` claims unstable clustering
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
[CARTO] Warning: 8.2% of files changed domain since last sync — clustering unstable.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The Leiden+CPM clusterer can be sensitive when the codebase is borderline between two stable partitions. If this happens repeatedly:
|
|
74
|
+
|
|
75
|
+
1. Pin the important files with `anchor` in `carto.config.json`. See [`docs/concepts/domains.md`](./concepts/domains.md).
|
|
76
|
+
2. If a domain is genuinely splitting (a refactor in progress), this is informational — accept the drift.
|
|
77
|
+
3. If you don't care about exact domain names but want stability, set keyword hints in `carto.config.json` so the names are forced.
|
|
78
|
+
|
|
79
|
+
### Index is huge
|
|
80
|
+
|
|
81
|
+
A 100K-file repo produces a 160 MB `carto.db` and 20 MB `bitmap.bin`. Larger than expected? Most likely you're indexing `node_modules/`, a `vendor/` dir, or a generated `dist/`. Run:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
carto inspect | head -20
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
That shows the file count. If it's way bigger than your source tree, edit `.cartoignore` to exclude the offender:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
node_modules/
|
|
91
|
+
vendor/
|
|
92
|
+
**/__generated__/
|
|
93
|
+
build/
|
|
94
|
+
dist/
|
|
95
|
+
out/
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## MCP / AI tool integration
|
|
99
|
+
|
|
100
|
+
### AI tool doesn't see Carto's tools
|
|
101
|
+
|
|
102
|
+
1. Restart the AI tool (it loads MCP config at startup).
|
|
103
|
+
2. Run `carto doctor` — it surfaces whether config files exist for the tools you have installed.
|
|
104
|
+
3. If `doctor` says "MCP configuration: no MCP config files found", re-run `carto init` (it auto-wires).
|
|
105
|
+
4. If `init` doesn't detect your tool, see the README's manual config snippets in the [Manual MCP wiring](../README.md) section (collapsed `<details>` block right after the install snippet).
|
|
106
|
+
|
|
107
|
+
### MCP server crashes
|
|
108
|
+
|
|
109
|
+
Pre-2.0.7 the MCP server could crash on tool errors and the AI tool would see `-32000 Failed to reconnect`. Fixed in 2.0.7. If you're seeing it on a newer version, set `CARTO_DEBUG=1` before launching the AI tool to capture stderr.
|
|
110
|
+
|
|
111
|
+
### "Lazy re-parse" doesn't fire on stale files
|
|
112
|
+
|
|
113
|
+
The MCP server stats the file on every file-aware query and re-parses if mtime is newer than the DB row. If a query returns stale data:
|
|
114
|
+
|
|
115
|
+
1. Check the file's mtime: `stat src/the/file.ts`
|
|
116
|
+
2. Compare against the DB row: `carto inspect | grep "last full sync"`
|
|
117
|
+
3. If mtime is older, sync was after edit and the data isn't stale.
|
|
118
|
+
4. If mtime is newer but the data is stale, file a bug — that's the regression test target.
|
|
119
|
+
|
|
120
|
+
## Validation API / PR Action
|
|
121
|
+
|
|
122
|
+
### `carto pr-impact` says "No carto index"
|
|
123
|
+
|
|
124
|
+
The action's cache restored an empty `.carto/`. Either:
|
|
125
|
+
|
|
126
|
+
- Cache key isn't invalidating. Check `hashFiles` glob in the workflow.
|
|
127
|
+
- First run on this branch — let it run `carto init` cold once.
|
|
128
|
+
- Index was removed; run `carto init` manually inside the workflow before pr-impact.
|
|
129
|
+
|
|
130
|
+
### Sticky comment isn't sticky
|
|
131
|
+
|
|
132
|
+
The action looks for the `<!-- carto-impact-report -->` marker in existing PR comments and updates the first match. If multiple comments have the marker (somehow), the action updates the first. If you see duplicate comments, check that the workflow isn't running twice (e.g. matrix builds, multiple triggers).
|
|
133
|
+
|
|
134
|
+
### `--fail-on HIGH` trips too often
|
|
135
|
+
|
|
136
|
+
Lower the threshold to `MEDIUM` for a softer gate, or use `--fail-on` only on `main`-bound PRs and not feature-branch PRs. Or accept that HIGH-risk PRs deserve careful review and let the gate work as intended.
|
|
137
|
+
|
|
138
|
+
## SWE-bench harness
|
|
139
|
+
|
|
140
|
+
### `bench/swe-bench/run.sh --task-set verified` says "dataset not found"
|
|
141
|
+
|
|
142
|
+
Download the SWE-bench-Verified dataset from Hugging Face:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pip install huggingface_hub
|
|
146
|
+
huggingface-cli download princeton-nlp/SWE-bench_Verified \
|
|
147
|
+
--repo-type dataset --local-dir ~/swe-bench-verified
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Or set `CARTO_SWE_VERIFIED_PATH=/path/to/test.jsonl`.
|
|
151
|
+
|
|
152
|
+
### AnthropicAgent throws on `--arm carto`
|
|
153
|
+
|
|
154
|
+
You ran `--task-set verified` without `ANTHROPIC_API_KEY`. Set the key, or stick to `--task-set sample` which uses the deterministic StubAgent.
|
|
155
|
+
|
|
156
|
+
### Cost runaway during a real-API run
|
|
157
|
+
|
|
158
|
+
Set an Anthropic spend limit on your account dashboard *before* a long run. The harness emits per-task token counts to the JSONL output — tail it during the run to spot anomalies early:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
tail -f bench/swe-bench/results/<run-id>.jsonl | jq .tokensInput
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## General
|
|
165
|
+
|
|
166
|
+
### `carto doctor` is the right starting point
|
|
167
|
+
|
|
168
|
+
For any "something is broken" report, run `carto doctor` first. It checks Node, native modules, the index, git hooks, MCP config, .cartoignore. The output points at the broken component with a `Fix:` line.
|
|
169
|
+
|
|
170
|
+
### Found a real bug
|
|
171
|
+
|
|
172
|
+
Open an issue with:
|
|
173
|
+
|
|
174
|
+
- `node --version`
|
|
175
|
+
- `carto --version`
|
|
176
|
+
- Output of `carto doctor`
|
|
177
|
+
- Output of `carto inspect | head -30`
|
|
178
|
+
- Steps to reproduce
|
|
179
|
+
|
|
180
|
+
The maintainer triage is fast on real bugs with reproducible info. The triage is slow on vague "carto doesn't work" reports.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carto-md",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "Structural intelligence
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Structural intelligence + episodic memory for AI coding tools. Indexes your codebase into SQLite — routes, models, import graph, blast radius, domains — and exposes ~75 MCP tools (validate_diff, get_change_plan, did_we_discuss_this, get_predictive_risk, …) for Cursor, Claude Code, Codex, Kiro, and any other MCP client.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"carto": "src/cli/index.js"
|
|
7
7
|
},
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"author": {
|
|
40
40
|
"name": "Ansh Sonkar",
|
|
41
41
|
"url": "https://github.com/theanshsonkar",
|
|
42
|
-
"
|
|
42
|
+
"linkedin": "https://www.linkedin.com/in/theanshsonkar"
|
|
43
43
|
},
|
|
44
44
|
"license": "MIT",
|
|
45
45
|
"keywords": [
|
|
@@ -59,8 +59,14 @@
|
|
|
59
59
|
"sqlite",
|
|
60
60
|
"blast-radius",
|
|
61
61
|
"import-graph",
|
|
62
|
+
"episodic-memory",
|
|
63
|
+
"validate-diff",
|
|
62
64
|
"kiro",
|
|
63
65
|
"claude",
|
|
64
|
-
"
|
|
66
|
+
"claude-code",
|
|
67
|
+
"codex",
|
|
68
|
+
"windsurf",
|
|
69
|
+
"zed",
|
|
70
|
+
"anci"
|
|
65
71
|
]
|
|
66
72
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scripts/gen-api-docs.js — generate `docs/api/<tool>.md` for every MCP
|
|
5
|
+
* tool registered in `src/mcp/server.js`. Re-run after adding a new tool.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/gen-api-docs.js # writes docs/api/
|
|
9
|
+
* node scripts/gen-api-docs.js --dry-run # print to stdout, no writes
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const projectRoot = path.join(__dirname, '..');
|
|
16
|
+
const docsDir = path.join(projectRoot, 'docs', 'api');
|
|
17
|
+
|
|
18
|
+
function loadTools() {
|
|
19
|
+
// Cheap eval — extract the TOOLS array literal from server.js so we
|
|
20
|
+
// don't have to wire MCP server startup just to read tool definitions.
|
|
21
|
+
// We require() the module and read the captured TOOLS via a re-export.
|
|
22
|
+
const serverPath = path.join(projectRoot, 'src', 'mcp', 'server.js');
|
|
23
|
+
const src = fs.readFileSync(serverPath, 'utf-8');
|
|
24
|
+
// Find the `const TOOLS = [` block.
|
|
25
|
+
const startIdx = src.indexOf('const TOOLS = [');
|
|
26
|
+
if (startIdx === -1) throw new Error('TOOLS array not found in server.js');
|
|
27
|
+
// Walk forward, balancing brackets.
|
|
28
|
+
let depth = 0, i = startIdx;
|
|
29
|
+
for (; i < src.length; i++) {
|
|
30
|
+
if (src[i] === '[') depth++;
|
|
31
|
+
else if (src[i] === ']') { depth--; if (depth === 0) { i++; break; } }
|
|
32
|
+
}
|
|
33
|
+
const arrayLiteral = src.slice(startIdx + 'const TOOLS = '.length, i);
|
|
34
|
+
// eslint-disable-next-line no-eval
|
|
35
|
+
const TOOLS = (new Function(`return ${arrayLiteral}`))();
|
|
36
|
+
return TOOLS;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function indexMarkdown(tools) {
|
|
40
|
+
const groups = groupByPrefix(tools);
|
|
41
|
+
const lines = [
|
|
42
|
+
'# Carto MCP Tools — API Reference',
|
|
43
|
+
'',
|
|
44
|
+
'Auto-generated from `src/mcp/server.js`. Re-run `node scripts/gen-api-docs.js` after adding tools.',
|
|
45
|
+
'',
|
|
46
|
+
`**${tools.length} tools** across ${groups.size} categories.`,
|
|
47
|
+
'',
|
|
48
|
+
'## Categories',
|
|
49
|
+
'',
|
|
50
|
+
];
|
|
51
|
+
for (const [groupName, names] of groups) {
|
|
52
|
+
lines.push(`### ${groupName} (${names.length})`);
|
|
53
|
+
for (const n of names) {
|
|
54
|
+
lines.push(`- [\`${n}\`](./${n}.md)`);
|
|
55
|
+
}
|
|
56
|
+
lines.push('');
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function groupByPrefix(tools) {
|
|
62
|
+
// Group by the first prefix segment of the tool name.
|
|
63
|
+
const groups = new Map();
|
|
64
|
+
for (const t of tools) {
|
|
65
|
+
let group = 'Other';
|
|
66
|
+
if (t.name.startsWith('get_arch') || t.name.startsWith('get_domain_evolution') || t.name.startsWith('get_hotspot') ||
|
|
67
|
+
t.name.startsWith('get_complexity') || t.name.startsWith('get_churn') || t.name.startsWith('get_domain_health') ||
|
|
68
|
+
t.name.startsWith('get_temporal') || t.name === 'get_architectural_drift') group = 'Temporal';
|
|
69
|
+
else if (t.name === 'get_invariants' || t.name === 'get_canonical_pattern' || t.name === 'get_conventions' ||
|
|
70
|
+
t.name === 'get_action_patterns' || t.name === 'scaffold_for_intent' || t.name === 'get_working_memory' ||
|
|
71
|
+
t.name === 'get_pending_decisions' || t.name === 'get_active_drift' || t.name === 'get_active_suggestions' ||
|
|
72
|
+
t.name === 'dismiss_suggestion') group = 'Brain';
|
|
73
|
+
else if (t.name === 'get_minimal_context_for_intent' || t.name === 'get_progressive_disclosure_tree' ||
|
|
74
|
+
t.name === 'get_token_budget_report' || t.name === 'get_decision_log' || t.name === 'get_evolution_delta' ||
|
|
75
|
+
t.name === 'get_change_velocity' || t.name === 'get_test_coverage_map' || t.name === 'get_safety_checklist' ||
|
|
76
|
+
t.name === 'get_data_flow' || t.name === 'get_interface_contract' || t.name === 'explain_change_in_natural_language' ||
|
|
77
|
+
t.name === 'get_stale_docs' || t.name === 'get_dependency_surface' || t.name === 'get_upgrade_risk') group = 'AI-native';
|
|
78
|
+
else if (t.name.startsWith('get_cross_language') || t.name === 'get_iac_resources' ||
|
|
79
|
+
t.name === 'ingest_otlp_traces' || t.name === 'get_risk_weighted_blast_radius' ||
|
|
80
|
+
t.name === 'get_dead_code_with_confidence' || t.name === 'get_hot_in_prod_no_tests' ||
|
|
81
|
+
t.name === 'get_semantic_diff' || t.name === 'get_llm_enrichment') group = 'Adjacent';
|
|
82
|
+
else if (t.name === 'get_predictive_risk' || t.name === 'get_microservice_cut_points' || t.name === 'validate_change' ||
|
|
83
|
+
t.name === 'get_file_ownership' || t.name === 'get_cross_team_coupling' || t.name === 'get_drift_digest' ||
|
|
84
|
+
t.name === 'get_ai_cost_attribution') group = 'Predictive';
|
|
85
|
+
else if (t.name === 'get_org_architecture' || t.name === 'get_service_dependency_graph' ||
|
|
86
|
+
t.name === 'get_cross_repo_blast_radius' || t.name === 'find_consumers_of_api' ||
|
|
87
|
+
t.name === 'get_org_domain_mapping' || t.name === 'get_service_boundary_violations' ||
|
|
88
|
+
t.name === 'get_microservices_migration_cut_points') group = 'Org-wide';
|
|
89
|
+
else if (t.name === 'validate_diff' || t.name === 'get_recent_decisions' || t.name === 'get_session_context' ||
|
|
90
|
+
t.name === 'did_we_discuss_this' || t.name === 'get_intervention_history') group = 'Episodic Memory';
|
|
91
|
+
else group = 'Core graph';
|
|
92
|
+
if (!groups.has(group)) groups.set(group, []);
|
|
93
|
+
groups.get(group).push(t.name);
|
|
94
|
+
}
|
|
95
|
+
const order = ['Core graph', 'Episodic Memory', 'Temporal', 'Brain', 'AI-native', 'Adjacent', 'Predictive', 'Org-wide', 'Other'];
|
|
96
|
+
const sorted = new Map();
|
|
97
|
+
for (const g of order) if (groups.has(g)) sorted.set(g, groups.get(g));
|
|
98
|
+
return sorted;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toolMarkdown(t) {
|
|
102
|
+
const lines = [
|
|
103
|
+
`# \`${t.name}\``,
|
|
104
|
+
'',
|
|
105
|
+
t.description || '_(no description)_',
|
|
106
|
+
'',
|
|
107
|
+
'## Input schema',
|
|
108
|
+
'',
|
|
109
|
+
'```json',
|
|
110
|
+
JSON.stringify(t.inputSchema || { type: 'object' }, null, 2),
|
|
111
|
+
'```',
|
|
112
|
+
'',
|
|
113
|
+
'## Required arguments',
|
|
114
|
+
'',
|
|
115
|
+
];
|
|
116
|
+
const required = (t.inputSchema && t.inputSchema.required) || [];
|
|
117
|
+
if (required.length === 0) lines.push('_None._');
|
|
118
|
+
else for (const r of required) lines.push(`- \`${r}\``);
|
|
119
|
+
lines.push('');
|
|
120
|
+
lines.push('## Properties');
|
|
121
|
+
lines.push('');
|
|
122
|
+
const props = (t.inputSchema && t.inputSchema.properties) || {};
|
|
123
|
+
if (Object.keys(props).length === 0) lines.push('_None._');
|
|
124
|
+
else {
|
|
125
|
+
lines.push('| Name | Type | Description |');
|
|
126
|
+
lines.push('|------|------|-------------|');
|
|
127
|
+
for (const [name, schema] of Object.entries(props)) {
|
|
128
|
+
const type = schema.type || (schema.items ? `array<${schema.items.type || 'string'}>` : 'any');
|
|
129
|
+
const desc = (schema.description || '').replace(/\|/g, '\\|');
|
|
130
|
+
lines.push(`| \`${name}\` | ${type} | ${desc} |`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
lines.push('');
|
|
134
|
+
lines.push('## See also');
|
|
135
|
+
lines.push('');
|
|
136
|
+
lines.push('- [Index of all MCP tools](./README.md)');
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function main() {
|
|
141
|
+
const dry = process.argv.includes('--dry-run');
|
|
142
|
+
const tools = loadTools();
|
|
143
|
+
if (!dry) {
|
|
144
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
145
|
+
}
|
|
146
|
+
let written = 0;
|
|
147
|
+
for (const t of tools) {
|
|
148
|
+
const p = path.join(docsDir, `${t.name}.md`);
|
|
149
|
+
const md = toolMarkdown(t);
|
|
150
|
+
if (dry) {
|
|
151
|
+
console.log(`\n=== ${p} ===\n${md}\n`);
|
|
152
|
+
} else {
|
|
153
|
+
fs.writeFileSync(p, md, 'utf-8');
|
|
154
|
+
written++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const indexPath = path.join(docsDir, 'README.md');
|
|
158
|
+
const indexMd = indexMarkdown(tools);
|
|
159
|
+
if (!dry) fs.writeFileSync(indexPath, indexMd, 'utf-8');
|
|
160
|
+
|
|
161
|
+
if (dry) console.log(`\n[dry-run] would write ${tools.length + 1} files.`);
|
|
162
|
+
else console.log(`[CARTO] Generated ${written} per-tool docs + 1 index → docs/api/`);
|
|
163
|
+
return tools.length;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (require.main === module) {
|
|
167
|
+
try { main(); } catch (err) { console.error(err.message); process.exit(1); }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { loadTools, toolMarkdown, indexMarkdown, main };
|
package/src/acp/agent.js
CHANGED
|
@@ -6,6 +6,7 @@ const { SessionManager } = require('./session');
|
|
|
6
6
|
const { buildSystemPrompt, buildContextBlock } = require('./prompt');
|
|
7
7
|
const { CARTO_TOOLS, executeTool } = require('./tools');
|
|
8
8
|
const { ProviderRegistry } = require('./providers');
|
|
9
|
+
const { loadAgentConfig, saveAgentConfig } = require('./config');
|
|
9
10
|
|
|
10
11
|
const MAX_ITERATIONS = 25;
|
|
11
12
|
|
|
@@ -14,13 +15,17 @@ class CartoAgent {
|
|
|
14
15
|
this.connection = connection;
|
|
15
16
|
this.sessions = new SessionManager();
|
|
16
17
|
this.providers = new ProviderRegistry();
|
|
18
|
+
// Rehydrate last-used provider config (no API key — that still comes
|
|
19
|
+
// from the environment or IDE settings). Done lazily so a fresh repo
|
|
20
|
+
// without `.carto/agent-config.json` doesn't pay any cost.
|
|
21
|
+
this._lastProviderConfig = null;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
async initialize(_params) {
|
|
20
25
|
return {
|
|
21
26
|
protocolVersion: acp.PROTOCOL_VERSION,
|
|
22
27
|
agentCapabilities: {
|
|
23
|
-
loadSession:
|
|
28
|
+
loadSession: true,
|
|
24
29
|
promptCapabilities: { image: false, audio: false, embeddedContext: true },
|
|
25
30
|
},
|
|
26
31
|
agentInfo: {
|
|
@@ -37,7 +42,12 @@ class CartoAgent {
|
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
async newSession(params) {
|
|
40
|
-
const
|
|
45
|
+
const cwd = params.cwd || process.cwd();
|
|
46
|
+
const session = this.sessions.create(cwd);
|
|
47
|
+
// Lazy-load any persisted provider config for this project.
|
|
48
|
+
if (!this._lastProviderConfig) {
|
|
49
|
+
this._lastProviderConfig = loadAgentConfig(cwd);
|
|
50
|
+
}
|
|
41
51
|
return { sessionId: session.id };
|
|
42
52
|
}
|
|
43
53
|
|
|
@@ -61,6 +71,10 @@ class CartoAgent {
|
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
session.abortController = null;
|
|
74
|
+
// Persist after every successful prompt so an editor crash never
|
|
75
|
+
// loses session history. Best-effort; failures don't surface to the
|
|
76
|
+
// client (we'd rather complete the turn than block on disk).
|
|
77
|
+
try { this.sessions.persist(session); } catch {}
|
|
64
78
|
return { stopReason: 'end_turn' };
|
|
65
79
|
}
|
|
66
80
|
|
|
@@ -77,9 +91,38 @@ class CartoAgent {
|
|
|
77
91
|
// editor settings; ProviderRegistry remains as the internal
|
|
78
92
|
// configuration carrier.
|
|
79
93
|
|
|
80
|
-
// Session list/load
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
// Session list/load — persisted ACP sessions.
|
|
95
|
+
// Sessions are persisted to `.carto/acp-sessions.db` after every
|
|
96
|
+
// prompt completion. `listSessions` returns persisted sessions for the
|
|
97
|
+
// working directory; `loadSession` rehydrates one into memory.
|
|
98
|
+
async listSessions(params) {
|
|
99
|
+
const cwd = (params && params.cwd) || process.cwd();
|
|
100
|
+
const rows = this.sessions.list(cwd);
|
|
101
|
+
return {
|
|
102
|
+
sessions: rows.map(r => ({
|
|
103
|
+
sessionId: r.id,
|
|
104
|
+
cwd: r.working_dir,
|
|
105
|
+
createdAt: r.created_at,
|
|
106
|
+
updatedAt: r.updated_at,
|
|
107
|
+
messageCount: r.msg_count || 0,
|
|
108
|
+
})),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async loadSession(params) {
|
|
112
|
+
if (!params || !params.sessionId || !params.cwd) return { sessionId: null };
|
|
113
|
+
const session = this.sessions.resume(params.sessionId, params.cwd);
|
|
114
|
+
return { sessionId: session ? session.id : null };
|
|
115
|
+
}
|
|
116
|
+
async closeSession(params) {
|
|
117
|
+
if (params && params.sessionId) {
|
|
118
|
+
const session = this.sessions.get(params.sessionId);
|
|
119
|
+
if (session) {
|
|
120
|
+
try { this.sessions.persist(session); } catch {}
|
|
121
|
+
}
|
|
122
|
+
this.sessions.delete(params.sessionId);
|
|
123
|
+
}
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
83
126
|
|
|
84
127
|
// ─── Agent Loop ──────────────────────────────────────────────────────────
|
|
85
128
|
|
|
@@ -128,19 +171,48 @@ class CartoAgent {
|
|
|
128
171
|
while (iterations++ < MAX_ITERATIONS) {
|
|
129
172
|
if (signal.aborted) throw new Error('cancelled');
|
|
130
173
|
|
|
131
|
-
|
|
174
|
+
// True LLM token streaming.
|
|
175
|
+
//
|
|
176
|
+
// The provider streams text deltas via `onTextChunk`. We forward
|
|
177
|
+
// each chunk to the editor as it arrives so the UI feels alive
|
|
178
|
+
// instead of stalling for the duration of the request. The
|
|
179
|
+
// *aggregated* assistant text is captured here so we can store
|
|
180
|
+
// it on `session.history` once the turn ends — the provider
|
|
181
|
+
// still returns the full `{ content: [...] }` object so tool_use
|
|
182
|
+
// blocks survive.
|
|
183
|
+
let streamedText = '';
|
|
184
|
+
const onTextChunk = (delta) => {
|
|
185
|
+
if (!delta) return;
|
|
186
|
+
streamedText += delta;
|
|
187
|
+
// Fire-and-forget — sessionUpdate returns a Promise but the SDK
|
|
188
|
+
// tolerates concurrent calls and we don't want each delta to
|
|
189
|
+
// block the next one.
|
|
190
|
+
this.connection.sessionUpdate({
|
|
191
|
+
sessionId: session.id,
|
|
192
|
+
update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: delta } },
|
|
193
|
+
}).catch(() => { /* connection errors surface elsewhere */ });
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const response = await provider.chat(messages, CARTO_TOOLS, signal, onTextChunk);
|
|
132
197
|
|
|
133
|
-
//
|
|
198
|
+
// After the stream completes, classify content blocks. Text was
|
|
199
|
+
// already forwarded chunk-by-chunk; here we only need to collect
|
|
200
|
+
// tool_use blocks and reconcile assistantText for history.
|
|
134
201
|
let assistantText = '';
|
|
135
202
|
let toolCalls = [];
|
|
136
203
|
|
|
137
204
|
for (const block of response.content) {
|
|
138
205
|
if (block.type === 'text' && block.text) {
|
|
139
206
|
assistantText += block.text;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
207
|
+
// If the provider didn't actually stream (e.g. test stub or
|
|
208
|
+
// a non-streaming compatible API), forward the text once here
|
|
209
|
+
// so the editor still sees it.
|
|
210
|
+
if (!streamedText) {
|
|
211
|
+
await this.connection.sessionUpdate({
|
|
212
|
+
sessionId: session.id,
|
|
213
|
+
update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: block.text } },
|
|
214
|
+
});
|
|
215
|
+
}
|
|
144
216
|
} else if (block.type === 'tool_use') {
|
|
145
217
|
toolCalls.push(block);
|
|
146
218
|
}
|