@arcbridge/core 0.4.1 → 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
@@ -57,7 +57,7 @@ var ArcBridgeConfigSchema = z2.object({
57
57
  "angular-app"
58
58
  ]).default("nextjs-app-router"),
59
59
  services: z2.array(ServiceSchema).default([]),
60
- platforms: z2.array(z2.enum(["claude", "copilot", "gemini", "codex"])).default(["claude"]),
60
+ platforms: z2.array(z2.enum(["claude", "copilot", "gemini", "codex", "opencode"])).default(["claude"]),
61
61
  quality_priorities: z2.array(QualityCategorySchema).default(["security", "performance", "accessibility"]).describe(QUALITY_PRIORITIES_DESCRIPTION),
62
62
  indexing: z2.object({
63
63
  include: z2.array(z2.string()).default(["src/**/*", "app/**/*"]),
@@ -195,7 +195,8 @@ var AgentRoleSchema = z6.object({
195
195
  platform_overrides: z6.object({
196
196
  claude: z6.record(z6.string(), z6.unknown()).optional(),
197
197
  copilot: z6.record(z6.string(), z6.unknown()).optional(),
198
- codex: z6.record(z6.string(), z6.unknown()).optional()
198
+ codex: z6.record(z6.string(), z6.unknown()).optional(),
199
+ opencode: z6.record(z6.string(), z6.unknown()).optional()
199
200
  }).default({}),
200
201
  // The markdown body (system prompt) is stored separately
201
202
  system_prompt: z6.string().min(1)
@@ -4963,8 +4964,9 @@ function ensureGitignore(targetDir) {
4963
4964
  }
4964
4965
 
4965
4966
  // src/indexer/index.ts
4966
- import { relative as relative5, join as join14 } from "path";
4967
- 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";
4968
4970
  import { execFileSync as execFileSync2 } from "child_process";
4969
4971
  import YAML from "yaml";
4970
4972
 
@@ -5332,7 +5334,7 @@ function buildSymbolLookup(symbols) {
5332
5334
  function extractDependencies(sourceFile, checker, relativePath, projectRoot, lookup) {
5333
5335
  const deps = [];
5334
5336
  const seen = /* @__PURE__ */ new Set();
5335
- function addDep2(sourceId, targetId, kind) {
5337
+ function addDep4(sourceId, targetId, kind) {
5336
5338
  const key = `${sourceId}|${targetId}|${kind}`;
5337
5339
  if (seen.has(key)) return;
5338
5340
  if (!lookup.allIds.has(sourceId) || !lookup.allIds.has(targetId)) return;
@@ -5368,7 +5370,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5368
5370
  if (!targetId) continue;
5369
5371
  const fileSymbols = getFileTopLevelSymbols(relativePath, lookup);
5370
5372
  for (const sourceId of fileSymbols) {
5371
- addDep2(sourceId, targetId, "imports");
5373
+ addDep4(sourceId, targetId, "imports");
5372
5374
  }
5373
5375
  }
5374
5376
  }
@@ -5380,7 +5382,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5380
5382
  if (targetId) {
5381
5383
  const fileSymbols = getFileTopLevelSymbols(relativePath, lookup);
5382
5384
  for (const sourceId of fileSymbols) {
5383
- addDep2(sourceId, targetId, "imports");
5385
+ addDep4(sourceId, targetId, "imports");
5384
5386
  }
5385
5387
  }
5386
5388
  }
@@ -5402,7 +5404,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5402
5404
  const resolved = exprSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(exprSymbol) : exprSymbol;
5403
5405
  const targetId = resolveSymbolId(resolved);
5404
5406
  if (targetId) {
5405
- addDep2(classId, targetId, kind);
5407
+ addDep4(classId, targetId, kind);
5406
5408
  }
5407
5409
  }
5408
5410
  }
@@ -5425,7 +5427,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5425
5427
  const resolved = calledSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(calledSymbol) : calledSymbol;
5426
5428
  const targetId = resolveSymbolId(resolved);
5427
5429
  if (targetId) {
5428
- addDep2(ownerId, targetId, "calls");
5430
+ addDep4(ownerId, targetId, "calls");
5429
5431
  }
5430
5432
  }
5431
5433
  }
@@ -5455,7 +5457,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5455
5457
  const resolved = calledSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(calledSymbol) : calledSymbol;
5456
5458
  const targetId = resolveSymbolId(resolved);
5457
5459
  if (targetId) {
5458
- addDep2(callOwnerId, targetId, "calls");
5460
+ addDep4(callOwnerId, targetId, "calls");
5459
5461
  }
5460
5462
  }
