carto-md 2.0.7 → 2.0.9
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/README.md +290 -26
- package/docs/anci/v0.1-DRAFT.md +420 -0
- package/docs/scale.md +129 -0
- package/package.json +10 -5
- package/scripts/postinstall.js +413 -0
- package/src/acp/agent.js +5 -5
- package/src/acp/providers/index.js +2 -2
- package/src/agents/leiden.js +11 -17
- package/src/agents/scan-structure.js +1 -1
- 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/bitset.js +190 -0
- package/src/bitmap/index.js +121 -0
- package/src/bitmap/sidecar.js +545 -0
- package/src/bitmap/tools.js +310 -0
- package/src/cli/anci.js +237 -0
- package/src/cli/check.js +57 -0
- package/src/cli/index.js +28 -2
- package/src/cli/init.js +297 -65
- package/src/cli/inspect.js +295 -0
- package/src/cli/pr-impact.js +497 -0
- package/src/cli/serve.js +1 -1
- package/src/cli/watch.js +6 -0
- package/src/engine/worker.js +24 -4
- package/src/extractors/imports.js +176 -0
- package/src/extractors/languages/html.js +4 -1
- package/src/extractors/languages/javascript.js +5 -0
- package/src/extractors/languages/prisma.js +4 -1
- package/src/extractors/languages/python.js +5 -1
- package/src/extractors/languages/r.js +4 -1
- package/src/extractors/languages/typescript.js +2 -0
- package/src/extractors/tree-sitter-parser.js +15 -0
- package/src/mcp/change-plan.js +8 -8
- package/src/mcp/diff-parser.js +246 -0
- package/src/mcp/server-v2.js +489 -8
- package/src/mcp/validate.js +304 -0
- package/src/store/config-loader.js +77 -0
- package/src/store/sqlite-store.js +389 -4
- package/src/store/sync-v2.js +472 -97
- package/BENCHMARK_RESULTS.md +0 -34
package/src/cli/init.js
CHANGED
|
@@ -50,8 +50,8 @@ async function run(projectRoot) {
|
|
|
50
50
|
'utf-8'
|
|
51
51
|
);
|
|
52
52
|
|
|
53
|
-
// Install
|
|
54
|
-
|
|
53
|
+
// Install git hooks (silent freshness on git events)
|
|
54
|
+
installGitHooks(projectRoot);
|
|
55
55
|
|
|
56
56
|
// Run first sync — V2 SQLite-backed indexer.
|
|
57
57
|
await runSyncV2({
|
|
@@ -62,102 +62,334 @@ async function run(projectRoot) {
|
|
|
62
62
|
// Auto-wire MCP config into installed AI tools
|
|
63
63
|
wireIDEs(projectRoot);
|
|
64
64
|
|
|
65
|
-
console.log('[CARTO] AGENTS.md generated.
|
|
65
|
+
console.log('[CARTO] AGENTS.md generated. Index stays fresh via git hooks + lazy MCP re-parse.');
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* installGitHooks(projectRoot)
|
|
70
|
+
*
|
|
71
|
+
* Installs four git hooks that call `carto sync` quietly:
|
|
72
|
+
* - pre-commit fires on `git commit` before the commit lands
|
|
73
|
+
* - post-checkout fires after `git checkout` (branch switch, file checkout)
|
|
74
|
+
* - post-merge fires after `git merge` and `git pull`
|
|
75
|
+
* - post-rewrite fires after `git rebase` / `git commit --amend`
|
|
76
|
+
*
|
|
77
|
+
* Together these cover the 90% case of "user did a normal git operation,
|
|
78
|
+
* index should re-sync." The remaining 10% (uncommitted edits) is handled
|
|
79
|
+
* by the lazy mtime check in the MCP server.
|
|
80
|
+
*
|
|
81
|
+
* Hooks are idempotent: re-running `carto init` does not duplicate the
|
|
82
|
+
* `carto sync` line. If a user already has a hook for other reasons, we
|
|
83
|
+
* append non-destructively.
|
|
84
|
+
*/
|
|
85
|
+
function installGitHooks(projectRoot) {
|
|
69
86
|
const gitDir = path.join(projectRoot, '.git');
|
|
70
87
|
if (!fs.existsSync(gitDir)) return;
|
|
71
88
|
|
|
72
89
|
const hooksDir = path.join(gitDir, 'hooks');
|
|
73
90
|
if (!fs.existsSync(hooksDir)) fs.mkdirSync(hooksDir, { recursive: true });
|
|
74
91
|
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
// Hook bodies. `>/dev/null 2>&1 || true` keeps git fast and never blocks
|
|
93
|
+
// the user's git command if carto exits non-zero (e.g. transient lock).
|
|
94
|
+
const HOOK_NAMES = ['pre-commit', 'post-checkout', 'post-merge', 'post-rewrite'];
|
|
95
|
+
const HOOK_LINE = 'carto sync >/dev/null 2>&1 || true\n';
|
|
96
|
+
const MARKER = '# carto-md: keep index fresh on git events';
|
|
77
97
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
98
|
+
const installed = [];
|
|
99
|
+
const skipped = [];
|
|
100
|
+
|
|
101
|
+
for (const name of HOOK_NAMES) {
|
|
102
|
+
const hookPath = path.join(hooksDir, name);
|
|
103
|
+
|
|
104
|
+
if (fs.existsSync(hookPath)) {
|
|
105
|
+
const existing = fs.readFileSync(hookPath, 'utf-8');
|
|
106
|
+
if (existing.includes('carto sync')) {
|
|
107
|
+
skipped.push(name);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Append to user's existing hook without clobbering it
|
|
111
|
+
fs.appendFileSync(hookPath, `\n${MARKER}\n${HOOK_LINE}`);
|
|
112
|
+
} else {
|
|
113
|
+
fs.writeFileSync(hookPath, `#!/bin/sh\n${MARKER}\n${HOOK_LINE}`);
|
|
83
114
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
|
|
116
|
+
try { fs.chmodSync(hookPath, 0o755); } catch {}
|
|
117
|
+
installed.push(name);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (installed.length > 0) {
|
|
121
|
+
console.log(`[CARTO] Git hooks installed: ${installed.join(', ')}`);
|
|
122
|
+
}
|
|
123
|
+
if (skipped.length > 0 && installed.length === 0) {
|
|
124
|
+
console.log(`[CARTO] Git hooks already installed (${skipped.join(', ')}).`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Back-compat alias — older code/tests import installGitHook.
|
|
129
|
+
const installGitHook = installGitHooks;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* binaryExists(name) → boolean
|
|
133
|
+
*
|
|
134
|
+
* Best-effort cross-platform check for whether a CLI is on the user's PATH.
|
|
135
|
+
* Uses `which` on macOS/Linux and `where` on Windows. Returns false on any
|
|
136
|
+
* error so the caller treats "couldn't tell" as "not present" — we'd rather
|
|
137
|
+
* fall back to dir-based detection than write a config the user didn't want.
|
|
138
|
+
*
|
|
139
|
+
* Test escape hatch: `CARTO_TEST_BINARY_OVERRIDES=claude=1,codex=0` lets the
|
|
140
|
+
* Init-flow suite drive each tool's detection deterministically without
|
|
141
|
+
* depending on what's installed on the dev box. Comma-separated list of
|
|
142
|
+
* `name=0|1` pairs; any binary not listed falls through to the real check.
|
|
143
|
+
*/
|
|
144
|
+
function binaryExists(name) {
|
|
145
|
+
const override = process.env.CARTO_TEST_BINARY_OVERRIDES;
|
|
146
|
+
if (override) {
|
|
147
|
+
for (const pair of override.split(',')) {
|
|
148
|
+
const [n, v] = pair.split('=');
|
|
149
|
+
if (n && n.trim() === name) return v && v.trim() === '1';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const { spawnSync } = require('child_process');
|
|
154
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
155
|
+
const r = spawnSync(cmd, [name], { stdio: 'ignore' });
|
|
156
|
+
return r.status === 0;
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* claudeDesktopConfigPath() → string
|
|
164
|
+
*
|
|
165
|
+
* Platform-specific Claude Desktop MCP config location.
|
|
166
|
+
* macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
167
|
+
* Windows: %APPDATA%\Claude\claude_desktop_config.json
|
|
168
|
+
* Linux: ~/.config/Claude/claude_desktop_config.json (unofficial — Anthropic
|
|
169
|
+
* doesn't ship a Linux build, but community wrappers/Wine installs
|
|
170
|
+
* put it here, so we honor the path if the dir exists.)
|
|
171
|
+
*/
|
|
172
|
+
function claudeDesktopConfigPath() {
|
|
173
|
+
const os = require('os');
|
|
174
|
+
const home = os.homedir();
|
|
175
|
+
if (process.platform === 'darwin') {
|
|
176
|
+
return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
177
|
+
}
|
|
178
|
+
if (process.platform === 'win32') {
|
|
179
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
180
|
+
return path.join(appData, 'Claude', 'claude_desktop_config.json');
|
|
181
|
+
}
|
|
182
|
+
return path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* mergeMcpJson(filePath, key, entry) — JSON-style MCP config helper.
|
|
187
|
+
*
|
|
188
|
+
* Reads `filePath` (creates {} if missing), merges `entry` under `[key].carto`
|
|
189
|
+
* (where `key` is `mcpServers` or `servers` depending on the host), writes it
|
|
190
|
+
* back. Used by every JSON-based MCP host: Cursor, Kiro, Claude Code, Claude
|
|
191
|
+
* Desktop, Windsurf, VS Code Copilot.
|
|
192
|
+
*
|
|
193
|
+
* Defensive against malformed existing JSON — a single bad config from another
|
|
194
|
+
* tool can't block the rest of the wiring.
|
|
195
|
+
*/
|
|
196
|
+
function mergeMcpJson(filePath, key, entry) {
|
|
197
|
+
let config = {};
|
|
198
|
+
if (fs.existsSync(filePath)) {
|
|
199
|
+
try {
|
|
200
|
+
config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
201
|
+
if (!config || typeof config !== 'object') config = {};
|
|
202
|
+
} catch {
|
|
203
|
+
// Existing file is malformed — start fresh rather than clobbering blindly.
|
|
204
|
+
// Print a warning so the user knows we replaced it.
|
|
205
|
+
console.warn(`[CARTO] ${filePath} was not valid JSON — rewriting.`);
|
|
206
|
+
config = {};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (!config[key] || typeof config[key] !== 'object') config[key] = {};
|
|
210
|
+
config[key].carto = entry;
|
|
211
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
212
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* upsertCodexToml(filePath, projectRoot) — Append/replace a `[mcp_servers.carto]`
|
|
217
|
+
* block in Codex's `config.toml`. We don't pull in a TOML parser dep — Codex's
|
|
218
|
+
* MCP block has a stable, simple shape, so a small regex+rewrite is enough.
|
|
219
|
+
*
|
|
220
|
+
* Behavior:
|
|
221
|
+
* - File missing → write a fresh file with just the carto block.
|
|
222
|
+
* - File present, no carto block → append the block at the end (preserves
|
|
223
|
+
* all existing content + comments).
|
|
224
|
+
* - File present, carto block present → replace it in-place via regex.
|
|
225
|
+
*
|
|
226
|
+
* The block we write is exactly:
|
|
227
|
+
*
|
|
228
|
+
* [mcp_servers.carto]
|
|
229
|
+
* command = "carto"
|
|
230
|
+
* args = ["serve"]
|
|
231
|
+
* cwd = "<project>"
|
|
232
|
+
* enabled = true
|
|
233
|
+
*/
|
|
234
|
+
function upsertCodexToml(filePath, projectRoot) {
|
|
235
|
+
const tomlEscape = (s) => '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
236
|
+
const block = [
|
|
237
|
+
'[mcp_servers.carto]',
|
|
238
|
+
`command = ${tomlEscape('carto')}`,
|
|
239
|
+
`args = [${tomlEscape('serve')}]`,
|
|
240
|
+
`cwd = ${tomlEscape(projectRoot)}`,
|
|
241
|
+
'enabled = true',
|
|
242
|
+
'',
|
|
243
|
+
].join('\n');
|
|
244
|
+
|
|
245
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
246
|
+
|
|
247
|
+
if (!fs.existsSync(filePath)) {
|
|
248
|
+
fs.writeFileSync(filePath, block, 'utf-8');
|
|
249
|
+
return;
|
|
87
250
|
}
|
|
88
251
|
|
|
89
|
-
fs.
|
|
90
|
-
|
|
252
|
+
const existing = fs.readFileSync(filePath, 'utf-8');
|
|
253
|
+
// Match the carto section header AND its body up to the next [section]
|
|
254
|
+
// header or end of file. Note: NO `m` flag — we want `$` to mean
|
|
255
|
+
// end-of-input, not end-of-line. The header `[mcp_servers.carto]` is
|
|
256
|
+
// unique per TOML file, so we don't need a line-start anchor.
|
|
257
|
+
const sectionRe = /\[mcp_servers\.carto\][\s\S]*?(?=\n\[|$)/;
|
|
258
|
+
let next;
|
|
259
|
+
if (sectionRe.test(existing)) {
|
|
260
|
+
next = existing.replace(sectionRe, block.trimEnd());
|
|
261
|
+
// Ensure trailing newline.
|
|
262
|
+
if (!next.endsWith('\n')) next += '\n';
|
|
263
|
+
} else {
|
|
264
|
+
// Append. Make sure existing content ends with a newline before the new block.
|
|
265
|
+
next = existing.endsWith('\n') ? existing + '\n' + block : existing + '\n\n' + block;
|
|
266
|
+
}
|
|
267
|
+
fs.writeFileSync(filePath, next, 'utf-8');
|
|
91
268
|
}
|
|
92
269
|
|
|
93
270
|
function wireIDEs(projectRoot) {
|
|
94
271
|
const os = require('os');
|
|
95
272
|
const home = os.homedir();
|
|
96
273
|
const wired = [];
|
|
274
|
+
const errors = [];
|
|
97
275
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
|
|
276
|
+
// ─── Cursor ────────────────────────────────────────────────────────
|
|
277
|
+
// Detection: `~/.cursor/` exists. Path stable across macOS/Linux/Windows.
|
|
278
|
+
if (fs.existsSync(path.join(home, '.cursor'))) {
|
|
279
|
+
try {
|
|
280
|
+
mergeMcpJson(
|
|
281
|
+
path.join(home, '.cursor', 'mcp.json'),
|
|
282
|
+
'mcpServers',
|
|
283
|
+
{ command: 'carto', args: ['serve'], cwd: projectRoot }
|
|
284
|
+
);
|
|
285
|
+
wired.push('Cursor');
|
|
286
|
+
} catch (err) { errors.push(`Cursor: ${err.message}`); }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ─── Claude Code ───────────────────────────────────────────────────
|
|
290
|
+
// Detection: `claude` binary OR `~/.claude/` directory exists. Without
|
|
291
|
+
// this gating we'd write `.mcp.json` into every project regardless of
|
|
292
|
+
// whether the user has Claude Code installed.
|
|
293
|
+
if (binaryExists('claude') || fs.existsSync(path.join(home, '.claude'))) {
|
|
294
|
+
try {
|
|
295
|
+
mergeMcpJson(
|
|
296
|
+
path.join(projectRoot, '.mcp.json'),
|
|
297
|
+
'mcpServers',
|
|
298
|
+
{ command: 'carto', args: ['serve'] }
|
|
299
|
+
);
|
|
300
|
+
wired.push('Claude Code');
|
|
301
|
+
} catch (err) { errors.push(`Claude Code: ${err.message}`); }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ─── Kiro ──────────────────────────────────────────────────────────
|
|
115
305
|
if (fs.existsSync(path.join(home, '.kiro'))) {
|
|
116
306
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
config.mcpServers = config.mcpServers || {};
|
|
123
|
-
config.mcpServers.carto = { command: 'carto', args: ['serve'], cwd: projectRoot };
|
|
124
|
-
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
307
|
+
mergeMcpJson(
|
|
308
|
+
path.join(home, '.kiro', 'settings', 'mcp.json'),
|
|
309
|
+
'mcpServers',
|
|
310
|
+
{ command: 'carto', args: ['serve'], cwd: projectRoot }
|
|
311
|
+
);
|
|
125
312
|
wired.push('Kiro');
|
|
126
|
-
} catch {}
|
|
313
|
+
} catch (err) { errors.push(`Kiro: ${err.message}`); }
|
|
127
314
|
}
|
|
128
315
|
|
|
129
|
-
// Claude Desktop
|
|
130
|
-
|
|
131
|
-
|
|
316
|
+
// ─── Claude Desktop (cross-platform) ───────────────────────────────
|
|
317
|
+
// macOS: ~/Library/Application Support/Claude/
|
|
318
|
+
// Windows: %APPDATA%\Claude\
|
|
319
|
+
// Linux: ~/.config/Claude/ (unofficial)
|
|
320
|
+
const claudeCfgPath = claudeDesktopConfigPath();
|
|
321
|
+
if (fs.existsSync(path.dirname(claudeCfgPath))) {
|
|
132
322
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
fs.writeFileSync(claudeConfig, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
323
|
+
mergeMcpJson(
|
|
324
|
+
claudeCfgPath,
|
|
325
|
+
'mcpServers',
|
|
326
|
+
{ command: 'carto', args: ['serve'], cwd: projectRoot }
|
|
327
|
+
);
|
|
139
328
|
wired.push('Claude Desktop');
|
|
140
|
-
} catch {}
|
|
329
|
+
} catch (err) { errors.push(`Claude Desktop: ${err.message}`); }
|
|
141
330
|
}
|
|
142
331
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
332
|
+
// ─── Codex ─────────────────────────────────────────────────────────
|
|
333
|
+
// Detection: `codex` binary OR `~/.codex/` directory exists.
|
|
334
|
+
// Format: TOML with `[mcp_servers.carto]` block.
|
|
335
|
+
if (binaryExists('codex') || fs.existsSync(path.join(home, '.codex'))) {
|
|
146
336
|
try {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
337
|
+
upsertCodexToml(path.join(home, '.codex', 'config.toml'), projectRoot);
|
|
338
|
+
wired.push('Codex');
|
|
339
|
+
} catch (err) { errors.push(`Codex: ${err.message}`); }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ─── Windsurf ──────────────────────────────────────────────────────
|
|
343
|
+
// Path: ~/.codeium/windsurf/mcp_config.json (NOT ~/.windsurf/, which
|
|
344
|
+
// older docs sometimes show — the real path lives under .codeium).
|
|
345
|
+
if (fs.existsSync(path.join(home, '.codeium', 'windsurf'))
|
|
346
|
+
|| fs.existsSync(path.join(home, '.codeium'))) {
|
|
347
|
+
try {
|
|
348
|
+
mergeMcpJson(
|
|
349
|
+
path.join(home, '.codeium', 'windsurf', 'mcp_config.json'),
|
|
350
|
+
'mcpServers',
|
|
351
|
+
{ command: 'carto', args: ['serve'], cwd: projectRoot }
|
|
352
|
+
);
|
|
353
|
+
wired.push('Windsurf');
|
|
354
|
+
} catch (err) { errors.push(`Windsurf: ${err.message}`); }
|
|
155
355
|
}
|
|
156
356
|
|
|
357
|
+
// ─── VS Code Copilot ───────────────────────────────────────────────
|
|
358
|
+
// Detection: `code` binary on PATH (most reliable; user-profile path
|
|
359
|
+
// varies too much across macOS/Linux/Windows + Insiders/stable to be
|
|
360
|
+
// a good detection signal). Schema differs from the standard MCP shape:
|
|
361
|
+
// VS Code uses `servers` (not `mcpServers`) and requires `type: stdio`.
|
|
362
|
+
if (binaryExists('code')) {
|
|
363
|
+
try {
|
|
364
|
+
mergeMcpJson(
|
|
365
|
+
path.join(projectRoot, '.vscode', 'mcp.json'),
|
|
366
|
+
'servers',
|
|
367
|
+
{ type: 'stdio', command: 'carto', args: ['serve'] }
|
|
368
|
+
);
|
|
369
|
+
wired.push('VS Code Copilot');
|
|
370
|
+
} catch (err) { errors.push(`VS Code Copilot: ${err.message}`); }
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ─── Reporting ─────────────────────────────────────────────────────
|
|
157
374
|
if (wired.length > 0) {
|
|
158
|
-
console.log(`[CARTO] MCP wired into: ${wired.join(', ')}`);
|
|
159
|
-
|
|
375
|
+
console.log(`[CARTO] MCP auto-wired into: ${wired.join(', ')}`);
|
|
376
|
+
} else {
|
|
377
|
+
console.log('[CARTO] No supported AI tools detected for auto-wiring.');
|
|
378
|
+
console.log('[CARTO] See https://github.com/theanshsonkar/carto#use-it-with-your-ai-tool for manual config.');
|
|
379
|
+
}
|
|
380
|
+
if (errors.length > 0) {
|
|
381
|
+
for (const e of errors) console.warn(`[CARTO] Could not wire: ${e}`);
|
|
160
382
|
}
|
|
161
383
|
}
|
|
162
384
|
|
|
163
385
|
module.exports = { run };
|
|
386
|
+
|
|
387
|
+
// Test-only exports — surfaced for the Init flow suite to assert detection
|
|
388
|
+
// + format behavior in isolation. Not part of the public API.
|
|
389
|
+
module.exports._internal = {
|
|
390
|
+
binaryExists,
|
|
391
|
+
claudeDesktopConfigPath,
|
|
392
|
+
mergeMcpJson,
|
|
393
|
+
upsertCodexToml,
|
|
394
|
+
wireIDEs,
|
|
395
|
+
};
|