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.
Files changed (172) hide show
  1. package/CONTRIBUTING.md +19 -15
  2. package/README.md +192 -415
  3. package/docs/api/README.md +99 -0
  4. package/docs/api/did_we_discuss_this.md +34 -0
  5. package/docs/api/dismiss_suggestion.md +34 -0
  6. package/docs/api/explain_change_in_natural_language.md +33 -0
  7. package/docs/api/find_consumers_of_api.md +34 -0
  8. package/docs/api/get_action_patterns.md +32 -0
  9. package/docs/api/get_active_drift.md +32 -0
  10. package/docs/api/get_active_suggestions.md +25 -0
  11. package/docs/api/get_ai_cost_attribution.md +32 -0
  12. package/docs/api/get_arch_events.md +42 -0
  13. package/docs/api/get_architectural_drift.md +37 -0
  14. package/docs/api/get_architecture.md +25 -0
  15. package/docs/api/get_blast_radius.md +34 -0
  16. package/docs/api/get_canonical_pattern.md +39 -0
  17. package/docs/api/get_change_plan.md +34 -0
  18. package/docs/api/get_change_velocity.md +32 -0
  19. package/docs/api/get_churn_vs_blast_radius.md +32 -0
  20. package/docs/api/get_complexity_trend.md +39 -0
  21. package/docs/api/get_context.md +34 -0
  22. package/docs/api/get_conventions.md +32 -0
  23. package/docs/api/get_cross_domain.md +25 -0
  24. package/docs/api/get_cross_language_call_graph.md +25 -0
  25. package/docs/api/get_cross_repo_blast_radius.md +34 -0
  26. package/docs/api/get_cross_team_coupling.md +25 -0
  27. package/docs/api/get_data_flow.md +33 -0
  28. package/docs/api/get_dead_code_with_confidence.md +32 -0
  29. package/docs/api/get_decision_log.md +32 -0
  30. package/docs/api/get_dependency_surface.md +25 -0
  31. package/docs/api/get_domain.md +34 -0
  32. package/docs/api/get_domain_evolution.md +39 -0
  33. package/docs/api/get_domain_health.md +32 -0
  34. package/docs/api/get_domains_list.md +25 -0
  35. package/docs/api/get_drift_digest.md +32 -0
  36. package/docs/api/get_env_vars.md +32 -0
  37. package/docs/api/get_evolution_delta.md +36 -0
  38. package/docs/api/get_file_ownership.md +33 -0
  39. package/docs/api/get_file_summary.md +34 -0
  40. package/docs/api/get_high_impact_files.md +32 -0
  41. package/docs/api/get_hot_in_prod_no_tests.md +34 -0
  42. package/docs/api/get_hotspot_files.md +37 -0
  43. package/docs/api/get_iac_resources.md +25 -0
  44. package/docs/api/get_interface_contract.md +33 -0
  45. package/docs/api/get_intervention_history.md +32 -0
  46. package/docs/api/get_invariants.md +37 -0
  47. package/docs/api/get_llm_enrichment.md +33 -0
  48. package/docs/api/get_microservice_cut_points.md +32 -0
  49. package/docs/api/get_microservices_migration_cut_points.md +25 -0
  50. package/docs/api/get_minimal_context_for_intent.md +39 -0
  51. package/docs/api/get_models.md +32 -0
  52. package/docs/api/get_neighbors.md +39 -0
  53. package/docs/api/get_org_architecture.md +25 -0
  54. package/docs/api/get_org_domain_mapping.md +25 -0
  55. package/docs/api/get_pending_decisions.md +32 -0
  56. package/docs/api/get_predictive_risk.md +32 -0
  57. package/docs/api/get_progressive_disclosure_tree.md +25 -0
  58. package/docs/api/get_recent_decisions.md +37 -0
  59. package/docs/api/get_risk_weighted_blast_radius.md +32 -0
  60. package/docs/api/get_routes.md +25 -0
  61. package/docs/api/get_safety_checklist.md +33 -0
  62. package/docs/api/get_semantic_diff.md +34 -0
  63. package/docs/api/get_service_boundary_violations.md +25 -0
  64. package/docs/api/get_service_dependency_graph.md +25 -0
  65. package/docs/api/get_session_context.md +32 -0
  66. package/docs/api/get_similar_patterns.md +39 -0
  67. package/docs/api/get_stale_docs.md +25 -0
  68. package/docs/api/get_structure.md +25 -0
  69. package/docs/api/get_temporal_context.md +34 -0
  70. package/docs/api/get_test_coverage_map.md +25 -0
  71. package/docs/api/get_token_budget_report.md +36 -0
  72. package/docs/api/get_upgrade_risk.md +25 -0
  73. package/docs/api/get_working_memory.md +25 -0
  74. package/docs/api/ingest_otlp_traces.md +34 -0
  75. package/docs/api/scaffold_for_intent.md +34 -0
  76. package/docs/api/search_routes.md +34 -0
  77. package/docs/api/simulate_change_impact.md +37 -0
  78. package/docs/api/validate_change.md +39 -0
  79. package/docs/api/validate_diff.md +39 -0
  80. package/docs/concepts/anci.md +87 -0
  81. package/docs/concepts/blast-radius.md +66 -0
  82. package/docs/concepts/domains.md +91 -0
  83. package/docs/concepts/import-graph.md +102 -0
  84. package/docs/concepts/mcp-integration.md +148 -0
  85. package/docs/guides/adding-feature-safely.md +101 -0
  86. package/docs/guides/ci-integration.md +175 -0
  87. package/docs/guides/monorepo-setup.md +121 -0
  88. package/docs/guides/onboarding-new-engineer.md +121 -0
  89. package/docs/guides/pre-merge-review.md +139 -0
  90. package/docs/migration/v1-to-v2.md +110 -0
  91. package/docs/quickstart.md +95 -0
  92. package/docs/scale.md +2 -2
  93. package/docs/troubleshooting.md +180 -0
  94. package/package.json +10 -4
  95. package/scripts/gen-api-docs.js +170 -0
  96. package/src/acp/agent.js +83 -11
  97. package/src/acp/config.js +64 -0
  98. package/src/acp/persistence.js +146 -0
  99. package/src/acp/providers/anthropic.js +179 -27
  100. package/src/acp/providers/index.js +15 -2
  101. package/src/acp/providers/openai.js +164 -38
  102. package/src/acp/providers/sse.js +82 -0
  103. package/src/acp/safety.js +128 -0
  104. package/src/acp/session.js +73 -0
  105. package/src/adjacent/call-graph.js +170 -0
  106. package/src/adjacent/iac.js +167 -0
  107. package/src/adjacent/llm-enrich.js +35 -0
  108. package/src/adjacent/runtime.js +216 -0
  109. package/src/adjacent/semantic-diff.js +143 -0
  110. package/src/agents/scan-structure.js +1 -1
  111. package/src/ai/context-builder.js +215 -0
  112. package/src/ai/retrieval/lexical.js +122 -0
  113. package/src/ai/retrieval/rrf.js +121 -0
  114. package/src/ai/retrieval/semantic.js +35 -0
  115. package/src/ai/retrieval/structural.js +82 -0
  116. package/src/ai/tools.js +423 -0
  117. package/src/anci/emit.js +2 -2
  118. package/src/bitmap/index.js +1 -1
  119. package/src/bitmap/tools.js +2 -2
  120. package/src/brain/conventions/index.js +185 -0
  121. package/src/brain/index.js +31 -0
  122. package/src/brain/invariants/index.js +252 -0
  123. package/src/brain/procedural/index.js +181 -0
  124. package/src/brain/suggestions/index.js +153 -0
  125. package/src/brain/working/index.js +170 -0
  126. package/src/cli/check.js +47 -1
  127. package/src/cli/diff.js +83 -0
  128. package/src/cli/doctor.js +270 -0
  129. package/src/cli/explain.js +61 -0
  130. package/src/cli/index.js +101 -0
  131. package/src/cli/init.js +143 -5
  132. package/src/cli/org.js +172 -0
  133. package/src/cli/pr-impact.js +59 -2
  134. package/src/cli/serve.js +1 -1
  135. package/src/cli/status.js +211 -0
  136. package/src/cli/sync.js +2 -2
  137. package/src/cli/temporal.js +188 -0
  138. package/src/cli/validate.js +201 -0
  139. package/src/cli/watch.js +4 -4
  140. package/src/cli/why.js +101 -0
  141. package/src/extractors/frameworks.js +236 -0
  142. package/src/extractors/languages/dart.js +138 -0
  143. package/src/extractors/languages/go.js +11 -1
  144. package/src/extractors/languages/javascript.js +13 -3
  145. package/src/extractors/languages/kotlin.js +169 -0
  146. package/src/extractors/languages/php.js +195 -0
  147. package/src/extractors/languages/python.js +12 -1
  148. package/src/extractors/languages/swift.js +140 -0
  149. package/src/extractors/languages/typescript.js +15 -2
  150. package/src/extractors/plugin-api.js +102 -0
  151. package/src/mcp/files-without-tests.js +285 -0
  152. package/src/mcp/middleware/index.js +451 -0
  153. package/src/mcp/server.js +2292 -0
  154. package/src/mcp/validate.js +1 -1
  155. package/src/org/detect.js +262 -0
  156. package/src/org/queries.js +144 -0
  157. package/src/org/store.js +173 -0
  158. package/src/org/sync.js +106 -0
  159. package/src/predictive/cut-points.js +83 -0
  160. package/src/predictive/drift-digest.js +88 -0
  161. package/src/predictive/ownership.js +145 -0
  162. package/src/predictive/risk-score.js +121 -0
  163. package/src/predictive/validate-change.js +55 -0
  164. package/src/store/store-adapter.js +3 -3
  165. package/src/store/{sync-v2.js → sync.js} +53 -13
  166. package/src/temporal/backfill.js +211 -0
  167. package/src/temporal/delta.js +85 -0
  168. package/src/temporal/events.js +180 -0
  169. package/src/temporal/queries.js +358 -0
  170. package/src/temporal/snapshot.js +151 -0
  171. package/src/temporal/store.js +400 -0
  172. 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 `runSyncV2` from a wiped `.carto/` directory.
12
- - **Sync time** — second `runSyncV2` immediately after, no files changed (mtime+hash skip path).
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.9",
4
- "description": "Structural intelligence layer for AI coding tools. Indexes your codebase into SQLite — routes, models, import graph, blast radius, domains — and exposes 22 MCP tools for Kiro, Cursor, and Claude.",
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
- "twitter": "https://x.com/theanshsonkar"
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
- "zed"
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: false,
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 session = this.sessions.create(params.cwd || process.cwd());
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 stubs
81
- async listSessions(_params) { return { sessions: [] }; }
82
- async closeSession(_params) { return {}; }
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
- const response = await provider.chat(messages, CARTO_TOOLS, signal);
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
- // Stream text content
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
- await this.connection.sessionUpdate({
141
- sessionId: session.id,
142
- update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: block.text } },
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
  }