5461
5463
  }
@@ -5487,7 +5489,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5487
5489
  const resolved = typeSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(typeSymbol) : typeSymbol;
5488
5490
  const targetId = resolveSymbolId(resolved);
5489
5491
  if (targetId) {
5490
- addDep2(ownerId, targetId, "uses_type");
5492
+ addDep4(ownerId, targetId, "uses_type");
5491
5493
  }
5492
5494
  }
5493
5495
  }
@@ -5511,7 +5513,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5511
5513
  const resolved = jsxSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(jsxSymbol) : jsxSymbol;
5512
5514
  const targetId = resolveSymbolId(resolved);
5513
5515
  if (targetId) {
5514
- addDep2(ownerId, targetId, "renders");
5516
+ addDep4(ownerId, targetId, "renders");
5515
5517
  }
5516
5518
  }
5517
5519
  if (ts4.isPropertyAccessExpression(tagName) && ts4.isIdentifier(tagName.name) && tagName.name.text === "Provider") {
@@ -5520,7 +5522,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5520
5522
  const resolved = ctxSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(ctxSymbol) : ctxSymbol;
5521
5523
  const targetId = resolveSymbolId(resolved);
5522
5524
  if (targetId) {
5523
- addDep2(ownerId, targetId, "provides_context");
5525
+ addDep4(ownerId, targetId, "provides_context");
5524
5526
  }
5525
5527
  }
5526
5528
  }
@@ -5567,7 +5569,7 @@ function extractDependencies(sourceFile, checker, relativePath, projectRoot, loo
5567
5569
  const resolved = ctxSymbol.flags & ts4.SymbolFlags.Alias ? checker.getAliasedSymbol(ctxSymbol) : ctxSymbol;
5568
5570
  const targetId = resolveSymbolId(resolved);
5569
5571
  if (targetId) {
5570
- addDep2(ownerId, targetId, "consumes_context");
5572
+ addDep4(ownerId, targetId, "consumes_context");
5571
5573
  }
5572
5574
  }
5573
5575
  }
@@ -6014,36 +6016,37 @@ function hashContent(content) {
6014
6016
  }
6015
6017
 
6016
6018
  // src/indexer/db-writer.ts
6017
- function getExistingHashes(db, service) {
6018
- const rows = db.prepare(
6019
- "SELECT DISTINCT file_path, content_hash FROM symbols WHERE service = ?"
6020
- ).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);
6021
6023
  const map = /* @__PURE__ */ new Map();
6022
6024
  for (const row of rows) {
6023
6025
  map.set(row.file_path, row.content_hash);
6024
6026
  }
6025
6027
  return map;
6026
6028
  }
6027
- function removeSymbolsForFiles(db, filePaths) {
6029
+ function removeScopedSymbolsForFiles(db, filePaths, service, language) {
6028
6030
  if (filePaths.length === 0) return;
6029
- const deleteSymbols = db.prepare(
6030
- "DELETE FROM symbols WHERE file_path = ?"
6031
- );
6031
+ const scope = "file_path = ? AND service = ? AND language = ?";
6032
6032
  const deleteDepsSource = db.prepare(
6033
- "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})`
6034
6034
  );
6035
6035
  const deleteDepsTarget = db.prepare(
6036
- "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})`
6037
6037
  );
6038
6038
  const deleteComponents = db.prepare(
6039
- "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}`
6040
6043
  );
6041
6044
  transaction(db, () => {
6042
6045
  for (const fp of filePaths) {
6043
- deleteDepsSource.run(fp);
6044
- deleteDepsTarget.run(fp);
6045
- deleteComponents.run(fp);
6046
- 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);
6047
6050
  }
6048
6051
  });
6049
6052
  }
@@ -6176,11 +6179,11 @@ function hasGlobalTool() {
6176
6179
  return false;
6177
6180
  }
