@zhn2605/topo 0.1.0 → 0.2.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/README.md +80 -76
- package/dist/grammars/tree-sitter-cpp.wasm +0 -0
- package/dist/topo.js +1061 -289
- package/dist/web/assets/answer-CbyA0kan.js +2 -0
- package/dist/web/assets/{elk.bundled-DHL5Qd5-.js → elk.bundled-BUXl0iSx.js} +1 -1
- package/dist/web/assets/index-51XzhJVO.css +1 -0
- package/dist/web/assets/index-IpMwIh-N.js +22 -0
- package/dist/web/index.html +2 -2
- package/dist/web/snapshot.html +43 -37
- package/package.json +2 -2
- package/dist/web/assets/index-Bx25CC1b.js +0 -17
- package/dist/web/assets/index-CJ5z47qU.css +0 -1
package/dist/topo.js
CHANGED
|
@@ -315,6 +315,9 @@ function findEntryPoints(graph, rootDir) {
|
|
|
315
315
|
if (n.language === "rust" && n.kind === "function" && n.name === "main" && RUST_BIN.test(n.file)) {
|
|
316
316
|
add(n.id, "nativeMain", "the binary's fn main");
|
|
317
317
|
}
|
|
318
|
+
if ((n.language === "c" || n.language === "cpp") && n.id === `${n.file}#main`) {
|
|
319
|
+
add(n.id, "nativeMain", "the program's main()");
|
|
320
|
+
}
|
|
318
321
|
}
|
|
319
322
|
for (const n of graph.nodes) {
|
|
320
323
|
if (n.kind !== "function" || !isStarter(n) || !n.exported) continue;
|
|
@@ -446,7 +449,7 @@ var init_entry = __esm({
|
|
|
446
449
|
robloxScript: 80,
|
|
447
450
|
// Foo.server.luau / Foo.client.luau: a Script/LocalScript Roblox runs itself
|
|
448
451
|
nativeMain: 80,
|
|
449
|
-
// Rust fn main in src/main.rs or src/bin/*.rs; a Python
|
|
452
|
+
// Rust fn main in src/main.rs or src/bin/*.rs; C/C++ main(); a Python __main__ guard
|
|
450
453
|
scriptRoot: 20,
|
|
451
454
|
// …that is a folder's init.server/init.client (the root of a script tree)
|
|
452
455
|
moduleRoot: 10,
|
|
@@ -504,12 +507,13 @@ var init_hubs = __esm({
|
|
|
504
507
|
import fs2 from "node:fs";
|
|
505
508
|
import path2 from "node:path";
|
|
506
509
|
function readLayers(repo) {
|
|
507
|
-
let raw;
|
|
508
510
|
try {
|
|
509
|
-
|
|
511
|
+
return parseLayers(JSON.parse(fs2.readFileSync(layersFile(repo), "utf8")));
|
|
510
512
|
} catch {
|
|
511
513
|
return void 0;
|
|
512
514
|
}
|
|
515
|
+
}
|
|
516
|
+
function parseLayers(raw) {
|
|
513
517
|
const d = raw;
|
|
514
518
|
if (!d || !Array.isArray(d.layers)) return void 0;
|
|
515
519
|
const layers = d.layers.filter(
|
|
@@ -521,6 +525,20 @@ function readLayers(repo) {
|
|
|
521
525
|
);
|
|
522
526
|
return { version: 1, layers, rules };
|
|
523
527
|
}
|
|
528
|
+
function writeLayers(repo, doc) {
|
|
529
|
+
fs2.mkdirSync(path2.dirname(layersFile(repo)), { recursive: true });
|
|
530
|
+
fs2.writeFileSync(layersFile(repo), `${JSON.stringify(doc, null, 2)}
|
|
531
|
+
`);
|
|
532
|
+
}
|
|
533
|
+
function layerMap(doc, graph) {
|
|
534
|
+
const layerOf = layerIndex(doc);
|
|
535
|
+
const byFile = {};
|
|
536
|
+
for (const n of graph.nodes) {
|
|
537
|
+
const l = n.kind === "file" ? layerOf(n.file) : void 0;
|
|
538
|
+
if (l) byFile[n.file] = l;
|
|
539
|
+
}
|
|
540
|
+
return { names: doc.layers.map((l) => l.name), byFile };
|
|
541
|
+
}
|
|
524
542
|
function layerIndex(doc) {
|
|
525
543
|
const compiled = doc.layers.map((l) => ({ name: l.name, res: l.paths.map(globToRegExp) }));
|
|
526
544
|
const memo = /* @__PURE__ */ new Map();
|
|
@@ -905,8 +923,8 @@ function parseUnifiedDiff(text) {
|
|
|
905
923
|
if (line.startsWith("diff --git ")) {
|
|
906
924
|
flush();
|
|
907
925
|
const both = line.slice("diff --git ".length);
|
|
908
|
-
const
|
|
909
|
-
cur = { oldPath:
|
|
926
|
+
const path27 = unquote(both.slice(0, (both.length - 1) / 2));
|
|
927
|
+
cur = { oldPath: path27, newPath: path27, status: "modified", oldRanges: [], newRanges: [] };
|
|
910
928
|
} else if (!cur) {
|
|
911
929
|
} else if (line.startsWith("new file mode")) {
|
|
912
930
|
cur.status = "added";
|
|
@@ -956,10 +974,10 @@ function getFileDiffs(repo, baseSha, headRef) {
|
|
|
956
974
|
const diffs = parseUnifiedDiff(git(repo, args).toString());
|
|
957
975
|
if (headRef) return diffs;
|
|
958
976
|
const untracked = git(repo, ["ls-files", "--others", "--exclude-standard", "-z"]).toString().split("\0").filter((p) => p && !p.endsWith("/"));
|
|
959
|
-
for (const
|
|
960
|
-
const n = lineCount(join(repo,
|
|
977
|
+
for (const path27 of untracked) {
|
|
978
|
+
const n = lineCount(join(repo, path27));
|
|
961
979
|
diffs.push({
|
|
962
|
-
newPath:
|
|
980
|
+
newPath: path27,
|
|
963
981
|
status: "added",
|
|
964
982
|
oldRanges: [],
|
|
965
983
|
newRanges: n ? [{ startLine: 1, endLine: n }] : []
|
|
@@ -1042,13 +1060,17 @@ function matchTarget(spec, targets, gamePath) {
|
|
|
1042
1060
|
}
|
|
1043
1061
|
function findTool(name, root) {
|
|
1044
1062
|
const rokit = path3.join(os.homedir(), ".rokit");
|
|
1045
|
-
const
|
|
1063
|
+
const store = path3.join(rokit, "tool-storage");
|
|
1064
|
+
const ls = (dir) => {
|
|
1046
1065
|
try {
|
|
1047
|
-
return fs3.
|
|
1066
|
+
return fs3.readdirSync(dir);
|
|
1048
1067
|
} catch {
|
|
1049
1068
|
return [];
|
|
1050
1069
|
}
|
|
1051
|
-
}
|
|
1070
|
+
};
|
|
1071
|
+
const stored = ls(store).flatMap(
|
|
1072
|
+
(author) => ls(path3.join(store, author, name)).map((v) => path3.join(author, name, v, name))
|
|
1073
|
+
).filter((p) => fs3.existsSync(path3.join(store, p))).sort((a, b) => a.localeCompare(b, "en", { numeric: true })).map((p) => path3.join(store, p)).reverse();
|
|
1052
1074
|
for (const bin of [name, path3.join(rokit, "bin", name), ...stored]) {
|
|
1053
1075
|
if (spawnSync2(bin, ["--version"], { cwd: root, stdio: "ignore" }).status === 0) return bin;
|
|
1054
1076
|
}
|
|
@@ -1200,6 +1222,7 @@ function indexerVersion() {
|
|
|
1200
1222
|
h.update(depVersion(path4.join(ENGINE_ROOT, "package.json"), "web-tree-sitter"));
|
|
1201
1223
|
h.update(depVersion(path4.join(ENGINE_ROOT, "package.json"), "tree-sitter-python"));
|
|
1202
1224
|
h.update(depVersion(path4.join(ENGINE_ROOT, "package.json"), "tree-sitter-rust"));
|
|
1225
|
+
h.update(depVersion(path4.join(ENGINE_ROOT, "package.json"), "tree-sitter-cpp"));
|
|
1203
1226
|
h.update(luauLspIdentity());
|
|
1204
1227
|
memoVersion = h.digest("hex").slice(0, 16);
|
|
1205
1228
|
return memoVersion;
|
|
@@ -1766,8 +1789,8 @@ function calleeName(call) {
|
|
|
1766
1789
|
if (ts2.isPropertyAccessExpression(c) && ts2.isIdentifier(c.name)) return c.name.text;
|
|
1767
1790
|
return void 0;
|
|
1768
1791
|
}
|
|
1769
|
-
function segsOf(
|
|
1770
|
-
return
|
|
1792
|
+
function segsOf(path27) {
|
|
1793
|
+
return path27.split("/").filter(Boolean).map((s) => s.includes("*") || s.startsWith("[...") || s.startsWith("[[...") ? "*" : s).map((s) => /^[:{[]/.test(s) ? ":" : s);
|
|
1771
1794
|
}
|
|
1772
1795
|
function urlPath(e) {
|
|
1773
1796
|
let text;
|
|
@@ -1832,8 +1855,8 @@ function routeCall(r, call, site, target, handlerOf) {
|
|
|
1832
1855
|
if (to) r.servers.push({ method: shape.method, segs: segsOf(shape.path), to });
|
|
1833
1856
|
if (to !== void 0) return;
|
|
1834
1857
|
}
|
|
1835
|
-
const
|
|
1836
|
-
if (!
|
|
1858
|
+
const path27 = arg && urlPath(arg);
|
|
1859
|
+
if (!path27) return;
|
|
1837
1860
|
const m = name.toLowerCase();
|
|
1838
1861
|
let method;
|
|
1839
1862
|
if (ts2.isIdentifier(call.expression) && CLIENT_FNS.has(name)) method = fetchMethod(call);
|
|
@@ -1841,9 +1864,9 @@ function routeCall(r, call, site, target, handlerOf) {
|
|
|
1841
1864
|
method = m.toUpperCase();
|
|
1842
1865
|
else if (target && (target.kind === "function" || target.kind === "method")) method = "ANY";
|
|
1843
1866
|
else return;
|
|
1844
|
-
const segs = segsOf(
|
|
1867
|
+
const segs = segsOf(path27);
|
|
1845
1868
|
if (segs.some((s) => s !== ":" && s !== "*"))
|
|
1846
|
-
r.clients.push({ ...site, method, segs, raw:
|
|
1869
|
+
r.clients.push({ ...site, method, segs, raw: path27 });
|
|
1847
1870
|
}
|
|
1848
1871
|
function serverLiteral(r, lit, to, prefix = false) {
|
|
1849
1872
|
const m = isStr(lit) ? ROUTE_LITERAL.exec(lit.text) : null;
|
|
@@ -2342,6 +2365,16 @@ function grammarWasm(pkg) {
|
|
|
2342
2365
|
const bundled = fileURLToPath2(new URL(`./grammars/${pkg}.wasm`, import.meta.url));
|
|
2343
2366
|
return fs5.existsSync(bundled) ? bundled : createRequire(import.meta.url).resolve(`${pkg}/${pkg}.wasm`);
|
|
2344
2367
|
}
|
|
2368
|
+
function leadingComments(stmt) {
|
|
2369
|
+
const out = [];
|
|
2370
|
+
let at = stmt;
|
|
2371
|
+
for (let p = stmt.previousNamedSibling; p?.type === "comment"; p = p.previousNamedSibling) {
|
|
2372
|
+
if (p.endPosition.row < at.startPosition.row - 1) break;
|
|
2373
|
+
out.unshift(p);
|
|
2374
|
+
at = p;
|
|
2375
|
+
}
|
|
2376
|
+
return out;
|
|
2377
|
+
}
|
|
2345
2378
|
var treeSitterReady;
|
|
2346
2379
|
var init_tree_sitter = __esm({
|
|
2347
2380
|
"packages/engine/src/indexer/tree-sitter.ts"() {
|
|
@@ -2350,11 +2383,547 @@ var init_tree_sitter = __esm({
|
|
|
2350
2383
|
}
|
|
2351
2384
|
});
|
|
2352
2385
|
|
|
2353
|
-
// packages/engine/src/indexer/
|
|
2354
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
2386
|
+
// packages/engine/src/indexer/cpp.ts
|
|
2355
2387
|
import fs6 from "node:fs";
|
|
2356
2388
|
import path6 from "node:path";
|
|
2357
2389
|
import { Language, Parser as Parser2 } from "web-tree-sitter";
|
|
2390
|
+
function qualSegs(n) {
|
|
2391
|
+
if (!n) return void 0;
|
|
2392
|
+
switch (n.type) {
|
|
2393
|
+
case "identifier":
|
|
2394
|
+
case "field_identifier":
|
|
2395
|
+
case "type_identifier":
|
|
2396
|
+
case "namespace_identifier":
|
|
2397
|
+
case "destructor_name":
|
|
2398
|
+
case "operator_name":
|
|
2399
|
+
return [n.text];
|
|
2400
|
+
case "template_type":
|
|
2401
|
+
case "template_function":
|
|
2402
|
+
case "template_method":
|
|
2403
|
+
return qualSegs(n.childForFieldName("name"));
|
|
2404
|
+
case "qualified_identifier": {
|
|
2405
|
+
const scope = n.childForFieldName("scope");
|
|
2406
|
+
const head = scope ? qualSegs(scope) : [];
|
|
2407
|
+
const tail = qualSegs(n.childForFieldName("name"));
|
|
2408
|
+
return head && tail ? [...head, ...tail] : void 0;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
return void 0;
|
|
2412
|
+
}
|
|
2413
|
+
function templateArg(n) {
|
|
2414
|
+
const t = n.type === "qualified_identifier" ? innermost(n) : n;
|
|
2415
|
+
const arg = t.childForFieldName("arguments")?.namedChildren[0];
|
|
2416
|
+
return arg?.type === "type_descriptor" ? arg.childForFieldName("type") : null;
|
|
2417
|
+
}
|
|
2418
|
+
function typeSegs(t) {
|
|
2419
|
+
if (!t) return void 0;
|
|
2420
|
+
if (t.type === "struct_specifier" || t.type === "class_specifier")
|
|
2421
|
+
return qualSegs(t.childForFieldName("name"));
|
|
2422
|
+
const segs = qualSegs(t);
|
|
2423
|
+
if (segs && SMART_PTRS.has(segs.at(-1) ?? "")) return typeSegs(templateArg(t));
|
|
2424
|
+
return segs;
|
|
2425
|
+
}
|
|
2426
|
+
function declName(d) {
|
|
2427
|
+
for (let n = d; n; ) {
|
|
2428
|
+
if (n.type === "identifier" || n.type === "field_identifier") return n.text;
|
|
2429
|
+
if (n.type === "function_declarator") return void 0;
|
|
2430
|
+
n = n.childForFieldName("declarator") ?? n.namedChildren.find((c) => c?.type === "identifier" || c?.type.endsWith("declarator")) ?? null;
|
|
2431
|
+
}
|
|
2432
|
+
return void 0;
|
|
2433
|
+
}
|
|
2434
|
+
function fnDeclarator(d) {
|
|
2435
|
+
for (let n = d; n; ) {
|
|
2436
|
+
if (n.type === "function_declarator") return n;
|
|
2437
|
+
n = n.childForFieldName("declarator") ?? n.namedChildren.find((c) => c?.type.endsWith("declarator")) ?? null;
|
|
2438
|
+
}
|
|
2439
|
+
return void 0;
|
|
2440
|
+
}
|
|
2441
|
+
function builtType(v) {
|
|
2442
|
+
if (v?.type === "new_expression") return typeSegs(v.childForFieldName("type"));
|
|
2443
|
+
if (v?.type === "call_expression") {
|
|
2444
|
+
const fn = v.childForFieldName("function");
|
|
2445
|
+
if (fn && MAKERS.has(qualSegs(fn)?.at(-1) ?? "")) return typeSegs(templateArg(fn));
|
|
2446
|
+
}
|
|
2447
|
+
return void 0;
|
|
2448
|
+
}
|
|
2449
|
+
function collect(file, text, tags, root) {
|
|
2450
|
+
const language = /\.c$/.test(file) ? "c" : "cpp";
|
|
2451
|
+
const lineCount2 = text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
|
|
2452
|
+
const fileNode = {
|
|
2453
|
+
id: fileId(file),
|
|
2454
|
+
kind: "file",
|
|
2455
|
+
name: path6.posix.basename(file),
|
|
2456
|
+
file,
|
|
2457
|
+
range: { startLine: 1, endLine: Math.max(1, lineCount2) },
|
|
2458
|
+
bodyHash: bodyHash(text),
|
|
2459
|
+
exported: false,
|
|
2460
|
+
language,
|
|
2461
|
+
tags: [...tags],
|
|
2462
|
+
mappingStatus: "full"
|
|
2463
|
+
};
|
|
2464
|
+
const out = {
|
|
2465
|
+
file,
|
|
2466
|
+
nodes: [fileNode],
|
|
2467
|
+
includes: [],
|
|
2468
|
+
defs: [],
|
|
2469
|
+
classes: [],
|
|
2470
|
+
calls: [],
|
|
2471
|
+
declared: /* @__PURE__ */ new Set()
|
|
2472
|
+
};
|
|
2473
|
+
const block2 = (item, outer, name, kind, bodyStart, exported) => {
|
|
2474
|
+
const comments2 = leadingComments(outer);
|
|
2475
|
+
const doc = comments2.map((c) => c.text.replace(/^\/\/\/?\s?|^\/\*+\s?|\s*\*+\/$/g, "").replace(/^\s*\* ?/gm, "")).join("\n").trim();
|
|
2476
|
+
return {
|
|
2477
|
+
id: "",
|
|
2478
|
+
// set once the qualified name is known
|
|
2479
|
+
kind,
|
|
2480
|
+
name,
|
|
2481
|
+
file,
|
|
2482
|
+
range: {
|
|
2483
|
+
startLine: (comments2[0] ?? outer).startPosition.row + 1,
|
|
2484
|
+
endLine: outer.endPosition.row + 1
|
|
2485
|
+
},
|
|
2486
|
+
signature: collapse(text.slice(item.startIndex, bodyStart)),
|
|
2487
|
+
...doc && { doc: doc.slice(0, DOC_CAP) },
|
|
2488
|
+
bodyHash: bodyHash(item.text, name),
|
|
2489
|
+
exported,
|
|
2490
|
+
language,
|
|
2491
|
+
tags: [...tags],
|
|
2492
|
+
mappingStatus: "full"
|
|
2493
|
+
};
|
|
2494
|
+
};
|
|
2495
|
+
const ctxOf = (s) => s.def ? defCtx(s.def) : s.ns;
|
|
2496
|
+
const walk2 = (n, s, inLoop, access) => {
|
|
2497
|
+
for (const c of n.namedChildren) {
|
|
2498
|
+
if (!c || c.type === "comment") continue;
|
|
2499
|
+
if (c.type === "access_specifier" && access) {
|
|
2500
|
+
access.v = c.text;
|
|
2501
|
+
continue;
|
|
2502
|
+
}
|
|
2503
|
+
if (c.type === "preproc_include") {
|
|
2504
|
+
const p = c.childForFieldName("path");
|
|
2505
|
+
if (p?.type === "string_literal" || p?.type === "system_lib_string") {
|
|
2506
|
+
const angle = p.type === "system_lib_string";
|
|
2507
|
+
out.includes.push({
|
|
2508
|
+
path: angle ? p.text.slice(1, -1) : p.text.replace(/^"|"$/g, ""),
|
|
2509
|
+
angle,
|
|
2510
|
+
line: c.startPosition.row + 1
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2515
|
+
if (c.type === "namespace_definition") {
|
|
2516
|
+
const name = c.childForFieldName("name")?.text;
|
|
2517
|
+
const body = c.childForFieldName("body");
|
|
2518
|
+
const segs = name ? name.split("::") : [];
|
|
2519
|
+
if (body) walk2(body, { ...s, ns: [...s.ns, ...segs] }, inLoop);
|
|
2520
|
+
continue;
|
|
2521
|
+
}
|
|
2522
|
+
const outer = c;
|
|
2523
|
+
const item = c.type === "template_declaration" ? c.namedChildren.at(-1) ?? c : c;
|
|
2524
|
+
const spec = item.type === "type_definition" ? item.childForFieldName("type") : item;
|
|
2525
|
+
if ((spec?.type === "class_specifier" || spec?.type === "struct_specifier") && spec.childForFieldName("body") && !s.def) {
|
|
2526
|
+
const body = spec.childForFieldName("body");
|
|
2527
|
+
const name = spec.childForFieldName("name")?.text ?? (item !== spec ? declName(item.childForFieldName("declarator")) : void 0);
|
|
2528
|
+
if (!name) {
|
|
2529
|
+
walk2(body, s, inLoop);
|
|
2530
|
+
continue;
|
|
2531
|
+
}
|
|
2532
|
+
const node = block2(spec, outer, name, "class", body.startIndex, true);
|
|
2533
|
+
const info = {
|
|
2534
|
+
q: join2([...s.ns, name]),
|
|
2535
|
+
node,
|
|
2536
|
+
ns: s.ns,
|
|
2537
|
+
fields: /* @__PURE__ */ new Map(),
|
|
2538
|
+
bases: []
|
|
2539
|
+
};
|
|
2540
|
+
node.id = nodeId(file, info.q);
|
|
2541
|
+
node.parentId = s.cls?.node.id ?? fileNode.id;
|
|
2542
|
+
for (const b of spec.namedChildren)
|
|
2543
|
+
if (b?.type === "base_class_clause")
|
|
2544
|
+
for (const t of b.namedChildren) {
|
|
2545
|
+
const segs = typeSegs(t);
|
|
2546
|
+
if (segs) info.bases.push(segs);
|
|
2547
|
+
}
|
|
2548
|
+
out.nodes.push(node);
|
|
2549
|
+
out.classes.push(info);
|
|
2550
|
+
for (const f of body.namedChildren) {
|
|
2551
|
+
if (f?.type !== "field_declaration") continue;
|
|
2552
|
+
const t = typeSegs(f.childForFieldName("type"));
|
|
2553
|
+
if (!t) continue;
|
|
2554
|
+
for (const d of f.childrenForFieldName("declarator")) {
|
|
2555
|
+
const fname = declName(d);
|
|
2556
|
+
if (fname) info.fields.set(fname, t);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
const isStruct = spec.type === "struct_specifier";
|
|
2560
|
+
walk2(body, { from: node, ns: [...s.ns, name], cls: info }, false, {
|
|
2561
|
+
v: isStruct ? "public" : "private"
|
|
2562
|
+
});
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
if (item.type === "function_definition" && !s.def) {
|
|
2566
|
+
const fd = fnDeclarator(item.childForFieldName("declarator"));
|
|
2567
|
+
const segs = qualSegs(fd?.childForFieldName("declarator") ?? null);
|
|
2568
|
+
const body = item.childForFieldName("body");
|
|
2569
|
+
const name = segs?.at(-1);
|
|
2570
|
+
if (!fd || !segs || !name || !body) continue;
|
|
2571
|
+
const isStatic = item.namedChildren.some(
|
|
2572
|
+
(m) => m?.type === "storage_class_specifier" && m.text === "static"
|
|
2573
|
+
);
|
|
2574
|
+
const inClass = !!s.cls;
|
|
2575
|
+
const exported = inClass ? access?.v !== "private" : !isStatic;
|
|
2576
|
+
const node = block2(
|
|
2577
|
+
item,
|
|
2578
|
+
outer,
|
|
2579
|
+
name,
|
|
2580
|
+
inClass ? "method" : "function",
|
|
2581
|
+
body.startIndex,
|
|
2582
|
+
exported
|
|
2583
|
+
);
|
|
2584
|
+
const def = {
|
|
2585
|
+
node,
|
|
2586
|
+
name,
|
|
2587
|
+
ns: s.ns,
|
|
2588
|
+
scope: segs.slice(0, -1),
|
|
2589
|
+
...s.cls && { cls: s.cls.q },
|
|
2590
|
+
locals: /* @__PURE__ */ new Map()
|
|
2591
|
+
};
|
|
2592
|
+
if (s.cls) node.parentId = s.cls.node.id;
|
|
2593
|
+
for (const p of fd.childForFieldName("parameters")?.namedChildren ?? []) {
|
|
2594
|
+
if (p?.type !== "parameter_declaration" && p?.type !== "optional_parameter_declaration")
|
|
2595
|
+
continue;
|
|
2596
|
+
const name2 = declName(p.childForFieldName("declarator"));
|
|
2597
|
+
const t = typeSegs(p.childForFieldName("type"));
|
|
2598
|
+
if (name2 && t) def.locals.set(name2, t);
|
|
2599
|
+
}
|
|
2600
|
+
out.nodes.push(node);
|
|
2601
|
+
out.defs.push(def);
|
|
2602
|
+
if (!s.cls) out.declared.add(join2([...s.ns, ...segs]));
|
|
2603
|
+
const inner = { from: def, ns: s.ns, def };
|
|
2604
|
+
for (const init of item.namedChildren)
|
|
2605
|
+
if (init?.type === "field_initializer_list") walk2(init, inner, false);
|
|
2606
|
+
walk2(body, inner, false);
|
|
2607
|
+
continue;
|
|
2608
|
+
}
|
|
2609
|
+
if ((item.type === "declaration" || item.type === "field_declaration") && !s.def) {
|
|
2610
|
+
const fd = fnDeclarator(item.childForFieldName("declarator"));
|
|
2611
|
+
const segs = fd && qualSegs(fd.childForFieldName("declarator"));
|
|
2612
|
+
if (segs) {
|
|
2613
|
+
if (!s.cls) out.declared.add(join2([...s.ns, ...segs]));
|
|
2614
|
+
continue;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
if (c.type === "declaration" && s.def) {
|
|
2618
|
+
const t = typeSegs(c.childForFieldName("type"));
|
|
2619
|
+
for (const d of c.childrenForFieldName("declarator")) {
|
|
2620
|
+
const wrapped = d?.type === "init_declarator" || d?.type === "function_declarator";
|
|
2621
|
+
const target = wrapped ? d.childForFieldName("declarator") : d;
|
|
2622
|
+
const v = d?.type === "init_declarator" ? d.childForFieldName("value") : null;
|
|
2623
|
+
const name = declName(target);
|
|
2624
|
+
const type = t ?? builtType(v);
|
|
2625
|
+
if (name && type) s.def.locals.set(name, type);
|
|
2626
|
+
const constructs = !v || v.type === "argument_list" || v.type === "initializer_list";
|
|
2627
|
+
if (t && target?.type === "identifier" && constructs) {
|
|
2628
|
+
out.calls.push({
|
|
2629
|
+
from: s.from,
|
|
2630
|
+
ctx: ctxOf(s),
|
|
2631
|
+
form: "ctor",
|
|
2632
|
+
segs: t,
|
|
2633
|
+
line: c.startPosition.row + 1,
|
|
2634
|
+
inLoop
|
|
2635
|
+
});
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
if (c.type === "new_expression") {
|
|
2640
|
+
const t = typeSegs(c.childForFieldName("type"));
|
|
2641
|
+
if (t)
|
|
2642
|
+
out.calls.push({
|
|
2643
|
+
from: s.from,
|
|
2644
|
+
ctx: ctxOf(s),
|
|
2645
|
+
form: "ctor",
|
|
2646
|
+
segs: t,
|
|
2647
|
+
line: c.startPosition.row + 1,
|
|
2648
|
+
inLoop
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
if (c.type === "call_expression") {
|
|
2652
|
+
const fn = c.childForFieldName("function");
|
|
2653
|
+
const site = { from: s.from, ctx: ctxOf(s), line: c.startPosition.row + 1, inLoop };
|
|
2654
|
+
if (fn?.type === "field_expression") {
|
|
2655
|
+
const recv = fn.childForFieldName("argument");
|
|
2656
|
+
const method = qualSegs(fn.childForFieldName("field"))?.at(-1);
|
|
2657
|
+
let segs;
|
|
2658
|
+
if (recv?.type === "this") {
|
|
2659
|
+
if (method) out.calls.push({ ...site, form: "this", segs: [], method });
|
|
2660
|
+
} else if (recv?.type === "identifier") segs = [recv.text];
|
|
2661
|
+
else if (recv?.type === "field_expression" && recv.childForFieldName("argument")?.type === "this")
|
|
2662
|
+
segs = ["this", recv.childForFieldName("field")?.text ?? ""];
|
|
2663
|
+
if (segs && method) out.calls.push({ ...site, form: "member", segs, method });
|
|
2664
|
+
} else if (fn) {
|
|
2665
|
+
const segs = qualSegs(fn);
|
|
2666
|
+
if (segs && MAKERS.has(segs.at(-1) ?? "")) {
|
|
2667
|
+
const t = typeSegs(templateArg(fn));
|
|
2668
|
+
if (t) out.calls.push({ ...site, form: "ctor", segs: t });
|
|
2669
|
+
} else if (segs) out.calls.push({ ...site, form: "name", segs });
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
walk2(c, s, inLoop || LOOPS.has(c.type));
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
walk2(root, { from: fileNode, ns: [] }, false);
|
|
2676
|
+
return out;
|
|
2677
|
+
}
|
|
2678
|
+
function inScope(ctx, segs, map) {
|
|
2679
|
+
for (let i = ctx.length; i >= 0; i--) {
|
|
2680
|
+
const k = join2([...ctx.slice(0, i), ...segs]);
|
|
2681
|
+
if (map.has(k)) return k;
|
|
2682
|
+
}
|
|
2683
|
+
return void 0;
|
|
2684
|
+
}
|
|
2685
|
+
function includeDirs(root, files) {
|
|
2686
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
2687
|
+
for (const f of files) {
|
|
2688
|
+
const segs = f.split("/");
|
|
2689
|
+
const i = segs.lastIndexOf("include");
|
|
2690
|
+
if (i >= 0 && i < segs.length - 1) dirs.add(segs.slice(0, i + 1).join("/"));
|
|
2691
|
+
}
|
|
2692
|
+
const read = (name) => {
|
|
2693
|
+
try {
|
|
2694
|
+
return fs6.readFileSync(path6.join(root, name), "utf8");
|
|
2695
|
+
} catch {
|
|
2696
|
+
return "";
|
|
2697
|
+
}
|
|
2698
|
+
};
|
|
2699
|
+
const flags = [...read("Makefile").matchAll(/-I\s*([^\s"']+)/g)].map((m) => m[1]);
|
|
2700
|
+
for (const m of read("CMakeLists.txt").matchAll(/include_directories\s*\(([^)]*)\)/gi))
|
|
2701
|
+
flags.push(...m[1].split(/\s+/));
|
|
2702
|
+
for (const raw of flags) {
|
|
2703
|
+
const d = raw.replace(/^"|"$/g, "").replace(/^\$\{(CMAKE_CURRENT_SOURCE_DIR|PROJECT_SOURCE_DIR|CMAKE_SOURCE_DIR)\}\/?/, "");
|
|
2704
|
+
if (!d || d.startsWith("/") || d.includes("$") || /^[A-Z]+$/.test(d)) continue;
|
|
2705
|
+
dirs.add(path6.posix.normalize(d).replace(/\/$/, "").replace(/^\.$/, ""));
|
|
2706
|
+
}
|
|
2707
|
+
dirs.add("");
|
|
2708
|
+
return [...dirs];
|
|
2709
|
+
}
|
|
2710
|
+
function buildGraph(root, parsed) {
|
|
2711
|
+
const byFile = new Map(parsed.map((f) => [f.file, f]));
|
|
2712
|
+
const dirs = includeDirs(root, [...byFile.keys()]);
|
|
2713
|
+
const { edges, add } = edgeSink();
|
|
2714
|
+
const classes = /* @__PURE__ */ new Map();
|
|
2715
|
+
for (const f of parsed) for (const c of f.classes) if (!classes.has(c.q)) classes.set(c.q, c);
|
|
2716
|
+
const methods = /* @__PURE__ */ new Map();
|
|
2717
|
+
const funcs = /* @__PURE__ */ new Map();
|
|
2718
|
+
const push2 = (m, k, d) => m.set(k, [...m.get(k) ?? [], d]);
|
|
2719
|
+
for (const f of parsed) {
|
|
2720
|
+
const fileNode = f.nodes[0];
|
|
2721
|
+
const taken = new Set(f.classes.map((c) => c.q));
|
|
2722
|
+
for (const d of f.defs) {
|
|
2723
|
+
if (!d.cls && d.scope.length) {
|
|
2724
|
+
const cls = inScope(d.ns, d.scope, classes);
|
|
2725
|
+
if (cls) {
|
|
2726
|
+
d.cls = cls;
|
|
2727
|
+
d.node.kind = "method";
|
|
2728
|
+
const owner = classes.get(cls);
|
|
2729
|
+
d.node.parentId = owner.node.file === f.file ? owner.node.id : fileNode.id;
|
|
2730
|
+
d.node.exported = true;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
d.node.parentId ??= fileNode.id;
|
|
2734
|
+
const base = d.cls ? `${d.cls}.${d.name}` : join2([...d.ns, ...d.scope, d.name]);
|
|
2735
|
+
let q = base;
|
|
2736
|
+
for (let i = 2; taken.has(q); i++) q = `${base}@${d.node.kind}${i === 2 ? "" : i}`;
|
|
2737
|
+
taken.add(q);
|
|
2738
|
+
d.node.id = nodeId(f.file, q);
|
|
2739
|
+
push2(d.cls ? methods : funcs, base, d);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
const resolveInclude = (from, inc) => {
|
|
2743
|
+
const tries = inc.angle ? [] : [path6.posix.join(path6.posix.dirname(from), inc.path)];
|
|
2744
|
+
for (const d of dirs) tries.push(path6.posix.join(d, inc.path));
|
|
2745
|
+
return tries.map((t) => path6.posix.normalize(t)).find((t) => byFile.has(t));
|
|
2746
|
+
};
|
|
2747
|
+
const direct = /* @__PURE__ */ new Map();
|
|
2748
|
+
for (const f of parsed) {
|
|
2749
|
+
const to = [];
|
|
2750
|
+
for (const inc of f.includes) {
|
|
2751
|
+
const hit = resolveInclude(f.file, inc);
|
|
2752
|
+
if (!hit) continue;
|
|
2753
|
+
to.push(hit);
|
|
2754
|
+
add(fileId(f.file), "imports", fileId(hit), ["static"], 1, { line: inc.line, inLoop: false });
|
|
2755
|
+
}
|
|
2756
|
+
direct.set(f.file, to);
|
|
2757
|
+
}
|
|
2758
|
+
const closures = /* @__PURE__ */ new Map();
|
|
2759
|
+
const closure = (file) => {
|
|
2760
|
+
let seen = closures.get(file);
|
|
2761
|
+
if (seen) return seen;
|
|
2762
|
+
seen = /* @__PURE__ */ new Set([file]);
|
|
2763
|
+
const stack = [file];
|
|
2764
|
+
while (stack.length) {
|
|
2765
|
+
for (const g of direct.get(stack.pop()) ?? []) {
|
|
2766
|
+
if (!seen.has(g)) {
|
|
2767
|
+
seen.add(g);
|
|
2768
|
+
stack.push(g);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
closures.set(file, seen);
|
|
2773
|
+
return seen;
|
|
2774
|
+
};
|
|
2775
|
+
const ctorOf = (q) => methods.get(`${q}.${classes.get(q).node.name}`)?.[0]?.node.id;
|
|
2776
|
+
const methodOf = (q, name, depth = 0) => {
|
|
2777
|
+
const hit = methods.get(`${q}.${name}`)?.[0];
|
|
2778
|
+
if (hit || depth > 4) return hit;
|
|
2779
|
+
const cls = classes.get(q);
|
|
2780
|
+
for (const b of cls?.bases ?? []) {
|
|
2781
|
+
const bq = inScope(cls?.ns ?? [], b, classes);
|
|
2782
|
+
const m = bq && methodOf(bq, name, depth + 1);
|
|
2783
|
+
if (m) return m;
|
|
2784
|
+
}
|
|
2785
|
+
return void 0;
|
|
2786
|
+
};
|
|
2787
|
+
const fieldOf = (q, name, depth = 0) => {
|
|
2788
|
+
const cls = classes.get(q);
|
|
2789
|
+
if (!cls || depth > 4) return void 0;
|
|
2790
|
+
const t = cls.fields.get(name);
|
|
2791
|
+
if (t) return { type: t, ns: cls.ns };
|
|
2792
|
+
for (const b of cls.bases) {
|
|
2793
|
+
const bq = inScope(cls.ns, b, classes);
|
|
2794
|
+
const hit = bq && fieldOf(bq, name, depth + 1);
|
|
2795
|
+
if (hit) return hit;
|
|
2796
|
+
}
|
|
2797
|
+
return void 0;
|
|
2798
|
+
};
|
|
2799
|
+
for (const f of parsed) {
|
|
2800
|
+
const seen = closure(f.file);
|
|
2801
|
+
const visible = (q) => [...seen].some((g) => byFile.get(g)?.declared.has(q));
|
|
2802
|
+
const pickFn = (q) => {
|
|
2803
|
+
const cands = funcs.get(q) ?? [];
|
|
2804
|
+
const here = cands.find((d) => d.node.file === f.file);
|
|
2805
|
+
if (here) return { id: here.node.id, how: STATIC };
|
|
2806
|
+
const inc = cands.filter((d) => seen.has(d.node.file));
|
|
2807
|
+
if (inc.length === 1) return { id: inc[0].node.id, how: CROSS };
|
|
2808
|
+
if (cands.length !== 1) return void 0;
|
|
2809
|
+
return { id: cands[0].node.id, how: visible(q) ? CROSS : GUESS };
|
|
2810
|
+
};
|
|
2811
|
+
const how = (to) => to.split("#")[0] === f.file ? STATIC : CROSS;
|
|
2812
|
+
for (const c of f.calls) {
|
|
2813
|
+
const def = "locals" in c.from ? c.from : void 0;
|
|
2814
|
+
const fromId = def ? def.node.id : c.from.id;
|
|
2815
|
+
const site = { line: c.line, inLoop: c.inLoop };
|
|
2816
|
+
const link = (to, h = to ? how(to) : STATIC) => {
|
|
2817
|
+
if (to) add(fromId, "calls", to, ...h, site);
|
|
2818
|
+
};
|
|
2819
|
+
if (c.form === "ctor") {
|
|
2820
|
+
const q = inScope(c.ctx, c.segs, classes);
|
|
2821
|
+
if (q) link(ctorOf(q));
|
|
2822
|
+
continue;
|
|
2823
|
+
}
|
|
2824
|
+
if (c.form === "this") {
|
|
2825
|
+
if (def?.cls && c.method) link(methodOf(def.cls, c.method)?.node.id);
|
|
2826
|
+
continue;
|
|
2827
|
+
}
|
|
2828
|
+
if (c.form === "member") {
|
|
2829
|
+
const [head, field] = c.segs;
|
|
2830
|
+
let t;
|
|
2831
|
+
if (head === "this") t = def?.cls && field ? fieldOf(def.cls, field) : void 0;
|
|
2832
|
+
else {
|
|
2833
|
+
const local = def?.locals.get(head);
|
|
2834
|
+
t = local ? { type: local, ns: c.ctx } : def?.cls ? fieldOf(def.cls, head) : void 0;
|
|
2835
|
+
}
|
|
2836
|
+
const q = t && inScope(t.ns, t.type, classes);
|
|
2837
|
+
if (q && c.method) link(methodOf(q, c.method)?.node.id);
|
|
2838
|
+
continue;
|
|
2839
|
+
}
|
|
2840
|
+
const name = c.segs.at(-1);
|
|
2841
|
+
if (c.segs.length === 1 && def?.cls) {
|
|
2842
|
+
const m = methodOf(def.cls, name);
|
|
2843
|
+
if (m) {
|
|
2844
|
+
link(m.node.id);
|
|
2845
|
+
continue;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
if (c.segs.length > 1) {
|
|
2849
|
+
const cls = inScope(c.ctx, c.segs.slice(0, -1), classes);
|
|
2850
|
+
if (cls) {
|
|
2851
|
+
link(methodOf(cls, name)?.node.id);
|
|
2852
|
+
continue;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
const fq = inScope(c.ctx, c.segs, funcs);
|
|
2856
|
+
const fn = fq && pickFn(fq);
|
|
2857
|
+
if (fn) {
|
|
2858
|
+
link(fn.id, fn.how);
|
|
2859
|
+
continue;
|
|
2860
|
+
}
|
|
2861
|
+
const cq = inScope(c.ctx, c.segs, classes);
|
|
2862
|
+
if (cq) link(ctorOf(cq));
|
|
2863
|
+
}
|
|
2864
|
+
for (const cls of f.classes) {
|
|
2865
|
+
for (const b of cls.bases) {
|
|
2866
|
+
const bq = inScope(cls.ns, b, classes);
|
|
2867
|
+
if (bq) {
|
|
2868
|
+
const to = classes.get(bq).node.id;
|
|
2869
|
+
add(cls.node.id, "inherits", to, ...how(to), {
|
|
2870
|
+
line: cls.node.range.startLine,
|
|
2871
|
+
inLoop: false
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
return { nodes: parsed.flatMap((f) => f.nodes), edges: [...edges.values()] };
|
|
2878
|
+
}
|
|
2879
|
+
function indexCpp(root, files, tagsFor2) {
|
|
2880
|
+
const parsed = [];
|
|
2881
|
+
for (const file of [...files].sort()) {
|
|
2882
|
+
const text = fs6.readFileSync(path6.join(root, file), "utf8");
|
|
2883
|
+
if (GENERATED.test(text.split("\n", 30).join("\n"))) continue;
|
|
2884
|
+
const tree = parser.parse(text);
|
|
2885
|
+
if (!tree) continue;
|
|
2886
|
+
try {
|
|
2887
|
+
parsed.push(collect(file, text, tagsFor2(file), tree.rootNode));
|
|
2888
|
+
} finally {
|
|
2889
|
+
tree.delete();
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
return buildGraph(root, parsed);
|
|
2893
|
+
}
|
|
2894
|
+
var parser, STATIC, CROSS, GUESS, LOOPS, SMART_PTRS, MAKERS, GENERATED, join2, innermost, defCtx;
|
|
2895
|
+
var init_cpp = __esm({
|
|
2896
|
+
async "packages/engine/src/indexer/cpp.ts"() {
|
|
2897
|
+
"use strict";
|
|
2898
|
+
init_ids();
|
|
2899
|
+
init_blocks();
|
|
2900
|
+
init_edges();
|
|
2901
|
+
init_tree_sitter();
|
|
2902
|
+
await treeSitterReady;
|
|
2903
|
+
parser = new Parser2();
|
|
2904
|
+
parser.setLanguage(await Language.load(grammarWasm("tree-sitter-cpp")));
|
|
2905
|
+
STATIC = [["static"], 0.95];
|
|
2906
|
+
CROSS = [["inferred"], 0.9];
|
|
2907
|
+
GUESS = [["inferred"], 0.5];
|
|
2908
|
+
LOOPS = /* @__PURE__ */ new Set(["for_statement", "for_range_loop", "while_statement", "do_statement"]);
|
|
2909
|
+
SMART_PTRS = /* @__PURE__ */ new Set(["unique_ptr", "shared_ptr", "weak_ptr"]);
|
|
2910
|
+
MAKERS = /* @__PURE__ */ new Set(["make_unique", "make_shared"]);
|
|
2911
|
+
GENERATED = /generated by|do not edit/i;
|
|
2912
|
+
join2 = (segs) => segs.join(".");
|
|
2913
|
+
innermost = (n) => {
|
|
2914
|
+
let at = n;
|
|
2915
|
+
while (at.type === "qualified_identifier") at = at.childForFieldName("name") ?? at;
|
|
2916
|
+
return at;
|
|
2917
|
+
};
|
|
2918
|
+
defCtx = (d) => d.cls ? d.cls.split(".") : [...d.ns, ...d.scope];
|
|
2919
|
+
}
|
|
2920
|
+
});
|
|
2921
|
+
|
|
2922
|
+
// packages/engine/src/indexer/luau.ts
|
|
2923
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2924
|
+
import fs7 from "node:fs";
|
|
2925
|
+
import path7 from "node:path";
|
|
2926
|
+
import { Language as Language2, Parser as Parser3 } from "web-tree-sitter";
|
|
2358
2927
|
function chainOf(n) {
|
|
2359
2928
|
if (n?.type === "identifier") return n.text;
|
|
2360
2929
|
if (n?.type !== "dot_index_expression") return void 0;
|
|
@@ -2371,7 +2940,7 @@ function ownLine(c) {
|
|
|
2371
2940
|
const prev = c.previousSibling;
|
|
2372
2941
|
return !prev || prev.endPosition.row < c.startPosition.row;
|
|
2373
2942
|
}
|
|
2374
|
-
function
|
|
2943
|
+
function leadingComments2(stmt) {
|
|
2375
2944
|
const out = [];
|
|
2376
2945
|
let below = stmt;
|
|
2377
2946
|
for (let c = above(stmt); c?.type === "comment"; c = above(c)) {
|
|
@@ -2398,20 +2967,20 @@ function signatureOf(fn, text) {
|
|
|
2398
2967
|
return collapse(text.slice(start, stop?.startIndex ?? params.endIndex));
|
|
2399
2968
|
}
|
|
2400
2969
|
function walkFile(file, text, tags) {
|
|
2401
|
-
const tree =
|
|
2970
|
+
const tree = parser2.parse(text);
|
|
2402
2971
|
if (!tree) throw new Error(`tree-sitter could not parse ${file}`);
|
|
2403
2972
|
try {
|
|
2404
|
-
return
|
|
2973
|
+
return collect2(file, text, tags, tree.rootNode);
|
|
2405
2974
|
} finally {
|
|
2406
2975
|
tree.delete();
|
|
2407
2976
|
}
|
|
2408
2977
|
}
|
|
2409
|
-
function
|
|
2978
|
+
function collect2(file, text, tags, root) {
|
|
2410
2979
|
const lineCount2 = text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
|
|
2411
2980
|
const fileNode = {
|
|
2412
2981
|
id: fileId(file),
|
|
2413
2982
|
kind: "file",
|
|
2414
|
-
name:
|
|
2983
|
+
name: path7.posix.basename(file),
|
|
2415
2984
|
file,
|
|
2416
2985
|
range: { startLine: 1, endLine: Math.max(1, lineCount2) },
|
|
2417
2986
|
bodyHash: bodyHash(text),
|
|
@@ -2450,7 +3019,7 @@ function collect(file, text, tags, root) {
|
|
|
2450
3019
|
q = `${qname}@${kind}`;
|
|
2451
3020
|
for (let n = 2; out.byQname.has(q); n++) q = `${qname}@${kind}${n}`;
|
|
2452
3021
|
}
|
|
2453
|
-
const docs =
|
|
3022
|
+
const docs = leadingComments2(stmt);
|
|
2454
3023
|
const node = {
|
|
2455
3024
|
id: nodeId(file, q),
|
|
2456
3025
|
kind,
|
|
@@ -2790,7 +3359,7 @@ function parseAllFiles(root, files, tagsFor2, cacheRepo) {
|
|
|
2790
3359
|
const nextCache = {};
|
|
2791
3360
|
const parsed = /* @__PURE__ */ new Map();
|
|
2792
3361
|
for (const file of [...files].sort()) {
|
|
2793
|
-
const text =
|
|
3362
|
+
const text = fs7.readFileSync(path7.join(root, file), "utf8");
|
|
2794
3363
|
const hash = createHash4("sha256").update(text).digest("hex");
|
|
2795
3364
|
const cached = onDisk[file];
|
|
2796
3365
|
const lf = cached?.hash === hash ? deserializeLuauFile(cached.data) : walkFile(file, text, tagsFor2(file));
|
|
@@ -2800,7 +3369,7 @@ function parseAllFiles(root, files, tagsFor2, cacheRepo) {
|
|
|
2800
3369
|
if (useCache) writeJsonCacheAtomic(cacheFile, nextCache);
|
|
2801
3370
|
return parsed;
|
|
2802
3371
|
}
|
|
2803
|
-
function
|
|
3372
|
+
function buildGraph2(parsed, graph) {
|
|
2804
3373
|
if (!graph && !warned) {
|
|
2805
3374
|
warned = true;
|
|
2806
3375
|
console.warn("topo: luau-lsp not found or failed; Luau imports resolve relative requires only");
|
|
@@ -2826,12 +3395,12 @@ function buildGraph(parsed, graph) {
|
|
|
2826
3395
|
function indexLuau(root, files, tagsFor2, cacheRepo = root) {
|
|
2827
3396
|
const parsed = parseAllFiles(root, files, tagsFor2, cacheRepo);
|
|
2828
3397
|
const graph = requireGraph(root, [...parsed.keys()]);
|
|
2829
|
-
return
|
|
3398
|
+
return buildGraph2(parsed, graph);
|
|
2830
3399
|
}
|
|
2831
3400
|
function indexLuauState(root, files, tagsFor2, cacheRepo = root) {
|
|
2832
3401
|
const parsed = parseAllFiles(root, files, tagsFor2, cacheRepo);
|
|
2833
3402
|
const graph = requireGraph(root, [...parsed.keys()]);
|
|
2834
|
-
return { graph:
|
|
3403
|
+
return { graph: buildGraph2(parsed, graph), state: { parsed, requireGraph: graph } };
|
|
2835
3404
|
}
|
|
2836
3405
|
function updateLuau(root, files, changedFiles, tagsFor2, prev, forceRequireGraph = false) {
|
|
2837
3406
|
const fileSet = new Set(files);
|
|
@@ -2845,13 +3414,13 @@ function updateLuau(root, files, changedFiles, tagsFor2, prev, forceRequireGraph
|
|
|
2845
3414
|
parsed.set(f, old);
|
|
2846
3415
|
continue;
|
|
2847
3416
|
}
|
|
2848
|
-
const text =
|
|
3417
|
+
const text = fs7.readFileSync(path7.join(root, f), "utf8");
|
|
2849
3418
|
const fresh = walkFile(f, text, tagsFor2(f));
|
|
2850
3419
|
if (!old || !sameSpecs(old.requires, fresh.requires)) requiresChanged = true;
|
|
2851
3420
|
parsed.set(f, fresh);
|
|
2852
3421
|
}
|
|
2853
3422
|
const graph = requiresChanged ? requireGraph(root, files) : prev.requireGraph;
|
|
2854
|
-
return { graph:
|
|
3423
|
+
return { graph: buildGraph2(parsed, graph), state: { parsed, requireGraph: graph } };
|
|
2855
3424
|
}
|
|
2856
3425
|
function resolveRef2(f, r, files) {
|
|
2857
3426
|
if (r.base === void 0) {
|
|
@@ -2861,14 +3430,14 @@ function resolveRef2(f, r, files) {
|
|
|
2861
3430
|
const req = r.binding?.req;
|
|
2862
3431
|
if (req) {
|
|
2863
3432
|
const to2 = !r.base.includes(".") && req.target ? files.get(req.target)?.exports.get(r.name) : void 0;
|
|
2864
|
-
return to2 && { to: to2, how:
|
|
3433
|
+
return to2 && { to: to2, how: CROSS2 };
|
|
2865
3434
|
}
|
|
2866
3435
|
const self = r.binding?.selfOf;
|
|
2867
3436
|
const table = self ? r.base.replace(/^self\b/, self) : r.base;
|
|
2868
3437
|
const to = f.byQname.get(`${table}.${r.name}`);
|
|
2869
3438
|
return to && to.kind !== "file" ? { to, how: CALL } : void 0;
|
|
2870
3439
|
}
|
|
2871
|
-
var
|
|
3440
|
+
var parser2, SKIP, CALL, CROSS2, NOT_DOC, optDoc2, named2, qnameOf, isRequire, unwrapCast, warned, sameSpecs, same2;
|
|
2872
3441
|
var init_luau = __esm({
|
|
2873
3442
|
async "packages/engine/src/indexer/luau.ts"() {
|
|
2874
3443
|
"use strict";
|
|
@@ -2879,11 +3448,11 @@ var init_luau = __esm({
|
|
|
2879
3448
|
init_luau_requires();
|
|
2880
3449
|
init_tree_sitter();
|
|
2881
3450
|
await treeSitterReady;
|
|
2882
|
-
|
|
2883
|
-
|
|
3451
|
+
parser2 = new Parser3();
|
|
3452
|
+
parser2.setLanguage(await Language2.load(grammarWasm("tree-sitter-luau")));
|
|
2884
3453
|
SKIP = /* @__PURE__ */ new Set(["comment", "type_definition", "number", "true", "false", "nil"]);
|
|
2885
3454
|
CALL = [["static"], 0.95];
|
|
2886
|
-
|
|
3455
|
+
CROSS2 = [["inferred"], 0.9];
|
|
2887
3456
|
NOT_DOC = /^--!|^--\s*(selene|stylua):|copyright|SPDX-License-Identifier/i;
|
|
2888
3457
|
optDoc2 = (doc) => doc ? { doc } : {};
|
|
2889
3458
|
named2 = (n, type) => n.namedChildren.find((c) => c.type === type);
|
|
@@ -2897,24 +3466,14 @@ var init_luau = __esm({
|
|
|
2897
3466
|
});
|
|
2898
3467
|
|
|
2899
3468
|
// packages/engine/src/indexer/python.ts
|
|
2900
|
-
import
|
|
2901
|
-
import
|
|
2902
|
-
import { Language as
|
|
3469
|
+
import fs8 from "node:fs";
|
|
3470
|
+
import path8 from "node:path";
|
|
3471
|
+
import { Language as Language3, Parser as Parser4 } from "web-tree-sitter";
|
|
2903
3472
|
function docstring(body) {
|
|
2904
3473
|
const first = body?.namedChildren[0];
|
|
2905
3474
|
const str = first?.type === "expression_statement" ? first.namedChildren[0] : void 0;
|
|
2906
3475
|
return str?.type === "string" ? stripQuotes(str.text) : void 0;
|
|
2907
3476
|
}
|
|
2908
|
-
function leadingComments2(stmt) {
|
|
2909
|
-
const out = [];
|
|
2910
|
-
let at = stmt;
|
|
2911
|
-
for (let p = stmt.previousNamedSibling; p?.type === "comment"; p = p.previousNamedSibling) {
|
|
2912
|
-
if (p.endPosition.row < at.startPosition.row - 1) break;
|
|
2913
|
-
out.unshift(p);
|
|
2914
|
-
at = p;
|
|
2915
|
-
}
|
|
2916
|
-
return out;
|
|
2917
|
-
}
|
|
2918
3477
|
function dotted(n) {
|
|
2919
3478
|
if (!n) return void 0;
|
|
2920
3479
|
if (n.type === "identifier") return [n.text];
|
|
@@ -2923,13 +3482,13 @@ function dotted(n) {
|
|
|
2923
3482
|
const attr = n.childForFieldName("attribute");
|
|
2924
3483
|
return head && attr ? [...head, attr.text] : void 0;
|
|
2925
3484
|
}
|
|
2926
|
-
function
|
|
3485
|
+
function collect3(file, text, tags, root) {
|
|
2927
3486
|
const lineCount2 = text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
|
|
2928
3487
|
const fileDoc = docstring(root);
|
|
2929
3488
|
const fileNode = {
|
|
2930
3489
|
id: fileId(file),
|
|
2931
3490
|
kind: "file",
|
|
2932
|
-
name:
|
|
3491
|
+
name: path8.posix.basename(file),
|
|
2933
3492
|
file,
|
|
2934
3493
|
range: { startLine: 1, endLine: Math.max(1, lineCount2) },
|
|
2935
3494
|
...fileDoc && { doc: fileDoc.slice(0, DOC_CAP) },
|
|
@@ -2956,7 +3515,7 @@ function collect2(file, text, tags, root) {
|
|
|
2956
3515
|
const base = parent.kind === "file" ? name : `${qnameOf2(parent)}.${name}`;
|
|
2957
3516
|
let q = base;
|
|
2958
3517
|
for (let i = 2; out.byQname.has(q); i++) q = `${base}@${kind}${i === 2 ? "" : i}`;
|
|
2959
|
-
const comments2 =
|
|
3518
|
+
const comments2 = leadingComments(outer);
|
|
2960
3519
|
const doc = docstring(def.childForFieldName("body")) ?? comments2.map((c) => c.text.replace(/^#\s?/, "")).join("\n");
|
|
2961
3520
|
const params = def.childForFieldName(isClass ? "superclasses" : "parameters")?.text ?? "";
|
|
2962
3521
|
const ret = def.childForFieldName("return_type")?.text;
|
|
@@ -3029,7 +3588,7 @@ function collect2(file, text, tags, root) {
|
|
|
3029
3588
|
});
|
|
3030
3589
|
}
|
|
3031
3590
|
}
|
|
3032
|
-
walk2(c, block2, fnScopes, classQ, inLoop ||
|
|
3591
|
+
walk2(c, block2, fnScopes, classQ, inLoop || LOOPS2.has(c.type));
|
|
3033
3592
|
}
|
|
3034
3593
|
};
|
|
3035
3594
|
walk2(root, fileNode, [], void 0, false);
|
|
@@ -3067,9 +3626,9 @@ function importsOf(stmt) {
|
|
|
3067
3626
|
function importRoots(files) {
|
|
3068
3627
|
const roots = /* @__PURE__ */ new Set(["", "src"]);
|
|
3069
3628
|
for (const f of files) {
|
|
3070
|
-
if (
|
|
3071
|
-
const parent =
|
|
3072
|
-
if (!files.has(
|
|
3629
|
+
if (path8.posix.basename(f) !== "__init__.py") continue;
|
|
3630
|
+
const parent = path8.posix.dirname(path8.posix.dirname(f));
|
|
3631
|
+
if (!files.has(path8.posix.join(parent, "__init__.py"))) roots.add(parent === "." ? "" : parent);
|
|
3073
3632
|
}
|
|
3074
3633
|
return [...roots];
|
|
3075
3634
|
}
|
|
@@ -3077,15 +3636,15 @@ function moduleFile(files, roots, from, module, level) {
|
|
|
3077
3636
|
const parts = module ? module.split(".") : [];
|
|
3078
3637
|
let bases;
|
|
3079
3638
|
if (level > 0) {
|
|
3080
|
-
let dir =
|
|
3081
|
-
for (let i = 1; i < level; i++) dir =
|
|
3639
|
+
let dir = path8.posix.dirname(from);
|
|
3640
|
+
for (let i = 1; i < level; i++) dir = path8.posix.dirname(dir);
|
|
3082
3641
|
bases = [dir];
|
|
3083
3642
|
} else {
|
|
3084
|
-
bases = [...roots,
|
|
3643
|
+
bases = [...roots, path8.posix.dirname(from)];
|
|
3085
3644
|
}
|
|
3086
3645
|
for (const b of bases) {
|
|
3087
|
-
const p =
|
|
3088
|
-
for (const f of [`${p}.py`,
|
|
3646
|
+
const p = path8.posix.join(b === "." ? "" : b, ...parts);
|
|
3647
|
+
for (const f of [`${p}.py`, path8.posix.join(p, "__init__.py")]) if (files.has(f)) return f;
|
|
3089
3648
|
}
|
|
3090
3649
|
return void 0;
|
|
3091
3650
|
}
|
|
@@ -3119,7 +3678,7 @@ function bindingsOf(f, files, roots, add) {
|
|
|
3119
3678
|
}
|
|
3120
3679
|
return binds;
|
|
3121
3680
|
}
|
|
3122
|
-
function
|
|
3681
|
+
function buildGraph3(parsed) {
|
|
3123
3682
|
const byFile = new Map(parsed.map((f) => [f.file, f]));
|
|
3124
3683
|
const files = new Set(byFile.keys());
|
|
3125
3684
|
const roots = importRoots(files);
|
|
@@ -3131,32 +3690,32 @@ function buildGraph2(parsed) {
|
|
|
3131
3690
|
roots,
|
|
3132
3691
|
(to, line) => add(fileId(f.file), "imports", fileId(to), ["static"], 1, { line, inLoop: false })
|
|
3133
3692
|
);
|
|
3134
|
-
const inModule = (file,
|
|
3693
|
+
const inModule = (file, path27) => byFile.get(file)?.byQname.get(path27.join("."));
|
|
3135
3694
|
const resolve2 = (callee, fnScopes, classQ) => {
|
|
3136
3695
|
const [head, ...rest] = callee;
|
|
3137
3696
|
if (!head) return void 0;
|
|
3138
3697
|
if ((head === "self" || head === "cls") && classQ && rest.length === 1) {
|
|
3139
3698
|
const hit = f.byQname.get(`${classQ}.${rest[0]}`);
|
|
3140
|
-
return hit && { node: hit, how:
|
|
3699
|
+
return hit && { node: hit, how: STATIC2 };
|
|
3141
3700
|
}
|
|
3142
3701
|
if (rest.length === 0) {
|
|
3143
3702
|
for (const scope of [...fnScopes].reverse()) {
|
|
3144
3703
|
const hit = f.byQname.get(`${scope}.${head}`);
|
|
3145
|
-
if (hit) return { node: hit, how:
|
|
3704
|
+
if (hit) return { node: hit, how: STATIC2 };
|
|
3146
3705
|
}
|
|
3147
3706
|
}
|
|
3148
3707
|
const local = f.byQname.get(callee.join("."));
|
|
3149
|
-
if (local) return { node: local, how:
|
|
3708
|
+
if (local) return { node: local, how: STATIC2 };
|
|
3150
3709
|
for (let i = callee.length - 1; i >= 1; i--) {
|
|
3151
3710
|
const b2 = binds.get(callee.slice(0, i).join("."));
|
|
3152
3711
|
if (!b2) continue;
|
|
3153
3712
|
const hit = inModule(b2.module, [...b2.member ? [b2.member] : [], ...callee.slice(i)]);
|
|
3154
|
-
return hit && { node: hit, how:
|
|
3713
|
+
return hit && { node: hit, how: CROSS3 };
|
|
3155
3714
|
}
|
|
3156
3715
|
const b = binds.get(head);
|
|
3157
3716
|
if (b?.member && rest.length === 0) {
|
|
3158
3717
|
const hit = inModule(b.module, [b.member]);
|
|
3159
|
-
return hit && { node: hit, how:
|
|
3718
|
+
return hit && { node: hit, how: CROSS3 };
|
|
3160
3719
|
}
|
|
3161
3720
|
return void 0;
|
|
3162
3721
|
};
|
|
@@ -3175,18 +3734,18 @@ function buildGraph2(parsed) {
|
|
|
3175
3734
|
function indexPython(root, files, tagsFor2) {
|
|
3176
3735
|
const parsed = [];
|
|
3177
3736
|
for (const file of [...files].sort()) {
|
|
3178
|
-
const text =
|
|
3179
|
-
const tree =
|
|
3737
|
+
const text = fs8.readFileSync(path8.join(root, file), "utf8");
|
|
3738
|
+
const tree = parser3.parse(text);
|
|
3180
3739
|
if (!tree) continue;
|
|
3181
3740
|
try {
|
|
3182
|
-
parsed.push(
|
|
3741
|
+
parsed.push(collect3(file, text, tagsFor2(file), tree.rootNode));
|
|
3183
3742
|
} finally {
|
|
3184
3743
|
tree.delete();
|
|
3185
3744
|
}
|
|
3186
3745
|
}
|
|
3187
|
-
return
|
|
3746
|
+
return buildGraph3(parsed);
|
|
3188
3747
|
}
|
|
3189
|
-
var
|
|
3748
|
+
var parser3, STATIC2, CROSS3, LOOPS2, stripQuotes;
|
|
3190
3749
|
var init_python = __esm({
|
|
3191
3750
|
async "packages/engine/src/indexer/python.ts"() {
|
|
3192
3751
|
"use strict";
|
|
@@ -3195,23 +3754,23 @@ var init_python = __esm({
|
|
|
3195
3754
|
init_edges();
|
|
3196
3755
|
init_tree_sitter();
|
|
3197
3756
|
await treeSitterReady;
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3757
|
+
parser3 = new Parser4();
|
|
3758
|
+
parser3.setLanguage(await Language3.load(grammarWasm("tree-sitter-python")));
|
|
3759
|
+
STATIC2 = [["static"], 0.95];
|
|
3760
|
+
CROSS3 = [["inferred"], 0.9];
|
|
3761
|
+
LOOPS2 = /* @__PURE__ */ new Set(["for_statement", "while_statement", "list_comprehension", "for_in_clause"]);
|
|
3203
3762
|
stripQuotes = (s) => s.replace(/^[rbuf]*("""|'''|"|')/i, "").replace(/("""|'''|"|')$/, "").trim();
|
|
3204
3763
|
}
|
|
3205
3764
|
});
|
|
3206
3765
|
|
|
3207
3766
|
// packages/engine/src/indexer/rust.ts
|
|
3208
|
-
import
|
|
3209
|
-
import
|
|
3210
|
-
import { Language as
|
|
3767
|
+
import fs9 from "node:fs";
|
|
3768
|
+
import path9 from "node:path";
|
|
3769
|
+
import { Language as Language4, Parser as Parser5 } from "web-tree-sitter";
|
|
3211
3770
|
function moduleOf(root, file) {
|
|
3212
|
-
for (let dir =
|
|
3213
|
-
if (
|
|
3214
|
-
const rel =
|
|
3771
|
+
for (let dir = path9.posix.dirname(file); ; dir = path9.posix.dirname(dir)) {
|
|
3772
|
+
if (fs9.existsSync(path9.join(root, dir, "Cargo.toml"))) {
|
|
3773
|
+
const rel = path9.posix.relative(path9.posix.join(dir, "src"), file);
|
|
3215
3774
|
const bin = rel === "main.rs" || rel.startsWith("bin/") || rel.startsWith("..");
|
|
3216
3775
|
if (bin) return { crate: file, segs: [], lib: dir };
|
|
3217
3776
|
const segs = rel.replace(/\.rs$/, "").split("/");
|
|
@@ -3226,7 +3785,7 @@ function moduleOf(root, file) {
|
|
|
3226
3785
|
function libName(root, crate) {
|
|
3227
3786
|
let toml;
|
|
3228
3787
|
try {
|
|
3229
|
-
toml =
|
|
3788
|
+
toml = fs9.readFileSync(path9.join(root, crate, "Cargo.toml"), "utf8");
|
|
3230
3789
|
} catch {
|
|
3231
3790
|
return void 0;
|
|
3232
3791
|
}
|
|
@@ -3272,12 +3831,12 @@ function pathSegs(n) {
|
|
|
3272
3831
|
const h = pathSegs(head);
|
|
3273
3832
|
return h ? [...h, name] : void 0;
|
|
3274
3833
|
}
|
|
3275
|
-
function
|
|
3834
|
+
function collect4(root, file, text, tags, tree, ix) {
|
|
3276
3835
|
const lineCount2 = text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
|
|
3277
3836
|
const fileNode = {
|
|
3278
3837
|
id: fileId(file),
|
|
3279
3838
|
kind: "file",
|
|
3280
|
-
name:
|
|
3839
|
+
name: path9.posix.basename(file),
|
|
3281
3840
|
file,
|
|
3282
3841
|
range: { startLine: 1, endLine: Math.max(1, lineCount2) },
|
|
3283
3842
|
bodyHash: bodyHash(text),
|
|
@@ -3419,7 +3978,7 @@ function collect3(root, file, text, tags, tree, ix) {
|
|
|
3419
3978
|
});
|
|
3420
3979
|
}
|
|
3421
3980
|
}
|
|
3422
|
-
walk2(c, block2, mod, prefix, implType, inLoop ||
|
|
3981
|
+
walk2(c, block2, mod, prefix, implType, inLoop || LOOPS3.has(c.type), testy);
|
|
3423
3982
|
}
|
|
3424
3983
|
};
|
|
3425
3984
|
walk2(tree, fileNode, fileMod, "", void 0, false, false);
|
|
@@ -3484,17 +4043,17 @@ function indexRust(root, files, tagsFor2) {
|
|
|
3484
4043
|
};
|
|
3485
4044
|
const parsed = [];
|
|
3486
4045
|
for (const file of [...files].sort()) {
|
|
3487
|
-
const text =
|
|
3488
|
-
const tree =
|
|
4046
|
+
const text = fs9.readFileSync(path9.join(root, file), "utf8");
|
|
4047
|
+
const tree = parser4.parse(text);
|
|
3489
4048
|
if (!tree) continue;
|
|
3490
4049
|
try {
|
|
3491
|
-
parsed.push(
|
|
4050
|
+
parsed.push(collect4(root, file, text, tagsFor2(file), tree.rootNode, ix));
|
|
3492
4051
|
} finally {
|
|
3493
4052
|
tree.delete();
|
|
3494
4053
|
}
|
|
3495
4054
|
}
|
|
3496
4055
|
const { edges, add } = edgeSink();
|
|
3497
|
-
const how = (from, to) => ix.fileOf.get(to) === from.split("#")[0] ?
|
|
4056
|
+
const how = (from, to) => ix.fileOf.get(to) === from.split("#")[0] ? STATIC3 : CROSS4;
|
|
3498
4057
|
for (const f of parsed) {
|
|
3499
4058
|
for (const c of f.calls) {
|
|
3500
4059
|
const [head, ...rest] = c.callee;
|
|
@@ -3528,7 +4087,7 @@ function indexRust(root, files, tagsFor2) {
|
|
|
3528
4087
|
const commands = new Map(parsed.flatMap((f) => f.commands.map((c) => [c.name, c.id])));
|
|
3529
4088
|
return { nodes: parsed.flatMap((f) => f.nodes), edges: [...edges.values()], commands };
|
|
3530
4089
|
}
|
|
3531
|
-
var
|
|
4090
|
+
var parser4, STATIC3, CROSS4, LOOPS3, TYPE_ITEMS, modKey;
|
|
3532
4091
|
var init_rust = __esm({
|
|
3533
4092
|
async "packages/engine/src/indexer/rust.ts"() {
|
|
3534
4093
|
"use strict";
|
|
@@ -3537,11 +4096,11 @@ var init_rust = __esm({
|
|
|
3537
4096
|
init_edges();
|
|
3538
4097
|
init_tree_sitter();
|
|
3539
4098
|
await treeSitterReady;
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
4099
|
+
parser4 = new Parser5();
|
|
4100
|
+
parser4.setLanguage(await Language4.load(grammarWasm("tree-sitter-rust")));
|
|
4101
|
+
STATIC3 = [["static"], 0.95];
|
|
4102
|
+
CROSS4 = [["inferred"], 0.9];
|
|
4103
|
+
LOOPS3 = /* @__PURE__ */ new Set(["for_expression", "while_expression", "loop_expression"]);
|
|
3545
4104
|
TYPE_ITEMS = {
|
|
3546
4105
|
struct_item: "class",
|
|
3547
4106
|
enum_item: "class",
|
|
@@ -3554,13 +4113,13 @@ var init_rust = __esm({
|
|
|
3554
4113
|
});
|
|
3555
4114
|
|
|
3556
4115
|
// packages/engine/src/indexer/tauri.ts
|
|
3557
|
-
import
|
|
3558
|
-
import
|
|
4116
|
+
import fs10 from "node:fs";
|
|
4117
|
+
import path10 from "node:path";
|
|
3559
4118
|
function linkTauri(root, files, graph, commands) {
|
|
3560
4119
|
if (commands.size === 0) return [];
|
|
3561
4120
|
const { edges, add } = edgeSink();
|
|
3562
4121
|
for (const file of files) {
|
|
3563
|
-
const text =
|
|
4122
|
+
const text = fs10.readFileSync(path10.join(root, file), "utf8");
|
|
3564
4123
|
if (!text.includes("invoke")) continue;
|
|
3565
4124
|
const blocks = graph.nodes.filter((n) => n.file === file && n.kind !== "file");
|
|
3566
4125
|
for (const m of text.matchAll(INVOKE)) {
|
|
@@ -3589,18 +4148,18 @@ var init_tauri = __esm({
|
|
|
3589
4148
|
});
|
|
3590
4149
|
|
|
3591
4150
|
// packages/engine/src/indexer/index.ts
|
|
3592
|
-
import
|
|
3593
|
-
import
|
|
4151
|
+
import fs11 from "node:fs";
|
|
4152
|
+
import path11 from "node:path";
|
|
3594
4153
|
import { API } from "typescript/unstable/sync";
|
|
3595
4154
|
function isRepoSource(root, abs) {
|
|
3596
|
-
const rel =
|
|
3597
|
-
return !rel.startsWith("..") && !
|
|
4155
|
+
const rel = path11.relative(root, abs);
|
|
4156
|
+
return !rel.startsWith("..") && !path11.isAbsolute(rel) && SOURCE_EXT.test(rel) && !DECL_FILE.test(rel) && !toPosix(rel).split("/").includes("node_modules");
|
|
3598
4157
|
}
|
|
3599
4158
|
function walk(dir, skip, want, out = []) {
|
|
3600
|
-
for (const e of
|
|
3601
|
-
const full =
|
|
4159
|
+
for (const e of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
4160
|
+
const full = path11.join(dir, e.name);
|
|
3602
4161
|
if (e.isDirectory()) {
|
|
3603
|
-
const nested =
|
|
4162
|
+
const nested = fs11.existsSync(path11.join(full, ".git"));
|
|
3604
4163
|
if (!skip.has(e.name) && !e.name.startsWith(".") && !nested) walk(full, skip, want, out);
|
|
3605
4164
|
} else if (e.isFile() && want(e.name)) {
|
|
3606
4165
|
out.push(full);
|
|
@@ -3609,22 +4168,22 @@ function walk(dir, skip, want, out = []) {
|
|
|
3609
4168
|
return out;
|
|
3610
4169
|
}
|
|
3611
4170
|
function references(config) {
|
|
3612
|
-
if (!
|
|
4171
|
+
if (!fs11.existsSync(config)) return [];
|
|
3613
4172
|
try {
|
|
3614
|
-
const json2 =
|
|
4173
|
+
const json2 = fs11.readFileSync(config, "utf8").replace(/("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (_, str) => str ?? "").replace(/,(\s*[}\]])/g, "$1");
|
|
3615
4174
|
const refs = JSON.parse(json2).references ?? [];
|
|
3616
4175
|
return refs.map((r) => {
|
|
3617
|
-
const p =
|
|
3618
|
-
return
|
|
3619
|
-
}).filter((p) =>
|
|
4176
|
+
const p = path11.resolve(path11.dirname(config), r.path);
|
|
4177
|
+
return fs11.existsSync(p) && fs11.statSync(p).isDirectory() ? path11.join(p, "tsconfig.json") : p;
|
|
4178
|
+
}).filter((p) => fs11.existsSync(p));
|
|
3620
4179
|
} catch {
|
|
3621
4180
|
return [];
|
|
3622
4181
|
}
|
|
3623
4182
|
}
|
|
3624
4183
|
function discover(api, root, sources) {
|
|
3625
|
-
const listed = (c) => api.parseConfigFile(c).fileNames.filter((f) => isRepoSource(root, f)).map((f) =>
|
|
3626
|
-
const rootConfig =
|
|
3627
|
-
if (
|
|
4184
|
+
const listed = (c) => api.parseConfigFile(c).fileNames.filter((f) => isRepoSource(root, f)).map((f) => path11.resolve(f));
|
|
4185
|
+
const rootConfig = path11.join(root, "tsconfig.json");
|
|
4186
|
+
if (fs11.existsSync(rootConfig)) {
|
|
3628
4187
|
const files = listed(rootConfig);
|
|
3629
4188
|
if (files.length) return { units: [{ config: rootConfig, files }], loose: [] };
|
|
3630
4189
|
}
|
|
@@ -3632,7 +4191,7 @@ function discover(api, root, sources) {
|
|
|
3632
4191
|
const configs = [.../* @__PURE__ */ new Set([...found, ...references(rootConfig)])].sort();
|
|
3633
4192
|
const projects = configs.map((config) => ({
|
|
3634
4193
|
config,
|
|
3635
|
-
dir:
|
|
4194
|
+
dir: path11.dirname(config) + path11.sep,
|
|
3636
4195
|
listed: new Set(listed(config)),
|
|
3637
4196
|
files: []
|
|
3638
4197
|
})).filter((p) => p.listed.size).sort((a, b) => b.dir.length - a.dir.length);
|
|
@@ -3654,56 +4213,52 @@ function tagsFor(file) {
|
|
|
3654
4213
|
return tags;
|
|
3655
4214
|
}
|
|
3656
4215
|
function discoverSourceFiles(rootDir) {
|
|
3657
|
-
const root =
|
|
4216
|
+
const root = path11.resolve(rootDir);
|
|
3658
4217
|
const found = walk(
|
|
3659
4218
|
root,
|
|
3660
4219
|
SKIP_DIRS,
|
|
3661
|
-
(n) => isSourceName(n) || LUAU_EXT.test(n) || PY_EXT.test(n) || RS_EXT.test(n)
|
|
4220
|
+
(n) => isSourceName(n) || LUAU_EXT.test(n) || PY_EXT.test(n) || RS_EXT.test(n) || CPP_EXT.test(n)
|
|
3662
4221
|
);
|
|
3663
|
-
const ts4 = found.filter((f) => isSourceName(
|
|
3664
|
-
const rel = found.map((f) => toPosix(
|
|
4222
|
+
const ts4 = found.filter((f) => isSourceName(path11.basename(f)));
|
|
4223
|
+
const rel = found.map((f) => toPosix(path11.relative(root, f)));
|
|
3665
4224
|
return {
|
|
3666
4225
|
ts: ts4,
|
|
3667
4226
|
luau: rel.filter(isLuauSourcePath),
|
|
3668
4227
|
py: rel.filter(isPySourcePath),
|
|
3669
|
-
rs: rel.filter(isRsSourcePath)
|
|
4228
|
+
rs: rel.filter(isRsSourcePath),
|
|
4229
|
+
cpp: rel.filter(isCppSourcePath)
|
|
3670
4230
|
};
|
|
3671
4231
|
}
|
|
3672
4232
|
function mergeGraphs(root, ts4, parts) {
|
|
3673
|
-
const graphs = [parts.ts, parts.luau, parts.py, parts.rs].filter(
|
|
4233
|
+
const graphs = [parts.ts, parts.luau, parts.py, parts.rs, parts.cpp].filter(
|
|
4234
|
+
(g) => !!g
|
|
4235
|
+
);
|
|
3674
4236
|
const merged = { nodes: graphs.flatMap((g) => g.nodes), edges: graphs.flatMap((g) => g.edges) };
|
|
3675
4237
|
if (parts.ts && parts.rs?.commands.size) {
|
|
3676
|
-
const rel = ts4.map((f) => toPosix(
|
|
4238
|
+
const rel = ts4.map((f) => toPosix(path11.relative(root, f)));
|
|
3677
4239
|
merged.edges.push(...linkTauri(root, rel, parts.ts, parts.rs.commands));
|
|
3678
4240
|
}
|
|
3679
4241
|
return merged;
|
|
3680
4242
|
}
|
|
3681
4243
|
function indexRepo(rootDir, cacheRepo = rootDir) {
|
|
3682
|
-
const root =
|
|
3683
|
-
const { ts: ts4, luau, py, rs } = discoverSourceFiles(rootDir);
|
|
4244
|
+
const root = path11.resolve(rootDir);
|
|
4245
|
+
const { ts: ts4, luau, py, rs, cpp } = discoverSourceFiles(rootDir);
|
|
3684
4246
|
return mergeGraphs(root, ts4, {
|
|
3685
4247
|
...ts4.length > 0 && { ts: indexTypeScript(root, ts4) },
|
|
3686
4248
|
...luau.length > 0 && { luau: indexLuau(root, luau, tagsFor, cacheRepo) },
|
|
3687
4249
|
...py.length > 0 && { py: indexPython(root, py, tagsFor) },
|
|
3688
|
-
...rs.length > 0 && { rs: indexRust(root, rs, tagsFor) }
|
|
4250
|
+
...rs.length > 0 && { rs: indexRust(root, rs, tagsFor) },
|
|
4251
|
+
...cpp.length > 0 && { cpp: indexCpp(root, cpp, tagsFor) }
|
|
3689
4252
|
});
|
|
3690
4253
|
}
|
|
3691
4254
|
function filesRead(rootDir) {
|
|
3692
|
-
const root =
|
|
3693
|
-
const
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
(n) => isSourceName(n) || LUAU_EXT.test(n) || FINGERPRINT_CONFIG_NAMES.has(n)
|
|
3697
|
-
);
|
|
3698
|
-
return found.filter((f) => {
|
|
3699
|
-
const base = path10.basename(f);
|
|
3700
|
-
if (isSourceName(base) || FINGERPRINT_CONFIG_NAMES.has(base)) return true;
|
|
3701
|
-
const rel = toPosix(path10.relative(root, f));
|
|
3702
|
-
return !LUAU_DECL.test(rel) && !rel.split("/").some((s) => LUAU_SKIP_DIRS.has(s));
|
|
3703
|
-
});
|
|
4255
|
+
const root = path11.resolve(rootDir);
|
|
4256
|
+
const { ts: ts4, luau, py, rs, cpp } = discoverSourceFiles(root);
|
|
4257
|
+
const rel = [...luau, ...py, ...rs, ...cpp].map((f) => path11.join(root, f));
|
|
4258
|
+
return [...ts4, ...rel, ...walk(root, SKIP_DIRS, (n) => FINGERPRINT_CONFIG_NAMES.has(n))];
|
|
3704
4259
|
}
|
|
3705
4260
|
function openTs(root, sources) {
|
|
3706
|
-
const virtualConfig =
|
|
4261
|
+
const virtualConfig = path11.join(root, VIRTUAL_CONFIG);
|
|
3707
4262
|
const virtualBox = { text: "" };
|
|
3708
4263
|
const api = new API({
|
|
3709
4264
|
cwd: root,
|
|
@@ -3745,7 +4300,7 @@ function collectGraph(root, open) {
|
|
|
3745
4300
|
});
|
|
3746
4301
|
for (const { ctx, sfs } of projects) {
|
|
3747
4302
|
for (const sf of sfs) {
|
|
3748
|
-
const file = toPosix(
|
|
4303
|
+
const file = toPosix(path11.relative(root, sf.fileName));
|
|
3749
4304
|
collectFile(ctx, sf, file, tagsFor(file));
|
|
3750
4305
|
}
|
|
3751
4306
|
}
|
|
@@ -3781,11 +4336,12 @@ function reopenTsLive(root, sources, prevOpen) {
|
|
|
3781
4336
|
prevOpen.api.close();
|
|
3782
4337
|
return indexTsLive(root, sources);
|
|
3783
4338
|
}
|
|
3784
|
-
var SOURCE_EXT, DECL_FILE, LUAU_EXT, LUAU_DECL, LUAU_SKIP_DIRS, PY_EXT, RS_EXT, PY_SKIP_DIRS, SKIP_DIRS, SKIP_CONFIG_DIRS, UTIL_SEGMENTS, DEFAULT_OPTIONS, VIRTUAL_CONFIG, toPosix, isTsSourcePath, isLuauSourcePath, isPySourcePath, isRsSourcePath, isSourceName, FINGERPRINT_CONFIG_NAMES;
|
|
4339
|
+
var SOURCE_EXT, DECL_FILE, LUAU_EXT, LUAU_DECL, LUAU_SKIP_DIRS, PY_EXT, RS_EXT, CPP_EXT, CPP_SKIP_DIRS, PY_SKIP_DIRS, SKIP_DIRS, SKIP_CONFIG_DIRS, UTIL_SEGMENTS, DEFAULT_OPTIONS, VIRTUAL_CONFIG, toPosix, isTsSourcePath, isLuauSourcePath, isPySourcePath, isRsSourcePath, isCppSourcePath, isSourceName, FINGERPRINT_CONFIG_NAMES;
|
|
3785
4340
|
var init_indexer = __esm({
|
|
3786
4341
|
async "packages/engine/src/indexer/index.ts"() {
|
|
3787
4342
|
"use strict";
|
|
3788
4343
|
init_blocks();
|
|
4344
|
+
await init_cpp();
|
|
3789
4345
|
init_edges();
|
|
3790
4346
|
await init_luau();
|
|
3791
4347
|
await init_python();
|
|
@@ -3798,6 +4354,16 @@ var init_indexer = __esm({
|
|
|
3798
4354
|
LUAU_SKIP_DIRS = /* @__PURE__ */ new Set(["vendor", "Packages", "ServerPackages", "DevPackages"]);
|
|
3799
4355
|
PY_EXT = /\.py$/;
|
|
3800
4356
|
RS_EXT = /\.rs$/;
|
|
4357
|
+
CPP_EXT = /\.(c|h|cc|cpp|cxx|hpp|hh|hxx)$/;
|
|
4358
|
+
CPP_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
4359
|
+
"build",
|
|
4360
|
+
"out",
|
|
4361
|
+
"third_party",
|
|
4362
|
+
"external",
|
|
4363
|
+
"extern",
|
|
4364
|
+
"vendor",
|
|
4365
|
+
"deps"
|
|
4366
|
+
]);
|
|
3801
4367
|
PY_SKIP_DIRS = /* @__PURE__ */ new Set(["venv", "env", "site-packages", "__pycache__", "build"]);
|
|
3802
4368
|
SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist"]);
|
|
3803
4369
|
SKIP_CONFIG_DIRS = /* @__PURE__ */ new Set([...SKIP_DIRS, "build", "coverage", "fixtures"]);
|
|
@@ -3815,25 +4381,29 @@ var init_indexer = __esm({
|
|
|
3815
4381
|
types: ["node"]
|
|
3816
4382
|
};
|
|
3817
4383
|
VIRTUAL_CONFIG = "__topo__.tsconfig.json";
|
|
3818
|
-
toPosix = (p) => p.split(
|
|
3819
|
-
isTsSourcePath = (rel) => isSourceName(
|
|
4384
|
+
toPosix = (p) => p.split(path11.sep).join("/");
|
|
4385
|
+
isTsSourcePath = (rel) => isSourceName(path11.posix.basename(rel));
|
|
3820
4386
|
isLuauSourcePath = (rel) => LUAU_EXT.test(rel) && !LUAU_DECL.test(rel) && !rel.split("/").some((s) => LUAU_SKIP_DIRS.has(s));
|
|
3821
4387
|
isPySourcePath = (rel) => PY_EXT.test(rel) && !rel.split("/").some((s) => PY_SKIP_DIRS.has(s));
|
|
3822
4388
|
isRsSourcePath = (rel) => RS_EXT.test(rel) && !rel.split("/").includes("target");
|
|
4389
|
+
isCppSourcePath = (rel) => CPP_EXT.test(rel) && !rel.split("/").some((s) => CPP_SKIP_DIRS.has(s) || s.startsWith("cmake-build-"));
|
|
3823
4390
|
isSourceName = (n) => SOURCE_EXT.test(n) && !DECL_FILE.test(n);
|
|
3824
4391
|
FINGERPRINT_CONFIG_NAMES = /* @__PURE__ */ new Set([
|
|
3825
4392
|
"tsconfig.json",
|
|
3826
4393
|
"package.json",
|
|
3827
4394
|
".luaurc",
|
|
3828
4395
|
"sourcemap.json",
|
|
3829
|
-
"default.project.json"
|
|
4396
|
+
"default.project.json",
|
|
4397
|
+
"Cargo.toml",
|
|
4398
|
+
"Makefile",
|
|
4399
|
+
"CMakeLists.txt"
|
|
3830
4400
|
]);
|
|
3831
4401
|
}
|
|
3832
4402
|
});
|
|
3833
4403
|
|
|
3834
4404
|
// packages/engine/src/lenses.ts
|
|
3835
|
-
import
|
|
3836
|
-
import
|
|
4405
|
+
import fs12 from "node:fs";
|
|
4406
|
+
import path12 from "node:path";
|
|
3837
4407
|
function lensProblem(l) {
|
|
3838
4408
|
if (typeof l.name !== "string" || !l.name) return "missing name";
|
|
3839
4409
|
if (l.mode !== void 0 && l.mode !== "review" && l.mode !== "flow") return "bad mode";
|
|
@@ -3850,10 +4420,10 @@ function lensProblem(l) {
|
|
|
3850
4420
|
return void 0;
|
|
3851
4421
|
}
|
|
3852
4422
|
function loadLenses(dir, warn = console.warn) {
|
|
3853
|
-
const file =
|
|
4423
|
+
const file = path12.join(dir, ".topo", "lenses.json");
|
|
3854
4424
|
let raw;
|
|
3855
4425
|
try {
|
|
3856
|
-
raw = JSON.parse(
|
|
4426
|
+
raw = JSON.parse(fs12.readFileSync(file, "utf8"));
|
|
3857
4427
|
} catch (e) {
|
|
3858
4428
|
if (e.code !== "ENOENT") warn(`topo: ignoring ${file}: ${e}`);
|
|
3859
4429
|
return BUILTIN_LENSES;
|
|
@@ -3950,15 +4520,21 @@ var init_lenses = __esm({
|
|
|
3950
4520
|
});
|
|
3951
4521
|
|
|
3952
4522
|
// packages/engine/src/live.ts
|
|
3953
|
-
import
|
|
4523
|
+
import path13 from "node:path";
|
|
3954
4524
|
function toRel(root, f) {
|
|
3955
|
-
const abs =
|
|
3956
|
-
return toPosix(
|
|
4525
|
+
const abs = path13.isAbsolute(f) ? f : path13.resolve(root, f);
|
|
4526
|
+
return toPosix(path13.relative(root, abs));
|
|
3957
4527
|
}
|
|
3958
4528
|
function createLiveIndexer(root, opts) {
|
|
3959
|
-
const rootAbs =
|
|
4529
|
+
const rootAbs = path13.resolve(root);
|
|
3960
4530
|
const cacheRepo = opts?.cacheRepo ?? rootAbs;
|
|
3961
|
-
let {
|
|
4531
|
+
let {
|
|
4532
|
+
ts: tsFiles,
|
|
4533
|
+
luau: luauFiles,
|
|
4534
|
+
py: pyFiles,
|
|
4535
|
+
rs: rsFiles,
|
|
4536
|
+
cpp: cppFiles
|
|
4537
|
+
} = discoverSourceFiles(rootAbs);
|
|
3962
4538
|
let tsState;
|
|
3963
4539
|
let tsGraph = { nodes: [], edges: [] };
|
|
3964
4540
|
if (tsFiles.length) {
|
|
@@ -3975,17 +4551,19 @@ function createLiveIndexer(root, opts) {
|
|
|
3975
4551
|
}
|
|
3976
4552
|
let pyGraph = pyFiles.length ? indexPython(rootAbs, pyFiles, tagsFor) : { nodes: [], edges: [] };
|
|
3977
4553
|
let rsGraph = rsFiles.length ? indexRust(rootAbs, rsFiles, tagsFor) : void 0;
|
|
4554
|
+
let cppGraph = cppFiles.length ? indexCpp(rootAbs, cppFiles, tagsFor) : { nodes: [], edges: [] };
|
|
3978
4555
|
const all = () => mergeGraphs(rootAbs, tsFiles, {
|
|
3979
4556
|
ts: tsGraph,
|
|
3980
4557
|
luau: luauGraph,
|
|
3981
4558
|
py: pyGraph,
|
|
3982
|
-
...rsGraph && { rs: rsGraph }
|
|
4559
|
+
...rsGraph && { rs: rsGraph },
|
|
4560
|
+
cpp: cppGraph
|
|
3983
4561
|
});
|
|
3984
4562
|
let current = all();
|
|
3985
4563
|
function update(changedFiles) {
|
|
3986
4564
|
const prev = current;
|
|
3987
4565
|
const rel = [...new Set(changedFiles.map((f) => toRel(rootAbs, f)))];
|
|
3988
|
-
const basenames = rel.map((f) =>
|
|
4566
|
+
const basenames = rel.map((f) => path13.posix.basename(f));
|
|
3989
4567
|
const tsConfigChanged = basenames.some((b) => TS_CONFIG_NAMES.has(b));
|
|
3990
4568
|
const luauConfigChanged = basenames.some((b) => LUAU_CONFIG_NAMES.has(b));
|
|
3991
4569
|
const tsTouched = rel.filter(isTsSourcePath);
|
|
@@ -3997,7 +4575,7 @@ function createLiveIndexer(root, opts) {
|
|
|
3997
4575
|
if (!tsConfigChanged && sameFileSet && tsState) {
|
|
3998
4576
|
const r = updateTsLive(
|
|
3999
4577
|
tsState,
|
|
4000
|
-
tsTouched.map((f) =>
|
|
4578
|
+
tsTouched.map((f) => path13.resolve(rootAbs, f))
|
|
4001
4579
|
);
|
|
4002
4580
|
tsState = r.state;
|
|
4003
4581
|
tsGraph = r.graph;
|
|
@@ -4026,6 +4604,10 @@ function createLiveIndexer(root, opts) {
|
|
|
4026
4604
|
rsFiles = discoverSourceFiles(rootAbs).rs;
|
|
4027
4605
|
rsGraph = rsFiles.length ? indexRust(rootAbs, rsFiles, tagsFor) : void 0;
|
|
4028
4606
|
}
|
|
4607
|
+
if (rel.some(isCppSourcePath)) {
|
|
4608
|
+
cppFiles = discoverSourceFiles(rootAbs).cpp;
|
|
4609
|
+
cppGraph = indexCpp(rootAbs, cppFiles, tagsFor);
|
|
4610
|
+
}
|
|
4029
4611
|
current = all();
|
|
4030
4612
|
return { graph: cloneGraph(current), delta: graphDelta(prev, current) };
|
|
4031
4613
|
}
|
|
@@ -4045,6 +4627,7 @@ var init_live = __esm({
|
|
|
4045
4627
|
async "packages/engine/src/live.ts"() {
|
|
4046
4628
|
"use strict";
|
|
4047
4629
|
init_delta();
|
|
4630
|
+
await init_cpp();
|
|
4048
4631
|
await init_indexer();
|
|
4049
4632
|
await init_luau();
|
|
4050
4633
|
await init_python();
|
|
@@ -4308,7 +4891,7 @@ function build(text, root) {
|
|
|
4308
4891
|
}
|
|
4309
4892
|
function highlightLuau(text) {
|
|
4310
4893
|
try {
|
|
4311
|
-
const tree =
|
|
4894
|
+
const tree = parser2.parse(text);
|
|
4312
4895
|
if (!tree) return [];
|
|
4313
4896
|
try {
|
|
4314
4897
|
return build(text, tree.rootNode);
|
|
@@ -4529,15 +5112,15 @@ var init_highlight = __esm({
|
|
|
4529
5112
|
});
|
|
4530
5113
|
|
|
4531
5114
|
// packages/engine/src/source.ts
|
|
4532
|
-
import
|
|
4533
|
-
import
|
|
5115
|
+
import fs13 from "node:fs";
|
|
5116
|
+
import path14 from "node:path";
|
|
4534
5117
|
function readInside(dir, file) {
|
|
4535
|
-
const root =
|
|
4536
|
-
const abs =
|
|
4537
|
-
if (!abs.startsWith(root +
|
|
5118
|
+
const root = path14.resolve(dir);
|
|
5119
|
+
const abs = path14.resolve(root, file);
|
|
5120
|
+
if (!abs.startsWith(root + path14.sep)) return void 0;
|
|
4538
5121
|
try {
|
|
4539
|
-
const text =
|
|
4540
|
-
const { mtimeMs } =
|
|
5122
|
+
const text = fs13.readFileSync(abs, "utf8");
|
|
5123
|
+
const { mtimeMs } = fs13.statSync(abs);
|
|
4541
5124
|
let hit = tokenCache.get(abs);
|
|
4542
5125
|
if (hit?.mtimeMs !== mtimeMs) {
|
|
4543
5126
|
hit = { mtimeMs, tokens: highlight(text, file) };
|
|
@@ -4911,6 +5494,7 @@ __export(src_exports, {
|
|
|
4911
5494
|
indexRepo: () => indexRepo,
|
|
4912
5495
|
indexerVersion: () => indexerVersion,
|
|
4913
5496
|
layerIndex: () => layerIndex,
|
|
5497
|
+
layerMap: () => layerMap,
|
|
4914
5498
|
layersFile: () => layersFile,
|
|
4915
5499
|
lensProblem: () => lensProblem,
|
|
4916
5500
|
loadLenses: () => loadLenses,
|
|
@@ -4919,6 +5503,7 @@ __export(src_exports, {
|
|
|
4919
5503
|
noCache: () => noCache,
|
|
4920
5504
|
nodeId: () => nodeId,
|
|
4921
5505
|
outsideTerritory: () => outsideTerritory,
|
|
5506
|
+
parseLayers: () => parseLayers,
|
|
4922
5507
|
readLayers: () => readLayers,
|
|
4923
5508
|
refreshReview: () => refreshReview,
|
|
4924
5509
|
repoCacheRoot: () => repoCacheRoot,
|
|
@@ -4930,7 +5515,8 @@ __export(src_exports, {
|
|
|
4930
5515
|
tagHubs: () => tagHubs,
|
|
4931
5516
|
territoryContext: () => territoryContext,
|
|
4932
5517
|
toPayload: () => toPayload,
|
|
4933
|
-
visibleSubgraph: () => visibleSubgraph
|
|
5518
|
+
visibleSubgraph: () => visibleSubgraph,
|
|
5519
|
+
writeLayers: () => writeLayers
|
|
4934
5520
|
});
|
|
4935
5521
|
var init_src = __esm({
|
|
4936
5522
|
async "packages/engine/src/index.ts"() {
|
|
@@ -5141,7 +5727,7 @@ var init_graph = __esm({
|
|
|
5141
5727
|
await init_format();
|
|
5142
5728
|
HELP = `Usage: topo graph [path] [--json] [--no-cache]
|
|
5143
5729
|
|
|
5144
|
-
Index a codebase (TypeScript, Luau, Python, Rust) and print a summary with likely entry points (or the full graph with --json).
|
|
5730
|
+
Index a codebase (TypeScript, Luau, Python, Rust, C/C++) and print a summary with likely entry points (or the full graph with --json).
|
|
5145
5731
|
|
|
5146
5732
|
Options:
|
|
5147
5733
|
--json print the full Graph as JSON
|
|
@@ -5216,12 +5802,12 @@ Options:
|
|
|
5216
5802
|
});
|
|
5217
5803
|
|
|
5218
5804
|
// packages/cli/src/activity.ts
|
|
5219
|
-
import
|
|
5805
|
+
import path15 from "node:path";
|
|
5220
5806
|
function toRepoRelative(repo, file) {
|
|
5221
|
-
const abs =
|
|
5222
|
-
const rel =
|
|
5223
|
-
if (rel === "" || rel.startsWith("..") ||
|
|
5224
|
-
return rel.split(
|
|
5807
|
+
const abs = path15.isAbsolute(file) ? file : path15.resolve(repo, file);
|
|
5808
|
+
const rel = path15.relative(repo, abs);
|
|
5809
|
+
if (rel === "" || rel.startsWith("..") || path15.isAbsolute(rel)) return void 0;
|
|
5810
|
+
return rel.split(path15.sep).join("/");
|
|
5225
5811
|
}
|
|
5226
5812
|
function parseActivity(body, repo, inWorktree) {
|
|
5227
5813
|
if (!isRecord(body)) return void 0;
|
|
@@ -5229,7 +5815,7 @@ function parseActivity(body, repo, inWorktree) {
|
|
|
5229
5815
|
if (input) {
|
|
5230
5816
|
const file = input.file_path ?? input.notebook_path;
|
|
5231
5817
|
if (typeof file !== "string") return void 0;
|
|
5232
|
-
const abs =
|
|
5818
|
+
const abs = path15.isAbsolute(file) ? file : path15.resolve(repo, file);
|
|
5233
5819
|
const wt = inWorktree?.(abs);
|
|
5234
5820
|
const rel = wt?.rel ?? toRepoRelative(repo, file);
|
|
5235
5821
|
if (!rel) return void 0;
|
|
@@ -5330,40 +5916,40 @@ Options:
|
|
|
5330
5916
|
|
|
5331
5917
|
// packages/cli/src/agents.ts
|
|
5332
5918
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
5333
|
-
import
|
|
5919
|
+
import fs14 from "node:fs";
|
|
5334
5920
|
import os2 from "node:os";
|
|
5335
|
-
import
|
|
5921
|
+
import path16 from "node:path";
|
|
5336
5922
|
function resolveBin(bin) {
|
|
5337
5923
|
const tryExec = (p) => {
|
|
5338
5924
|
try {
|
|
5339
|
-
|
|
5925
|
+
fs14.accessSync(p, fs14.constants.X_OK);
|
|
5340
5926
|
return p;
|
|
5341
5927
|
} catch {
|
|
5342
5928
|
return void 0;
|
|
5343
5929
|
}
|
|
5344
5930
|
};
|
|
5345
|
-
if (bin.includes(
|
|
5346
|
-
const dirs = (process.env.PATH ?? "").split(
|
|
5931
|
+
if (bin.includes(path16.sep)) return tryExec(bin);
|
|
5932
|
+
const dirs = (process.env.PATH ?? "").split(path16.delimiter).filter(Boolean);
|
|
5347
5933
|
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD").split(";") : [""];
|
|
5348
5934
|
for (const dir of dirs) {
|
|
5349
5935
|
for (const ext of exts) {
|
|
5350
|
-
const hit = tryExec(
|
|
5936
|
+
const hit = tryExec(path16.join(dir, bin + ext));
|
|
5351
5937
|
if (hit) return hit;
|
|
5352
5938
|
}
|
|
5353
5939
|
}
|
|
5354
5940
|
return void 0;
|
|
5355
5941
|
}
|
|
5356
5942
|
function writeTempJson(prefix, value) {
|
|
5357
|
-
const file =
|
|
5943
|
+
const file = path16.join(
|
|
5358
5944
|
os2.tmpdir(),
|
|
5359
5945
|
`${prefix}-${process.pid}-${Math.random().toString(36).slice(2)}.json`
|
|
5360
5946
|
);
|
|
5361
|
-
|
|
5947
|
+
fs14.writeFileSync(file, JSON.stringify(value));
|
|
5362
5948
|
return file;
|
|
5363
5949
|
}
|
|
5364
5950
|
function rmQuiet(file) {
|
|
5365
5951
|
try {
|
|
5366
|
-
|
|
5952
|
+
fs14.unlinkSync(file);
|
|
5367
5953
|
} catch {
|
|
5368
5954
|
}
|
|
5369
5955
|
}
|
|
@@ -5397,9 +5983,25 @@ function createAgentManager(opts) {
|
|
|
5397
5983
|
const children = /* @__PURE__ */ new Map();
|
|
5398
5984
|
let runSeq = 0;
|
|
5399
5985
|
let cmdSeq = 0;
|
|
5986
|
+
let exchanges = [];
|
|
5987
|
+
let session;
|
|
5400
5988
|
function emitRuns() {
|
|
5401
5989
|
opts.bus.emit({ type: "runs", runs: [...runs.values()] });
|
|
5402
5990
|
}
|
|
5991
|
+
function emitExchanges() {
|
|
5992
|
+
opts.bus.emit({ type: "exchanges", exchanges });
|
|
5993
|
+
}
|
|
5994
|
+
function updateExchange(id, patch) {
|
|
5995
|
+
const i = exchanges.findIndex((x2) => x2.id === id);
|
|
5996
|
+
const x = exchanges[i];
|
|
5997
|
+
if (!x) return;
|
|
5998
|
+
exchanges = exchanges.with(i, { ...x, ...patch(x) });
|
|
5999
|
+
emitExchanges();
|
|
6000
|
+
}
|
|
6001
|
+
opts.bus.subscribe((e) => {
|
|
6002
|
+
if ((e.type === "explain" || e.type === "focus" || e.type === "lens") && e.command)
|
|
6003
|
+
updateExchange(e.command, () => ({ view: e }));
|
|
6004
|
+
});
|
|
5403
6005
|
function setTerritoryStatus(territoryId, patch) {
|
|
5404
6006
|
const territories = opts.getTerritories();
|
|
5405
6007
|
const next = territories.map((t) => t.id === territoryId ? { ...t, ...patch } : t);
|
|
@@ -5545,27 +6147,30 @@ function createAgentManager(opts) {
|
|
|
5545
6147
|
const contextLine = context ? `
|
|
5546
6148
|
|
|
5547
6149
|
Context: ${JSON.stringify(context)}` : "";
|
|
6150
|
+
exchanges = [
|
|
6151
|
+
...exchanges.slice(-(MAX_EXCHANGES - 1)),
|
|
6152
|
+
{ id, text, status: "running", tools: [], at: Date.now() }
|
|
6153
|
+
];
|
|
5548
6154
|
if (!resolved) {
|
|
5549
6155
|
const nodes = findBlocks(opts.getIndex().graph, text, 20);
|
|
5550
|
-
opts.bus.emit({ type: "focus", nodeIds: nodes.map((n) => n.id) });
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
status: "done",
|
|
5555
|
-
text: "No agent CLI found; showing search results"
|
|
5556
|
-
});
|
|
6156
|
+
opts.bus.emit({ type: "focus", nodeIds: nodes.map((n) => n.id), command: id });
|
|
6157
|
+
const line = nodes.length ? "No agent CLI found, so this is a plain search: the matches are on the map." : "No agent CLI found, so this is a plain search, and nothing matched.";
|
|
6158
|
+
opts.bus.emit({ type: "command", id, status: "done", text: line });
|
|
6159
|
+
updateExchange(id, () => ({ status: "done", answer: line }));
|
|
5557
6160
|
return id;
|
|
5558
6161
|
}
|
|
6162
|
+
emitExchanges();
|
|
5559
6163
|
const mcpConfig = writeTempJson("topo-director-mcp", {
|
|
5560
6164
|
mcpServers: {
|
|
5561
6165
|
topo: {
|
|
5562
6166
|
type: "http",
|
|
5563
6167
|
url: `http://127.0.0.1:${opts.port()}/mcp`,
|
|
5564
|
-
headers: { "x-topo-director": opts.directorToken }
|
|
6168
|
+
headers: { "x-topo-director": opts.directorToken, "x-topo-command": id }
|
|
5565
6169
|
}
|
|
5566
6170
|
}
|
|
5567
6171
|
});
|
|
5568
6172
|
const model = opts.model ?? process.env.TOPO_COMMAND_MODEL ?? "sonnet";
|
|
6173
|
+
const resumeFrom = session;
|
|
5569
6174
|
opts.bus.emit({ type: "command", id, status: "running", text });
|
|
5570
6175
|
const child = spawn3(
|
|
5571
6176
|
resolved,
|
|
@@ -5586,12 +6191,17 @@ Context: ${JSON.stringify(context)}` : "";
|
|
|
5586
6191
|
"--model",
|
|
5587
6192
|
model,
|
|
5588
6193
|
"--append-system-prompt",
|
|
5589
|
-
DIRECTOR_SYSTEM_PROMPT
|
|
6194
|
+
DIRECTOR_SYSTEM_PROMPT,
|
|
6195
|
+
...resumeFrom ? ["--resume", resumeFrom] : []
|
|
5590
6196
|
],
|
|
5591
6197
|
{ cwd: opts.repo, stdio: ["ignore", "pipe", "pipe"] }
|
|
5592
6198
|
);
|
|
5593
6199
|
let gotResult = false;
|
|
5594
6200
|
const cleanup = () => rmQuiet(mcpConfig);
|
|
6201
|
+
const fail = (text2) => {
|
|
6202
|
+
opts.bus.emit({ type: "command", id, status: "failed", text: text2 });
|
|
6203
|
+
updateExchange(id, () => ({ status: "failed", answer: text2 ?? "The director stopped." }));
|
|
6204
|
+
};
|
|
5595
6205
|
child.stdout?.on(
|
|
5596
6206
|
"data",
|
|
5597
6207
|
makeLineFeed((obj) => {
|
|
@@ -5607,30 +6217,35 @@ Context: ${JSON.stringify(context)}` : "";
|
|
|
5607
6217
|
status: "running",
|
|
5608
6218
|
text: `${block2.name}(${shortArgs(block2.input)})`
|
|
5609
6219
|
});
|
|
6220
|
+
const tool = block2.name.replace(/^mcp__topo__/, "");
|
|
6221
|
+
updateExchange(id, (x) => ({ tools: [...x.tools, tool] }));
|
|
5610
6222
|
}
|
|
5611
6223
|
}
|
|
5612
6224
|
} else if (obj.type === "result") {
|
|
5613
6225
|
gotResult = true;
|
|
5614
6226
|
const result = obj;
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
text: result.result
|
|
5620
|
-
});
|
|
6227
|
+
const status = result.is_error ? "failed" : "done";
|
|
6228
|
+
if (result.session_id && exchanges.some((x) => x.id === id)) session = result.session_id;
|
|
6229
|
+
opts.bus.emit({ type: "command", id, status, text: result.result });
|
|
6230
|
+
updateExchange(id, () => ({ status, answer: result.result }));
|
|
5621
6231
|
}
|
|
5622
6232
|
})
|
|
5623
6233
|
);
|
|
5624
6234
|
child.on("error", (e) => {
|
|
5625
|
-
if (!gotResult)
|
|
6235
|
+
if (!gotResult) fail(e.message);
|
|
5626
6236
|
cleanup();
|
|
5627
6237
|
});
|
|
5628
6238
|
child.on("close", () => {
|
|
5629
|
-
if (!gotResult)
|
|
6239
|
+
if (!gotResult) fail();
|
|
5630
6240
|
cleanup();
|
|
5631
6241
|
});
|
|
5632
6242
|
return id;
|
|
5633
6243
|
}
|
|
6244
|
+
function newConversation() {
|
|
6245
|
+
exchanges = [];
|
|
6246
|
+
session = void 0;
|
|
6247
|
+
emitExchanges();
|
|
6248
|
+
}
|
|
5634
6249
|
return {
|
|
5635
6250
|
get available() {
|
|
5636
6251
|
return resolved !== void 0;
|
|
@@ -5639,16 +6254,25 @@ Context: ${JSON.stringify(context)}` : "";
|
|
|
5639
6254
|
get: (runId) => runs.get(runId),
|
|
5640
6255
|
launch,
|
|
5641
6256
|
stop,
|
|
5642
|
-
runCommand
|
|
6257
|
+
runCommand,
|
|
6258
|
+
exchanges: () => exchanges,
|
|
6259
|
+
newConversation
|
|
5643
6260
|
};
|
|
5644
6261
|
}
|
|
5645
|
-
var DIRECTOR_SYSTEM_PROMPT, isActive;
|
|
6262
|
+
var DIRECTOR_SYSTEM_PROMPT, MAX_EXCHANGES, isActive;
|
|
5646
6263
|
var init_agents = __esm({
|
|
5647
6264
|
async "packages/cli/src/agents.ts"() {
|
|
5648
6265
|
"use strict";
|
|
5649
6266
|
await init_src();
|
|
5650
6267
|
init_hook();
|
|
5651
|
-
DIRECTOR_SYSTEM_PROMPT =
|
|
6268
|
+
DIRECTOR_SYSTEM_PROMPT = [
|
|
6269
|
+
"You answer the human's questions about this codebase and drive the Topo map for them, through the topo tools only. You never edit code.",
|
|
6270
|
+
"For a question like how does X work, where is X, or what happens when Y: trace it with find, callees, callers and get_block (read the source of the blocks that matter), then call explain once with 3-8 steps in execution order. Pick only blocks that matter to the question; leave out UI, logging and plumbing that merely touches it. If explain reports unresolved steps, fix them and call it again.",
|
|
6271
|
+
"For a request to change the view (a lens, a flow, focusing something), territories, agents or the inbox: use those tools and do it.",
|
|
6272
|
+
"To sort the code into layers (or regions, areas, parts): call get_layers, read what the files hold, then set_layers with 3-8 layers named by what the code does, so every source file lands in one. Say what each layer is in a line.",
|
|
6273
|
+
"Then reply in plain prose: a few sentences, or a numbered list that mirrors the steps. No headings, no tables, no preamble about which tools you called."
|
|
6274
|
+
].join("\n\n");
|
|
6275
|
+
MAX_EXCHANGES = 50;
|
|
5652
6276
|
isActive = (status) => status === "starting" || status === "running";
|
|
5653
6277
|
}
|
|
5654
6278
|
});
|
|
@@ -5690,7 +6314,7 @@ var init_attribution = __esm({
|
|
|
5690
6314
|
|
|
5691
6315
|
// packages/cli/src/editor.ts
|
|
5692
6316
|
import { spawn } from "node:child_process";
|
|
5693
|
-
import
|
|
6317
|
+
import path17 from "node:path";
|
|
5694
6318
|
function editorTemplate(flag) {
|
|
5695
6319
|
return flag ?? process.env.TOPO_EDITOR ?? DEFAULT_EDITOR;
|
|
5696
6320
|
}
|
|
@@ -5719,10 +6343,10 @@ function splitTemplate(template) {
|
|
|
5719
6343
|
return out;
|
|
5720
6344
|
}
|
|
5721
6345
|
function resolveInRepo(repo, file) {
|
|
5722
|
-
const root =
|
|
5723
|
-
const abs =
|
|
5724
|
-
const rel =
|
|
5725
|
-
if (rel === "" || rel.startsWith("..") ||
|
|
6346
|
+
const root = path17.resolve(repo);
|
|
6347
|
+
const abs = path17.resolve(root, file);
|
|
6348
|
+
const rel = path17.relative(root, abs);
|
|
6349
|
+
if (rel === "" || rel.startsWith("..") || path17.isAbsolute(rel)) {
|
|
5726
6350
|
throw new Error(`path outside repository: ${file}`);
|
|
5727
6351
|
}
|
|
5728
6352
|
return abs;
|
|
@@ -6117,8 +6741,8 @@ var init_live2 = __esm({
|
|
|
6117
6741
|
});
|
|
6118
6742
|
|
|
6119
6743
|
// packages/cli/src/mcp.ts
|
|
6120
|
-
import
|
|
6121
|
-
import
|
|
6744
|
+
import fs15 from "node:fs";
|
|
6745
|
+
import path18 from "node:path";
|
|
6122
6746
|
function qualifiedName(n) {
|
|
6123
6747
|
const i = n.id.indexOf("#");
|
|
6124
6748
|
return i === -1 ? n.name : n.id.slice(i + 1);
|
|
@@ -6287,21 +6911,21 @@ function shapeViewTool(deps, args, ctx) {
|
|
|
6287
6911
|
return `proposed lens "${name}"${savedNote}${skipNote}; the human will approve or deny it`;
|
|
6288
6912
|
}
|
|
6289
6913
|
function saveLensToFile(repo, lens) {
|
|
6290
|
-
const file =
|
|
6914
|
+
const file = path18.join(repo, ".topo", "lenses.json");
|
|
6291
6915
|
let existing = [];
|
|
6292
6916
|
try {
|
|
6293
|
-
const raw = JSON.parse(
|
|
6917
|
+
const raw = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
6294
6918
|
const list = Array.isArray(raw) ? raw : raw?.lenses;
|
|
6295
6919
|
if (Array.isArray(list)) existing = list;
|
|
6296
6920
|
} catch {
|
|
6297
6921
|
}
|
|
6298
6922
|
const byName = new Map(existing.map((l) => [l.name, l]));
|
|
6299
6923
|
byName.set(lens.name, lens);
|
|
6300
|
-
|
|
6924
|
+
fs15.mkdirSync(path18.dirname(file), { recursive: true });
|
|
6301
6925
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
6302
|
-
|
|
6926
|
+
fs15.writeFileSync(tmp, `${JSON.stringify({ lenses: [...byName.values()] }, null, 2)}
|
|
6303
6927
|
`);
|
|
6304
|
-
|
|
6928
|
+
fs15.renameSync(tmp, file);
|
|
6305
6929
|
}
|
|
6306
6930
|
async function callTool(deps, params, ctx) {
|
|
6307
6931
|
const name = params.name;
|
|
@@ -6373,7 +6997,7 @@ function createMcpHandler(deps) {
|
|
|
6373
6997
|
}
|
|
6374
6998
|
};
|
|
6375
6999
|
}
|
|
6376
|
-
var PROTOCOL_VERSIONS, DEFAULT_PROTOCOL_VERSION, INSTRUCTIONS, isRecord2, isStrArr, isDirection, isStatus, TOOLS, rpcResult, rpcError;
|
|
7000
|
+
var PROTOCOL_VERSIONS, DEFAULT_PROTOCOL_VERSION, INSTRUCTIONS, isRecord2, isStrArr, isDirection, isStatus, MAX_LAYER_FILES, withCommand, MAX_STEPS, TOOLS, rpcResult, rpcError;
|
|
6377
7001
|
var init_mcp = __esm({
|
|
6378
7002
|
async "packages/cli/src/mcp.ts"() {
|
|
6379
7003
|
"use strict";
|
|
@@ -6385,6 +7009,9 @@ var init_mcp = __esm({
|
|
|
6385
7009
|
isStrArr = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
6386
7010
|
isDirection = (v) => v === "out" || v === "in" || v === "both";
|
|
6387
7011
|
isStatus = (v) => v === "proposed" || v === "active" || v === "review" || v === "done";
|
|
7012
|
+
MAX_LAYER_FILES = 300;
|
|
7013
|
+
withCommand = (ctx) => ctx.command ? { command: ctx.command } : {};
|
|
7014
|
+
MAX_STEPS = 12;
|
|
6388
7015
|
TOOLS = {
|
|
6389
7016
|
find: {
|
|
6390
7017
|
description: "Search blocks (files, classes, functions, methods) by name or text. Use this instead of grep to locate where something lives before reading or changing it.",
|
|
@@ -6661,7 +7288,7 @@ var init_mcp = __esm({
|
|
|
6661
7288
|
label: { type: "string" }
|
|
6662
7289
|
}
|
|
6663
7290
|
},
|
|
6664
|
-
run: (deps, args) => {
|
|
7291
|
+
run: (deps, args, ctx) => {
|
|
6665
7292
|
const index = deps.getIndex();
|
|
6666
7293
|
const { ids: roots, skipped } = Array.isArray(args.roots) ? resolveRefs(index, args.roots) : { ids: [], skipped: [] };
|
|
6667
7294
|
if (skipped.length && !roots.length) {
|
|
@@ -6677,7 +7304,12 @@ var init_mcp = __esm({
|
|
|
6677
7304
|
};
|
|
6678
7305
|
const problem = lensProblem(candidate);
|
|
6679
7306
|
if (problem) throw new Error(`invalid lens: ${problem}`);
|
|
6680
|
-
deps.bus.emit({
|
|
7307
|
+
deps.bus.emit({
|
|
7308
|
+
type: "lens",
|
|
7309
|
+
lens: candidate,
|
|
7310
|
+
apply: true,
|
|
7311
|
+
...withCommand(ctx)
|
|
7312
|
+
});
|
|
6681
7313
|
const skipNote = skipped.length ? `; unresolved roots skipped: ${skipped.join(", ")}` : "";
|
|
6682
7314
|
return `applied view "${label}"${skipNote}`;
|
|
6683
7315
|
}
|
|
@@ -6690,15 +7322,145 @@ var init_mcp = __esm({
|
|
|
6690
7322
|
properties: { blocks: { type: "array", items: { type: "string" } } },
|
|
6691
7323
|
required: ["blocks"]
|
|
6692
7324
|
},
|
|
6693
|
-
run: (deps, args) => {
|
|
7325
|
+
run: (deps, args, ctx) => {
|
|
6694
7326
|
if (!Array.isArray(args.blocks)) throw new Error('missing required "blocks" (array)');
|
|
6695
7327
|
const { ids, skipped } = resolveRefs(deps.getIndex(), args.blocks);
|
|
6696
7328
|
if (!ids.length) throw new Error("none of those blocks resolved");
|
|
6697
|
-
deps.bus.emit({ type: "focus", nodeIds: ids });
|
|
7329
|
+
deps.bus.emit({ type: "focus", nodeIds: ids, ...withCommand(ctx) });
|
|
6698
7330
|
const skipNote = skipped.length ? `; unresolved blocks skipped: ${skipped.join(", ")}` : "";
|
|
6699
7331
|
return `focused on ${ids.length} block(s)${skipNote}`;
|
|
6700
7332
|
}
|
|
6701
7333
|
},
|
|
7334
|
+
explain: {
|
|
7335
|
+
access: "director",
|
|
7336
|
+
description: "Walk the human through how something works: the map shows only these blocks, numbered in step order, with the calls between them. Use for 'how does X work / where is X / what happens when' questions, after tracing with find/callees/callers/get_block. Give 3-8 steps in execution (or reading) order, only blocks that matter to the question; each step's text says what happens there in one or two sentences.",
|
|
7337
|
+
inputSchema: {
|
|
7338
|
+
type: "object",
|
|
7339
|
+
properties: {
|
|
7340
|
+
title: { type: "string" },
|
|
7341
|
+
steps: {
|
|
7342
|
+
type: "array",
|
|
7343
|
+
items: {
|
|
7344
|
+
type: "object",
|
|
7345
|
+
properties: { block: { type: "string" }, text: { type: "string" } },
|
|
7346
|
+
required: ["block", "text"]
|
|
7347
|
+
}
|
|
7348
|
+
}
|
|
7349
|
+
},
|
|
7350
|
+
required: ["title", "steps"]
|
|
7351
|
+
},
|
|
7352
|
+
run: (deps, args, ctx) => {
|
|
7353
|
+
const title = requireString(args, "title");
|
|
7354
|
+
const raw = Array.isArray(args.steps) ? args.steps : [];
|
|
7355
|
+
if (!raw.length || raw.length > MAX_STEPS) {
|
|
7356
|
+
throw new Error(`"steps" must hold 1-${MAX_STEPS} {block, text} items`);
|
|
7357
|
+
}
|
|
7358
|
+
const index = deps.getIndex();
|
|
7359
|
+
const steps = [];
|
|
7360
|
+
const problems = [];
|
|
7361
|
+
raw.forEach((s, i) => {
|
|
7362
|
+
const ref = isRecord2(s) && typeof s.block === "string" ? s.block : "";
|
|
7363
|
+
const text = isRecord2(s) && typeof s.text === "string" ? s.text.trim() : "";
|
|
7364
|
+
const r = resolveOrText(index, ref);
|
|
7365
|
+
if ("node" in r) steps.push({ nodeId: r.node.id, text });
|
|
7366
|
+
else problems.push(`step ${i + 1} (${ref || "no block"}): ${r.text}`);
|
|
7367
|
+
});
|
|
7368
|
+
if (problems.length) {
|
|
7369
|
+
throw new Error(
|
|
7370
|
+
["nothing shown; fix these steps and call explain again:", ...problems].join("\n")
|
|
7371
|
+
);
|
|
7372
|
+
}
|
|
7373
|
+
deps.bus.emit({ type: "explain", title, steps, ...withCommand(ctx) });
|
|
7374
|
+
return `showing "${title}": ${steps.length} step(s) on the map`;
|
|
7375
|
+
}
|
|
7376
|
+
},
|
|
7377
|
+
get_layers: {
|
|
7378
|
+
description: "The repo's layers (.topo/layers.json: named groups of files by path glob, which color the map) and which files are in none. With no layers yet, a folder-based draft. Lists every source file with its main blocks, so you can group them by what the code does.",
|
|
7379
|
+
inputSchema: { type: "object", properties: {} },
|
|
7380
|
+
run: (deps) => {
|
|
7381
|
+
const graph = deps.getReview().head;
|
|
7382
|
+
const doc = readLayers(deps.repo);
|
|
7383
|
+
const lines = [];
|
|
7384
|
+
if (doc) {
|
|
7385
|
+
const { byFile: byFile2 } = layerMap(doc, graph);
|
|
7386
|
+
lines.push("layers (.topo/layers.json):");
|
|
7387
|
+
for (const l of doc.layers) {
|
|
7388
|
+
const n = Object.values(byFile2).filter((x) => x === l.name).length;
|
|
7389
|
+
lines.push(`${l.name} ${l.paths.join(", ")} (${n} files)`);
|
|
7390
|
+
}
|
|
7391
|
+
for (const r of doc.rules)
|
|
7392
|
+
lines.push(`rule: ${r.from} may not reach ${r.notTo}${r.why ? ` (${r.why})` : ""}`);
|
|
7393
|
+
} else {
|
|
7394
|
+
lines.push("no .topo/layers.json yet; folder-based draft:");
|
|
7395
|
+
for (const l of suggestLayers(graph).layers) lines.push(`${l.name} ${l.paths.join(", ")}`);
|
|
7396
|
+
}
|
|
7397
|
+
const byFile = doc ? layerMap(doc, graph).byFile : {};
|
|
7398
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
7399
|
+
for (const n of graph.nodes)
|
|
7400
|
+
if (n.kind !== "file")
|
|
7401
|
+
blocks.set(n.file, [...blocks.get(n.file) ?? [], qualifiedName(n)]);
|
|
7402
|
+
const files = graph.nodes.filter((n) => n.kind === "file").map((n) => n.file).sort();
|
|
7403
|
+
lines.push("", `files (${files.length}):`);
|
|
7404
|
+
for (const f of files.slice(0, MAX_LAYER_FILES)) {
|
|
7405
|
+
const b = blocks.get(f) ?? [];
|
|
7406
|
+
const names = b.slice(0, 5).join(", ") + (b.length > 5 ? ` +${b.length - 5}` : "");
|
|
7407
|
+
lines.push(`${f} [${byFile[f] ?? (doc ? "no layer" : "-")}] ${names}`);
|
|
7408
|
+
}
|
|
7409
|
+
if (files.length > MAX_LAYER_FILES) lines.push(`\u2026 +${files.length - MAX_LAYER_FILES} more`);
|
|
7410
|
+
return lines.join("\n");
|
|
7411
|
+
}
|
|
7412
|
+
},
|
|
7413
|
+
set_layers: {
|
|
7414
|
+
access: "director",
|
|
7415
|
+
description: "Write .topo/layers.json: 3-8 layers named by what the code does (physics, rendering, input), each a list of path globs (src/physics/**, src/render*.cpp, exact files). A file belongs to the first layer that matches; aim for every source file landing in one. Replaces the layers; existing rules are kept unless you pass rules. The map colors blocks by layer right away.",
|
|
7416
|
+
inputSchema: {
|
|
7417
|
+
type: "object",
|
|
7418
|
+
properties: {
|
|
7419
|
+
layers: {
|
|
7420
|
+
type: "array",
|
|
7421
|
+
items: {
|
|
7422
|
+
type: "object",
|
|
7423
|
+
properties: {
|
|
7424
|
+
name: { type: "string" },
|
|
7425
|
+
paths: { type: "array", items: { type: "string" } }
|
|
7426
|
+
},
|
|
7427
|
+
required: ["name", "paths"]
|
|
7428
|
+
}
|
|
7429
|
+
},
|
|
7430
|
+
rules: {
|
|
7431
|
+
type: "array",
|
|
7432
|
+
items: {
|
|
7433
|
+
type: "object",
|
|
7434
|
+
properties: {
|
|
7435
|
+
from: { type: "string" },
|
|
7436
|
+
notTo: { type: "string" },
|
|
7437
|
+
why: { type: "string" }
|
|
7438
|
+
},
|
|
7439
|
+
required: ["from", "notTo"]
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
},
|
|
7443
|
+
required: ["layers"]
|
|
7444
|
+
},
|
|
7445
|
+
run: (deps, args) => {
|
|
7446
|
+
const rules = Array.isArray(args.rules) ? args.rules : readLayers(deps.repo)?.rules ?? [];
|
|
7447
|
+
const doc = parseLayers({ layers: args.layers, rules });
|
|
7448
|
+
if (!doc?.layers.length || doc.layers.some((l) => !l.name || !l.paths.length)) {
|
|
7449
|
+
throw new Error('"layers" must be a non-empty list of {name, paths: [glob, ...]}');
|
|
7450
|
+
}
|
|
7451
|
+
const map = layerMap(doc, deps.getReview().head);
|
|
7452
|
+
writeLayers(deps.repo, doc);
|
|
7453
|
+
deps.bus.emit({ type: "layers", layers: map });
|
|
7454
|
+
const files = deps.getReview().head.nodes.filter((n) => n.kind === "file").map((n) => n.file);
|
|
7455
|
+
const counts = doc.layers.map(
|
|
7456
|
+
(l) => `${l.name}: ${files.filter((f) => map.byFile[f] === l.name).length} files`
|
|
7457
|
+
);
|
|
7458
|
+
const loose = files.filter((f) => !map.byFile[f]);
|
|
7459
|
+
const looseNote = loose.length ? `
|
|
7460
|
+
${loose.length} file(s) in no layer: ${loose.slice(0, 20).join(", ")}${loose.length > 20 ? " \u2026" : ""}` : "";
|
|
7461
|
+
return `wrote .topo/layers.json; ${counts.join(", ")}${looseNote}`;
|
|
7462
|
+
}
|
|
7463
|
+
},
|
|
6702
7464
|
create_territory: {
|
|
6703
7465
|
access: "director",
|
|
6704
7466
|
description: "Create a territory (a region of the map) and optionally hand it to an agent. Use to carve out scope before launching a worker.",
|
|
@@ -6867,11 +7629,11 @@ var init_mcp = __esm({
|
|
|
6867
7629
|
});
|
|
6868
7630
|
|
|
6869
7631
|
// packages/cli/src/territories.ts
|
|
6870
|
-
import
|
|
6871
|
-
import
|
|
7632
|
+
import fs16 from "node:fs";
|
|
7633
|
+
import path19 from "node:path";
|
|
6872
7634
|
function readTerritories(repo) {
|
|
6873
7635
|
try {
|
|
6874
|
-
const doc = parseTerritoryDoc(JSON.parse(
|
|
7636
|
+
const doc = parseTerritoryDoc(JSON.parse(fs16.readFileSync(territoriesFile(repo), "utf8")));
|
|
6875
7637
|
if (doc) return doc;
|
|
6876
7638
|
} catch {
|
|
6877
7639
|
}
|
|
@@ -6879,11 +7641,11 @@ function readTerritories(repo) {
|
|
|
6879
7641
|
}
|
|
6880
7642
|
function writeTerritories(repo, doc) {
|
|
6881
7643
|
const file = territoriesFile(repo);
|
|
6882
|
-
|
|
7644
|
+
fs16.mkdirSync(path19.dirname(file), { recursive: true });
|
|
6883
7645
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
6884
|
-
|
|
7646
|
+
fs16.writeFileSync(tmp, `${JSON.stringify(doc, null, 2)}
|
|
6885
7647
|
`);
|
|
6886
|
-
|
|
7648
|
+
fs16.renameSync(tmp, file);
|
|
6887
7649
|
}
|
|
6888
7650
|
function parseTerritoryDoc(v) {
|
|
6889
7651
|
const o = v;
|
|
@@ -6917,7 +7679,7 @@ var init_territories = __esm({
|
|
|
6917
7679
|
"packages/cli/src/territories.ts"() {
|
|
6918
7680
|
"use strict";
|
|
6919
7681
|
SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
6920
|
-
territoriesFile = (repo) =>
|
|
7682
|
+
territoriesFile = (repo) => path19.join(repo, ".topo", "territories.json");
|
|
6921
7683
|
isStrArr2 = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
6922
7684
|
STATUSES = ["proposed", "active", "review", "done"];
|
|
6923
7685
|
}
|
|
@@ -6925,9 +7687,9 @@ var init_territories = __esm({
|
|
|
6925
7687
|
|
|
6926
7688
|
// packages/cli/src/server.ts
|
|
6927
7689
|
import crypto from "node:crypto";
|
|
6928
|
-
import
|
|
7690
|
+
import fs17 from "node:fs";
|
|
6929
7691
|
import http from "node:http";
|
|
6930
|
-
import
|
|
7692
|
+
import path20 from "node:path";
|
|
6931
7693
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6932
7694
|
function send(res, status, body, type) {
|
|
6933
7695
|
res.writeHead(status, { "content-type": type, "cache-control": "no-store" });
|
|
@@ -6972,21 +7734,21 @@ function parseLayout(v) {
|
|
|
6972
7734
|
return { version: 1, views: o.views };
|
|
6973
7735
|
}
|
|
6974
7736
|
function serveStatic(res, staticDir, urlPath2) {
|
|
6975
|
-
const root =
|
|
6976
|
-
const index =
|
|
7737
|
+
const root = path20.resolve(staticDir);
|
|
7738
|
+
const index = path20.join(root, "index.html");
|
|
6977
7739
|
let decoded;
|
|
6978
7740
|
try {
|
|
6979
7741
|
decoded = decodeURIComponent(urlPath2);
|
|
6980
7742
|
} catch {
|
|
6981
7743
|
throw new HttpError(400, "bad path");
|
|
6982
7744
|
}
|
|
6983
|
-
let file =
|
|
6984
|
-
if (file !== root && !file.startsWith(root +
|
|
6985
|
-
if (!
|
|
6986
|
-
if (!
|
|
6987
|
-
const type = TYPES[
|
|
7745
|
+
let file = path20.resolve(root, `.${decoded}`);
|
|
7746
|
+
if (file !== root && !file.startsWith(root + path20.sep)) throw new HttpError(403, "forbidden");
|
|
7747
|
+
if (!fs17.statSync(file, { throwIfNoEntry: false })?.isFile()) file = index;
|
|
7748
|
+
if (!fs17.existsSync(file)) return send(res, 200, MISSING_DIST, TYPES[".html"]);
|
|
7749
|
+
const type = TYPES[path20.extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
6988
7750
|
res.writeHead(200, { "content-type": type });
|
|
6989
|
-
|
|
7751
|
+
fs17.createReadStream(file).pipe(res);
|
|
6990
7752
|
}
|
|
6991
7753
|
function isLocalhostOrigin(origin) {
|
|
6992
7754
|
try {
|
|
@@ -7023,12 +7785,15 @@ function createServer(opts) {
|
|
|
7023
7785
|
if (origin && !isLocalhostOrigin(origin)) throw new HttpError(403, "forbidden origin");
|
|
7024
7786
|
const rawRunId = req.headers["x-topo-run"];
|
|
7025
7787
|
const runId = typeof rawRunId === "string" && opts.agents?.get(rawRunId) ? rawRunId : void 0;
|
|
7788
|
+
const director = constantTimeEqual(
|
|
7789
|
+
req.headers["x-topo-director"],
|
|
7790
|
+
opts.directorToken
|
|
7791
|
+
);
|
|
7792
|
+
const command = req.headers["x-topo-command"];
|
|
7026
7793
|
const ctx = {
|
|
7027
7794
|
runId,
|
|
7028
|
-
director
|
|
7029
|
-
|
|
7030
|
-
opts.directorToken
|
|
7031
|
-
)
|
|
7795
|
+
director,
|
|
7796
|
+
...director && typeof command === "string" && { command }
|
|
7032
7797
|
};
|
|
7033
7798
|
const result = await opts.mcp(await readBody(req), ctx);
|
|
7034
7799
|
if (result.body === void 0) {
|
|
@@ -7040,7 +7805,7 @@ function createServer(opts) {
|
|
|
7040
7805
|
if (route === "GET /api/meta") {
|
|
7041
7806
|
return json(res, 200, {
|
|
7042
7807
|
api: API_VERSION,
|
|
7043
|
-
languages: ["typescript", "javascript", "luau", "python", "rust"],
|
|
7808
|
+
languages: ["typescript", "javascript", "luau", "python", "rust", "c", "cpp"],
|
|
7044
7809
|
mcp: "/mcp",
|
|
7045
7810
|
events: "/api/events",
|
|
7046
7811
|
docs: "https://github.com/zhn2605/topo/blob/main/docs/api.md"
|
|
@@ -7120,6 +7885,10 @@ function createServer(opts) {
|
|
|
7120
7885
|
const id = opts.agents.runCommand(body.text, parseCommandContext(body.context));
|
|
7121
7886
|
return json(res, 200, { id });
|
|
7122
7887
|
}
|
|
7888
|
+
if (route === "POST /api/command/new" && opts.agents) {
|
|
7889
|
+
opts.agents.newConversation();
|
|
7890
|
+
return json(res, 200, { ok: true });
|
|
7891
|
+
}
|
|
7123
7892
|
if (route === "POST /api/open") {
|
|
7124
7893
|
const body = await readJson(req);
|
|
7125
7894
|
const line = Number(body?.line ?? 1);
|
|
@@ -7198,7 +7967,7 @@ var init_server = __esm({
|
|
|
7198
7967
|
KEEPALIVE_MS = 15e3;
|
|
7199
7968
|
REPO_WEB_DIST = fileURLToPath3(new URL("../../../apps/web/dist/", import.meta.url));
|
|
7200
7969
|
DEFAULT_STATIC_DIR = [fileURLToPath3(new URL("./web/", import.meta.url)), REPO_WEB_DIST].find(
|
|
7201
|
-
(d) =>
|
|
7970
|
+
(d) => fs17.existsSync(path20.join(d, "index.html"))
|
|
7202
7971
|
) ?? REPO_WEB_DIST;
|
|
7203
7972
|
MAX_BODY = 1024 * 1024;
|
|
7204
7973
|
TYPES = {
|
|
@@ -7236,8 +8005,8 @@ var init_server = __esm({
|
|
|
7236
8005
|
});
|
|
7237
8006
|
|
|
7238
8007
|
// packages/cli/src/watch.ts
|
|
7239
|
-
import
|
|
7240
|
-
import
|
|
8008
|
+
import fs18 from "node:fs";
|
|
8009
|
+
import path21 from "node:path";
|
|
7241
8010
|
function isIndexable(relPath) {
|
|
7242
8011
|
const parts = relPath.split(/[\\/]/).filter(Boolean);
|
|
7243
8012
|
const base = parts.at(-1);
|
|
@@ -7279,14 +8048,14 @@ function watchRepo(root, onBatch, opts = {}) {
|
|
|
7279
8048
|
if (watchers.has(dir)) return;
|
|
7280
8049
|
let w;
|
|
7281
8050
|
try {
|
|
7282
|
-
w =
|
|
8051
|
+
w = fs18.watch(dir, (_event, filename) => {
|
|
7283
8052
|
if (!filename) return;
|
|
7284
|
-
const abs =
|
|
7285
|
-
if (!watchers.has(abs) &&
|
|
7286
|
-
if (!isIgnoredDir(
|
|
8053
|
+
const abs = path21.join(dir, filename.toString());
|
|
8054
|
+
if (!watchers.has(abs) && fs18.statSync(abs, { throwIfNoEntry: false })?.isDirectory()) {
|
|
8055
|
+
if (!isIgnoredDir(path21.basename(abs))) walkDirs(abs);
|
|
7287
8056
|
return;
|
|
7288
8057
|
}
|
|
7289
|
-
const rel =
|
|
8058
|
+
const rel = path21.relative(root, abs).split(path21.sep).join("/");
|
|
7290
8059
|
if (!isIndexable(rel)) return;
|
|
7291
8060
|
pending.add(rel);
|
|
7292
8061
|
scheduleFlush();
|
|
@@ -7304,11 +8073,11 @@ function watchRepo(root, onBatch, opts = {}) {
|
|
|
7304
8073
|
watchDir(dir);
|
|
7305
8074
|
let entries = [];
|
|
7306
8075
|
try {
|
|
7307
|
-
entries =
|
|
8076
|
+
entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
7308
8077
|
} catch {
|
|
7309
8078
|
}
|
|
7310
8079
|
for (const e of entries)
|
|
7311
|
-
if (e.isDirectory() && !isIgnoredDir(e.name)) walkDirs(
|
|
8080
|
+
if (e.isDirectory() && !isIgnoredDir(e.name)) walkDirs(path21.join(dir, e.name));
|
|
7312
8081
|
};
|
|
7313
8082
|
walkDirs(root);
|
|
7314
8083
|
return {
|
|
@@ -7333,8 +8102,8 @@ var init_watch = __esm({
|
|
|
7333
8102
|
|
|
7334
8103
|
// packages/cli/src/worktrees.ts
|
|
7335
8104
|
import { execFileSync } from "node:child_process";
|
|
7336
|
-
import
|
|
7337
|
-
import
|
|
8105
|
+
import fs19 from "node:fs";
|
|
8106
|
+
import path22 from "node:path";
|
|
7338
8107
|
function listWorktrees(repo) {
|
|
7339
8108
|
let out;
|
|
7340
8109
|
try {
|
|
@@ -7348,17 +8117,17 @@ function listWorktrees(repo) {
|
|
|
7348
8117
|
}
|
|
7349
8118
|
const roots = out.split("\n").filter((l) => l.startsWith("worktree ")).map((l) => l.slice("worktree ".length));
|
|
7350
8119
|
const own = roots.find((r) => isInside2(r, repo));
|
|
7351
|
-
return roots.filter((r) => r !== own).map((root) => ({ root, name:
|
|
8120
|
+
return roots.filter((r) => r !== own).map((root) => ({ root, name: path22.basename(root) }));
|
|
7352
8121
|
}
|
|
7353
8122
|
function mapWorktreePath(repo, worktrees, file) {
|
|
7354
8123
|
const wt = worktrees.filter((w) => isInside2(w.root, file)).sort((a, b) => b.root.length - a.root.length)[0];
|
|
7355
8124
|
if (!wt) return void 0;
|
|
7356
8125
|
const mainRoot = gitRoot(repo);
|
|
7357
|
-
const sub = mainRoot ?
|
|
7358
|
-
const inTree =
|
|
7359
|
-
const rel = sub ?
|
|
7360
|
-
if (rel.startsWith("..") ||
|
|
7361
|
-
return { rel: rel.split(
|
|
8126
|
+
const sub = mainRoot ? path22.relative(mainRoot, repo) : "";
|
|
8127
|
+
const inTree = path22.relative(wt.root, file);
|
|
8128
|
+
const rel = sub ? path22.relative(sub, inTree) : inTree;
|
|
8129
|
+
if (rel.startsWith("..") || path22.isAbsolute(rel)) return void 0;
|
|
8130
|
+
return { rel: rel.split(path22.sep).join("/"), worktree: wt };
|
|
7362
8131
|
}
|
|
7363
8132
|
function gitRoot(dir) {
|
|
7364
8133
|
try {
|
|
@@ -7372,7 +8141,7 @@ function gitRoot(dir) {
|
|
|
7372
8141
|
}
|
|
7373
8142
|
}
|
|
7374
8143
|
function changedLines(base, changed) {
|
|
7375
|
-
if (!
|
|
8144
|
+
if (!fs19.existsSync(base) || !fs19.existsSync(changed)) return [];
|
|
7376
8145
|
let text = "";
|
|
7377
8146
|
try {
|
|
7378
8147
|
execFileSync("git", ["diff", "--no-index", "-U0", "--no-color", base, changed], {
|
|
@@ -7405,8 +8174,8 @@ var init_worktrees = __esm({
|
|
|
7405
8174
|
"packages/cli/src/worktrees.ts"() {
|
|
7406
8175
|
"use strict";
|
|
7407
8176
|
isInside2 = (dir, p) => {
|
|
7408
|
-
const rel =
|
|
7409
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8177
|
+
const rel = path22.relative(dir, p);
|
|
8178
|
+
return rel === "" || !rel.startsWith("..") && !path22.isAbsolute(rel);
|
|
7410
8179
|
};
|
|
7411
8180
|
}
|
|
7412
8181
|
});
|
|
@@ -7422,8 +8191,8 @@ __export(review_exports, {
|
|
|
7422
8191
|
});
|
|
7423
8192
|
import { spawn as spawn2 } from "node:child_process";
|
|
7424
8193
|
import crypto2 from "node:crypto";
|
|
7425
|
-
import
|
|
7426
|
-
import
|
|
8194
|
+
import fs20 from "node:fs";
|
|
8195
|
+
import path23 from "node:path";
|
|
7427
8196
|
import { parseArgs as parseArgs4 } from "node:util";
|
|
7428
8197
|
function openBrowser(url) {
|
|
7429
8198
|
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
@@ -7462,7 +8231,7 @@ function run4(argv) {
|
|
|
7462
8231
|
if (!base) throw new Error(`missing <base> ref
|
|
7463
8232
|
|
|
7464
8233
|
${HELP4}`);
|
|
7465
|
-
const repo =
|
|
8234
|
+
const repo = path23.resolve(values.repo ?? process.cwd());
|
|
7466
8235
|
return serve({
|
|
7467
8236
|
compute: () => computeReview(repo, base, values.head),
|
|
7468
8237
|
repo,
|
|
@@ -7477,7 +8246,7 @@ ${HELP4}`);
|
|
|
7477
8246
|
}
|
|
7478
8247
|
function readLayout(repo) {
|
|
7479
8248
|
try {
|
|
7480
|
-
const doc = JSON.parse(
|
|
8249
|
+
const doc = JSON.parse(fs20.readFileSync(layoutFile(repo), "utf8"));
|
|
7481
8250
|
if (doc?.version === 1 && doc.views && typeof doc.views === "object") return doc;
|
|
7482
8251
|
} catch {
|
|
7483
8252
|
}
|
|
@@ -7485,12 +8254,12 @@ function readLayout(repo) {
|
|
|
7485
8254
|
}
|
|
7486
8255
|
function writeLayout(repo, doc) {
|
|
7487
8256
|
const file = layoutFile(repo);
|
|
7488
|
-
|
|
8257
|
+
fs20.mkdirSync(path23.dirname(file), { recursive: true });
|
|
7489
8258
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
7490
8259
|
const text = JSON.stringify(doc, null, 2).replace(/\[\s+(-?\d+),\s+(-?\d+)\s+\]/g, "[$1, $2]");
|
|
7491
|
-
|
|
8260
|
+
fs20.writeFileSync(tmp, `${text}
|
|
7492
8261
|
`);
|
|
7493
|
-
|
|
8262
|
+
fs20.renameSync(tmp, file);
|
|
7494
8263
|
}
|
|
7495
8264
|
function serve(opts) {
|
|
7496
8265
|
const port = Number(opts.port);
|
|
@@ -7539,7 +8308,7 @@ function serve(opts) {
|
|
|
7539
8308
|
const nodeIds = blocksAt(
|
|
7540
8309
|
state.index,
|
|
7541
8310
|
parsed.file,
|
|
7542
|
-
changedLines(
|
|
8311
|
+
changedLines(path23.join(repo, parsed.file), parsed.absFile)
|
|
7543
8312
|
);
|
|
7544
8313
|
const run9 = agents.list().findLast((r) => r.agent === parsed.agent || r.agent === parsed.session);
|
|
7545
8314
|
const { absFile: _abs, worktree, ...activity } = parsed;
|
|
@@ -7628,7 +8397,10 @@ function serve(opts) {
|
|
|
7628
8397
|
state.payload.inbox = inbox.list();
|
|
7629
8398
|
state.payload.runs = agents.list();
|
|
7630
8399
|
state.payload.commandBar = agents.available ? "agent" : "search";
|
|
8400
|
+
state.payload.exchanges = agents.exchanges();
|
|
7631
8401
|
if (opts.skipPermissions) state.payload.skipPermissions = true;
|
|
8402
|
+
const layers = readLayers(repo);
|
|
8403
|
+
state.payload.layers = layers && layerMap(layers, state.review.head);
|
|
7632
8404
|
return state.payload;
|
|
7633
8405
|
},
|
|
7634
8406
|
index: () => state.index,
|
|
@@ -7716,7 +8488,7 @@ Options:
|
|
|
7716
8488
|
--dangerously-skip-permissions launched agents run with no permission prompts (like Claude Code's
|
|
7717
8489
|
flag of the same name): nothing waits in the Inbox. For this server session only.
|
|
7718
8490
|
-h, --help`;
|
|
7719
|
-
layoutFile = (repo) =>
|
|
8491
|
+
layoutFile = (repo) => path23.join(repo, ".topo", "layout.json");
|
|
7720
8492
|
}
|
|
7721
8493
|
});
|
|
7722
8494
|
|
|
@@ -7727,8 +8499,8 @@ __export(export_exports, {
|
|
|
7727
8499
|
run: () => run5,
|
|
7728
8500
|
snapshotPage: () => snapshotPage
|
|
7729
8501
|
});
|
|
7730
|
-
import
|
|
7731
|
-
import
|
|
8502
|
+
import fs21 from "node:fs";
|
|
8503
|
+
import path24 from "node:path";
|
|
7732
8504
|
import { parseArgs as parseArgs5 } from "node:util";
|
|
7733
8505
|
function snapshotPage(template, data) {
|
|
7734
8506
|
const json2 = JSON.stringify(data).replaceAll("<", "\\u003c");
|
|
@@ -7758,7 +8530,7 @@ function run5(argv) {
|
|
|
7758
8530
|
if (!base) throw new Error(`missing <base> ref
|
|
7759
8531
|
|
|
7760
8532
|
${HELP5}`);
|
|
7761
|
-
const repo =
|
|
8533
|
+
const repo = path24.resolve(values.repo ?? process.cwd());
|
|
7762
8534
|
const review = computeReview(repo, base, values.head);
|
|
7763
8535
|
const payload = { ...toPayload(repo, review, ""), live: void 0 };
|
|
7764
8536
|
const sources = {};
|
|
@@ -7768,14 +8540,14 @@ ${HELP5}`);
|
|
|
7768
8540
|
if (s) sources[`${side} ${b.nodeId}`] = s;
|
|
7769
8541
|
}
|
|
7770
8542
|
}
|
|
7771
|
-
const templateFile =
|
|
7772
|
-
if (!
|
|
8543
|
+
const templateFile = path24.join(DEFAULT_STATIC_DIR, "snapshot.html");
|
|
8544
|
+
if (!fs21.existsSync(templateFile)) {
|
|
7773
8545
|
throw new Error("the web UI isn't built: run `pnpm --filter @topo/web build` first");
|
|
7774
8546
|
}
|
|
7775
|
-
const template =
|
|
7776
|
-
const out =
|
|
7777
|
-
|
|
7778
|
-
const kb = Math.round(
|
|
8547
|
+
const template = fs21.readFileSync(templateFile, "utf8");
|
|
8548
|
+
const out = path24.resolve(values.out);
|
|
8549
|
+
fs21.writeFileSync(out, snapshotPage(template, { payload, sources }));
|
|
8550
|
+
const kb = Math.round(fs21.statSync(out).size / 1024);
|
|
7779
8551
|
console.log(`topo: ${review.changeSet.blocks.length} changed block(s) \u2192 ${out} (${kb} kB)`);
|
|
7780
8552
|
}
|
|
7781
8553
|
var HELP5;
|
|
@@ -7804,8 +8576,8 @@ __export(layers_exports, {
|
|
|
7804
8576
|
HELP: () => HELP6,
|
|
7805
8577
|
run: () => run6
|
|
7806
8578
|
});
|
|
7807
|
-
import
|
|
7808
|
-
import
|
|
8579
|
+
import fs22 from "node:fs";
|
|
8580
|
+
import path25 from "node:path";
|
|
7809
8581
|
import { parseArgs as parseArgs6 } from "node:util";
|
|
7810
8582
|
function run6(argv) {
|
|
7811
8583
|
const { values, positionals } = parseArgs6({
|
|
@@ -7817,18 +8589,18 @@ function run6(argv) {
|
|
|
7817
8589
|
console.log(HELP6);
|
|
7818
8590
|
return;
|
|
7819
8591
|
}
|
|
7820
|
-
const repo =
|
|
8592
|
+
const repo = path25.resolve(positionals[0] ?? process.cwd());
|
|
7821
8593
|
const graph = indexRepo(repo);
|
|
7822
8594
|
let doc = readLayers(repo);
|
|
7823
8595
|
if (values.suggest) {
|
|
7824
8596
|
if (doc) {
|
|
7825
8597
|
console.log(
|
|
7826
|
-
`topo: ${
|
|
8598
|
+
`topo: ${path25.relative(process.cwd(), layersFile(repo)) || layersFile(repo)} already exists; edit it instead.`
|
|
7827
8599
|
);
|
|
7828
8600
|
} else {
|
|
7829
8601
|
doc = suggestLayers(graph);
|
|
7830
|
-
|
|
7831
|
-
|
|
8602
|
+
fs22.mkdirSync(path25.dirname(layersFile(repo)), { recursive: true });
|
|
8603
|
+
fs22.writeFileSync(layersFile(repo), `${JSON.stringify(doc, null, 2)}
|
|
7832
8604
|
`);
|
|
7833
8605
|
console.log(
|
|
7834
8606
|
`topo: wrote ${doc.layers.length} suggested layer(s) to ${layersFile(repo)}; name them, merge them, and add rules.`
|
|
@@ -7878,7 +8650,7 @@ __export(map_exports, {
|
|
|
7878
8650
|
HELP: () => HELP7,
|
|
7879
8651
|
run: () => run7
|
|
7880
8652
|
});
|
|
7881
|
-
import
|
|
8653
|
+
import path26 from "node:path";
|
|
7882
8654
|
import { parseArgs as parseArgs7 } from "node:util";
|
|
7883
8655
|
function run7(argv) {
|
|
7884
8656
|
const { values, positionals } = parseArgs7({
|
|
@@ -7901,7 +8673,7 @@ function run7(argv) {
|
|
|
7901
8673
|
return;
|
|
7902
8674
|
}
|
|
7903
8675
|
if (!values.cache) process.env.TOPO_NO_CACHE = "1";
|
|
7904
|
-
const dir =
|
|
8676
|
+
const dir = path26.resolve(positionals[0] ?? process.cwd());
|
|
7905
8677
|
return serve({
|
|
7906
8678
|
compute: () => computeExplore(dir),
|
|
7907
8679
|
repo: dir,
|
|
@@ -7984,7 +8756,7 @@ async function main(argv) {
|
|
|
7984
8756
|
${HELP8}`);
|
|
7985
8757
|
await (await cmd()).run(rest);
|
|
7986
8758
|
}
|
|
7987
|
-
if (
|
|
8759
|
+
if (true) {
|
|
7988
8760
|
main(process.argv.slice(2)).catch((e) => {
|
|
7989
8761
|
console.error(`topo: ${friendlyError(e)}`);
|
|
7990
8762
|
process.exit(1);
|