@arcbridge/core 0.4.2 → 0.5.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/dist/index.js CHANGED
@@ -4964,8 +4964,9 @@ function ensureGitignore(targetDir) {
4964
4964
  }
4965
4965
 
4966
4966
  // src/indexer/index.ts
4967
- import { relative as relative5, join as join14 } from "path";
4968
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
4967
+ import ts6 from "typescript";
4968
+ import { relative as relative5, join as join16 } from "path";
4969
+ import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
4969
4970
  import { execFileSync as execFileSync2 } from "child_process";
4970
4971
  import YAML from "yaml";
4971
4972
 
@@ -5333,7 +5334,7 @@ function buildSymbolLookup(symbols) {
5333
5334
  function extractDependencies(sourceFile, checker, relativePath, projectRoot, lookup) {
5334
5335
  const deps = [];
5335
5336
  const seen = /* @__PURE__ */ new Set();
5336
- function addDep2(sourceId, targetId, kind) {
5337
+ function addDep4(sourceId, targetId, kind) {
5337
5338
  const key = `${sourceId}|${targetId}|${kind}`;
5338
5339
  if (seen.has(key)) return;
5339
5340
  if (!lookup.allIds.has(sourceId) || !lookup.allIds.has(targetId)) return;
@@ -5369,7 +5370,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5369
5370
  if (!targetId) continue;
5370
5371
  const fileSymbols = getFileTopLevelSymbols(relativePath, lookup);
5371
5372
  for (const sourceId of fileSymbols) {
5372
- addDep2(sourceId, targetId, "imports");
5373
+ addDep4(sourceId, targetId, "imports");
5373
5374
  }
5374
5375
  }
5375
5376
  }
@@ -5381,7 +5382,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5381
5382
  if (targetId) {
5382
5383
  const fileSymbols = getFileTopLevelSymbols(relativePath, lookup);
5383
5384
  for (const sourceId of fileSymbols) {
5384
- addDep2(sourceId, targetId, "imports");
5385
+ addDep4(sourceId, targetId, "imports");
5385
5386
  }
5386
5387
  }
5387
5388
  }
@@ -5403,7 +5404,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5403
5404
  const resolved = exprSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(exprSymbol) : exprSymbol;
5404
5405
  const targetId = resolveSymbolId(resolved);
5405
5406
  if (targetId) {
5406
- addDep2(classId, targetId, kind);
5407
+ addDep4(classId, targetId, kind);
5407
5408
  }
5408
5409
  }
5409
5410
  }
@@ -5426,7 +5427,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5426
5427
  const resolved = calledSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(calledSymbol) : calledSymbol;
5427
5428
  const targetId = resolveSymbolId(resolved);
5428
5429
  if (targetId) {
5429
- addDep2(ownerId, targetId, "calls");
5430
+ addDep4(ownerId, targetId, "calls");
5430
5431
  }
5431
5432
  }
5432
5433
  }
@@ -5456,7 +5457,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5456
5457
  const resolved = calledSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(calledSymbol) : calledSymbol;
5457
5458
  const targetId = resolveSymbolId(resolved);
5458
5459
  if (targetId) {
5459
- addDep2(callOwnerId, targetId, "calls");
5460
+ addDep4(callOwnerId, targetId, "calls");
5460
5461
  }
5461
5462
  }
5462
5463
  }
@@ -5488,7 +5489,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5488
5489
  const resolved = typeSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(typeSymbol) : typeSymbol;
5489
5490
  const targetId = resolveSymbolId(resolved);
5490
5491
  if (targetId) {
5491
- addDep2(ownerId, targetId, "uses_type");
5492
+ addDep4(ownerId, targetId, "uses_type");
5492
5493
  }
5493
5494
  }
5494
5495
  }
@@ -5512,7 +5513,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5512
5513
  const resolved = jsxSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(jsxSymbol) : jsxSymbol;
5513
5514
  const targetId = resolveSymbolId(resolved);
5514
5515
  if (targetId) {
5515
- addDep2(ownerId, targetId, "renders");
5516
+ addDep4(ownerId, targetId, "renders");
5516
5517
  }
5517
5518
  }
5518
5519
  if (ts4.isPropertyAccessExpression(tagName) && ts4.isIdentifier(tagName.name) && tagName.name.text === "Provider") {
@@ -5521,7 +5522,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5521
5522
  const resolved = ctxSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(ctxSymbol) : ctxSymbol;
5522
5523
  const targetId = resolveSymbolId(resolved);
5523
5524
  if (targetId) {
5524
- addDep2(ownerId, targetId, "provides_context");
5525
+ addDep4(ownerId, targetId, "provides_context");
5525
5526
  }
5526
5527
  }
5527
5528
  }
@@ -5568,7 +5569,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5568
5569
  const resolved = ctxSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(ctxSymbol) : ctxSymbol;
5569
5570
  const targetId = resolveSymbolId(resolved);
5570
5571
  if (targetId) {
5571
- addDep2(ownerId, targetId, "consumes_context");
5572
+ addDep4(ownerId, targetId, "consumes_context");
5572
5573
  }
5573
5574
  }
5574
5575
  }
@@ -6015,36 +6016,37 @@ function hashContent(content) {
6015
6016
  }
6016
6017
 
6017
6018
  // src/indexer/db-writer.ts