6178
6181
  function resolveIndexerProject() {
6179
- const currentDir2 = dirname2(fileURLToPath(import.meta.url));
6182
+ const currentDir4 = dirname2(fileURLToPath(import.meta.url));
6180
6183
  const candidates = [
6181
- resolve(currentDir2, "../../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
6182
- resolve(currentDir2, "../../../dotnet-indexer/ArcBridge.DotnetIndexer.csproj"),
6183
- 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")
6184
6187
  ];
6185
6188
  for (const candidate of candidates) {
6186
6189
  if (existsSync5(candidate)) return candidate;
@@ -6246,7 +6249,7 @@ function indexDotnetProjectRoslyn(db, options) {
6246
6249
  "No .sln or .csproj file found in project root. The .NET indexer requires a project or solution file."
6247
6250
  );
6248
6251
  }
6249
- const existingHashes = getExistingHashes(db, service);
6252
+ const existingHashes = getExistingHashes(db, service, "csharp");
6250
6253
  const hashesJson = JSON.stringify(Object.fromEntries(existingHashes));
6251
6254
  const stdout = runDotnetIndexer(dotnetProject, hashesJson, projectRoot);
6252
6255
  const lines = stdout.trim().split("\n");
@@ -6263,7 +6266,7 @@ function indexDotnetProjectRoslyn(db, options) {
6263
6266
  );
6264
6267
  }
6265
6268
  const filesToClean = [...output.changedFiles, ...output.removedFiles];
6266
- removeSymbolsForFiles(db, filesToClean);
6269
+ removeScopedSymbolsForFiles(db, filesToClean, service, "csharp");
6267
6270
  const symbols = output.symbols.map((s) => ({
6268
6271
  id: s.id,
6269
6272
  name: s.name,
@@ -7183,7 +7186,7 @@ async function indexCSharpTreeSitter(db, options) {
7183
7186
  ignore: ignorePatterns,
7184
7187
  absolute: false
7185
7188
  });
7186
- const existingHashes = getExistingHashes(db, service);
7189
+ const existingHashes = getExistingHashes(db, service, "csharp");
7187
7190
  const currentPaths = /* @__PURE__ */ new Set();
7188
7191
  const fileCache = /* @__PURE__ */ new Map();
7189
7192
  const changedFiles = [];
@@ -7210,7 +7213,7 @@ async function indexCSharpTreeSitter(db, options) {
7210
7213
  }
7211
7214
  }
7212
7215
  const filesToClean = [...removed, ...changedFiles];
7213
- removeSymbolsForFiles(db, filesToClean);
7216
+ removeScopedSymbolsForFiles(db, filesToClean, service, "csharp");
7214
7217
  const allNewSymbols = [];
7215
7218
  for (const relPath of changedFiles) {
7216
7219
  const cached = fileCache.get(relPath);
@@ -7219,7 +7222,7 @@ async function indexCSharpTreeSitter(db, options) {
7219
7222
  allNewSymbols.push(...symbols);
7220
7223
  }
7221
7224
  writeSymbols(db, allNewSymbols, service, "csharp");
7222
- 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);
7223
7226
  const symbolLookup = buildCSharpSymbolLookup(allDbSymbols);
7224
7227
  const allDeps = [];
7225
7228
  for (const [relPath, cached] of fileCache) {
@@ -7266,215 +7269,1358 @@ async function indexCSharpTreeSitter(db, options) {
7266
7269
  };
7267
7270
  }
7268
7271
 
7269
- // src/indexer/package-deps.ts
7272
+ // src/indexer/python/indexer.ts
7273
+ import { readFileSync as readFileSync5 } from "fs";
7270
7274
  import { join as join12 } from "path";
7271
- import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync5 } from "fs";
7272
- function indexPackageDependencies(db, projectRoot, service = "main") {
7273
- const deps = [];
7274
- const pkgJsonPath = join12(projectRoot, "package.json");
7275
- if (existsSync6(pkgJsonPath)) {
7276
- deps.push(...parsePackageJson(pkgJsonPath));
7277
- }
7278
- const csprojFiles = findCsprojFiles(projectRoot);
7279
- for (const csproj of csprojFiles) {
7280
- 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
+ }
7281
7299
  }
7282
- if (deps.length === 0) return 0;
7283
- db.prepare("DELETE FROM package_dependencies WHERE service = ?").run(service);
7284
- const insert = db.prepare(
7285
- "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."
7286
7302
  );
7287
- transaction(db, () => {
7288
- for (const dep of deps) {
7289
- 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;
7290
7320
  }
7291
- });
7292
- return deps.length;
7321
+ })();
7322
+ return initPromise2;
7293
7323
  }
