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
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// scripts/postinstall.js
|
|
5
|
+
//
|
|
6
|
+
// Three-step resilience flow:
|
|
7
|
+
// 1. Probe each tree-sitter grammar via require(). All ok → exit silent.
|
|
8
|
+
// 2. For each failure, try to fetch a prebuilt tarball from carto-md's
|
|
9
|
+
// GitHub Release and extract into node_modules/<pkg>/. Re-probe.
|
|
10
|
+
// 3. Anything still broken → print OS-specific build guidance.
|
|
11
|
+
//
|
|
12
|
+
// Always exits 0 — install must never fail because of this script.
|
|
13
|
+
//
|
|
14
|
+
// Env opt-outs:
|
|
15
|
+
// CARTO_NO_POSTINSTALL=1 skip the entire script
|
|
16
|
+
// CARTO_NO_PREBUILD=1 skip step 2 (go straight from probe to guidance)
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const os = require('os');
|
|
21
|
+
const https = require('https');
|
|
22
|
+
const { spawnSync } = require('child_process');
|
|
23
|
+
|
|
24
|
+
// ---- Static metadata ----
|
|
25
|
+
|
|
26
|
+
const GRAMMARS = [
|
|
27
|
+
{ pkg: 'tree-sitter-javascript', langs: 'JavaScript' },
|
|
28
|
+
{ pkg: 'tree-sitter-typescript', langs: 'TypeScript' },
|
|
29
|
+
{ pkg: 'tree-sitter-python', langs: 'Python' },
|
|
30
|
+
{ pkg: 'tree-sitter-go', langs: 'Go' },
|
|
31
|
+
{ pkg: 'tree-sitter-rust', langs: 'Rust' },
|
|
32
|
+
{ pkg: 'tree-sitter-java', langs: 'Java' },
|
|
33
|
+
{ pkg: 'tree-sitter-cpp', langs: 'C/C++' },
|
|
34
|
+
{ pkg: 'tree-sitter-c-sharp', langs: 'C#' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// tree-sitter core lives in dependencies (not optionalDependencies). If its
|
|
38
|
+
// compile fails the whole `npm install` fails before this script runs, so
|
|
39
|
+
// we don't probe it — but the prebuilds workflow tarballs it too, in case
|
|
40
|
+
// future logic wants to recover from a corrupted node_modules.
|
|
41
|
+
const CORE_PKG = 'tree-sitter';
|
|
42
|
+
|
|
43
|
+
const DEFAULT_RELEASE_BASE_URL =
|
|
44
|
+
'https://github.com/theanshsonkar/carto/releases/download';
|
|
45
|
+
|
|
46
|
+
// ---- Pure helpers (testable) ----
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* detectLibc() → 'glibc' | 'musl' | null
|
|
50
|
+
* Linux only. macOS / Windows return null because libc is part of the OS.
|
|
51
|
+
*/
|
|
52
|
+
function detectLibc() {
|
|
53
|
+
if (process.platform !== 'linux') return null;
|
|
54
|
+
try {
|
|
55
|
+
if (fs.existsSync('/etc/alpine-release')) return 'musl';
|
|
56
|
+
} catch { /* ignore */ }
|
|
57
|
+
try {
|
|
58
|
+
const report =
|
|
59
|
+
process.report && typeof process.report.getReport === 'function'
|
|
60
|
+
? process.report.getReport()
|
|
61
|
+
: null;
|
|
62
|
+
const glibc = report && report.header && report.header.glibcVersionRuntime;
|
|
63
|
+
if (glibc) return 'glibc';
|
|
64
|
+
// process.report exists but no glibc field → musl runtime.
|
|
65
|
+
if (report && report.header) return 'musl';
|
|
66
|
+
} catch { /* ignore */ }
|
|
67
|
+
// Conservative default: assume glibc. If wrong, fetch will 404 and the
|
|
68
|
+
// caller falls through to build-toolchain guidance.
|
|
69
|
+
return 'glibc';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* getPlatformInfo() → { platform, arch, libc, key }
|
|
74
|
+
* key examples:
|
|
75
|
+
* 'linux-x64-glibc' 'linux-x64-musl'
|
|
76
|
+
* 'darwin-arm64' 'darwin-x64'
|
|
77
|
+
* 'win32-x64'
|
|
78
|
+
*/
|
|
79
|
+
function getPlatformInfo() {
|
|
80
|
+
const platform = process.platform;
|
|
81
|
+
const arch = process.arch;
|
|
82
|
+
const libc = detectLibc();
|
|
83
|
+
const key =
|
|
84
|
+
platform === 'linux'
|
|
85
|
+
? `linux-${arch}${libc ? '-' + libc : ''}`
|
|
86
|
+
: `${platform}-${arch}`;
|
|
87
|
+
return { platform, arch, libc, key };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* assetName({ pkg, pkgVersion, platformKey }) → string
|
|
92
|
+
* tree-sitter-typescript-v0.23.2-linux-x64-glibc.tar.gz
|
|
93
|
+
* tree-sitter-v0.25.0-darwin-arm64.tar.gz
|
|
94
|
+
*/
|
|
95
|
+
function assetName({ pkg, pkgVersion, platformKey }) {
|
|
96
|
+
return `${pkg}-v${pkgVersion}-${platformKey}.tar.gz`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* assetUrl({ cartoVersion, name, baseUrl? }) → string
|
|
101
|
+
*/
|
|
102
|
+
function assetUrl({ cartoVersion, name, baseUrl }) {
|
|
103
|
+
const base = (baseUrl || DEFAULT_RELEASE_BASE_URL).replace(/\/+$/, '');
|
|
104
|
+
return `${base}/v${cartoVersion}/${name}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* probeGrammars(requireFn?) → Array<{ pkg, langs, ok }>
|
|
109
|
+
* Tests pass a fake require function; production passes nothing.
|
|
110
|
+
*/
|
|
111
|
+
function probeGrammars(requireFn) {
|
|
112
|
+
const r = requireFn || require;
|
|
113
|
+
return GRAMMARS.map((g) => {
|
|
114
|
+
let ok = true;
|
|
115
|
+
try { r(g.pkg); } catch { ok = false; }
|
|
116
|
+
return { pkg: g.pkg, langs: g.langs, ok };
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* selectFailures(probeResults) → Array<{ pkg, langs }>
|
|
122
|
+
*/
|
|
123
|
+
function selectFailures(probeResults) {
|
|
124
|
+
return probeResults.filter((r) => !r.ok).map((r) => ({ pkg: r.pkg, langs: r.langs }));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* resolveExpectedVersion(packageRoot, pkg) → string | null
|
|
129
|
+
* Tries the installed copy first, then carto's own pinned constraint.
|
|
130
|
+
* Returns null only if both lookups fail (or carto pins a non-exact range).
|
|
131
|
+
*/
|
|
132
|
+
function resolveExpectedVersion(packageRoot, pkg) {
|
|
133
|
+
// 1. Installed copy (rare in the failure path, but possible if the binary
|
|
134
|
+
// is missing despite the JS being present — e.g., a botched rebuild).
|
|
135
|
+
try {
|
|
136
|
+
const installed = path.join(packageRoot, 'node_modules', pkg, 'package.json');
|
|
137
|
+
if (fs.existsSync(installed)) {
|
|
138
|
+
const meta = JSON.parse(fs.readFileSync(installed, 'utf-8'));
|
|
139
|
+
if (meta && meta.version) return meta.version;
|
|
140
|
+
}
|
|
141
|
+
} catch { /* ignore */ }
|
|
142
|
+
|
|
143
|
+
// 2. Constraint from carto's own package.json. Carto pins exact versions
|
|
144
|
+
// (e.g. "0.23.2"), so the raw string IS the version. If a future
|
|
145
|
+
// maintainer changes the pin to a range, this returns null and we
|
|
146
|
+
// skip the prebuild path for that package.
|
|
147
|
+
try {
|
|
148
|
+
const meta = JSON.parse(
|
|
149
|
+
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'),
|
|
150
|
+
);
|
|
151
|
+
const constraint =
|
|
152
|
+
(meta.optionalDependencies && meta.optionalDependencies[pkg]) ||
|
|
153
|
+
(meta.dependencies && meta.dependencies[pkg]);
|
|
154
|
+
if (constraint && /^[0-9]+\.[0-9]+\.[0-9]+$/.test(constraint)) {
|
|
155
|
+
return constraint;
|
|
156
|
+
}
|
|
157
|
+
} catch { /* ignore */ }
|
|
158
|
+
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---- I/O wrappers (stubbable via runMain options) ----
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* fetchToFile(url, destPath, opts?) → Promise<void>
|
|
166
|
+
* Throws on non-2xx (including 404). Follows redirects up to maxRedirects.
|
|
167
|
+
*/
|
|
168
|
+
function fetchToFile(url, destPath, opts) {
|
|
169
|
+
const timeoutMs = (opts && opts.timeoutMs) || 30_000;
|
|
170
|
+
const maxRedirects = (opts && opts.maxRedirects) != null ? opts.maxRedirects : 5;
|
|
171
|
+
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
const visited = new Set();
|
|
174
|
+
|
|
175
|
+
const go = (currentUrl, redirectsLeft) => {
|
|
176
|
+
if (visited.has(currentUrl)) {
|
|
177
|
+
return reject(new Error(`redirect loop at ${currentUrl}`));
|
|
178
|
+
}
|
|
179
|
+
visited.add(currentUrl);
|
|
180
|
+
|
|
181
|
+
const req = https.get(currentUrl, { timeout: timeoutMs }, (res) => {
|
|
182
|
+
const sc = res.statusCode || 0;
|
|
183
|
+
if (sc >= 300 && sc < 400 && res.headers.location) {
|
|
184
|
+
if (redirectsLeft <= 0) {
|
|
185
|
+
res.resume();
|
|
186
|
+
return reject(new Error(`too many redirects from ${url}`));
|
|
187
|
+
}
|
|
188
|
+
const next = new URL(res.headers.location, currentUrl).toString();
|
|
189
|
+
res.resume();
|
|
190
|
+
return go(next, redirectsLeft - 1);
|
|
191
|
+
}
|
|
192
|
+
if (sc < 200 || sc >= 300) {
|
|
193
|
+
res.resume();
|
|
194
|
+
return reject(new Error(`HTTP ${sc} fetching ${currentUrl}`));
|
|
195
|
+
}
|
|
196
|
+
const out = fs.createWriteStream(destPath);
|
|
197
|
+
res.pipe(out);
|
|
198
|
+
out.on('finish', () =>
|
|
199
|
+
out.close((err) => (err ? reject(err) : resolve())),
|
|
200
|
+
);
|
|
201
|
+
out.on('error', (err) => {
|
|
202
|
+
try { fs.unlinkSync(destPath); } catch { /* ignore */ }
|
|
203
|
+
reject(err);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
req.on('error', reject);
|
|
207
|
+
req.on('timeout', () => {
|
|
208
|
+
req.destroy(new Error(`timeout fetching ${currentUrl}`));
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
go(url, maxRedirects);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* extractTarGz(tarPath, destDir) → void
|
|
218
|
+
* Uses the system `tar` command. Available on macOS, Linux, and modern
|
|
219
|
+
* Windows (since Win10 1803 / Win Server 2019). If tar is unavailable the
|
|
220
|
+
* call throws and the caller falls through to the build-toolchain guidance.
|
|
221
|
+
*/
|
|
222
|
+
function extractTarGz(tarPath, destDir) {
|
|
223
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
224
|
+
const result = spawnSync('tar', ['-xzf', tarPath, '-C', destDir], {
|
|
225
|
+
stdio: 'pipe', encoding: 'utf-8',
|
|
226
|
+
});
|
|
227
|
+
if (result.status !== 0) {
|
|
228
|
+
const err = result.stderr || result.stdout || `exit ${result.status}`;
|
|
229
|
+
throw new Error(`tar extract failed: ${String(err).trim()}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* tryFetchPrebuild(opts) → Promise<{ ok: boolean, reason?: string, name?: string }>
|
|
235
|
+
* Single-package prebuild attempt. Best-effort: never throws.
|
|
236
|
+
*/
|
|
237
|
+
async function tryFetchPrebuild({
|
|
238
|
+
pkg, pkgVersion, cartoVersion, packageRoot, platformInfo,
|
|
239
|
+
baseUrl, fetcher, extractor, log,
|
|
240
|
+
}) {
|
|
241
|
+
const _fetch = fetcher || fetchToFile;
|
|
242
|
+
const _extract = extractor || extractTarGz;
|
|
243
|
+
const _log = log || (() => {});
|
|
244
|
+
|
|
245
|
+
if (!pkgVersion) return { ok: false, reason: 'no-version' };
|
|
246
|
+
|
|
247
|
+
const name = assetName({ pkg, pkgVersion, platformKey: platformInfo.key });
|
|
248
|
+
const url = assetUrl({ cartoVersion, name, baseUrl });
|
|
249
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'carto-prebuild-'));
|
|
250
|
+
const tarPath = path.join(tmpDir, name);
|
|
251
|
+
const nodeModules = path.join(packageRoot, 'node_modules');
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
_log(`[CARTO] fetching ${name}`);
|
|
255
|
+
await _fetch(url, tarPath, {});
|
|
256
|
+
_log(`[CARTO] extracting ${pkg}`);
|
|
257
|
+
fs.mkdirSync(nodeModules, { recursive: true });
|
|
258
|
+
_extract(tarPath, nodeModules);
|
|
259
|
+
return { ok: true, name };
|
|
260
|
+
} catch (err) {
|
|
261
|
+
return { ok: false, name, reason: err && err.message ? err.message : String(err) };
|
|
262
|
+
} finally {
|
|
263
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* buildSpec12Message(remainingFailures, platform) → string[]
|
|
269
|
+
* Returns the line array (caller decides how to print). Parameterised over
|
|
270
|
+
* what's still failing.
|
|
271
|
+
*/
|
|
272
|
+
function buildSpec12Message(remainingFailures, platform) {
|
|
273
|
+
const langs = remainingFailures.map((g) => g.langs).join(', ');
|
|
274
|
+
let fix;
|
|
275
|
+
if (platform === 'win32') {
|
|
276
|
+
fix =
|
|
277
|
+
'Install "Desktop development with C++" from ' +
|
|
278
|
+
'https://aka.ms/vs/17/release/vs_BuildTools.exe then re-run: npm rebuild';
|
|
279
|
+
} else if (platform === 'darwin') {
|
|
280
|
+
fix = 'Run: xcode-select --install && npm rebuild';
|
|
281
|
+
} else {
|
|
282
|
+
fix =
|
|
283
|
+
'Run: sudo apt-get install -y build-essential && npm rebuild ' +
|
|
284
|
+
'(or equivalent for your distro)';
|
|
285
|
+
}
|
|
286
|
+
return [
|
|
287
|
+
'',
|
|
288
|
+
'[CARTO] ⚠️ Some tree-sitter grammars failed to install.',
|
|
289
|
+
`[CARTO] Affected languages: ${langs}`,
|
|
290
|
+
'[CARTO] These languages will use regex-only extraction (less accurate).',
|
|
291
|
+
`[CARTO] To fix: ${fix}`,
|
|
292
|
+
'',
|
|
293
|
+
];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ---- Main entry (testable via runMain) ----
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* runMain(options) → Promise<{ exitCode, attempted, succeeded, stillFailed, skipped? }>
|
|
300
|
+
* options.env, options.console, options.requireFn, options.fetcher,
|
|
301
|
+
* options.extractor, options.packageRoot, options.cartoVersion,
|
|
302
|
+
* options.platformInfo, options.baseUrl
|
|
303
|
+
*/
|
|
304
|
+
async function runMain(options) {
|
|
305
|
+
const opts = options || {};
|
|
306
|
+
const env = opts.env || process.env;
|
|
307
|
+
const writer = opts.console || console;
|
|
308
|
+
const log = (...a) => writer.log(...a);
|
|
309
|
+
|
|
310
|
+
if (env.CARTO_NO_POSTINSTALL === '1') {
|
|
311
|
+
return { exitCode: 0, attempted: 0, succeeded: 0, stillFailed: 0, skipped: true };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const initial = probeGrammars(opts.requireFn);
|
|
315
|
+
const failures = selectFailures(initial);
|
|
316
|
+
if (failures.length === 0) {
|
|
317
|
+
return { exitCode: 0, attempted: 0, succeeded: 0, stillFailed: 0 };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const skipPrebuild = env.CARTO_NO_PREBUILD === '1';
|
|
321
|
+
let attempted = 0;
|
|
322
|
+
let succeeded = 0;
|
|
323
|
+
// Track which failures remain after prebuild fetch — used by the second
|
|
324
|
+
// probe to know which packages to recheck.
|
|
325
|
+
let remaining = failures.slice();
|
|
326
|
+
|
|
327
|
+
if (!skipPrebuild) {
|
|
328
|
+
const packageRoot = opts.packageRoot || path.resolve(__dirname, '..');
|
|
329
|
+
const cartoVersion =
|
|
330
|
+
opts.cartoVersion ||
|
|
331
|
+
JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8')).version;
|
|
332
|
+
const platformInfo = opts.platformInfo || getPlatformInfo();
|
|
333
|
+
|
|
334
|
+
log(
|
|
335
|
+
`[CARTO] ${failures.length} grammar(s) missing — ` +
|
|
336
|
+
`trying prebuilt binaries for ${platformInfo.key}`,
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const stillBroken = [];
|
|
340
|
+
for (const failure of failures) {
|
|
341
|
+
attempted += 1;
|
|
342
|
+
const pkgVersion = resolveExpectedVersion(packageRoot, failure.pkg);
|
|
343
|
+
const result = await tryFetchPrebuild({
|
|
344
|
+
pkg: failure.pkg,
|
|
345
|
+
pkgVersion,
|
|
346
|
+
cartoVersion,
|
|
347
|
+
packageRoot,
|
|
348
|
+
platformInfo,
|
|
349
|
+
baseUrl: opts.baseUrl,
|
|
350
|
+
fetcher: opts.fetcher,
|
|
351
|
+
extractor: opts.extractor,
|
|
352
|
+
log,
|
|
353
|
+
});
|
|
354
|
+
if (result.ok) {
|
|
355
|
+
succeeded += 1;
|
|
356
|
+
} else {
|
|
357
|
+
log(`[CARTO] ${failure.pkg}: prebuild unavailable (${result.reason || 'unknown'})`);
|
|
358
|
+
stillBroken.push(failure);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
remaining = stillBroken;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Re-probe to pick up any freshly extracted packages. Node's module cache
|
|
365
|
+
// doesn't cache failed requires, so a retry will see the new files.
|
|
366
|
+
let stillFailing;
|
|
367
|
+
if (succeeded > 0) {
|
|
368
|
+
const second = probeGrammars(opts.requireFn);
|
|
369
|
+
stillFailing = selectFailures(second);
|
|
370
|
+
} else {
|
|
371
|
+
stillFailing = remaining;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (stillFailing.length === 0) {
|
|
375
|
+
log(`[CARTO] ✓ Restored ${succeeded}/${attempted} grammar(s) from prebuilt binaries.`);
|
|
376
|
+
return { exitCode: 0, attempted, succeeded, stillFailed: 0 };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
for (const line of buildSpec12Message(stillFailing, process.platform)) log(line);
|
|
380
|
+
return { exitCode: 0, attempted, succeeded, stillFailed: stillFailing.length };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ---- Exports for tests ----
|
|
384
|
+
|
|
385
|
+
module.exports = {
|
|
386
|
+
GRAMMARS,
|
|
387
|
+
CORE_PKG,
|
|
388
|
+
DEFAULT_RELEASE_BASE_URL,
|
|
389
|
+
detectLibc,
|
|
390
|
+
getPlatformInfo,
|
|
391
|
+
assetName,
|
|
392
|
+
assetUrl,
|
|
393
|
+
probeGrammars,
|
|
394
|
+
selectFailures,
|
|
395
|
+
resolveExpectedVersion,
|
|
396
|
+
fetchToFile,
|
|
397
|
+
extractTarGz,
|
|
398
|
+
tryFetchPrebuild,
|
|
399
|
+
buildSpec12Message,
|
|
400
|
+
runMain,
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// ---- Direct invocation ----
|
|
404
|
+
|
|
405
|
+
if (require.main === module) {
|
|
406
|
+
runMain({}).catch((err) => {
|
|
407
|
+
// runMain is supposed to absorb all errors; this is belt + suspenders.
|
|
408
|
+
try {
|
|
409
|
+
console.log(`[CARTO] postinstall internal error: ${err && err.message}`);
|
|
410
|
+
} catch { /* ignore */ }
|
|
411
|
+
process.exit(0);
|
|
412
|
+
});
|
|
413
|
+
}
|
package/src/acp/agent.js
CHANGED
|
@@ -71,11 +71,11 @@ class CartoAgent {
|
|
|
71
71
|
|
|
72
72
|
// Provider methods
|
|
73
73
|
// The custom `unstable_*` provider-management methods (list / set / disable)
|
|
74
|
-
// were removed
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
74
|
+
// were removed. The ACP SDK only dispatches the methods declared in
|
|
75
|
+
// its AGENT_METHODS constant, so those custom routes returned -32601
|
|
76
|
+
// "Method not found" anyway. Provider config flows through env vars /
|
|
77
|
+
// editor settings; ProviderRegistry remains as the internal
|
|
78
|
+
// configuration carrier.
|
|
79
79
|
|
|
80
80
|
// Session list/load stubs
|
|
81
81
|
async listSessions(_params) { return { sessions: [] }; }
|
|
@@ -30,8 +30,8 @@ class ProviderRegistry {
|
|
|
30
30
|
this._config = null;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// list() was removed
|
|
34
|
-
//
|
|
33
|
+
// list() was removed — it was only called by a dropped custom
|
|
34
|
+
// provider-list ACP method that the SDK never actually dispatched.
|
|
35
35
|
// SUPPORTED_PROVIDERS is still exported for external callers / tests.
|
|
36
36
|
|
|
37
37
|
/**
|
package/src/agents/leiden.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Pure JS implementation (~250 lines). Zero external dependencies.
|
|
7
7
|
* Based on: Traag, Waltman & van Eck (2019) "From Louvain to Leiden"
|
|
8
8
|
*
|
|
9
|
-
* Key difference from Louvain: the refinement
|
|
9
|
+
* Key difference from Louvain: the refinement step guarantees that
|
|
10
10
|
* every community is a connected subgraph (Louvain can produce
|
|
11
11
|
* disconnected communities). This matters for import graphs where
|
|
12
12
|
* disconnected clusters produce nonsensical domain names.
|
|
@@ -60,7 +60,7 @@ function leiden(nodes, edges, gamma = 0.03) {
|
|
|
60
60
|
improved = false;
|
|
61
61
|
iterations++;
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// Local-move pass: try to move each node to a neighboring community
|
|
64
64
|
const order = shuffleIndices(n);
|
|
65
65
|
for (const i of order) {
|
|
66
66
|
const currentComm = community[i];
|
|
@@ -109,7 +109,7 @@ function leiden(nodes, edges, gamma = 0.03) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
// Refinement
|
|
112
|
+
// Refinement pass: split internally disconnected communities
|
|
113
113
|
_refinementPhase(n, adj, community);
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -127,7 +127,7 @@ function leiden(nodes, edges, gamma = 0.03) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
* Refinement
|
|
130
|
+
* Refinement pass: for each community, check connectivity.
|
|
131
131
|
* If a community is internally disconnected, split it into
|
|
132
132
|
* connected components. This is the key Leiden improvement over Louvain.
|
|
133
133
|
*/
|
|
@@ -300,24 +300,18 @@ function clusterByGraph(importGraph, gamma = 0.03, keywordSeeds = {}) {
|
|
|
300
300
|
|
|
301
301
|
// Name each community
|
|
302
302
|
const commNames = new Map();
|
|
303
|
-
const usedNames = new Map(); // name → count (for deduplication)
|
|
304
303
|
|
|
305
304
|
for (const [commId, members] of communities) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
// Deduplicate: if name already used, append a number
|
|
309
|
-
if (usedNames.has(name)) {
|
|
310
|
-
const count = usedNames.get(name) + 1;
|
|
311
|
-
usedNames.set(name, count);
|
|
312
|
-
name = `${name}_${count}`;
|
|
313
|
-
} else {
|
|
314
|
-
usedNames.set(name, 1);
|
|
315
|
-
}
|
|
316
|
-
|
|
305
|
+
const name = nameCommunity(members, keywordSeeds);
|
|
317
306
|
commNames.set(commId, name);
|
|
318
307
|
}
|
|
319
308
|
|
|
320
|
-
// Build final file → domain map
|
|
309
|
+
// Build final file → domain map.
|
|
310
|
+
// When multiple communities resolve to the same base name (e.g. with a
|
|
311
|
+
// dense import graph, several disjoint sub-systems can each rank highest
|
|
312
|
+
// for AUTH), merge them into a single domain. The keyword that names the
|
|
313
|
+
// domain is the user-visible truth; preserving "AUTH_2", "AUTH_3", ...
|
|
314
|
+
// suffixes leaks Leiden's internal community count into the UI.
|
|
321
315
|
const result = new Map();
|
|
322
316
|
for (const [node, commId] of communityMap) {
|
|
323
317
|
result.set(node, commNames.get(commId) || 'CORE');
|
|
@@ -16,7 +16,7 @@ const fs = require('fs');
|
|
|
16
16
|
* the structure block is about to be merged into (`AGENTS.md`).
|
|
17
17
|
*
|
|
18
18
|
* Anchored on the original V1 set in src/sync.js so existing AGENTS.md
|
|
19
|
-
* outputs
|
|
19
|
+
* outputs stay stable after the V1 → V2 cleanup.
|
|
20
20
|
*/
|
|
21
21
|
const IGNORE_DIRS = new Set([
|
|
22
22
|
'node_modules',
|