6018
- function getExistingHashes(db, service) {
6019
- const rows = db.prepare(
6020
- "SELECT DISTINCT file_path, content_hash FROM symbols WHERE service = ?"
6021
- ).all(service);
6019
+ function getExistingHashes(db, service, language) {
6020
+ const query = language ? "SELECT DISTINCT file_path, content_hash FROM symbols WHERE service = ? AND language = ?" : "SELECT DISTINCT file_path, content_hash FROM symbols WHERE service = ?";
6021
+ const params = language ? [service, language] : [service];
6022
+ const rows = db.prepare(query).all(...params);
6022
6023
  const map = /* @__PURE__ */ new Map();
6023
6024
  for (const row of rows) {
6024
6025
  map.set(row.file_path, row.content_hash);
6025
6026
  }
6026
6027
  return map;
6027
6028
  }
6028
- function removeSymbolsForFiles(db, filePaths) {
6029
+ function removeScopedSymbolsForFiles(db, filePaths, service, language) {
6029
6030
  if (filePaths.length === 0) return;
6030
- const deleteSymbols = db.prepare(
6031
- "DELETE FROM symbols WHERE file_path = ?"
6032
- );
6031
+ const scope = "file_path = ? AND service = ? AND language = ?";
6033
6032
  const deleteDepsSource = db.prepare(
6034
- "DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE file_path = ?)"
6033
+ `DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE ${scope})`
6035
6034
  );
6036
6035
  const deleteDepsTarget = db.prepare(
6037
- "DELETE FROM dependencies WHERE target_symbol IN (SELECT id FROM symbols WHERE file_path = ?)"
6036
+ `DELETE FROM dependencies WHERE target_symbol IN (SELECT id FROM symbols WHERE ${scope})`
6038
6037
  );
6039
6038
  const deleteComponents = db.prepare(
6040
- "DELETE FROM components WHERE symbol_id IN (SELECT id FROM symbols WHERE file_path = ?)"
6039
+ `DELETE FROM components WHERE symbol_id IN (SELECT id FROM symbols WHERE ${scope})`
6040
+ );
6041
+ const deleteSymbols = db.prepare(
6042
+ `DELETE FROM symbols WHERE ${scope}`
6041
6043
  );
6042
6044
  transaction(db, () => {
6043
6045
  for (const fp of filePaths) {
6044
- deleteDepsSource.run(fp);
6045
- deleteDepsTarget.run(fp);
6046
- deleteComponents.run(fp);
6047
- deleteSymbols.run(fp);
6046
+ deleteDepsSource.run(fp, service, language);
6047
+ deleteDepsTarget.run(fp, service, language);
6048
+ deleteComponents.run(fp, service, language);
6049
+ deleteSymbols.run(fp, service, language);
6048
6050
  }
6049
6051
  });
6050
6052
  }
@@ -6177,11 +6179,11 @@ function hasGlobalTool() {
6177
6179
  return false;
6178
6180
  }
6179
6181
  function resolveIndexerProject() {
6180
- const currentDir2 = dirname2(fileURLToPath(import.meta.url));
6182
+ const currentDir4 = dirname2(fileURLToPath(import.meta.url));
6181
6183
  const candidates = [
6182
- resolve(currentDir2, "../../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
6183
- resolve(currentDir2, "../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
6184
- resolve(currentDir2, "../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj")
6184
+ resolve(currentDir4, "../../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
6185
+ resolve(currentDir4, "../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
6186
+ resolve(currentDir4, "../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj")
6185
6187
  ];
6186
6188
  for (const candidate of candidates) {
6187
6189
  if (existsSync5(candidate)) return candidate;
@@ -6247,7 +6249,7 @@ function indexDotnetProjectRoslyn(db, options) {
6247
6249
  "No .sln or .csproj file found in project root. The .NET indexer requires a project or solution file."
6248
6250
  );
6249
6251
  }
6250
- const existingHashes = getExistingHashes(db, service);
6252
+ const existingHashes = getExistingHashes(db, service, "csharp");
6251
6253
  const hashesJson = JSON.stringify(Object.fromEntries(existingHashes));
6252
6254
  const stdout = runDotnetIndexer(dotnetProject, hashesJson, projectRoot);
6253
6255
  const lines = stdout.trim().split("\n");
@@ -6264,7 +6266,7 @@ function indexDotnetProjectRoslyn(db, options) {
6264
6266
  );
6265
6267
  }
6266
6268
  const filesToClean = [...output.changedFiles, ...output.removedFiles];
6267
- removeSymbolsForFiles(db, filesToClean);
6269
+ removeScopedSymbolsForFiles(db, filesToClean, service, "csharp");
6268
6270
  const symbols = output.symbols.map((s) => ({
6269
6271
  id: s.id,
6270
6272
  name: s.name,
@@ -7184,7 +7186,7 @@ async function indexCSharpTreeSitter(db, options) {
7184
7186
  ignore: ignorePatterns,
7185
7187
  absolute: false
7186
7188
  });
7187
- const existingHashes = getExistingHashes(db, service);
7189
+ const existingHashes = getExistingHashes(db, service, "csharp");
7188
7190
  const currentPaths = /* @__PURE__ */ new Set();
7189
7191
  const fileCache = /* @__PURE__ */ new Map();
7190
7192
  const changedFiles = [];
@@ -7211,7 +7213,7 @@ async function indexCSharpTreeSitter(db, options) {
7211
7213
  }
7212
7214
  }
7213
7215
  const filesToClean = [...removed, ...changedFiles];
7214
- removeSymbolsForFiles(db, filesToClean);
7216
+ removeScopedSymbolsForFiles(db, filesToClean, service, "csharp");
7215
7217
  const allNewSymbols = [];
7216
7218
  for (const relPath of changedFiles) {
7217
7219
  const cached = fileCache.get(relPath);
@@ -7220,7 +7222,7 @@ async function indexCSharpTreeSitter(db, options) {
7220
7222
  allNewSymbols.push(...symbols);
7221
7223
  }
7222
7224
  writeSymbols(db, allNewSymbols, service, "csharp");
7223
- const allDbSymbols = db.prepare("SELECT id, file_path as filePath, name, kind, start_line as startLine, end_line as endLine FROM symbols WHERE service = ?").all(service);
7225
+ const allDbSymbols = db.prepare("SELECT id, file_path as filePath, name, kind, start_line as startLine, end_line as endLine FROM symbols WHERE service = ? AND language = 'csharp'").all(service);
7224
7226
  const symbolLookup = buildCSharpSymbolLookup(allDbSymbols);
7225
7227
  const allDeps = [];
7226
7228
  for (const [relPath, cached] of fileCache) {
@@ -7267,215 +7269,1358 @@ async function indexCSharpTreeSitter(db, options) {
7267
7269
  };
7268
7270
  }
7269
7271
 
7270
- // src/indexer/package-deps.ts
7272
+ // src/indexer/python/indexer.ts
7273
+ import { readFileSync as readFileSync5 } from "fs";
7271
7274
  import { join as join12 } from "path";
7272
- import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync5 } from "fs";
7273
- function indexPackageDependencies(db, projectRoot, service = "main") {
7274
- const deps = [];
7275
- const pkgJsonPath = join12(projectRoot, "package.json");
7276
- if (existsSync6(pkgJsonPath)) {
7277
- deps.push(...parsePackageJson(pkgJsonPath));
7278
- }
7279
- const csprojFiles = findCsprojFiles(projectRoot);
7280
- for (const csproj of csprojFiles) {
7281
- deps.push(...parseCsproj(csproj));
7275
+ import { globbySync as globbySync2 } from "globby";
7276
+
7277
+ // src/indexer/python/parser.ts
7278
+ import { accessSync as accessSync3, constants as constants3 } from "fs";
7279
+ import { dirname as dirname4, resolve as resolve3 } from "path";
7280
+ import { fileURLToPath as fileURLToPath3 } from "url";
7281
+ import "web-tree-sitter";
7282
+ var currentDir2 = dirname4(fileURLToPath3(import.meta.url));
7283
+ var cachedParser2 = null;
7284
+ var initPromise2 = null;
7285
+ function resolveGrammarPath2() {
7286
+ const candidates = [
7287
+ resolve3(currentDir2, "../../../wasm/tree-sitter-python.wasm"),
7288
+ // from src/indexer/python/
7289
+ resolve3(currentDir2, "../wasm/tree-sitter-python.wasm")
7290
+ // from dist/ (tsup flat bundle)
7291
+ ];
7292
+ for (const candidate of candidates) {
7293
+ try {
7294
+ accessSync3(candidate, constants3.R_OK);
7295
+ return candidate;
7296
+ } catch {
7297
+ continue;
7298
+ }
7282
7299
  }
7283
- if (deps.length === 0) return 0;
7284
- db.prepare("DELETE FROM package_dependencies WHERE service = ?").run(service);
7285
- const insert = db.prepare(
7286
- "INSERT OR IGNORE INTO package_dependencies (name, version, source, service) VALUES (?, ?, ?, ?)"
7300
+ throw new Error(
7301
+ "Could not find tree-sitter-python.wasm. Ensure the wasm/ directory is present in the @arcbridge/core package."
7287
7302
  );
7288
- transaction(db, () => {
7289
- for (const dep of deps) {
7290
- insert.run(dep.name, dep.version, dep.source, service);
7303
+ }
7304
+ async function ensurePythonParser() {
7305
+ if (cachedParser2) return cachedParser2;
7306
+ if (initPromise2) return initPromise2;
7307
+ initPromise2 = (async () => {
7308
+ try {
7309
+ const TreeSitter = await import("web-tree-sitter");
7310
+ await TreeSitter.Parser.init();
7311
+ const grammarPath = resolveGrammarPath2();
7312
+ const Python = await TreeSitter.Language.load(grammarPath);
7313
+ const parser = new TreeSitter.Parser();
7314
+ parser.setLanguage(Python);
7315
+ cachedParser2 = parser;
7316
+ return parser;
7317
+ } catch (err) {
7318
+ initPromise2 = null;
7319
+ throw err;
7291
7320
  }
7292
- });
7293
- return deps.length;
7321
+ })();
7322
+ return initPromise2;
7294
7323
  }
7295
- function parsePackageJson(filePath) {
7296
- try {
7297
- const content = readFileSync5(filePath, "utf-8");
7298
- const pkg = JSON.parse(content);
7299
- const deps = [];
7300
- if (pkg.dependencies) {
7301
- for (const [name, version] of Object.entries(pkg.dependencies)) {
7302
- deps.push({ name, version, source: "npm" });
7324
+ function parsePython(content) {
7325
+ if (!cachedParser2) {
7326
+ throw new Error(
7327
+ "Python parser not initialized. Call await ensurePythonParser() first."
7328
+ );
7329
+ }
7330
+ const tree = cachedParser2.parse(content);
7331
+ if (!tree) {
7332
+ throw new Error("Failed to parse Python content");
7333
+ }
7334
+ return tree;
7335
+ }
7336
+
7337
+ // src/indexer/python/symbol-extractor.ts
7338
+ function extractPythonSymbols(tree, relativePath, contentHash) {
7339
+ const symbols = [];
7340
+ const root = tree.rootNode;
7341
+ for (const child of root.namedChildren) {
7342
+ walkNode2(child, null, relativePath, contentHash, symbols);
7343
+ }
7344
+ return symbols;
7345
+ }
7346
+ function walkNode2(node, currentClassName, relativePath, contentHash, symbols) {
7347
+ switch (node.type) {
7348
+ case "decorated_definition": {
7349
+ const decorators = extractDecorators(node);
7350
+ const definition = findChild4(node, "function_definition") ?? findChild4(node, "class_definition");
7351
+ if (definition) {
7352
+ walkDefinitionNode(definition, currentClassName, relativePath, contentHash, symbols, decorators);
7303
7353
  }
7354
+ return;
7304
7355
  }
7305
- if (pkg.devDependencies) {
7306
- for (const [name, version] of Object.entries(pkg.devDependencies)) {
7307
- deps.push({ name, version, source: "npm-dev" });
7356
+ case "function_definition":
7357
+ case "class_definition":
7358
+ walkDefinitionNode(node, currentClassName, relativePath, contentHash, symbols, []);
7359
+ return;
7360
+ case "expression_statement": {
7361
+ if (!currentClassName) {
7362
+ extractAssignment(node, relativePath, contentHash, symbols);
7308
7363
  }
7364
+ return;
7309
7365
  }
7310
- return deps;
7311
- } catch {
7312
- return [];
7313
7366
  }
7314
- }
7315
- function parseCsproj(filePath) {
7316
- try {
7317
- const content = readFileSync5(filePath, "utf-8");
7318
- const deps = [];
7319
- const pattern = /<PackageReference\s+Include="([^"]+)"(?:\s+Version="([^"]*)")?[^>]*\/?>/gi;
7320
- let match;
7321
- while ((match = pattern.exec(content)) !== null) {
7322
- deps.push({
7323
- name: match[1],
7324
- version: match[2] ?? null,
7325
- source: "nuget"
7326
- });
7327
- }
7328
- return deps;
7329
- } catch {
7330
- return [];
7367
+ for (const child of node.namedChildren) {
7368
+ walkNode2(child, currentClassName, relativePath, contentHash, symbols);
7369
+ }
7370
+ }
7371
+ function walkDefinitionNode(node, currentClassName, relativePath, contentHash, symbols, decorators) {
7372
+ if (node.type === "function_definition") {
7373
+ const name = getIdentifierName2(node);
7374
+ if (!name) return;
7375
+ const isMethod = currentClassName !== null;
7376
+ const qualifiedName = isMethod ? `${currentClassName}.${name}` : name;
7377
+ const isAsync = hasAsyncKeyword(node);
7378
+ const signature = extractFunctionSignature(node);
7379
+ const returnType = extractReturnType2(node);
7380
+ const docComment = extractDocstring(node);
7381
+ const decoratorText = decorators.length > 0 ? decorators.join("\n") : null;
7382
+ const fullDocComment = decoratorText ? docComment ? `${decoratorText}
7383
+ ${docComment}` : decoratorText : docComment;
7384
+ symbols.push(makeSymbol2({
7385
+ name,
7386
+ qualifiedName,
7387
+ kind: "function",
7388
+ node,
7389
+ relativePath,
7390
+ contentHash,
7391
+ isExported: !name.startsWith("_"),
7392
+ isAsync,
7393
+ signature,
7394
+ returnType,
7395
+ docComment: fullDocComment
7396
+ }));
7397
+ return;
7331
7398
  }
7332
- }
7333
- function findCsprojFiles(dir, maxDepth = 4) {
7334
- const results = [];
7335
- function walk(currentDir2, depth) {
7336
- if (depth > maxDepth) return;
7337
- try {
7338
- const entries = readdirSync5(currentDir2, { withFileTypes: true });
7339
- for (const entry of entries) {
7340
- if (entry.name === "bin" || entry.name === "obj" || entry.name === "node_modules" || entry.name === ".git") continue;
7341
- const fullPath = join12(currentDir2, entry.name);
7342
- if (entry.isFile() && entry.name.endsWith(".csproj")) {
7343
- results.push(fullPath);
7344
- } else if (entry.isDirectory()) {
7345
- walk(fullPath, depth + 1);
7346
- }
7399
+ if (node.type === "class_definition") {
7400
+ const name = getIdentifierName2(node);
7401
+ if (!name) return;
7402
+ const qualifiedName = currentClassName ? `${currentClassName}.${name}` : name;
7403
+ const docComment = extractDocstring(node);
7404
+ const decoratorText = decorators.length > 0 ? decorators.join("\n") : null;
7405
+ const fullDocComment = decoratorText ? docComment ? `${decoratorText}
7406
+ ${docComment}` : decoratorText : docComment;
7407
+ symbols.push(makeSymbol2({
7408
+ name,
7409
+ qualifiedName,
7410
+ kind: "class",
7411
+ node,
7412
+ relativePath,
7413
+ contentHash,
7414
+ isExported: !name.startsWith("_"),
7415
+ docComment: fullDocComment
7416
+ }));
7417
+ const body = findChild4(node, "block");
7418
+ if (body) {
7419
+ for (const child of body.namedChildren) {
7420
+ walkNode2(child, qualifiedName, relativePath, contentHash, symbols);
7347
7421
  }
7348
- } catch {
7349
7422
  }
7423
+ return;
7350
7424
  }
7351
- walk(dir, 0);
7352
- return results;
7353
7425
  }
7354
-
7355
- // src/config/loader.ts
7356
- import { readFileSync as readFileSync6 } from "fs";
7357
- import { join as join13 } from "path";
7358
- import yaml from "yaml";
7359
- function loadConfig(projectRoot) {
7360
- const configPath = join13(projectRoot, ".arcbridge", "config.yaml");
7361
- try {
7362
- const raw = readFileSync6(configPath, "utf-8");
7363
- const parsed = ArcBridgeConfigSchema.safeParse(yaml.parse(raw));
7364
- if (parsed.success) {
7365
- return { config: parsed.data, error: null };
7366
- }
7367
- const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
7368
- return { config: null, error: `Config validation failed: ${issues}` };
7369
- } catch (err) {
7370
- if (err instanceof Error && "code" in err && err.code === "ENOENT") {
7371
- return { config: null, error: null };
7372
- }
7373
- return { config: null, error: `Config load error: ${err instanceof Error ? err.message : String(err)}` };
7426
+ function extractAssignment(node, relativePath, contentHash, symbols) {
7427
+ const assignment = findChild4(node, "assignment");
7428
+ if (!assignment) return;
7429
+ const left = assignment.childForFieldName("left") ?? assignment.namedChildren[0];
7430
+ if (!left || left.type !== "identifier") return;
7431
+ const name = left.text;
7432
+ if (name.startsWith("__") && name.endsWith("__")) return;
7433
+ const isAllCaps = /^[A-Z][A-Z0-9_]*$/.test(name);
7434
+ const kind = isAllCaps ? "constant" : "variable";
7435
+ const typeNode = assignment.childForFieldName("type");
7436
+ const returnType = typeNode ? typeNode.text : null;
7437
+ symbols.push(makeSymbol2({
7438
+ name,
7439
+ qualifiedName: name,
7440
+ kind,
7441
+ node: assignment,
7442
+ relativePath,
7443
+ contentHash,
7444
+ isExported: !name.startsWith("_"),
7445
+ returnType
7446
+ }));
7447
+ }
7448
+ function makeSymbol2(args) {
7449
+ const { name, qualifiedName, kind, node, relativePath, contentHash } = args;
7450
+ return {
7451
+ id: `${relativePath}::${qualifiedName}#${kind}`,
7452
+ name,
7453
+ qualifiedName,
7454
+ kind,
7455
+ filePath: relativePath,
7456
+ startLine: node.startPosition.row + 1,
7457
+ endLine: node.endPosition.row + 1,
7458
+ startCol: node.startPosition.column + 1,
7459
+ endCol: node.endPosition.column + 1,
7460
+ signature: args.signature ?? null,
7461
+ returnType: args.returnType ?? null,
7462
+ docComment: args.docComment ?? null,
7463
+ isExported: args.isExported ?? true,
7464
+ isAsync: args.isAsync ?? false,
7465
+ contentHash
7466
+ };
7467
+ }
7468
+ function getIdentifierName2(node) {
7469
+ const nameNode = node.childForFieldName("name");
7470
+ if (nameNode) return nameNode.text;
7471
+ for (const child of node.namedChildren) {
7472
+ if (child.type === "identifier") return child.text;
7374
7473
  }
7474
+ return null;
7375
7475
  }
7376
-
7377
- // src/indexer/index.ts
7378
- function detectProjectLanguage(projectRoot) {
7379
- if (existsSync7(join14(projectRoot, "ProjectSettings")) && existsSync7(join14(projectRoot, "Assets"))) {
7380
- return "csharp";
7476
+ function findChild4(node, type) {
7477
+ for (const child of node.namedChildren) {
7478
+ if (child.type === type) return child;
7381
7479
  }
7382
- if (existsSync7(join14(projectRoot, "tsconfig.json"))) return "typescript";
7383
- if (existsSync7(join14(projectRoot, "package.json"))) return "typescript";
7384
- if (findDotnetProject(projectRoot)) return "csharp";
7385
- return "typescript";
7480
+ return null;
7386
7481
  }
7387
- async function indexProject(db, options) {
7388
- const language = options.language ?? "auto";
7389
- const resolvedLanguage = language === "auto" ? detectProjectLanguage(options.projectRoot) : language;
7390
- indexPackageDependencies(db, options.projectRoot, options.service ?? "main");
7391
- if (resolvedLanguage === "csharp") {
7392
- const backend = resolveCSharpBackend(options.projectRoot);
7393
- if (backend === "roslyn") {
7394
- return indexDotnetProjectRoslyn(db, {
7395
- projectRoot: options.projectRoot,
7396
- service: options.service
7397
- });
7482
+ function hasAsyncKeyword(node) {
7483
+ for (const child of node.children) {
7484
+ if (child.type === "async") return true;
7485
+ if (child.type === "def") break;
7486
+ }
7487
+ return false;
7488
+ }
7489
+ function extractFunctionSignature(node) {
7490
+ const params = node.childForFieldName("parameters") ?? findChild4(node, "parameters");
7491
+ if (!params) return null;
7492
+ const name = getIdentifierName2(node) ?? "func";
7493
+ return `${name}${params.text}`;
7494
+ }
7495
+ function extractReturnType2(node) {
7496
+ const returnType = node.childForFieldName("return_type");
7497
+ if (returnType) return returnType.text;
7498
+ return null;
7499
+ }
7500
+ function extractDocstring(node) {
7501
+ const body = findChild4(node, "block");
7502
+ if (!body || body.namedChildren.length === 0) return null;
7503
+ const firstStmt = body.namedChildren[0];
7504
+ if (firstStmt.type !== "expression_statement") return null;
7505
+ const expr = firstStmt.namedChildren[0];
7506
+ if (!expr || expr.type !== "string") return null;
7507
+ let text = expr.text;
7508
+ if (text.startsWith('"""') && text.endsWith('"""')) {
7509
+ text = text.slice(3, -3);
7510
+ } else if (text.startsWith("'''") && text.endsWith("'''")) {
7511
+ text = text.slice(3, -3);
7512
+ } else if (text.startsWith('"') && text.endsWith('"')) {
7513
+ text = text.slice(1, -1);
7514
+ } else if (text.startsWith("'") && text.endsWith("'")) {
7515
+ text = text.slice(1, -1);
7516
+ }
7517
+ return text.trim() || null;
7518
+ }
7519
+ function extractDecorators(node) {
7520
+ const decorators = [];
7521
+ for (const child of node.namedChildren) {
7522
+ if (child.type === "decorator") {
7523
+ decorators.push(child.text);
7398
7524
  }
7399
- return await indexCSharpTreeSitter(db, {
7400
- projectRoot: options.projectRoot,
7401
- service: options.service
7402
- });
7403
7525
  }
7404
- return indexTypeScriptProject(db, options);
7526
+ return decorators;
7405
7527
  }
7406
- function resolveCSharpBackend(projectRoot) {
7407
- const { config, error } = loadConfig(projectRoot);
7408
- let setting = config?.indexing?.csharp_indexer;
7409
- if (!setting && error) {
7410
- try {
7411
- const raw = readFileSync7(join14(projectRoot, ".arcbridge", "config.yaml"), "utf-8");
7412
- const parsed = YAML.parse(raw);
7413
- const rawSetting = parsed?.indexing?.csharp_indexer;
7414
- if (rawSetting === "roslyn" || rawSetting === "tree-sitter") {
7415
- setting = rawSetting;
7416
- }
7417
- } catch {
7528
+
7529
+ // src/indexer/python/dependency-extractor.ts
7530
+ function buildPythonSymbolLookup(symbols) {
7531
+ const lookup = /* @__PURE__ */ new Map();
7532
+ for (const sym of symbols) {
7533
+ const existing = lookup.get(sym.name);
7534
+ if (existing) {
7535
+ existing.push(sym.id);
7536
+ } else {
7537
+ lookup.set(sym.name, [sym.id]);
7418
7538
  }
7419
7539
  }
7420
- if (setting === "roslyn" || setting === "tree-sitter") {
7421
- return setting;
7540
+ return lookup;
7541
+ }
7542
+ function extractPythonDependencies(tree, relativePath, allSymbols, symbolLookup) {
7543
+ const deps = [];
7544
+ const seen = /* @__PURE__ */ new Set();
7545
+ const fileSymbols = allSymbols.filter((s) => s.filePath === relativePath);
7546
+ walkForDependencies2(tree.rootNode, fileSymbols, symbolLookup, deps, seen);
7547
+ return deps;
7548
+ }
7549
+ function walkForDependencies2(node, fileSymbols, lookup, deps, seen) {
7550
+ switch (node.type) {
7551
+ case "class_definition":
7552
+ extractInheritanceDeps2(node, fileSymbols, lookup, deps, seen);
7553
+ break;
7554
+ case "call":
7555
+ extractCallDeps2(node, fileSymbols, lookup, deps, seen);
7556
+ break;
7422
7557
  }
7423
- if (hasGlobalTool()) {
7424
- return "roslyn";
7558
+ if (node.type === "typed_parameter" || node.type === "typed_default_parameter") {
7559
+ extractTypeAnnotationDeps(node, fileSymbols, lookup, deps, seen);
7425
7560
  }
7426
- if (hasIndexerProject()) {
7427
- try {
7428
- execFileSync2("dotnet", ["--version"], {
7429
- encoding: "utf-8",
7430
- timeout: 5e3
7431
- });
7432
- return "roslyn";
7433
- } catch {
7434
- }
7561
+ for (const child of node.namedChildren) {
7562
+ walkForDependencies2(child, fileSymbols, lookup, deps, seen);
7435
7563
  }
7436
- return "tree-sitter";
7437
7564
  }
7438
- function indexTypeScriptProject(db, options) {
7439
- const start = Date.now();
7440
- const service = options.service ?? "main";
7441
- const { checker, sourceFiles, projectRoot } = createTsProgram(options);
7442
- const existingHashes = getExistingHashes(db, service);
7443
- const changed = [];
7444
- const currentPaths = /* @__PURE__ */ new Set();
7445
- let filesSkipped = 0;
7446
- for (const sf of sourceFiles) {
7447
- const relPath = relative5(projectRoot, sf.fileName);
7448
- currentPaths.add(relPath);
7449
- const hash = hashContent(sf.getFullText());
7450
- const existingHash = existingHashes.get(relPath);
7451
- if (existingHash === hash) {
7452
- filesSkipped++;
7453
- } else {
7454
- changed.push({ sourceFile: sf, relativePath: relPath, hash });
7455
- }
7456
- }
7457
- const removed = [];
7458
- for (const existingPath of existingHashes.keys()) {
7459
- if (!currentPaths.has(existingPath)) {
7460
- removed.push(existingPath);
7565
+ function extractInheritanceDeps2(node, fileSymbols, lookup, deps, seen) {
7566
+ const className = getNodeIdentifier2(node);
7567
+ if (!className) return;
7568
+ const sourceSymbol = findEnclosingSymbol2(className, fileSymbols, "class");
7569
+ if (!sourceSymbol) return;
7570
+ const argList = node.childForFieldName("superclasses") ?? findChild5(node, "argument_list");
7571
+ if (!argList) return;
7572
+ for (const child of argList.namedChildren) {
7573
+ const baseName = extractSimpleTypeName2(child);
7574
+ if (!baseName || baseName === className) continue;
7575
+ const targetIds = lookup.get(baseName);
7576
+ if (!targetIds) continue;
7577
+ for (const targetId of targetIds) {
7578
+ if (targetId === sourceSymbol.id) continue;
7579
+ addDep2(deps, seen, sourceSymbol.id, targetId, "extends");
7580
+ }
7581
+ }
7582
+ }
7583
+ function extractCallDeps2(node, fileSymbols, lookup, deps, seen) {
7584
+ const calledName = extractCalledFunctionName(node);
7585
+ if (!calledName) return;
7586
+ const enclosing = findEnclosingFunctionForNode(node, fileSymbols);
7587
+ if (!enclosing) return;
7588
+ const targetIds = lookup.get(calledName);
7589
+ if (!targetIds) return;
7590
+ for (const targetId of targetIds) {
7591
+ if (targetId === enclosing.id) continue;
7592
+ addDep2(deps, seen, enclosing.id, targetId, "calls");
7593
+ }
7594
+ }
7595
+ function extractTypeAnnotationDeps(node, fileSymbols, lookup, deps, seen) {
7596
+ const typeNames = collectTypeIdentifiers2(node);
7597
+ if (typeNames.length === 0) return;
7598
+ const enclosing = findEnclosingSymbolForNode2(node, fileSymbols);
7599
+ if (!enclosing) return;
7600
+ for (const typeName of typeNames) {
7601
+ const targetIds = lookup.get(typeName);
7602
+ if (!targetIds) continue;
7603
+ for (const targetId of targetIds) {
7604
+ if (targetId === enclosing.id) continue;
7605
+ addDep2(deps, seen, enclosing.id, targetId, "uses_type");
7606
+ }
7607
+ }
7608
+ }
7609
+ function addDep2(deps, seen, source, target, kind) {
7610
+ const key = `${source}|${target}|${kind}`;
7611
+ if (seen.has(key)) return;
7612
+ seen.add(key);
7613
+ deps.push({ sourceSymbolId: source, targetSymbolId: target, kind });
7614
+ }
7615
+ function findChild5(node, type) {
7616
+ for (const child of node.namedChildren) {
7617
+ if (child.type === type) return child;
7618
+ }
7619
+ return null;
7620
+ }
7621
+ function getNodeIdentifier2(node) {
7622
+ const nameNode = node.childForFieldName("name");
7623
+ if (nameNode) return nameNode.text;
7624
+ for (const child of node.namedChildren) {
7625
+ if (child.type === "identifier") return child.text;
7626
+ }
7627
+ return null;
7628
+ }
7629
+ function extractSimpleTypeName2(node) {
7630
+ if (node.type === "identifier") return node.text;
7631
+ if (node.type === "attribute") {
7632
+ const attrNode = node.childForFieldName("attribute");
7633
+ return attrNode?.text ?? null;
7634
+ }
7635
+ if (node.type === "subscript") {
7636
+ const value = node.childForFieldName("value") ?? node.namedChildren[0];
7637
+ if (value) return extractSimpleTypeName2(value);
7638
+ }
7639
+ if (node.type === "keyword_argument") {
7640
+ const value = node.childForFieldName("value") ?? node.namedChildren[1];
7641
+ if (value) return extractSimpleTypeName2(value);
7642
+ }
7643
+ for (const child of node.namedChildren) {
7644
+ if (child.type === "identifier") return child.text;
7645
+ }
7646
+ return null;
7647
+ }
7648
+ function extractCalledFunctionName(node) {
7649
+ const funcNode = node.childForFieldName("function") ?? node.namedChildren[0];
7650
+ if (!funcNode) return null;
7651
+ if (funcNode.type === "identifier") return funcNode.text;
7652
+ if (funcNode.type === "attribute") {
7653
+ const attrNode = funcNode.childForFieldName("attribute");
7654
+ return attrNode?.text ?? null;
7655
+ }
7656
+ return null;
7657
+ }
7658
+ function findEnclosingSymbol2(name, fileSymbols, ...kinds) {
7659
+ return fileSymbols.find(
7660
+ (s) => s.name === name && kinds.includes(s.kind)
7661
+ );
7662
+ }
7663
+ function findEnclosingFunctionForNode(node, fileSymbols) {
7664
+ let current = node.parent;
7665
+ while (current) {
7666
+ if (current.type === "function_definition") {
7667
+ const name = getNodeIdentifier2(current);
7668
+ if (name) {
7669
+ return fileSymbols.find(
7670
+ (s) => s.kind === "function" && s.name === name && s.startLine <= current.startPosition.row + 1 && s.endLine >= current.endPosition.row + 1
7671
+ );
7672
+ }
7673
+ }
7674
+ current = current.parent;
7675
+ }
7676
+ return void 0;
7677
+ }
7678
+ function findEnclosingSymbolForNode2(node, fileSymbols) {
7679
+ let current = node.parent;
7680
+ while (current) {
7681
+ if (current.type === "function_definition") {
7682
+ const name = getNodeIdentifier2(current);
7683
+ if (name) {
7684
+ const found = fileSymbols.find(
7685
+ (s) => s.kind === "function" && s.name === name && s.startLine <= current.startPosition.row + 1 && s.endLine >= current.endPosition.row + 1
7686
+ );
7687
+ if (found) return found;
7688
+ }
7689
+ }
7690
+ if (current.type === "class_definition") {
7691
+ const name = getNodeIdentifier2(current);
7692
+ if (name) {
7693
+ return fileSymbols.find(
7694
+ (s) => s.name === name && s.kind === "class"
7695
+ );
7696
+ }
7697
+ }
7698
+ current = current.parent;
7699
+ }
7700
+ return void 0;
7701
+ }
7702
+ function collectTypeIdentifiers2(node) {
7703
+ const names = [];
7704
+ collectTypeIdentifiersRecursive2(node, names);
7705
+ return [...new Set(names)];
7706
+ }
7707
+ function collectTypeIdentifiersRecursive2(node, names) {
7708
+ if (node.type === "attribute") {
7709
+ const attrNode = node.childForFieldName("attribute");
7710
+ if (attrNode && attrNode.type === "identifier") {
7711
+ names.push(attrNode.text);
7712
+ }
7713
+ return;
7714
+ }
7715
+ if (node.type === "identifier" && isTypeContext2(node)) {
7716
+ names.push(node.text);
7717
+ }
7718
+ for (const child of node.namedChildren) {
7719
+ collectTypeIdentifiersRecursive2(child, names);
7720
+ }
7721
+ }
7722
+ function isTypeContext2(node) {
7723
+ const parent = node.parent;
7724
+ if (!parent) return false;
7725
+ return parent.type === "type" || parent.type === "subscript" || // In typed_parameter, the type annotation is the second named child
7726
+ parent.type === "typed_parameter" && node !== parent.childForFieldName("name") || parent.type === "typed_default_parameter" && node !== parent.childForFieldName("name");
7727
+ }
7728
+
7729
+ // src/indexer/python/indexer.ts
7730
+ async function indexPythonTreeSitter(db, options) {
7731
+ const start = Date.now();
7732
+ await ensurePythonParser();
7733
+ const service = options.service ?? "main";
7734
+ const projectRoot = options.projectRoot;
7735
+ const ignorePatterns = [
7736
+ "**/__pycache__/**",
7737
+ "**/.venv/**",
7738
+ "**/venv/**",
7739
+ "**/.tox/**",
7740
+ "**/node_modules/**",
7741
+ "**/.git/**",
7742
+ "**/dist/**",
7743
+ "**/build/**",
7744
+ "**/.eggs/**",
7745
+ "**/*.egg-info/**"
7746
+ ];
7747
+ const pyFiles = globbySync2("**/*.py", {
7748
+ cwd: projectRoot,
7749
+ ignore: ignorePatterns,
7750
+ absolute: false
7751
+ });
7752
+ const existingHashes = getExistingHashes(db, service, "python");
7753
+ const currentPaths = /* @__PURE__ */ new Set();
7754
+ const fileCache = /* @__PURE__ */ new Map();
7755
+ const changedFiles = [];
7756
+ let filesSkipped = 0;
7757
+ for (const filePath of pyFiles) {
7758
+ const relPath = filePath.replace(/\\/g, "/");
7759
+ currentPaths.add(relPath);
7760
+ const fullPath = join12(projectRoot, relPath);
7761
+ const content = readFileSync5(fullPath, "utf-8");
7762
+ const hash = hashContent(content);
7763
+ const tree = parsePython(content);
7764
+ fileCache.set(relPath, { content, tree, hash });
7765
+ const existingHash = existingHashes.get(relPath);
7766
+ if (existingHash === hash) {
7767
+ filesSkipped++;
7768
+ } else {
7769
+ changedFiles.push(relPath);
7770
+ }
7771
+ }
7772
+ const removed = [];
7773
+ for (const existingPath of existingHashes.keys()) {
7774
+ if (!currentPaths.has(existingPath)) {
7775
+ removed.push(existingPath);
7776
+ }
7777
+ }
7778
+ const filesToClean = [...removed, ...changedFiles];
7779
+ removeScopedSymbolsForFiles(db, filesToClean, service, "python");
7780
+ const allNewSymbols = [];
7781
+ for (const relPath of changedFiles) {
7782
+ const cached = fileCache.get(relPath);
7783
+ if (!cached) continue;
7784
+ const symbols = extractPythonSymbols(cached.tree, relPath, cached.hash);
7785
+ allNewSymbols.push(...symbols);
7786
+ }
7787
+ writeSymbols(db, allNewSymbols, service, "python");
7788
+ const allDbSymbols = db.prepare("SELECT id, file_path as filePath, name, kind, start_line as startLine, end_line as endLine FROM symbols WHERE service = ? AND language = 'python'").all(service);
7789
+ const symbolLookup = buildPythonSymbolLookup(allDbSymbols);
7790
+ const allDeps = [];
7791
+ for (const [relPath, cached] of fileCache) {
7792
+ const fileDeps = extractPythonDependencies(cached.tree, relPath, allDbSymbols, symbolLookup);
7793
+ allDeps.push(...fileDeps);
7794
+ }
7795
+ db.prepare(
7796
+ "DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ? AND language = 'python')"
7797
+ ).run(service);
7798
+ writeDependencies(db, allDeps);
7799
+ return {
7800
+ symbolsIndexed: allNewSymbols.length,
7801
+ dependenciesIndexed: allDeps.length,
7802
+ componentsAnalyzed: 0,
7803
+ routesAnalyzed: 0,
7804
+ filesProcessed: changedFiles.length,
7805
+ filesSkipped,
7806
+ filesRemoved: removed.length,
7807
+ durationMs: Date.now() - start
7808
+ };
7809
+ }
7810
+
7811
+ // src/indexer/go/indexer.ts
7812
+ import { readFileSync as readFileSync6 } from "fs";
7813
+ import { join as join13 } from "path";
7814
+ import { globbySync as globbySync3 } from "globby";
7815
+
7816
+ // src/indexer/go/parser.ts
7817
+ import { accessSync as accessSync4, constants as constants4 } from "fs";
7818
+ import { dirname as dirname5, resolve as resolve4 } from "path";
7819
+ import { fileURLToPath as fileURLToPath4 } from "url";
7820
+ import "web-tree-sitter";
7821
+ var currentDir3 = dirname5(fileURLToPath4(import.meta.url));
7822
+ var cachedParser3 = null;
7823
+ var initPromise3 = null;
7824
+ function resolveGrammarPath3() {
7825
+ const candidates = [
7826
+ resolve4(currentDir3, "../../../wasm/tree-sitter-go.wasm"),
7827
+ // from src/indexer/go/
7828
+ resolve4(currentDir3, "../wasm/tree-sitter-go.wasm")
7829
+ // from dist/ (tsup flat bundle)
7830
+ ];
7831
+ for (const candidate of candidates) {
7832
+ try {
7833
+ accessSync4(candidate, constants4.R_OK);
7834
+ return candidate;
7835
+ } catch {
7836
+ continue;
7837
+ }
7838
+ }
7839
+ throw new Error(
7840
+ "Could not find tree-sitter-go.wasm. Ensure the wasm/ directory is present in the @arcbridge/core package."
7841
+ );
7842
+ }
7843
+ async function ensureGoParser() {
7844
+ if (cachedParser3) return cachedParser3;
7845
+ if (initPromise3) return initPromise3;
7846
+ initPromise3 = (async () => {
7847
+ try {
7848
+ const TreeSitter = await import("web-tree-sitter");
7849
+ await TreeSitter.Parser.init();
7850
+ const grammarPath = resolveGrammarPath3();
7851
+ const Go = await TreeSitter.Language.load(grammarPath);
7852
+ const parser = new TreeSitter.Parser();
7853
+ parser.setLanguage(Go);
7854
+ cachedParser3 = parser;
7855
+ return parser;
7856
+ } catch (err) {
7857
+ initPromise3 = null;
7858
+ throw err;
7859
+ }
7860
+ })();
7861
+ return initPromise3;
7862
+ }
7863
+ function parseGo(content) {
7864
+ if (!cachedParser3) {
7865
+ throw new Error(
7866
+ "Go parser not initialized. Call await ensureGoParser() first."
7867
+ );
7868
+ }
7869
+ const tree = cachedParser3.parse(content);
7870
+ if (!tree) {
7871
+ throw new Error("Failed to parse Go content");
7872
+ }
7873
+ return tree;
7874
+ }
7875
+
7876
+ // src/indexer/go/symbol-extractor.ts
7877
+ function extractGoSymbols(tree, relativePath, contentHash) {
7878
+ const symbols = [];
7879
+ const root = tree.rootNode;
7880
+ for (const child of root.namedChildren) {
7881
+ walkNode3(child, relativePath, contentHash, symbols);
7882
+ }
7883
+ return symbols;
7884
+ }
7885
+ function walkNode3(node, relativePath, contentHash, symbols) {
7886
+ switch (node.type) {
7887
+ case "function_declaration": {
7888
+ const name = getFieldName(node);
7889
+ if (!name) break;
7890
+ const signature = extractFunctionSignature2(node, name);
7891
+ const returnType = extractReturnType3(node);
7892
+ symbols.push(makeSymbol3({
7893
+ name,
7894
+ qualifiedName: name,
7895
+ kind: "function",
7896
+ node,
7897
+ relativePath,
7898
+ contentHash,
7899
+ docComment: extractDocComment2(node),
7900
+ signature,
7901
+ returnType
7902
+ }));
7903
+ return;
7904
+ }
7905
+ case "method_declaration": {
7906
+ const name = getFieldName(node);
7907
+ if (!name) break;
7908
+ const receiverType = extractReceiverType(node);
7909
+ const qualifiedName = receiverType ? `${receiverType}.${name}` : name;
7910
+ const signature = extractFunctionSignature2(node, name);
7911
+ const returnType = extractReturnType3(node);
7912
+ symbols.push(makeSymbol3({
7913
+ name,
7914
+ qualifiedName,
7915
+ kind: "function",
7916
+ node,
7917
+ relativePath,
7918
+ contentHash,
7919
+ docComment: extractDocComment2(node),
7920
+ signature,
7921
+ returnType
7922
+ }));
7923
+ return;
7924
+ }
7925
+ case "type_declaration": {
7926
+ for (const child of node.namedChildren) {
7927
+ if (child.type === "type_spec") {
7928
+ extractTypeSpec(child, node, relativePath, contentHash, symbols);
7929
+ }
7930
+ }
7931
+ return;
7932
+ }
7933
+ case "const_declaration": {
7934
+ for (const child of node.namedChildren) {
7935
+ if (child.type === "const_spec") {
7936
+ const names = getSpecNames(child);
7937
+ const doc = extractDocComment2(child) ?? extractDocComment2(node);
7938
+ for (const name of names) {
7939
+ symbols.push(makeSymbol3({
7940
+ name,
7941
+ qualifiedName: name,
7942
+ kind: "constant",
7943
+ node: child,
7944
+ relativePath,
7945
+ contentHash,
7946
+ docComment: doc
7947
+ }));
7948
+ }
7949
+ }
7950
+ }
7951
+ return;
7952
+ }
7953
+ case "var_declaration": {
7954
+ for (const child of node.namedChildren) {
7955
+ if (child.type === "var_spec") {
7956
+ const names = getSpecNames(child);
7957
+ const doc = extractDocComment2(child) ?? extractDocComment2(node);
7958
+ for (const name of names) {
7959
+ symbols.push(makeSymbol3({
7960
+ name,
7961
+ qualifiedName: name,
7962
+ kind: "variable",
7963
+ node: child,
7964
+ relativePath,
7965
+ contentHash,
7966
+ docComment: doc
7967
+ }));
7968
+ }
7969
+ }
7970
+ }
7971
+ return;
7972
+ }
7973
+ }
7974
+ for (const child of node.namedChildren) {
7975
+ walkNode3(child, relativePath, contentHash, symbols);
7976
+ }
7977
+ }
7978
+ function extractTypeSpec(spec, parentDecl, relativePath, contentHash, symbols) {
7979
+ const name = getFieldName(spec);
7980
+ if (!name) return;
7981
+ const typeNode = spec.childForFieldName("type");
7982
+ let kind = "type";
7983
+ if (typeNode) {
7984
+ if (typeNode.type === "struct_type") {
7985
+ kind = "class";
7986
+ } else if (typeNode.type === "interface_type") {
7987
+ kind = "interface";
7988
+ }
7989
+ }
7990
+ symbols.push(makeSymbol3({
7991
+ name,
7992
+ qualifiedName: name,
7993
+ kind,
7994
+ node: spec,
7995
+ relativePath,
7996
+ contentHash,
7997
+ docComment: extractDocComment2(spec) ?? extractDocComment2(parentDecl)
7998
+ }));
7999
+ }
8000
+ function makeSymbol3(args) {
8001
+ const { name, qualifiedName, kind, node, relativePath, contentHash, docComment } = args;
8002
+ return {
8003
+ id: `${relativePath}::${qualifiedName}#${kind}`,
8004
+ name,
8005
+ qualifiedName,
8006
+ kind,
8007
+ filePath: relativePath,
8008
+ startLine: node.startPosition.row + 1,
8009
+ endLine: node.endPosition.row + 1,
8010
+ startCol: node.startPosition.column + 1,
8011
+ endCol: node.endPosition.column + 1,
8012
+ signature: args.signature ?? null,
8013
+ returnType: args.returnType ?? null,
8014
+ docComment,
8015
+ isExported: /^[A-Z]/.test(name),
8016
+ isAsync: false,
8017
+ contentHash
8018
+ };
8019
+ }
8020
+ function getFieldName(node) {
8021
+ const nameNode = node.childForFieldName("name");
8022
+ if (nameNode) return nameNode.text;
8023
+ for (const child of node.namedChildren) {
8024
+ if (child.type === "identifier") return child.text;
8025
+ }
8026
+ return null;
8027
+ }
8028
+ function getSpecNames(node) {
8029
+ const names = [];
8030
+ for (const child of node.namedChildren) {
8031
+ if (child.type === "identifier") {
8032
+ names.push(child.text);
8033
+ } else {
8034
+ break;
8035
+ }
8036
+ }
8037
+ return names;
8038
+ }
8039
+ function extractReceiverType(node) {
8040
+ const receiver = node.childForFieldName("receiver");
8041
+ if (!receiver) return null;
8042
+ for (const child of receiver.namedChildren) {
8043
+ if (child.type === "parameter_declaration") {
8044
+ const typeNode = child.childForFieldName("type");
8045
+ if (typeNode) {
8046
+ if (typeNode.type === "pointer_type") {
8047
+ const inner = typeNode.namedChildren[0];
8048
+ return inner ? inner.text : typeNode.text.replace(/^\*/, "");
8049
+ }
8050
+ return typeNode.text;
8051
+ }
8052
+ }
8053
+ }
8054
+ return null;
8055
+ }
8056
+ function extractDocComment2(node) {
8057
+ const comments = [];
8058
+ let sibling = node.previousSibling;
8059
+ while (sibling) {
8060
+ if (sibling.type === "comment" && sibling.text.startsWith("//")) {
8061
+ comments.unshift(sibling.text);
8062
+ } else {
8063
+ break;
8064
+ }
8065
+ sibling = sibling.previousSibling;
8066
+ }
8067
+ if (comments.length === 0) return null;
8068
+ return comments.map((c) => c.replace(/^\s*\/\/\s?/, "").trim()).filter(Boolean).join(" ");
8069
+ }
8070
+ function extractFunctionSignature2(node, name) {
8071
+ const params = node.childForFieldName("parameters");
8072
+ if (!params) return null;
8073
+ return `${name}${params.text}`;
8074
+ }
8075
+ function extractReturnType3(node) {
8076
+ const result = node.childForFieldName("result");
8077
+ if (!result) return null;
8078
+ return result.text;
8079
+ }
8080
+
8081
+ // src/indexer/go/dependency-extractor.ts
8082
+ function buildGoSymbolLookup(symbols) {
8083
+ const lookup = /* @__PURE__ */ new Map();
8084
+ for (const sym of symbols) {
8085
+ const existing = lookup.get(sym.name);
8086
+ if (existing) {
8087
+ existing.push(sym.id);
8088
+ } else {
8089
+ lookup.set(sym.name, [sym.id]);
8090
+ }
8091
+ }
8092
+ return lookup;
8093
+ }
8094
+ function extractGoDependencies(tree, relativePath, allSymbols, symbolLookup) {
8095
+ const deps = [];
8096
+ const seen = /* @__PURE__ */ new Set();
8097
+ const fileSymbols = allSymbols.filter((s) => s.filePath === relativePath);
8098
+ walkForDependencies3(tree.rootNode, fileSymbols, symbolLookup, deps, seen);
8099
+ return deps;
8100
+ }
8101
+ function walkForDependencies3(node, fileSymbols, lookup, deps, seen) {
8102
+ switch (node.type) {
8103
+ case "type_declaration":
8104
+ for (const child of node.namedChildren) {
8105
+ if (child.type === "type_spec") {
8106
+ extractEmbeddingDeps(child, fileSymbols, lookup, deps, seen);
8107
+ }
8108
+ }
8109
+ break;
8110
+ case "call_expression":
8111
+ extractCallDeps3(node, fileSymbols, lookup, deps, seen);
8112
+ break;
8113
+ }
8114
+ if (node.type === "parameter_declaration" || node.type === "field_declaration" || node.type === "var_spec") {
8115
+ extractTypeRefDeps2(node, fileSymbols, lookup, deps, seen);
8116
+ }
8117
+ for (const child of node.namedChildren) {
8118
+ walkForDependencies3(child, fileSymbols, lookup, deps, seen);
8119
+ }
8120
+ }
8121
+ function extractEmbeddingDeps(typeSpec, fileSymbols, lookup, deps, seen) {
8122
+ const typeName = getNodeIdentifier3(typeSpec);
8123
+ if (!typeName) return;
8124
+ const sourceSymbol = findEnclosingSymbol3(typeName, fileSymbols, "class", "interface");
8125
+ if (!sourceSymbol) return;
8126
+ const typeNode = typeSpec.childForFieldName("type");
8127
+ if (!typeNode) return;
8128
+ if (typeNode.type === "struct_type" || typeNode.type === "interface_type") {
8129
+ const fieldList = findChild6(typeNode, "field_declaration_list") ?? typeNode;
8130
+ for (const field of fieldList.namedChildren) {
8131
+ if (field.type === "field_declaration") {
8132
+ const nameNode = field.childForFieldName("name");
8133
+ if (nameNode) continue;
8134
+ const embeddedTypeName = extractSimpleTypeName3(field);
8135
+ if (!embeddedTypeName || embeddedTypeName === typeName) continue;
8136
+ const targetIds = lookup.get(embeddedTypeName);
8137
+ if (!targetIds) continue;
8138
+ for (const targetId of targetIds) {
8139
+ if (targetId === sourceSymbol.id) continue;
8140
+ addDep3(deps, seen, sourceSymbol.id, targetId, "extends");
8141
+ }
8142
+ }
8143
+ if (field.type === "type_name" || field.type === "qualified_type" || field.type === "type_identifier") {
8144
+ const embeddedName = extractSimpleTypeNameFromNode(field);
8145
+ if (!embeddedName || embeddedName === typeName) continue;
8146
+ const targetIds = lookup.get(embeddedName);
8147
+ if (!targetIds) continue;
8148
+ for (const targetId of targetIds) {
8149
+ if (targetId === sourceSymbol.id) continue;
8150
+ addDep3(deps, seen, sourceSymbol.id, targetId, "extends");
8151
+ }
8152
+ }
8153
+ }
8154
+ }
8155
+ }
8156
+ function extractCallDeps3(node, fileSymbols, lookup, deps, seen) {
8157
+ const calledName = extractCalledFunctionName2(node);
8158
+ if (!calledName) return;
8159
+ const enclosing = findEnclosingFunctionForNode2(node, fileSymbols);
8160
+ if (!enclosing) return;
8161
+ const targetIds = lookup.get(calledName);
8162
+ if (!targetIds) return;
8163
+ for (const targetId of targetIds) {
8164
+ if (targetId === enclosing.id) continue;
8165
+ addDep3(deps, seen, enclosing.id, targetId, "calls");
8166
+ }
8167
+ }
8168
+ function extractTypeRefDeps2(node, fileSymbols, lookup, deps, seen) {
8169
+ const typeNames = collectTypeIdentifiers3(node);
8170
+ if (typeNames.length === 0) return;
8171
+ const enclosing = findEnclosingSymbolForNode3(node, fileSymbols);
8172
+ if (!enclosing) return;
8173
+ for (const typeName of typeNames) {
8174
+ const targetIds = lookup.get(typeName);
8175
+ if (!targetIds) continue;
8176
+ for (const targetId of targetIds) {
8177
+ if (targetId === enclosing.id) continue;
8178
+ addDep3(deps, seen, enclosing.id, targetId, "uses_type");
8179
+ }
8180
+ }
8181
+ }
8182
+ function addDep3(deps, seen, source, target, kind) {
8183
+ const key = `${source}|${target}|${kind}`;
8184
+ if (seen.has(key)) return;
8185
+ seen.add(key);
8186
+ deps.push({ sourceSymbolId: source, targetSymbolId: target, kind });
8187
+ }
8188
+ function findChild6(node, type) {
8189
+ for (const child of node.namedChildren) {
8190
+ if (child.type === type) return child;
8191
+ }
8192
+ return null;
8193
+ }
8194
+ function getNodeIdentifier3(node) {
8195
+ const nameNode = node.childForFieldName("name");
8196
+ if (nameNode) return nameNode.text;
8197
+ for (const child of node.namedChildren) {
8198
+ if (child.type === "identifier" || child.type === "type_identifier") return child.text;
8199
+ }
8200
+ return null;
8201
+ }
8202
+ function extractSimpleTypeName3(node) {
8203
+ for (const child of node.namedChildren) {
8204
+ if (child.type === "type_identifier" || child.type === "identifier") return child.text;
8205
+ if (child.type === "pointer_type") {
8206
+ const inner = child.namedChildren[0];
8207
+ return inner ? extractSimpleTypeNameFromNode(inner) : null;
8208
+ }
8209
+ if (child.type === "qualified_type") {
8210
+ const parts = child.text.split(".");
8211
+ return parts[parts.length - 1];
8212
+ }
8213
+ }
8214
+ return null;
8215
+ }
8216
+ function extractSimpleTypeNameFromNode(node) {
8217
+ if (node.type === "type_identifier" || node.type === "identifier") return node.text;
8218
+ if (node.type === "qualified_type") {
8219
+ const parts = node.text.split(".");
8220
+ return parts[parts.length - 1];
8221
+ }
8222
+ return null;
8223
+ }
8224
+ function extractCalledFunctionName2(node) {
8225
+ const funcNode = node.childForFieldName("function");
8226
+ if (!funcNode) return node.namedChildren[0]?.text ?? null;
8227
+ if (funcNode.type === "selector_expression") {
8228
+ const fieldNode = funcNode.childForFieldName("field");
8229
+ return fieldNode?.text ?? null;
8230
+ }
8231
+ if (funcNode.type === "identifier") {
8232
+ return funcNode.text;
8233
+ }
8234
+ return null;
8235
+ }
8236
+ function findEnclosingSymbol3(name, fileSymbols, ...kinds) {
8237
+ return fileSymbols.find(
8238
+ (s) => s.name === name && kinds.includes(s.kind)
8239
+ );
8240
+ }
8241
+ function findEnclosingFunctionForNode2(node, fileSymbols) {
8242
+ let current = node.parent;
8243
+ while (current) {
8244
+ if (current.type === "function_declaration" || current.type === "method_declaration") {
8245
+ const name = getNodeIdentifier3(current);
8246
+ if (name) {
8247
+ return fileSymbols.find(
8248
+ (s) => s.kind === "function" && s.name === name && s.startLine <= current.startPosition.row + 1 && s.endLine >= current.endPosition.row + 1
8249
+ );
8250
+ }
8251
+ }
8252
+ current = current.parent;
8253
+ }
8254
+ return void 0;
8255
+ }
8256
+ function findEnclosingSymbolForNode3(node, fileSymbols) {
8257
+ let current = node.parent;
8258
+ while (current) {
8259
+ if (current.type === "function_declaration" || current.type === "method_declaration") {
8260
+ const name = getNodeIdentifier3(current);
8261
+ if (name) {
8262
+ const found = fileSymbols.find(
8263
+ (s) => s.kind === "function" && s.name === name && s.startLine <= current.startPosition.row + 1 && s.endLine >= current.endPosition.row + 1
8264
+ );
8265
+ if (found) return found;
8266
+ }
8267
+ }
8268
+ if (current.type === "type_spec") {
8269
+ const name = getNodeIdentifier3(current);
8270
+ if (name) {
8271
+ return fileSymbols.find(
8272
+ (s) => s.name === name && (s.kind === "class" || s.kind === "interface")
8273
+ );
8274
+ }
8275
+ }
8276
+ current = current.parent;
8277
+ }
8278
+ return void 0;
8279
+ }
8280
+ function collectTypeIdentifiers3(node) {
8281
+ const names = [];
8282
+ collectTypeIdentifiersRecursive3(node, names);
8283
+ return [...new Set(names)];
8284
+ }
8285
+ function collectTypeIdentifiersRecursive3(node, names) {
8286
+ if (node.type === "type_identifier") {
8287
+ names.push(node.text);
8288
+ } else if (node.type === "qualified_type") {
8289
+ const parts = node.text.split(".");
8290
+ names.push(parts[parts.length - 1]);
8291
+ } else if (node.type === "pointer_type") {
8292
+ for (const child of node.namedChildren) {
8293
+ collectTypeIdentifiersRecursive3(child, names);
8294
+ }
8295
+ return;
8296
+ }
8297
+ for (const child of node.namedChildren) {
8298
+ collectTypeIdentifiersRecursive3(child, names);
8299
+ }
8300
+ }
8301
+
8302
+ // src/indexer/go/indexer.ts
8303
+ async function indexGoTreeSitter(db, options) {
8304
+ const start = Date.now();
8305
+ await ensureGoParser();
8306
+ const service = options.service ?? "main";
8307
+ const projectRoot = options.projectRoot;
8308
+ const ignorePatterns = [
8309
+ "**/vendor/**",
8310
+ "**/node_modules/**",
8311
+ "**/.git/**",
8312
+ "**/testdata/**",
8313
+ "**/*_test.go",
8314
+ "**/*_gen.go",
8315
+ "**/*_generated.go",
8316
+ "**/*.pb.go"
8317
+ ];
8318
+ const goFiles = globbySync3("**/*.go", {
8319
+ cwd: projectRoot,
8320
+ ignore: ignorePatterns,
8321
+ absolute: false
8322
+ });
8323
+ const existingHashes = getExistingHashes(db, service, "go");
8324
+ const currentPaths = /* @__PURE__ */ new Set();
8325
+ const fileCache = /* @__PURE__ */ new Map();
8326
+ const changedFiles = [];
8327
+ let filesSkipped = 0;
8328
+ for (const filePath of goFiles) {
8329
+ const relPath = filePath.replace(/\\/g, "/");
8330
+ currentPaths.add(relPath);
8331
+ const fullPath = join13(projectRoot, relPath);
8332
+ const content = readFileSync6(fullPath, "utf-8");
8333
+ const hash = hashContent(content);
8334
+ const tree = parseGo(content);
8335
+ fileCache.set(relPath, { content, tree, hash });
8336
+ const existingHash = existingHashes.get(relPath);
8337
+ if (existingHash === hash) {
8338
+ filesSkipped++;
8339
+ } else {
8340
+ changedFiles.push(relPath);
8341
+ }
8342
+ }
8343
+ const removed = [];
8344
+ for (const existingPath of existingHashes.keys()) {
8345
+ if (!currentPaths.has(existingPath)) {
8346
+ removed.push(existingPath);
8347
+ }
8348
+ }
8349
+ const filesToClean = [...removed, ...changedFiles];
8350
+ removeScopedSymbolsForFiles(db, filesToClean, service, "go");
8351
+ const allNewSymbols = [];
8352
+ for (const relPath of changedFiles) {
8353
+ const cached = fileCache.get(relPath);
8354
+ if (!cached) continue;
8355
+ const symbols = extractGoSymbols(cached.tree, relPath, cached.hash);
8356
+ allNewSymbols.push(...symbols);
8357
+ }
8358
+ writeSymbols(db, allNewSymbols, service, "go");
8359
+ const allDbSymbols = db.prepare("SELECT id, file_path as filePath, name, kind, start_line as startLine, end_line as endLine FROM symbols WHERE service = ? AND language = 'go'").all(service);
8360
+ const symbolLookup = buildGoSymbolLookup(allDbSymbols);
8361
+ const allDeps = [];
8362
+ for (const [relPath, cached] of fileCache) {
8363
+ const fileDeps = extractGoDependencies(cached.tree, relPath, allDbSymbols, symbolLookup);
8364
+ allDeps.push(...fileDeps);
8365
+ }
8366
+ db.prepare(
8367
+ "DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ? AND language = 'go')"
8368
+ ).run(service);
8369
+ writeDependencies(db, allDeps);
8370
+ return {
8371
+ symbolsIndexed: allNewSymbols.length,
8372
+ dependenciesIndexed: allDeps.length,
8373
+ componentsAnalyzed: 0,
8374
+ routesAnalyzed: 0,
8375
+ filesProcessed: changedFiles.length,
8376
+ filesSkipped,
8377
+ filesRemoved: removed.length,
8378
+ durationMs: Date.now() - start
8379
+ };
8380
+ }
8381
+
8382
+ // src/indexer/package-deps.ts
8383
+ import { join as join14 } from "path";
8384
+ import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync5 } from "fs";
8385
+ function indexPackageDependencies(db, projectRoot, service = "main") {
8386
+ const deps = [];
8387
+ const pkgJsonPath = join14(projectRoot, "package.json");
8388
+ if (existsSync6(pkgJsonPath)) {
8389
+ deps.push(...parsePackageJson(pkgJsonPath));
8390
+ }
8391
+ const csprojFiles = findCsprojFiles(projectRoot);
8392
+ for (const csproj of csprojFiles) {
8393
+ deps.push(...parseCsproj(csproj));
8394
+ }
8395
+ if (deps.length === 0) return 0;
8396
+ db.prepare("DELETE FROM package_dependencies WHERE service = ?").run(service);
8397
+ const insert = db.prepare(
8398
+ "INSERT OR IGNORE INTO package_dependencies (name, version, source, service) VALUES (?, ?, ?, ?)"
8399
+ );
8400
+ transaction(db, () => {
8401
+ for (const dep of deps) {
8402
+ insert.run(dep.name, dep.version, dep.source, service);
8403
+ }
8404
+ });
8405
+ return deps.length;
8406
+ }
8407
+ function parsePackageJson(filePath) {
8408
+ try {
8409
+ const content = readFileSync7(filePath, "utf-8");
8410
+ const pkg = JSON.parse(content);
8411
+ const deps = [];
8412
+ if (pkg.dependencies) {
8413
+ for (const [name, version] of Object.entries(pkg.dependencies)) {
8414
+ deps.push({ name, version, source: "npm" });
8415
+ }
8416
+ }
8417
+ if (pkg.devDependencies) {
8418
+ for (const [name, version] of Object.entries(pkg.devDependencies)) {
8419
+ deps.push({ name, version, source: "npm-dev" });
8420
+ }
8421
+ }
8422
+ return deps;
8423
+ } catch {
8424
+ return [];
8425
+ }
8426
+ }
8427
+ function parseCsproj(filePath) {
8428
+ try {
8429
+ const content = readFileSync7(filePath, "utf-8");
8430
+ const deps = [];
8431
+ const pattern = /<PackageReference\s+Include="([^"]+)"(?:\s+Version="([^"]*)")?[^>]*\/?>/gi;
8432
+ let match;
8433
+ while ((match = pattern.exec(content)) !== null) {
8434
+ deps.push({
8435
+ name: match[1],
8436
+ version: match[2] ?? null,
8437
+ source: "nuget"
8438
+ });
8439
+ }
8440
+ return deps;
8441
+ } catch {
8442
+ return [];
8443
+ }
8444
+ }
8445
+ function findCsprojFiles(dir, maxDepth = 4) {
8446
+ const results = [];
8447
+ function walk(currentDir4, depth) {
8448
+ if (depth > maxDepth) return;
8449
+ try {
8450
+ const entries = readdirSync5(currentDir4, { withFileTypes: true });
8451
+ for (const entry of entries) {
8452
+ if (entry.name === "bin" || entry.name === "obj" || entry.name === "node_modules" || entry.name === ".git") continue;
8453
+ const fullPath = join14(currentDir4, entry.name);
8454
+ if (entry.isFile() && entry.name.endsWith(".csproj")) {
8455
+ results.push(fullPath);
8456
+ } else if (entry.isDirectory()) {
8457
+ walk(fullPath, depth + 1);
8458
+ }
8459
+ }
8460
+ } catch {
8461
+ }
8462
+ }
8463
+ walk(dir, 0);
8464
+ return results;
8465
+ }
8466
+
8467
+ // src/config/loader.ts
8468
+ import { readFileSync as readFileSync8 } from "fs";
8469
+ import { join as join15 } from "path";
8470
+ import yaml from "yaml";
8471
+ function loadConfig(projectRoot) {
8472
+ const configPath = join15(projectRoot, ".arcbridge", "config.yaml");
8473
+ try {
8474
+ const raw = readFileSync8(configPath, "utf-8");
8475
+ const parsed = ArcBridgeConfigSchema.safeParse(yaml.parse(raw));
8476
+ if (parsed.success) {
8477
+ return { config: parsed.data, error: null };
8478
+ }
8479
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
8480
+ return { config: null, error: `Config validation failed: ${issues}` };
8481
+ } catch (err) {
8482
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
8483
+ return { config: null, error: null };
8484
+ }
8485
+ return { config: null, error: `Config load error: ${err instanceof Error ? err.message : String(err)}` };
8486
+ }
8487
+ }
8488
+
8489
+ // src/indexer/index.ts
8490
+ function detectProjectLanguage(projectRoot) {
8491
+ if (existsSync7(join16(projectRoot, "ProjectSettings")) && existsSync7(join16(projectRoot, "Assets"))) {
8492
+ return "csharp";
8493
+ }
8494
+ if (existsSync7(join16(projectRoot, "tsconfig.json"))) return "typescript";
8495
+ if (findDotnetProject(projectRoot)) return "csharp";
8496
+ if (existsSync7(join16(projectRoot, "go.mod"))) return "go";
8497
+ if (existsSync7(join16(projectRoot, "pyproject.toml")) || existsSync7(join16(projectRoot, "requirements.txt")) || existsSync7(join16(projectRoot, "setup.py"))) {
8498
+ return "python";
8499
+ }
8500
+ if (existsSync7(join16(projectRoot, "package.json"))) return "typescript";
8501
+ return "typescript";
8502
+ }
8503
+ async function indexProject(db, options) {
8504
+ const language = options.language ?? "auto";
8505
+ const resolvedLanguage = language === "auto" ? detectProjectLanguage(options.projectRoot) : language;
8506
+ indexPackageDependencies(db, options.projectRoot, options.service ?? "main");
8507
+ if (resolvedLanguage === "csharp") {
8508
+ const backend = resolveCSharpBackend(options.projectRoot);
8509
+ if (backend === "roslyn") {
8510
+ return indexDotnetProjectRoslyn(db, {
8511
+ projectRoot: options.projectRoot,
8512
+ service: options.service
8513
+ });
8514
+ }
8515
+ return await indexCSharpTreeSitter(db, {
8516
+ projectRoot: options.projectRoot,
8517
+ service: options.service
8518
+ });
8519
+ }
8520
+ if (resolvedLanguage === "python") {
8521
+ return await indexPythonTreeSitter(db, {
8522
+ projectRoot: options.projectRoot,
8523
+ service: options.service
8524
+ });
8525
+ }
8526
+ if (resolvedLanguage === "go") {
8527
+ return await indexGoTreeSitter(db, {
8528
+ projectRoot: options.projectRoot,
8529
+ service: options.service
8530
+ });
8531
+ }
8532
+ const resolvedTsconfigPath = options.tsconfigPath ?? ts6.findConfigFile(options.projectRoot, ts6.sys.fileExists, "tsconfig.json");
8533
+ if (!resolvedTsconfigPath) {
8534
+ return {
8535
+ symbolsIndexed: 0,
8536
+ dependenciesIndexed: 0,
8537
+ componentsAnalyzed: 0,
8538
+ routesAnalyzed: 0,
8539
+ filesProcessed: 0,
8540
+ filesSkipped: 0,
8541
+ filesRemoved: 0,
8542
+ durationMs: 0,
8543
+ skippedReason: "no tsconfig.json found"
8544
+ };
8545
+ }
8546
+ return indexTypeScriptProject(db, {
8547
+ ...options,
8548
+ tsconfigPath: resolvedTsconfigPath
8549
+ });
8550
+ }
8551
+ function resolveCSharpBackend(projectRoot) {
8552
+ const { config, error } = loadConfig(projectRoot);
8553
+ let setting = config?.indexing?.csharp_indexer;
8554
+ if (!setting && error) {
8555
+ try {
8556
+ const raw = readFileSync9(join16(projectRoot, ".arcbridge", "config.yaml"), "utf-8");
8557
+ const parsed = YAML.parse(raw);
8558
+ const rawSetting = parsed?.indexing?.csharp_indexer;
8559
+ if (rawSetting === "roslyn" || rawSetting === "tree-sitter") {
8560
+ setting = rawSetting;
8561
+ }
8562
+ } catch {
8563
+ }
8564
+ }
8565
+ if (setting === "roslyn" || setting === "tree-sitter") {
8566
+ return setting;
8567
+ }
8568
+ if (hasGlobalTool()) {
8569
+ return "roslyn";
8570
+ }
8571
+ if (hasIndexerProject()) {
8572
+ try {
8573
+ execFileSync2("dotnet", ["--version"], {
8574
+ encoding: "utf-8",
8575
+ timeout: 5e3
8576
+ });
8577
+ return "roslyn";
8578
+ } catch {
8579
+ }
8580
+ }
8581
+ return "tree-sitter";
8582
+ }
8583
+ function indexTypeScriptProject(db, options) {
8584
+ const start = Date.now();
8585
+ const service = options.service ?? "main";
8586
+ const { checker, sourceFiles, projectRoot } = createTsProgram(options);
8587
+ const existingHashes = getExistingHashes(db, service, "typescript");
8588
+ const changed = [];
8589
+ const currentPaths = /* @__PURE__ */ new Set();
8590
+ let filesSkipped = 0;
8591
+ for (const sf of sourceFiles) {
8592
+ const relPath = relative5(projectRoot, sf.fileName);
8593
+ currentPaths.add(relPath);
8594
+ const hash = hashContent(sf.getFullText());
8595
+ const existingHash = existingHashes.get(relPath);
8596
+ if (existingHash === hash) {
8597
+ filesSkipped++;
8598
+ } else {
8599
+ changed.push({ sourceFile: sf, relativePath: relPath, hash });
8600
+ }
8601
+ }
8602
+ const removed = [];
8603
+ for (const existingPath of existingHashes.keys()) {
8604
+ if (!currentPaths.has(existingPath)) {
8605
+ removed.push(existingPath);
7461
8606
  }
7462
8607
  }
7463
8608
  const filesToClean = [
7464
8609
  ...removed,
7465
8610
  ...changed.map((f) => f.relativePath)
7466
8611
  ];
7467
- removeSymbolsForFiles(db, filesToClean);
8612
+ removeScopedSymbolsForFiles(db, filesToClean, service, "typescript");
7468
8613
  const allSymbols = changed.flatMap(
7469
8614
  (f) => extractSymbols(f.sourceFile, checker, f.relativePath, f.hash)
7470
8615
  );
7471
8616
  writeSymbols(db, allSymbols, service, "typescript");
7472
- const allDbSymbols = db.prepare("SELECT id, file_path as filePath, name FROM symbols WHERE service = ?").all(service);
8617
+ const allDbSymbols = db.prepare("SELECT id, file_path as filePath, name FROM symbols WHERE service = ? AND language = 'typescript'").all(service);
7473
8618
  const lookup = buildSymbolLookup(allDbSymbols);
7474
8619
  const allDeps = sourceFiles.flatMap((sf) => {
7475
8620
  const relPath = relative5(projectRoot, sf.fileName);
7476
8621
  return extractDependencies(sf, checker, relPath, projectRoot, lookup);
7477
8622
  });
7478
- db.prepare("DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ?)").run(service);
8623
+ db.prepare("DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ? AND language = 'typescript')").run(service);
7479
8624
  writeDependencies(db, allDeps);
7480
8625
  const CLIENT_ONLY_TEMPLATES = /* @__PURE__ */ new Set(["react-vite", "angular-app"]);
7481
8626
  const projectType = db.prepare("SELECT value FROM arcbridge_meta WHERE key = 'project_type'").get()?.value;
@@ -7806,21 +8951,21 @@ function safeParseJson(value, fallback) {
7806
8951
  }
7807
8952
 
7808
8953
  // src/sync/yaml-writer.ts
7809
- import { join as join15 } from "path";
7810
- import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync } from "fs";
8954
+ import { join as join17 } from "path";
8955
+ import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync } from "fs";
7811
8956
  import { parse as parse2, stringify as stringify4 } from "yaml";
7812
8957
  function readTaskFile(projectRoot, phaseId) {
7813
- const path = join15(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
8958
+ const path = join17(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
7814
8959
  if (!existsSync8(path)) return { error: "not-found" };
7815
- const raw = readFileSync8(path, "utf-8");
8960
+ const raw = readFileSync10(path, "utf-8");
7816
8961
  const result = TaskFileSchema.safeParse(parse2(raw));
7817
8962
  if (!result.success) return { error: "invalid" };
7818
8963
  return { data: result.data, path };
7819
8964
  }
7820
8965
  function readPhasesFile(projectRoot) {
7821
- const path = join15(projectRoot, ".arcbridge", "plan", "phases.yaml");
8966
+ const path = join17(projectRoot, ".arcbridge", "plan", "phases.yaml");
7822
8967
  if (!existsSync8(path)) return { error: "not-found" };
7823
- const raw = readFileSync8(path, "utf-8");
8968
+ const raw = readFileSync10(path, "utf-8");
7824
8969
  const result = PhasesFileSchema.safeParse(parse2(raw));
7825
8970
  if (!result.success) return { error: "invalid" };
7826
8971
  return { data: result.data, path };
@@ -7840,14 +8985,14 @@ function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
7840
8985
  writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
7841
8986
  }
7842
8987
  function addTaskToYaml(projectRoot, phaseId, task) {
7843
- const tasksDir = join15(projectRoot, ".arcbridge", "plan", "tasks");
8988
+ const tasksDir = join17(projectRoot, ".arcbridge", "plan", "tasks");
7844
8989
  mkdirSync5(tasksDir, { recursive: true });
7845
8990
  const readResult = readTaskFile(projectRoot, phaseId);
7846
8991
  const taskFile = "error" in readResult ? { schema_version: 1, phase_id: phaseId, tasks: [] } : readResult.data;
7847
8992
  if (!taskFile.tasks.some((t) => t.id === task.id)) {
7848
8993
  taskFile.tasks.push(task);
7849
8994
  }
7850
- const taskPath = join15(tasksDir, `${phaseId}.yaml`);
8995
+ const taskPath = join17(tasksDir, `${phaseId}.yaml`);
7851
8996
  writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
7852
8997
  }
7853
8998
  function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
@@ -7880,9 +9025,9 @@ function addPhaseToYaml(projectRoot, phase) {
7880
9025
  warning: `Phase number ${phase.phase_number} already used by '${conflicting?.id}'`
7881
9026
  };
7882
9027
  }
7883
- const tasksDir = join15(projectRoot, ".arcbridge", "plan", "tasks");
9028
+ const tasksDir = join17(projectRoot, ".arcbridge", "plan", "tasks");
7884
9029
  mkdirSync5(tasksDir, { recursive: true });
7885
- const taskFilePath = join15(tasksDir, `${phase.id}.yaml`);
9030
+ const taskFilePath = join17(tasksDir, `${phase.id}.yaml`);
7886
9031
  if (!existsSync8(taskFilePath)) {
7887
9032
  writeFileSync5(
7888
9033
  taskFilePath,
@@ -7912,14 +9057,14 @@ function addPhaseToYaml(projectRoot, phase) {
7912
9057
  }
7913
9058
  }
7914
9059
  function syncScenarioToYaml(projectRoot, scenarioId, status, linkedTests, verification) {
7915
- const scenarioPath = join15(
9060
+ const scenarioPath = join17(
7916
9061
  projectRoot,
7917
9062
  ".arcbridge",
7918
9063
  "arc42",
7919
9064
  "10-quality-scenarios.yaml"
7920
9065
  );
7921
9066
  if (!existsSync8(scenarioPath)) return;
7922
- const raw = readFileSync8(scenarioPath, "utf-8");
9067
+ const raw = readFileSync10(scenarioPath, "utf-8");
7923
9068
  const parsed = parse2(raw);
7924
9069
  const result = QualityScenariosFileSchema.safeParse(parsed);
7925
9070
  if (!result.success) return;
@@ -7978,7 +9123,7 @@ function deletePhaseFromYaml(projectRoot, phaseId) {
7978
9123
  return { success: false, warning: `Phase '${phaseId}' not found in phases.yaml` };
7979
9124
  }
7980
9125
  writeFileSync5(phasesPath, stringify4(phasesFile), "utf-8");
7981
- const taskFilePath = join15(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
9126
+ const taskFilePath = join17(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
7982
9127
  try {
7983
9128
  unlinkSync(taskFilePath);
7984
9129
  } catch (e) {
@@ -8122,7 +9267,7 @@ function safeParseJson2(value, fallback) {
8122
9267
 
8123
9268
  // src/generators/sync-generator.ts
8124
9269
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
8125
- import { join as join16, dirname as dirname4 } from "path";
9270
+ import { join as join18, dirname as dirname6 } from "path";
8126
9271
 
8127
9272
  // src/templates/sync/claude-skill.ts
8128
9273
  function claudeSkillTemplate(config) {
@@ -8300,21 +9445,21 @@ To enable automatic sync:
8300
9445
  function generateSyncFiles(targetDir, config) {
8301
9446
  const generated = [];
8302
9447
  const action = githubActionTemplate(config);
8303
- const actionPath = join16(targetDir, action.relativePath);
8304
- mkdirSync6(dirname4(actionPath), { recursive: true });
9448
+ const actionPath = join18(targetDir, action.relativePath);
9449
+ mkdirSync6(dirname6(actionPath), { recursive: true });
8305
9450
  writeFileSync6(actionPath, action.content, "utf-8");
8306
9451
  generated.push(action.relativePath);
8307
9452
  if (config.platforms.includes("claude")) {
8308
9453
  const skill = claudeSkillTemplate(config);
8309
- const skillPath = join16(targetDir, skill.relativePath);
8310
- mkdirSync6(dirname4(skillPath), { recursive: true });
9454
+ const skillPath = join18(targetDir, skill.relativePath);
9455
+ mkdirSync6(dirname6(skillPath), { recursive: true });
8311
9456
  writeFileSync6(skillPath, skill.content, "utf-8");
8312
9457
  generated.push(skill.relativePath);
8313
9458
  }
8314
9459
  if (config.platforms.includes("copilot")) {
8315
9460
  const hook = copilotHookTemplate(config);
8316
- const hookPath = join16(targetDir, hook.relativePath);
8317
- mkdirSync6(dirname4(hookPath), { recursive: true });
9461
+ const hookPath = join18(targetDir, hook.relativePath);
9462
+ mkdirSync6(dirname6(hookPath), { recursive: true });
8318
9463
  writeFileSync6(hookPath, hook.content, "utf-8");
8319
9464
  generated.push(hook.relativePath);
8320
9465
  }
@@ -8323,7 +9468,7 @@ function generateSyncFiles(targetDir, config) {
8323
9468
 
8324
9469
  // src/metrics/activity.ts
8325
9470
  import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
8326
- import { join as join17 } from "path";
9471
+ import { join as join19 } from "path";
8327
9472
  function insertActivity(db, params) {
8328
9473
  const totalTokens = params.totalTokens ?? (params.inputTokens != null && params.outputTokens != null ? params.inputTokens + params.outputTokens : null);
8329
9474
  const stmt = db.prepare(`
@@ -8481,7 +9626,7 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
8481
9626
  });
8482
9627
  const rows = result.rows;
8483
9628
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
8484
- const dir = join17(projectRoot, ".arcbridge", "metrics");
9629
+ const dir = join19(projectRoot, ".arcbridge", "metrics");
8485
9630
  mkdirSync7(dir, { recursive: true });
8486
9631
  let content;
8487
9632
  let filename;
@@ -8578,7 +9723,7 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
8578
9723
  break;
8579
9724
  }
8580
9725
  }
8581
- const filePath = join17(dir, filename);
9726
+ const filePath = join19(dir, filename);
8582
9727
  writeFileSync7(filePath, content, "utf-8");
8583
9728
  return filePath;
8584
9729
  }
@@ -8782,7 +9927,7 @@ function parseStatusCode(code) {
8782
9927
  // src/testing/runner.ts
8783
9928
  import { execFileSync as execFileSync4 } from "child_process";
8784
9929
  import { existsSync as existsSync9 } from "fs";
8785
- import { resolve as resolve3 } from "path";
9930
+ import { resolve as resolve5 } from "path";
8786
9931
  function verifyScenarios(db, projectRoot, options) {
8787
9932
  const results = [];
8788
9933
  const errors = [];
@@ -8819,7 +9964,7 @@ function verifyScenarios(db, projectRoot, options) {
8819
9964
  }
8820
9965
  if (testPaths.length === 0) continue;
8821
9966
  const missingPaths = testPaths.filter(
8822
- (tp) => !existsSync9(resolve3(projectRoot, tp))
9967
+ (tp) => !existsSync9(resolve5(projectRoot, tp))
8823
9968
  );
8824
9969
  if (missingPaths.length === testPaths.length) {
8825
9970
  results.push({
@@ -8834,7 +9979,7 @@ function verifyScenarios(db, projectRoot, options) {
8834
9979
  continue;
8835
9980
  }
8836
9981
  const existingPaths = testPaths.filter(
8837
- (tp) => existsSync9(resolve3(projectRoot, tp))
9982
+ (tp) => existsSync9(resolve5(projectRoot, tp))
8838
9983
  );
8839
9984
  const start = Date.now();
8840
9985
  let passed;
@@ -8888,11 +10033,11 @@ ${output}`;
8888
10033
  }
8889
10034
 
8890
10035
  // src/roles/loader.ts
8891
- import { readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
8892
- import { join as join18 } from "path";
10036
+ import { readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
10037
+ import { join as join20 } from "path";
8893
10038
  import matter4 from "gray-matter";
8894
10039
  function loadRoles(projectRoot) {
8895
- const agentsDir = join18(projectRoot, ".arcbridge", "agents");
10040
+ const agentsDir = join20(projectRoot, ".arcbridge", "agents");
8896
10041
  const roles = [];
8897
10042
  const errors = [];
8898
10043
  let files;
@@ -8902,9 +10047,9 @@ function loadRoles(projectRoot) {
8902
10047
  return { roles: [], errors: [`Agent directory not found: ${agentsDir}`] };
8903
10048
  }
8904
10049
  for (const file of files) {
8905
- const filePath = join18(agentsDir, file);
10050
+ const filePath = join20(agentsDir, file);
8906
10051
  try {
8907
- const raw = readFileSync9(filePath, "utf-8");
10052
+ const raw = readFileSync11(filePath, "utf-8");
8908
10053
  const parsed = matter4(raw);
8909
10054
  const input = {
8910
10055
  ...parsed.data,
@@ -8929,9 +10074,9 @@ function loadRole(projectRoot, roleId) {
8929
10074
  if (!/^[a-z0-9-]+$/.test(roleId)) {
8930
10075
  return { role: null, error: `Invalid role ID: "${roleId}" (must be kebab-case)` };
8931
10076
  }
8932
- const filePath = join18(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
10077
+ const filePath = join20(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
8933
10078
  try {
8934
- const raw = readFileSync9(filePath, "utf-8");
10079
+ const raw = readFileSync11(filePath, "utf-8");
8935
10080
  const parsed = matter4(raw);
8936
10081
  const input = {
8937
10082
  ...parsed.data,