7294
- function parsePackageJson(filePath) {
7295
- try {
7296
- const content = readFileSync5(filePath, "utf-8");
7297
- const pkg = JSON.parse(content);
7298
- const deps = [];
7299
- if (pkg.dependencies) {
7300
- for (const [name, version] of Object.entries(pkg.dependencies)) {
7301
- 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);
7302
7353
  }
7354
+ return;
7303
7355
  }
7304
- if (pkg.devDependencies) {
7305
- for (const [name, version] of Object.entries(pkg.devDependencies)) {
7306
- 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);
7307
7363
  }
7364
+ return;
7308
7365
  }
7309
- return deps;
7310
- } catch {
7311
- return [];
7312
7366
  }
7313
- }
7314
- function parseCsproj(filePath) {
7315
- try {
7316
- const content = readFileSync5(filePath, "utf-8");
7317
- const deps = [];
7318
- const pattern = /<PackageReference\s+Include="([^"]+)"(?:\s+Version="([^"]*)")?[^>]*\/?>/gi;
7319
- let match;
7320
- while ((match = pattern.exec(content)) !== null) {
7321
- deps.push({
7322
- name: match[1],
7323
- version: match[2] ?? null,
7324
- source: "nuget"
7325
- });
7326
- }
7327
- return deps;
7328
- } catch {
7329
- 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;
7330
7398
  }
7331
- }
7332
- function findCsprojFiles(dir, maxDepth = 4) {
7333
- const results = [];
7334
- function walk(currentDir2, depth) {
7335
- if (depth > maxDepth) return;
7336
- try {
7337
- const entries = readdirSync5(currentDir2, { withFileTypes: true });
7338
- for (const entry of entries) {
7339
- if (entry.name === "bin" || entry.name === "obj" || entry.name === "node_modules" || entry.name === ".git") continue;
7340
- const fullPath = join12(currentDir2, entry.name);
7341
- if (entry.isFile() && entry.name.endsWith(".csproj")) {
7342
- results.push(fullPath);
7343
- } else if (entry.isDirectory()) {
7344
- walk(fullPath, depth + 1);
7345
- }
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);
7346
7421
  }
7347
- } catch {
7348
7422
  }
7423
+ return;
7349
7424
  }
7350
- walk(dir, 0);
7351
- return results;
7352
7425
  }
7353
-
7354
- // src/config/loader.ts
7355
- import { readFileSync as readFileSync6 } from "fs";
7356
- import { join as join13 } from "path";
7357
- import yaml from "yaml";
7358
- function loadConfig(projectRoot) {
7359
- const configPath = join13(projectRoot, ".arcbridge", "config.yaml");
7360
- try {
7361
- const raw = readFileSync6(configPath, "utf-8");
7362
- const parsed = ArcBridgeConfigSchema.safeParse(yaml.parse(raw));
7363
- if (parsed.success) {
7364
- return { config: parsed.data, error: null };
7365
- }
7366
- const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
7367
- return { config: null, error: `Config validation failed: ${issues}` };
7368
- } catch (err) {
7369
- if (err instanceof Error && "code" in err && err.code === "ENOENT") {
7370
- return { config: null, error: null };
7371
- }
7372
- 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;
7373
7473
  }
7474
+ return null;
7374
7475
  }
