dsh-code-index 0.9.1 → 0.10.1

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
@@ -6,10 +6,12 @@ import path4 from "path";
6
6
  import { readFile } from "fs/promises";
7
7
  import { createRequire } from "module";
8
8
  import path from "path";
9
+ import { fileURLToPath } from "url";
9
10
  import { Language, Parser, Query } from "web-tree-sitter";
10
11
  var require2 = createRequire(import.meta.url);
11
12
  var WASM_DIR = path.dirname(require2.resolve("tree-sitter-wasms/out/tree-sitter-typescript.wasm"));
12
13
  var GRAMMAR_NAMES = {
14
+ arkts: "tree-sitter-arkts",
13
15
  typescript: "tree-sitter-typescript",
14
16
  javascript: "tree-sitter-javascript",
15
17
  python: "tree-sitter-python",
@@ -20,6 +22,7 @@ var GRAMMAR_NAMES = {
20
22
  c: "tree-sitter-c"
21
23
  };
22
24
  var EXT_TO_LANG = {
25
+ ".ets": "arkts",
23
26
  ".ts": "typescript",
24
27
  ".tsx": "typescript",
25
28
  ".mts": "typescript",
@@ -51,6 +54,19 @@ function languageForFile(filePath) {
51
54
  return EXT_TO_LANG[ext] ?? null;
52
55
  }
53
56
  var QUERIES = {
57
+ arkts: `
58
+ (struct_declaration) @class
59
+ (function_declaration) @function
60
+ (generator_function_declaration) @function
61
+ (method_definition) @method
62
+ (class_declaration) @class
63
+ (abstract_class_declaration) @class
64
+ (interface_declaration) @interface
65
+ (type_alias_declaration) @type
66
+ (enum_declaration) @enum
67
+ (variable_declarator) @variable
68
+ (public_field_definition) @field
69
+ `,
54
70
  typescript: `
55
71
  (function_declaration) @function
56
72
  (generator_function_declaration) @function
@@ -132,6 +148,7 @@ var CAPTURE_KINDS = {
132
148
  module: { kind: "module" }
133
149
  };
134
150
  var IMPORT_QUERIES = {
151
+ arkts: `(import_statement) @import (export_statement) @reexport`,
135
152
  typescript: `
136
153
  (import_statement) @import
137
154
  (export_statement) @reexport
@@ -163,6 +180,12 @@ var IMPORT_QUERIES = {
163
180
  `
164
181
  };
165
182
  var CALL_QUERIES = {
183
+ arkts: `
184
+ (call_expression function: (identifier) @call)
185
+ (call_expression function: (member_expression property: (property_identifier) @call))
186
+ (arkui_component_expression function: (identifier) @call)
187
+ (new_expression constructor: (identifier) @call)
188
+ `,
166
189
  typescript: `
167
190
  (call_expression function: (identifier) @call)
168
191
  (call_expression function: (member_expression property: (property_identifier) @call))
@@ -201,6 +224,7 @@ var CALL_QUERIES = {
201
224
  `
202
225
  };
203
226
  var CALLABLE_NODES = {
227
+ arkts: /* @__PURE__ */ new Set(["function_declaration", "generator_function_declaration", "method_definition", "function_expression", "arrow_function"]),
204
228
  typescript: /* @__PURE__ */ new Set([
205
229
  "function_declaration",
206
230
  "generator_function_declaration",
@@ -239,7 +263,7 @@ function getLanguage(id) {
239
263
  let entry = languageCache.get(id);
240
264
  if (!entry) {
241
265
  entry = getParser().then(async () => {
242
- const grammarPath = path.join(WASM_DIR, `${GRAMMAR_NAMES[id]}.wasm`);
266
+ const grammarPath = id === "arkts" ? fileURLToPath(new URL("../grammars/tree-sitter-arkts.wasm", import.meta.url)) : path.join(WASM_DIR, `${GRAMMAR_NAMES[id]}.wasm`);
243
267
  const bytes = await readFile(grammarPath);
244
268
  const lang = await Language.load(bytes);
245
269
  new Query(lang, QUERIES[id]).delete();
@@ -264,6 +288,7 @@ async function extractAll(code, id, file = "") {
264
288
  const tree = parser.parse(code);
265
289
  if (!tree) throw new Error(`tree-sitter parse returned null for a ${id} source`);
266
290
  try {
291
+ const diagnostic = parseDiagnostic(tree.rootNode);
267
292
  const drafts = [];
268
293
  const symbolQuery = new Query(lang, QUERIES[id]);
269
294
  try {
@@ -272,6 +297,7 @@ async function extractAll(code, id, file = "") {
272
297
  const def = CAPTURE_KINDS[cap.name];
273
298
  if (!def) continue;
274
299
  const node = cap.node;
300
+ if (id === "arkts" && !reliableArktsNode(node)) continue;
275
301
  if (def.kind === "variable" && !isModuleLevelVariable(id, node)) continue;
276
302
  const name2 = nameOf(id, node);
277
303
  if (!name2) continue;
@@ -279,10 +305,11 @@ async function extractAll(code, id, file = "") {
279
305
  symbol: {
280
306
  name: name2,
281
307
  kind: kindFor(id, node, def.kind),
282
- line: node.startPosition.row + 1,
308
+ line: id === "arkts" ? arktsDeclarationStart(node) : node.startPosition.row + 1,
283
309
  endLine: node.endPosition.row + 1,
284
310
  exported: isExported(id, node),
285
- signature: signatureFor(id, node)
311
+ signature: signatureFor(id, node),
312
+ ...id === "arkts" ? { decorators: arktsDecorators(node) } : {}
286
313
  },
287
314
  node
288
315
  });
@@ -298,6 +325,7 @@ async function extractAll(code, id, file = "") {
298
325
  const importQuery = new Query(lang, IMPORT_QUERIES[id]);
299
326
  try {
300
327
  for (const cap of importQuery.captures(tree.rootNode)) {
328
+ if (id === "arkts" && !reliableArktsNode(cap.node)) continue;
301
329
  for (const info of importInfoFor(id, cap.node)) importDetails.push(info);
302
330
  }
303
331
  } finally {
@@ -309,6 +337,7 @@ async function extractAll(code, id, file = "") {
309
337
  try {
310
338
  const seen = /* @__PURE__ */ new Set();
311
339
  for (const cap of callQuery.captures(tree.rootNode)) {
340
+ if (id === "arkts" && (!reliableArktsNode(cap.node) || isArkuiModifier(cap.node) || isArktsMetadataCall(cap.node))) continue;
312
341
  const name2 = cap.node.text.trim();
313
342
  if (!name2) continue;
314
343
  const line = cap.node.startPosition.row + 1;
@@ -318,7 +347,14 @@ async function extractAll(code, id, file = "") {
318
347
  const key = `${from}|${name2}|${line}`;
319
348
  if (seen.has(key)) continue;
320
349
  seen.add(key);
321
- calls.push({ name: name2, line, from, fromId, qualifier: qualifierOf(cap.node) });
350
+ calls.push({
351
+ name: name2,
352
+ line,
353
+ from,
354
+ fromId,
355
+ qualifier: qualifierOf(cap.node),
356
+ ...id === "arkts" && isArkuiCall(cap.node) ? { context: "arkui" } : {}
357
+ });
322
358
  }
323
359
  } finally {
324
360
  callQuery.delete();
@@ -327,11 +363,85 @@ async function extractAll(code, id, file = "") {
327
363
  symbols.sort(
328
364
  (a, b) => a.line - b.line || a.endLine - b.endLine || a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name)
329
365
  );
330
- return { symbols, imports, importDetails, calls };
366
+ if (diagnostic.status === "partial" && !symbols.length && !importDetails.length) diagnostic.status = "failed";
367
+ return { symbols, imports, importDetails, calls, diagnostic };
331
368
  } finally {
332
369
  tree.delete();
333
370
  }
334
371
  }
372
+ function parseDiagnostic(root) {
373
+ const diagnostic = { status: root.hasError ? "partial" : "parsed", errorCount: 0, missingCount: 0, issues: [] };
374
+ if (!root.hasError) return diagnostic;
375
+ const stack = [root];
376
+ while (stack.length) {
377
+ const node = stack.pop();
378
+ if (node.type === "ERROR" || node.isMissing) {
379
+ if (node.isMissing) diagnostic.missingCount++;
380
+ else diagnostic.errorCount++;
381
+ if (diagnostic.issues.length < 3) diagnostic.issues.push({ kind: node.isMissing ? "MISSING" : "ERROR", line: node.startPosition.row + 1 });
382
+ }
383
+ for (let i = node.childCount - 1; i >= 0; i--) {
384
+ const child = node.child(i);
385
+ if (child) stack.push(child);
386
+ }
387
+ }
388
+ return diagnostic;
389
+ }
390
+ function reliableArktsNode(node) {
391
+ if (node.hasError) return false;
392
+ for (let parent = node.parent; parent; parent = parent.parent) {
393
+ if (parent.type === "ERROR") return false;
394
+ if (["struct_declaration", "class_declaration", "function_declaration", "method_definition"].includes(parent.type) && parent.hasError) return false;
395
+ }
396
+ return true;
397
+ }
398
+ function arktsDecoratorNodes(node) {
399
+ const nodes = node.namedChildren.filter((child) => child?.type === "decorator");
400
+ for (let prev = node.previousNamedSibling; prev?.type === "decorator"; prev = prev.previousNamedSibling) nodes.unshift(prev);
401
+ if (node.parent?.type === "export_statement") {
402
+ nodes.unshift(...node.parent.namedChildren.filter((child) => child?.type === "decorator"));
403
+ }
404
+ return [...new Map(nodes.map((decorator) => [decorator.id, decorator])).values()];
405
+ }
406
+ function arktsDecorators(node) {
407
+ return arktsDecoratorNodes(node).map((decorator) => collapseSpace(decorator.text));
408
+ }
409
+ function arktsDeclarationStart(node) {
410
+ return Math.min(node.startPosition.row, ...arktsDecoratorNodes(node).map((decorator) => decorator.startPosition.row)) + 1;
411
+ }
412
+ function isArkuiCall(node) {
413
+ if (node.parent?.type === "arkui_component_expression") return true;
414
+ if (node.parent?.type !== "call_expression") return false;
415
+ if (!/^[A-Z]/.test(node.text)) return false;
416
+ for (let parent = node.parent; parent; parent = parent.parent) {
417
+ if (parent.type === "arkui_children") return true;
418
+ if (parent.type === "arrow_function" || parent.type === "function_expression") return false;
419
+ if (parent.type === "method_definition" || parent.type === "function_declaration") {
420
+ return parent.type === "method_definition" && nameOf("arkts", parent) === "build" && parent.parent?.type === "struct_body" || arktsDecorators(parent).some((text) => /^@Builder\b/.test(text));
421
+ }
422
+ }
423
+ return false;
424
+ }
425
+ function isArkuiModifier(node) {
426
+ if (node.parent?.type !== "member_expression") return false;
427
+ let receiver = node.parent.childForFieldName("object");
428
+ while (receiver?.type === "call_expression") {
429
+ const callee = receiver.childForFieldName("function");
430
+ if (callee?.type === "identifier") return isArkuiCall(callee);
431
+ if (callee?.type !== "member_expression") return false;
432
+ receiver = callee.childForFieldName("object");
433
+ }
434
+ return receiver?.type === "arkui_component_expression" || receiver?.type === "leading_dot_expression";
435
+ }
436
+ function isArktsMetadataCall(node) {
437
+ for (let parent = node.parent; parent; parent = parent.parent) {
438
+ if (parent.type === "decorator") return true;
439
+ if (parent.type === "arguments") return false;
440
+ if (parent.type === "leading_dot_expression") return true;
441
+ if (CALLABLE_NODES.arkts.has(parent.type)) return false;
442
+ }
443
+ return false;
444
+ }
335
445
  function dedupeDrafts(drafts) {
336
446
  const sorted = [...drafts].sort(
337
447
  (a, b) => a.symbol.line - b.symbol.line || a.node.startIndex - b.node.startIndex
@@ -882,6 +992,10 @@ function signatureFor(id, node) {
882
992
  }
883
993
  function rawSignature(id, node) {
884
994
  const name2 = nameOf(id, node);
995
+ if (id === "arkts" && node.type === "struct_declaration") return `struct ${name2}`;
996
+ if (id === "arkts" && node.type === "public_field_definition") {
997
+ return `${name2}${collapseSpace(node.childForFieldName("type")?.text ?? "")}`;
998
+ }
885
999
  const params = node.childForFieldName?.("parameters") ?? node.namedChildren.find(
886
1000
  (c) => c?.type === "formal_parameters" || c?.type === "method_parameters" || c?.type === "parameters" || c?.type === "parameter_list"
887
1001
  );
@@ -939,7 +1053,7 @@ function isExported(id, node) {
939
1053
  }
940
1054
  for (let parent = node.parent; parent; parent = parent.parent) {
941
1055
  if (parent.type === "export_statement") return true;
942
- if (parent.type === "statement_block" || parent.type === "class_body") return false;
1056
+ if (parent.type === "statement_block" || parent.type === "class_body" || parent.type === "struct_body") return false;
943
1057
  if (parent.type === "program") return false;
944
1058
  }
945
1059
  return false;
@@ -1030,6 +1144,7 @@ var DEFAULT_EXCLUDED_DIRS = [
1030
1144
  ".dsh-code-index"
1031
1145
  ];
1032
1146
  var SUPPORTED_EXTS = /* @__PURE__ */ new Set([
1147
+ ".ets",
1033
1148
  ".ts",
1034
1149
  ".tsx",
1035
1150
  ".mts",
@@ -1134,7 +1249,7 @@ import path3 from "path";
1134
1249
  import { createHash, randomUUID } from "crypto";
1135
1250
 
1136
1251
  // src/types.ts
1137
- var REPO_INDEX_SCHEMA_VERSION = 2;
1252
+ var REPO_INDEX_SCHEMA_VERSION = 3;
1138
1253
  function symbolCount(index) {
1139
1254
  return index.files.reduce((n, f) => n + f.symbols.length, 0);
1140
1255
  }
@@ -1198,7 +1313,7 @@ async function buildIndex(root, options = {}, previous = null, forcePaths = /* @
1198
1313
  batch.map(async (f) => {
1199
1314
  const lang = languageForFile(f.abs);
1200
1315
  if (!lang) return null;
1201
- if (prevMtime.get(f.rel) === f.mtimeMs && !forcePaths.has(f.rel) && !forcePaths.has("*")) {
1316
+ if (prevMtime.get(f.rel) === f.mtimeMs && prevByPath.get(f.rel)?.diagnostic?.status !== "failed" && !forcePaths.has(f.rel) && !forcePaths.has("*")) {
1202
1317
  const cached = prevByPath.get(f.rel);
1203
1318
  return {
1204
1319
  ...cached,
@@ -1210,9 +1325,33 @@ async function buildIndex(root, options = {}, previous = null, forcePaths = /* @
1210
1325
  try {
1211
1326
  code = await readFile4(f.abs, "utf8");
1212
1327
  } catch {
1213
- return null;
1328
+ return {
1329
+ path: f.rel,
1330
+ lang,
1331
+ mtimeMs: f.mtimeMs,
1332
+ symbols: [],
1333
+ imports: [],
1334
+ importDetails: [],
1335
+ calls: [],
1336
+ diagnostic: { status: "failed", errorCount: 0, missingCount: 0, issues: [], reason: "source-unavailable" }
1337
+ };
1338
+ }
1339
+ let extracted;
1340
+ try {
1341
+ extracted = await extractAll(code, lang, f.rel);
1342
+ } catch {
1343
+ return {
1344
+ path: f.rel,
1345
+ lang,
1346
+ mtimeMs: f.mtimeMs,
1347
+ symbols: [],
1348
+ imports: [],
1349
+ importDetails: [],
1350
+ calls: [],
1351
+ diagnostic: { status: "failed", errorCount: 0, missingCount: 0, issues: [], reason: "parser-failed" }
1352
+ };
1214
1353
  }
1215
- const { symbols, imports, importDetails, calls } = await extractAll(code, lang, f.rel);
1354
+ const { symbols, imports, importDetails, calls, diagnostic } = extracted;
1216
1355
  return {
1217
1356
  path: f.rel,
1218
1357
  lang,
@@ -1220,7 +1359,8 @@ async function buildIndex(root, options = {}, previous = null, forcePaths = /* @
1220
1359
  symbols: symbols.map((s) => ({ ...s, file: f.rel })),
1221
1360
  imports,
1222
1361
  importDetails,
1223
- calls
1362
+ calls,
1363
+ diagnostic
1224
1364
  };
1225
1365
  })
1226
1366
  );
@@ -1268,11 +1408,12 @@ function indexesEqual(left, right) {
1268
1408
  return false;
1269
1409
  }
1270
1410
  if (file.symbols.length !== other.symbols.length) return false;
1411
+ if (JSON.stringify(file.diagnostic) !== JSON.stringify(other.diagnostic)) return false;
1271
1412
  if (!callsEqual(file.calls, other.calls)) return false;
1272
1413
  if (!importsEqual(file.importDetails, other.importDetails)) return false;
1273
1414
  return file.symbols.every((symbol, symbolIndex) => {
1274
1415
  const candidate = other.symbols[symbolIndex];
1275
- return candidate !== void 0 && symbol.id === candidate.id && symbol.name === candidate.name && symbol.kind === candidate.kind && symbol.file === candidate.file && symbol.line === candidate.line && symbol.endLine === candidate.endLine && symbol.exported === candidate.exported && symbol.signature === candidate.signature && symbol.ordinal === candidate.ordinal && scopesEqual(symbol.scope, candidate.scope);
1416
+ return candidate !== void 0 && symbol.id === candidate.id && symbol.name === candidate.name && symbol.kind === candidate.kind && symbol.file === candidate.file && symbol.line === candidate.line && symbol.endLine === candidate.endLine && symbol.exported === candidate.exported && symbol.signature === candidate.signature && symbol.ordinal === candidate.ordinal && JSON.stringify(symbol.decorators) === JSON.stringify(candidate.decorators) && scopesEqual(symbol.scope, candidate.scope);
1276
1417
  });
1277
1418
  });
1278
1419
  }
@@ -1304,7 +1445,7 @@ function callsEqual(left, right) {
1304
1445
  if (a.length !== b.length) return false;
1305
1446
  return a.every((call, index) => {
1306
1447
  const other = b[index];
1307
- return other !== void 0 && call.name === other.name && call.line === other.line && call.from === other.from && call.fromId === other.fromId && call.qualifier === other.qualifier;
1448
+ return other !== void 0 && call.name === other.name && call.line === other.line && call.from === other.from && call.fromId === other.fromId && call.qualifier === other.qualifier && call.context === other.context;
1308
1449
  });
1309
1450
  }
1310
1451
  async function findRepoRoot(startDir, maxLevels = 12) {
@@ -1322,6 +1463,21 @@ async function findRepoRoot(startDir, maxLevels = 12) {
1322
1463
  return null;
1323
1464
  }
1324
1465
 
1466
+ // src/integrity.ts
1467
+ function indexIntegrityWarnings(index) {
1468
+ const affected = index.files.filter((file) => file.diagnostic && file.diagnostic.status !== "parsed");
1469
+ if (!affected.length) return [];
1470
+ const partial = affected.filter((file) => file.diagnostic.status === "partial").length;
1471
+ return [
1472
+ `index incomplete: ${partial} partial, ${affected.length - partial} failed; inspect code_index`,
1473
+ ...affected.slice(0, 3).map((file) => {
1474
+ const diagnostic = file.diagnostic;
1475
+ const location = diagnostic.issues[0]?.line;
1476
+ return `${file.path.slice(0, 100)}${location ? `:${location}` : ""}: ${diagnostic.status} (${diagnostic.reason ?? `${diagnostic.errorCount} ERROR, ${diagnostic.missingCount} MISSING`})`;
1477
+ })
1478
+ ];
1479
+ }
1480
+
1325
1481
  // src/repomap.ts
1326
1482
  import path5 from "path";
1327
1483
  var KIND_WEIGHT = {
@@ -1336,7 +1492,7 @@ var KIND_WEIGHT = {
1336
1492
  import: 0.05,
1337
1493
  module: 0.3
1338
1494
  };
1339
- var TEST_PATH_RE = /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\.[cm]?[jt]sx?$|(^|\/)(?:test_[^/]+\.py|[^/]+_test\.(?:py|go))$/;
1495
+ var TEST_PATH_RE = /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\.(?:[cm]?[jt]sx?|ets)$|(^|\/)(?:test_[^/]+\.py|[^/]+_test\.(?:py|go))$/;
1340
1496
  function scoreFile(file) {
1341
1497
  let score = 0;
1342
1498
  for (const sym of file.symbols) {
@@ -1428,6 +1584,7 @@ function referencePageRank(files, density) {
1428
1584
  }
1429
1585
  function resolveImportDetailed(spec, fromPath, fileSet) {
1430
1586
  if (!spec) return { target: null, suffixSkipped: false };
1587
+ if (/^@(ohos|kit)\./.test(spec)) return { target: null, suffixSkipped: false };
1431
1588
  if (spec.startsWith("./") || spec.startsWith("../")) {
1432
1589
  const base = path5.posix.normalize(path5.posix.join(path5.posix.dirname(fromPath), spec));
1433
1590
  return { target: candidateFor(base, fileSet), suffixSkipped: false };
@@ -1444,11 +1601,13 @@ function resolveImport(spec, fromPath, fileSet) {
1444
1601
  }
1445
1602
  function candidateFor(base, fileSet) {
1446
1603
  if (fileSet.has(base)) return base;
1604
+ if (fileSet.has(`${base}.ets`) && [...SUPPORTED_EXTS].some((ext) => ext !== ".ets" && fileSet.has(`${base}${ext}`))) return null;
1447
1605
  for (const ext of SUPPORTED_EXTS) {
1448
1606
  if (fileSet.has(`${base}${ext}`)) return `${base}${ext}`;
1449
1607
  }
1450
1608
  const aliased = tsSourceForJsSpecifier(base, fileSet);
1451
1609
  if (aliased) return aliased;
1610
+ if (fileSet.has(`${base}/index.ets`) && [...SUPPORTED_EXTS].some((ext) => ext !== ".ets" && fileSet.has(`${base}/index${ext}`))) return null;
1452
1611
  for (const ext of SUPPORTED_EXTS) {
1453
1612
  if (fileSet.has(`${base}/index${ext}`)) return `${base}/index${ext}`;
1454
1613
  if (fileSet.has(`${base}/__init__${ext}`)) return `${base}/__init__${ext}`;
@@ -1514,20 +1673,31 @@ function symbolRefs(index, symbol, limit = 200) {
1514
1673
  const defs = table.get(symbol) ?? [];
1515
1674
  const callers = [];
1516
1675
  const calleeFirstLine = /* @__PURE__ */ new Map();
1676
+ const arktsTargets = /* @__PURE__ */ new Map();
1677
+ const graph = index.files.some((file) => file.lang === "arkts") ? buildReferenceGraph(index) : null;
1678
+ const arktsEdgesBySite = /* @__PURE__ */ new Map();
1679
+ if (graph) for (const edge of [...graph.outgoing.values()].flat().concat(graph.moduleLevelEdges)) {
1680
+ const key = `${edge.callSite.file}\0${edge.callSite.line}\0${edge.name}\0${edge.sourceId}`;
1681
+ const rows = arktsEdgesBySite.get(key) ?? [];
1682
+ rows.push(edge);
1683
+ arktsEdgesBySite.set(key, rows);
1684
+ }
1517
1685
  for (const file of index.files) {
1518
1686
  for (const call of file.calls ?? []) {
1519
- if (call.name === symbol) {
1687
+ const resolved = file.lang === "arkts" && graph ? (arktsEdgesBySite.get(`${file.path}\0${call.line}\0${call.name}\0${call.fromId}`) ?? []).map((edge) => graph.symbolsById.get(edge.targetId)) : null;
1688
+ if (resolved ? resolved.some((target) => target.name === symbol) : call.name === symbol) {
1520
1689
  callers.push({ file: file.path, line: call.line, from: call.from });
1521
1690
  }
1522
1691
  if (call.from === symbol && !calleeFirstLine.has(call.name)) {
1523
1692
  calleeFirstLine.set(call.name, call.line);
1524
1693
  }
1694
+ if (call.from === symbol) arktsTargets.set(call.name, dedupeSymbols([...arktsTargets.get(call.name) ?? [], ...resolved ?? table.get(call.name) ?? []]));
1525
1695
  }
1526
1696
  }
1527
1697
  callers.sort(
1528
1698
  (a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.from.localeCompare(b.from)
1529
1699
  );
1530
- const callees = [...calleeFirstLine.entries()].map(([name2, line]) => ({ name: name2, line, targets: table.get(name2) ?? [] })).sort((a, b) => a.line - b.line || a.name.localeCompare(b.name));
1700
+ const callees = [...calleeFirstLine.entries()].map(([name2, line]) => ({ name: name2, line, targets: arktsTargets.get(name2) ?? table.get(name2) ?? [] })).sort((a, b) => a.line - b.line || a.name.localeCompare(b.name));
1531
1701
  return {
1532
1702
  defs,
1533
1703
  callers: callers.slice(0, limit),
@@ -1574,7 +1744,7 @@ function resolveCallTargetsFor(fileSet, file, call, symbolsByName, symbolsByFile
1574
1744
  const qualified = call.qualifier !== null;
1575
1745
  const imports = (file.importDetails ?? []).filter((info) => info.kind !== "reexport");
1576
1746
  if (!qualified) {
1577
- const locals = named.filter((s) => s.file === file.path && lexicallyVisible(s, call));
1747
+ const locals = named.filter((s) => s.file === file.path && lexicallyVisible(s, call) && (call.context !== "arkui" || s.signature.startsWith("struct ") || s.decorators?.some((text) => /^@Builder\b/.test(text))));
1578
1748
  if (locals.length > 0) {
1579
1749
  const unique2 = locals.length === 1;
1580
1750
  const owned = unique2 && isStructuralOwner(call, locals[0]);
@@ -1604,7 +1774,7 @@ function resolveCallTargetsFor(fileSet, file, call, symbolsByName, symbolsByFile
1604
1774
  }
1605
1775
  if (!isExplicitBinding(binding)) continue;
1606
1776
  for (const def of symbolsByName.get(binding.imported) ?? []) {
1607
- if (def.file === target) bound.push(def);
1777
+ if (def.file === target && (file.lang !== "arkts" && !target.endsWith(".ets") || def.exported && def.scope.length === 0)) bound.push(def);
1608
1778
  }
1609
1779
  }
1610
1780
  }
@@ -1612,6 +1782,7 @@ function resolveCallTargetsFor(fileSet, file, call, symbolsByName, symbolsByFile
1612
1782
  const label = bound.length === 1 && directBinding && !defaultBinding ? "exact" : "import-scoped";
1613
1783
  return toEdges(bound, call, file.path, "explicit-import-binding", label, maxCandidates);
1614
1784
  }
1785
+ if (call.context === "arkui") return [];
1615
1786
  const resolvedModules = /* @__PURE__ */ new Set();
1616
1787
  for (const info of imports) {
1617
1788
  const target = resolveImport(info.specifier, file.path, fileSet);
@@ -1826,7 +1997,7 @@ function findCycles(index) {
1826
1997
  return components;
1827
1998
  }
1828
1999
  var ENTRY_RE = /(^|\/)(index|main|cli|app|server|mod|lib|__init__)\.[A-Za-z0-9]+$|(^|\/)(bin|cmd|scripts)\/|\.config\.[A-Za-z0-9]+$/;
1829
- var TEST_RE = /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\.[cm]?[jt]sx?$|(^|\/)(?:test_[^/]+\.py|[^/]+_test\.(?:py|go))$/;
2000
+ var TEST_RE = /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\.(?:[cm]?[jt]sx?|ets)$|(^|\/)(?:test_[^/]+\.py|[^/]+_test\.(?:py|go))$/;
1830
2001
  function findOrphanModules(index) {
1831
2002
  const { nodes, edges } = buildModuleGraph(index);
1832
2003
  const incoming = /* @__PURE__ */ new Map();
@@ -2138,7 +2309,7 @@ var LABEL_STRENGTH = {
2138
2309
  };
2139
2310
  var ENTRY_KINDS = /* @__PURE__ */ new Set(["function", "method", "class"]);
2140
2311
  var ENTRY_FILE_RE = /(^|\/)(main|index|cli|server|app)\.[^./]+$/;
2141
- var TEST_RE2 = /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\.[cm]?[jt]sx?$|(^|\/)(?:test_[^/]+\.py|[^/]+_test\.(?:py|go))$/;
2312
+ var TEST_RE2 = /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\.(?:[cm]?[jt]sx?|ets)$|(^|\/)(?:test_[^/]+\.py|[^/]+_test\.(?:py|go))$/;
2142
2313
  function changeContextInputFromArgs(args, root) {
2143
2314
  const hasDiff = typeof args.diff === "string" && args.diff.trim().length > 0;
2144
2315
  const hasFiles = Array.isArray(args.files) && args.files.length > 0;
@@ -2188,7 +2359,7 @@ async function buildChangeContext(index, input, options = {}) {
2188
2359
  const fileSet = new Set(filesByPath.keys());
2189
2360
  const incomingCount = /* @__PURE__ */ new Map();
2190
2361
  for (const [id, edges] of graph.incoming) incomingCount.set(id, edges.length);
2191
- const warnings = [];
2362
+ const warnings = indexIntegrityWarnings(index);
2192
2363
  const changedByKey = /* @__PURE__ */ new Map();
2193
2364
  const unmapped = [];
2194
2365
  const changedFiles = /* @__PURE__ */ new Set();
@@ -2265,6 +2436,7 @@ async function buildChangeContext(index, input, options = {}) {
2265
2436
  const lang = languageForFile(filePath);
2266
2437
  if (!lang) return null;
2267
2438
  const extracted = await extractAll(content, lang, filePath);
2439
+ if (extracted.diagnostic.status !== "parsed") warnings.push(`base ${filePath.slice(0, 100)}: ${extracted.diagnostic.status} parse; change mapping incomplete`);
2268
2440
  return extracted.symbols;
2269
2441
  })();
2270
2442
  baseSymbolsCache.set(filePath, pending);
@@ -2930,7 +3102,7 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
2930
3102
  const graph = buildReferenceGraph(index);
2931
3103
  const candidates = [];
2932
3104
  const seen = /* @__PURE__ */ new Set();
2933
- const gaps = [];
3105
+ const gaps = [...context.warnings];
2934
3106
  const unavailable = /* @__PURE__ */ new Set();
2935
3107
  const source = async (symbol, kind, reason, extra = {}, focus) => {
2936
3108
  const side = extra.side ?? "current";
@@ -3046,7 +3218,7 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
3046
3218
  task: context.task.slice(0, 120),
3047
3219
  route: context.route.kind,
3048
3220
  items: [],
3049
- gaps: ["budget omitted some evidence"],
3221
+ gaps: context.warnings.some((warning) => warning.startsWith("index incomplete:")) ? ["index incomplete; inspect code_index"] : ["budget omitted some evidence"],
3050
3222
  budget: { budgetChars, usedChars: 0, packChars: 0, truncated: context.task.length > 120 }
3051
3223
  };
3052
3224
  const fits = () => {
@@ -3055,7 +3227,10 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
3055
3227
  };
3056
3228
  if (!fits()) {
3057
3229
  pack.task = "";
3058
- pack.gaps = [];
3230
+ fits();
3231
+ }
3232
+ if (!fits()) {
3233
+ pack.gaps = pack.gaps.filter((gap) => gap.startsWith("index incomplete"));
3059
3234
  fits();
3060
3235
  }
3061
3236
  if (!fits()) throw new Error("budget too small for repository identity");
@@ -3075,7 +3250,7 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
3075
3250
  }
3076
3251
  if (!added) pack.budget.truncated = true;
3077
3252
  }
3078
- if (!pack.budget.truncated) pack.gaps = [];
3253
+ if (!pack.budget.truncated) pack.gaps = pack.gaps.filter((gap) => gap.startsWith("index incomplete"));
3079
3254
  for (const item of pack.items) if (item.file && unavailable.has(`${item.side}:${item.file}`)) {
3080
3255
  gaps.push(`source unavailable or changed after indexing: ${item.file} (${item.side})`);
3081
3256
  }
@@ -3093,7 +3268,7 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
3093
3268
  }
3094
3269
 
3095
3270
  // src/context.ts
3096
- var TEST_PATH_RE2 = /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\.[cm]?[jt]sx?$|(^|\/)(?:test_[^/]+\.py|[^/]+_test\.(?:py|go))$/i;
3271
+ var TEST_PATH_RE2 = /(^|\/)(tests?|__tests__)(\/|$)|\.(?:spec|test)\.(?:[cm]?[jt]sx?|ets)$|(^|\/)(?:test_[^/]+\.py|[^/]+_test\.(?:py|go))$/i;
3097
3272
  var FILE_TOKEN_RE = /[A-Za-z0-9_./\\-]+\.[A-Za-z0-9]+/g;
3098
3273
  var CODE_TOKEN_RE = /[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)?/g;
3099
3274
  var STOP_WORDS = /* @__PURE__ */ new Set(
@@ -3359,7 +3534,7 @@ async function buildTaskContext(index, task, options = {}) {
3359
3534
  const maxSymbols = clamp2(options.maxSymbols, 1, 50, 10);
3360
3535
  const route = routeTask(task, index);
3361
3536
  let changeContext = null;
3362
- const warnings = [];
3537
+ const warnings = indexIntegrityWarnings(index);
3363
3538
  if (route.includeChangeContext) {
3364
3539
  try {
3365
3540
  changeContext = await buildChangeContext(index, {
@@ -3786,7 +3961,8 @@ var tools = [
3786
3961
  `files indexed: ${index.files.length}`,
3787
3962
  `symbols: ${total}`,
3788
3963
  `languages: ${[...langs].join(", ")}`,
3789
- index.files.length === 0 ? "no supported files found" : "index up to date"
3964
+ index.files.length === 0 ? "no supported files found" : "index up to date",
3965
+ ...indexIntegrityWarnings(index)
3790
3966
  ].join("\n");
3791
3967
  } catch (error) {
3792
3968
  return `code_index: ${error.message ?? String(error)}`;
@@ -4324,6 +4500,7 @@ export {
4324
4500
  findCycles,
4325
4501
  findOrphanModules,
4326
4502
  findRepoRoot,
4503
+ indexIntegrityWarnings,
4327
4504
  inject,
4328
4505
  languageForFile,
4329
4506
  loadIndex,