7375
-
7376
- // src/indexer/index.ts
7377
- function detectProjectLanguage(projectRoot) {
7378
- if (existsSync7(join14(projectRoot, "ProjectSettings")) && existsSync7(join14(projectRoot, "Assets"))) {
7379
- return "csharp";
7476
+ function findChild4(node, type) {
7477
+ for (const child of node.namedChildren) {
7478
+ if (child.type === type) return child;
7380
7479
  }
7381
- if (existsSync7(join14(projectRoot, "tsconfig.json"))) return "typescript";
7382
- if (existsSync7(join14(projectRoot, "package.json"))) return "typescript";
7383
- if (findDotnetProject(projectRoot)) return "csharp";
7384
- return "typescript";
7480
+ return null;
7385
7481
  }
7386
- async function indexProject(db, options) {
7387
- const language = options.language ?? "auto";
7388
- const resolvedLanguage = language === "auto" ? detectProjectLanguage(options.projectRoot) : language;
7389
- indexPackageDependencies(db, options.projectRoot, options.service ?? "main");
7390
- if (resolvedLanguage === "csharp") {
7391
- const backend = resolveCSharpBackend(options.projectRoot);
7392
- if (backend === "roslyn") {
7393
- return indexDotnetProjectRoslyn(db, {
7394
- projectRoot: options.projectRoot,
7395
- service: options.service
7396
- });
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);
7397
7524
  }
7398
- return await indexCSharpTreeSitter(db, {
7399
- projectRoot: options.projectRoot,
7400
- service: options.service
7401
- });
7402
7525
  }
7403
- return indexTypeScriptProject(db, options);
7526
+ return decorators;
7404
7527
  }
7405
- function resolveCSharpBackend(projectRoot) {
7406
- const { config, error } = loadConfig(projectRoot);
7407
- let setting = config?.indexing?.csharp_indexer;
7408
- if (!setting && error) {
7409
- try {
7410
- const raw = readFileSync7(join14(projectRoot, ".arcbridge", "config.yaml"), "utf-8");
7411
- const parsed = YAML.parse(raw);
7412
- const rawSetting = parsed?.indexing?.csharp_indexer;
7413
- if (rawSetting === "roslyn" || rawSetting === "tree-sitter") {
7414
- setting = rawSetting;
7415
- }
7416
- } 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]);
7417
7538
  }
7418
7539
  }
7419
- if (setting === "roslyn" || setting === "tree-sitter") {
7420
- 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;
7421
7557
  }
7422
- if (hasGlobalTool()) {
7423
- return "roslyn";
7558
+ if (node.type === "typed_parameter" || node.type === "typed_default_parameter") {
7559
+ extractTypeAnnotationDeps(node, fileSymbols, lookup, deps, seen);
7424
7560
  }
7425
- if (hasIndexerProject()) {
7426
- try {
7427
- execFileSync2("dotnet", ["--version"], {
7428
- encoding: "utf-8",
7429
- timeout: 5e3
7430
- });
7431
- return "roslyn";
7432
- } catch {
7433
- }
7561
+ for (const child of node.namedChildren) {
7562
+ walkForDependencies2(child, fileSymbols, lookup, deps, seen);
7434
7563
  }
7435
- return "tree-sitter";
7436
7564
  }
7437
- function indexTypeScriptProject(db, options) {
7438
- const start = Date.now();
7439
- const service = options.service ?? "main";
7440
- const { checker, sourceFiles, projectRoot } = createTsProgram(options);
7441
- const existingHashes = getExistingHashes(db, service);
7442
- const changed = [];
7443
- const currentPaths = /* @__PURE__ */ new Set();
7444
- let filesSkipped = 0;
7445
- for (const sf of sourceFiles) {
7446
- const relPath = relative5(projectRoot, sf.fileName);
7447
- currentPaths.add(relPath);
7448
- const hash = hashContent(sf.getFullText());
7449
- const existingHash = existingHashes.get(relPath);
7450
- if (existingHash === hash) {
7451
- filesSkipped++;
7452
- } else {
7453
- changed.push({ sourceFile: sf, relativePath: relPath, hash });
7454
- }
7455
- }
7456
- const removed = [];
7457
- for (const existingPath of existingHashes.keys()) {
7458
- if (!currentPaths.has(existingPath)) {
7459
- 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);
7460
8606
  }
7461
8607
  }
7462
8608
  const filesToClean = [
7463
8609
  ...removed,
7464
8610
  ...changed.map((f) => f.relativePath)
7465
8611
  ];
7466
- removeSymbolsForFiles(db, filesToClean);
8612
+ removeScopedSymbolsForFiles(db, filesToClean, service, "typescript");
7467
8613
  const allSymbols = changed.flatMap(
7468
8614
  (f) => extractSymbols(f.sourceFile, checker, f.relativePath, f.hash)
7469
8615
  );
7470
8616
  writeSymbols(db, allSymbols, service, "typescript");
7471
- 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);
7472
8618
  const lookup = buildSymbolLookup(allDbSymbols);
7473
8619
  const allDeps = sourceFiles.flatMap((sf) => {
7474
8620
  const relPath = relative5(projectRoot, sf.fileName);
7475
8621
  return extractDependencies(sf, checker, relPath, projectRoot, lookup);
7476
8622
  });
7477
- 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);
7478
8624
  writeDependencies(db, allDeps);
7479
8625
  const CLIENT_ONLY_TEMPLATES = /* @__PURE__ */ new Set(["react-vite", "angular-app"]);
7480
8626
  const projectType = db.prepare("SELECT value FROM arcbridge_meta WHERE key = 'project_type'").get()?.value;
@@ -7805,21 +8951,21 @@ function safeParseJson(value, fallback) {
7805
8951
  }
7806
8952
 
7807
8953
  // src/sync/yaml-writer.ts
7808
- import { join as join15 } from "path";
7809
- 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";
7810
8956
  import { parse as parse2, stringify as stringify4 } from "yaml";
7811
8957
  function readTaskFile(projectRoot, phaseId) {
7812
- const path = join15(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
8958
+ const path = join17(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
7813
8959
  if (!existsSync8(path)) return { error: "not-found" };
7814
- const raw = readFileSync8(path, "utf-8");
8960
+ const raw = readFileSync10(path, "utf-8");
7815
8961
  const result = TaskFileSchema.safeParse(parse2(raw));
7816
8962
  if (!result.success) return { error: "invalid" };
7817
8963
  return { data: result.data, path };
7818
8964
  }
7819
8965
  function readPhasesFile(projectRoot) {
7820
- const path = join15(projectRoot, ".arcbridge", "plan", "phases.yaml");
8966
+ const path = join17(projectRoot, ".arcbridge", "plan", "phases.yaml");
7821
8967
  if (!existsSync8(path)) return { error: "not-found" };
7822
- const raw = readFileSync8(path, "utf-8");
8968
+ const raw = readFileSync10(path, "utf-8");
7823
8969
  const result = PhasesFileSchema.safeParse(parse2(raw));
7824
8970
  if (!result.success) return { error: "invalid" };
7825
8971
  return { data: result.data, path };
@@ -7839,14 +8985,14 @@ function syncTaskToYaml(projectRoot, phaseId, taskId, status, completedAt) {
7839
8985
  writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
7840
8986
  }
7841
8987
  function addTaskToYaml(projectRoot, phaseId, task) {
7842
- const tasksDir = join15(projectRoot, ".arcbridge", "plan", "tasks");
8988
+ const tasksDir = join17(projectRoot, ".arcbridge", "plan", "tasks");
7843
8989
  mkdirSync5(tasksDir, { recursive: true });
7844
8990
  const readResult = readTaskFile(projectRoot, phaseId);
7845
8991
  const taskFile = "error" in readResult ? { schema_version: 1, phase_id: phaseId, tasks: [] } : readResult.data;
7846
8992
  if (!taskFile.tasks.some((t) => t.id === task.id)) {
7847
8993
  taskFile.tasks.push(task);
7848
8994
  }
7849
- const taskPath = join15(tasksDir, `${phaseId}.yaml`);
8995
+ const taskPath = join17(tasksDir, `${phaseId}.yaml`);
7850
8996
  writeFileSync5(taskPath, stringify4(taskFile), "utf-8");
7851
8997
  }
7852
8998
  function syncPhaseToYaml(projectRoot, phaseId, status, startedAt, completedAt) {
@@ -7879,9 +9025,9 @@ function addPhaseToYaml(projectRoot, phase) {
7879
9025
  warning: `Phase number ${phase.phase_number} already used by '${conflicting?.id}'`
7880
9026
  };
7881
9027
  }
7882
- const tasksDir = join15(projectRoot, ".arcbridge", "plan", "tasks");
9028
+ const tasksDir = join17(projectRoot, ".arcbridge", "plan", "tasks");
7883
9029
  mkdirSync5(tasksDir, { recursive: true });
7884
- const taskFilePath = join15(tasksDir, `${phase.id}.yaml`);
9030
+ const taskFilePath = join17(tasksDir, `${phase.id}.yaml`);
7885
9031
  if (!existsSync8(taskFilePath)) {
7886
9032
  writeFileSync5(
7887
9033
  taskFilePath,
@@ -7911,14 +9057,14 @@ function addPhaseToYaml(projectRoot, phase) {
7911
9057
  }
7912
9058
  }
7913
9059
  function syncScenarioToYaml(projectRoot, scenarioId, status, linkedTests, verification) {
7914
- const scenarioPath = join15(
9060
+ const scenarioPath = join17(
7915
9061
  projectRoot,
7916
9062
  ".arcbridge",
7917
9063
  "arc42",
7918
9064
  "10-quality-scenarios.yaml"
7919
9065
  );
7920
9066
  if (!existsSync8(scenarioPath)) return;
7921
- const raw = readFileSync8(scenarioPath, "utf-8");
9067
+ const raw = readFileSync10(scenarioPath, "utf-8");
7922
9068
  const parsed = parse2(raw);
7923
9069
  const result = QualityScenariosFileSchema.safeParse(parsed);
7924
9070
  if (!result.success) return;
@@ -7977,7 +9123,7 @@ function deletePhaseFromYaml(projectRoot, phaseId) {
7977
9123
  return { success: false, warning: `Phase '${phaseId}' not found in phases.yaml` };
7978
9124
  }
7979
9125
  writeFileSync5(phasesPath, stringify4(phasesFile), "utf-8");
7980
- const taskFilePath = join15(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
9126
+ const taskFilePath = join17(projectRoot, ".arcbridge", "plan", "tasks", `${phaseId}.yaml`);
7981
9127
  try {
7982
9128
  unlinkSync(taskFilePath);
7983
9129
  } catch (e) {
@@ -8121,7 +9267,7 @@ function safeParseJson2(value, fallback) {
8121
9267
 
8122
9268
  // src/generators/sync-generator.ts
8123
9269
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
8124
- import { join as join16, dirname as dirname4 } from "path";
9270
+ import { join as join18, dirname as dirname6 } from "path";
8125
9271
 
8126
9272
  // src/templates/sync/claude-skill.ts
8127
9273
  function claudeSkillTemplate(config) {
@@ -8299,21 +9445,21 @@ To enable automatic sync:
8299
9445
  function generateSyncFiles(targetDir, config) {
8300
9446
  const generated = [];
8301
9447
  const action = githubActionTemplate(config);
8302
- const actionPath = join16(targetDir, action.relativePath);
8303
- mkdirSync6(dirname4(actionPath), { recursive: true });
9448
+ const actionPath = join18(targetDir, action.relativePath);
9449
+ mkdirSync6(dirname6(actionPath), { recursive: true });
8304
9450
  writeFileSync6(actionPath, action.content, "utf-8");
8305
9451
  generated.push(action.relativePath);
8306
9452
  if (config.platforms.includes("claude")) {
8307
9453
  const skill = claudeSkillTemplate(config);
8308
- const skillPath = join16(targetDir, skill.relativePath);
8309
- mkdirSync6(dirname4(skillPath), { recursive: true });
9454
+ const skillPath = join18(targetDir, skill.relativePath);
9455
+ mkdirSync6(dirname6(skillPath), { recursive: true });
8310
9456
  writeFileSync6(skillPath, skill.content, "utf-8");
8311
9457
  generated.push(skill.relativePath);
8312
9458
  }
8313
9459
  if (config.platforms.includes("copilot")) {
8314
9460
  const hook = copilotHookTemplate(config);
8315
- const hookPath = join16(targetDir, hook.relativePath);
8316
- mkdirSync6(dirname4(hookPath), { recursive: true });
9461
+ const hookPath = join18(targetDir, hook.relativePath);
9462
+ mkdirSync6(dirname6(hookPath), { recursive: true });
8317
9463
  writeFileSync6(hookPath, hook.content, "utf-8");
8318
9464
  generated.push(hook.relativePath);
8319
9465
  }
@@ -8322,7 +9468,7 @@ function generateSyncFiles(targetDir, config) {
8322
9468
 
8323
9469
  // src/metrics/activity.ts
8324
9470
  import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
8325
- import { join as join17 } from "path";
9471
+ import { join as join19 } from "path";
8326
9472
  function insertActivity(db, params) {
8327
9473
  const totalTokens = params.totalTokens ?? (params.inputTokens != null && params.outputTokens != null ? params.inputTokens + params.outputTokens : null);
8328
9474
  const stmt = db.prepare(`
@@ -8480,7 +9626,7 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
8480
9626
  });
8481
9627
  const rows = result.rows;
8482
9628
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
8483
- const dir = join17(projectRoot, ".arcbridge", "metrics");
9629
+ const dir = join19(projectRoot, ".arcbridge", "metrics");
8484
9630
  mkdirSync7(dir, { recursive: true });
8485
9631
  let content;
8486
9632
  let filename;
@@ -8577,7 +9723,7 @@ function exportMetrics(db, projectRoot, format, params, maxRows = 1e5) {
8577
9723
  break;
8578
9724
  }
8579
9725
  }
8580
- const filePath = join17(dir, filename);
9726
+ const filePath = join19(dir, filename);
8581
9727
  writeFileSync7(filePath, content, "utf-8");
8582
9728
  return filePath;
8583
9729
  }
@@ -8781,7 +9927,7 @@ function parseStatusCode(code) {
8781
9927
  // src/testing/runner.ts
8782
9928
  import { execFileSync as execFileSync4 } from "child_process";
8783
9929
  import { existsSync as existsSync9 } from "fs";
8784
- import { resolve as resolve3 } from "path";
9930
+ import { resolve as resolve5 } from "path";
8785
9931
  function verifyScenarios(db, projectRoot, options) {
8786
9932
  const results = [];
8787
9933
  const errors = [];
@@ -8818,7 +9964,7 @@ function verifyScenarios(db, projectRoot, options) {
8818
9964
  }
8819
9965
  if (testPaths.length === 0) continue;
8820
9966
  const missingPaths = testPaths.filter(
8821
- (tp) => !existsSync9(resolve3(projectRoot, tp))
9967
+ (tp) => !existsSync9(resolve5(projectRoot, tp))
8822
9968
  );
8823
9969
  if (missingPaths.length === testPaths.length) {
8824
9970
  results.push({
@@ -8833,7 +9979,7 @@ function verifyScenarios(db, projectRoot, options) {
8833
9979
  continue;
8834
9980
  }
8835
9981
  const existingPaths = testPaths.filter(
8836
- (tp) => existsSync9(resolve3(projectRoot, tp))
9982
+ (tp) => existsSync9(resolve5(projectRoot, tp))
8837
9983
  );
8838
9984
  const start = Date.now();
8839
9985
  let passed;
@@ -8887,11 +10033,11 @@ ${output}`;
8887
10033
  }
8888
10034
 
8889
10035
  // src/roles/loader.ts
8890
- import { readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
8891
- import { join as join18 } from "path";
10036
+ import { readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
10037
+ import { join as join20 } from "path";
8892
10038
  import matter4 from "gray-matter";
8893
10039
  function loadRoles(projectRoot) {
8894
- const agentsDir = join18(projectRoot, ".arcbridge", "agents");
10040
+ const agentsDir = join20(projectRoot, ".arcbridge", "agents");
8895
10041
  const roles = [];
8896
10042
  const errors = [];
8897
10043
  let files;
@@ -8901,9 +10047,9 @@ function loadRoles(projectRoot) {
8901
10047
  return { roles: [], errors: [`Agent directory not found: ${agentsDir}`] };
8902
10048
  }
8903
10049
  for (const file of files) {
8904
- const filePath = join18(agentsDir, file);
10050
+ const filePath = join20(agentsDir, file);
8905
10051
  try {
8906
- const raw = readFileSync9(filePath, "utf-8");
10052
+ const raw = readFileSync11(filePath, "utf-8");
8907
10053
  const parsed = matter4(raw);
8908
10054
  const input = {
8909
10055
  ...parsed.data,
@@ -8928,9 +10074,9 @@ function loadRole(projectRoot, roleId) {
8928
10074
  if (!/^[a-z0-9-]+$/.test(roleId)) {
8929
10075
  return { role: null, error: `Invalid role ID: "${roleId}" (must be kebab-case)` };
8930
10076
  }
8931
- const filePath = join18(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
10077
+ const filePath = join20(projectRoot, ".arcbridge", "agents", `${roleId}.md`);
8932
10078
  try {
8933
- const raw = readFileSync9(filePath, "utf-8");
10079
+ const raw = readFileSync11(filePath, "utf-8");
8934
10080
  const parsed = matter4(raw);
8935
10081
  const input = {
8936
10082
  ...parsed.data,