dsh-code-index 0.9.0 → 0.10.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
@@ -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",
@@ -1056,32 +1171,38 @@ var SUPPORTED_EXTS = /* @__PURE__ */ new Set([
1056
1171
  ".inl",
1057
1172
  ".c"
1058
1173
  ]);
1059
- function isGitIgnoredPath(root, relativePath, cache = /* @__PURE__ */ new Map()) {
1060
- const rel = relativePath.replace(/\\/g, "/").replace(/^\.\//, "");
1174
+ function ignoredByGitLayers(layers, relativePath, isDirectory) {
1175
+ let ignored = false;
1176
+ for (const { base, matcher } of layers) {
1177
+ const scoped = base ? path2.posix.relative(base, relativePath) : relativePath;
1178
+ if (!scoped || scoped === ".." || scoped.startsWith("../")) continue;
1179
+ const result = matcher.test(isDirectory ? `${scoped.replace(/\/$/, "")}/` : scoped);
1180
+ if (result.ignored || result.unignored) ignored = result.ignored;
1181
+ }
1182
+ return ignored;
1183
+ }
1184
+ function isGitIgnoredPath(root, relativePath, cache = /* @__PURE__ */ new Map(), isDirectory = relativePath.endsWith("/")) {
1185
+ const rel = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
1061
1186
  if (!rel) return false;
1062
- const parentParts = rel.split("/").slice(0, -1);
1063
- for (let depth = 0; depth <= parentParts.length; depth += 1) {
1064
- const base = parentParts.slice(0, depth).join("/");
1187
+ const parts = rel.split("/");
1188
+ const layers = [];
1189
+ for (let depth = 0; depth < parts.length; depth += 1) {
1190
+ const base = parts.slice(0, depth).join("/");
1065
1191
  const ignoreFile = path2.join(root, base, ".gitignore");
1066
- let fileStat;
1067
1192
  try {
1068
- fileStat = statSync(ignoreFile);
1069
- } catch {
1070
- continue;
1071
- }
1072
- const previous = cache.get(ignoreFile);
1073
- let matcher = previous?.matcher;
1074
- if (!previous || previous.mtimeMs !== fileStat.mtimeMs || previous.size !== fileStat.size) {
1075
- try {
1193
+ const fileStat = statSync(ignoreFile);
1194
+ const previous = cache.get(ignoreFile);
1195
+ let matcher = previous?.matcher;
1196
+ if (!previous || previous.mtimeMs !== fileStat.mtimeMs || previous.size !== fileStat.size) {
1076
1197
  matcher = ignore().add(readFileSync(ignoreFile, "utf8"));
1077
1198
  cache.set(ignoreFile, { mtimeMs: fileStat.mtimeMs, size: fileStat.size, matcher });
1078
- } catch {
1079
- cache.delete(ignoreFile);
1080
- continue;
1081
1199
  }
1200
+ if (matcher) layers.push({ base, matcher });
1201
+ } catch {
1202
+ cache.delete(ignoreFile);
1082
1203
  }
1083
- const scoped = base ? path2.posix.relative(base, rel) : rel;
1084
- if (matcher?.ignores(scoped)) return true;
1204
+ const probe = parts.slice(0, depth + 1).join("/");
1205
+ if (ignoredByGitLayers(layers, probe, depth < parts.length - 1 || isDirectory)) return true;
1085
1206
  }
1086
1207
  return false;
1087
1208
  }
@@ -1097,10 +1218,6 @@ async function scanRepo(root, options = {}) {
1097
1218
  layers = [...inherited, { base: relDir, matcher: ignore().add(rules) }];
1098
1219
  } catch {
1099
1220
  }
1100
- const ignoredByGit = (relativePath) => layers.some(({ base, matcher }) => {
1101
- const scoped = base ? path2.posix.relative(base, relativePath) : relativePath;
1102
- return scoped !== ".." && !scoped.startsWith("../") && matcher.ignores(scoped);
1103
- });
1104
1221
  let entries;
1105
1222
  try {
1106
1223
  entries = await readdir(absDir, { withFileTypes: true });
@@ -1112,8 +1229,8 @@ async function scanRepo(root, options = {}) {
1112
1229
  const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
1113
1230
  if (entry.isDirectory()) {
1114
1231
  if (excluded.has(entry.name)) continue;
1115
- if (!ignoredByGit(rel)) queue.push([abs, rel, layers]);
1116
- } else if (entry.isFile() && !ignoredByGit(rel) && SUPPORTED_EXTS.has(path2.extname(entry.name).toLowerCase())) {
1232
+ if (!ignoredByGitLayers(layers, rel, true)) queue.push([abs, rel, layers]);
1233
+ } else if (entry.isFile() && !ignoredByGitLayers(layers, rel, false) && SUPPORTED_EXTS.has(path2.extname(entry.name).toLowerCase())) {
1117
1234
  try {
1118
1235
  const st = await stat(abs);
1119
1236
  results.push({ abs, rel, mtimeMs: st.mtimeMs });
@@ -1132,7 +1249,7 @@ import path3 from "path";
1132
1249
  import { createHash, randomUUID } from "crypto";
1133
1250
 
1134
1251
  // src/types.ts
1135
- var REPO_INDEX_SCHEMA_VERSION = 2;
1252
+ var REPO_INDEX_SCHEMA_VERSION = 3;
1136
1253
  function symbolCount(index) {
1137
1254
  return index.files.reduce((n, f) => n + f.symbols.length, 0);
1138
1255
  }
@@ -1196,7 +1313,7 @@ async function buildIndex(root, options = {}, previous = null, forcePaths = /* @
1196
1313
  batch.map(async (f) => {
1197
1314
  const lang = languageForFile(f.abs);
1198
1315
  if (!lang) return null;
1199
- 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("*")) {
1200
1317
  const cached = prevByPath.get(f.rel);
1201
1318
  return {
1202
1319
  ...cached,
@@ -1208,9 +1325,33 @@ async function buildIndex(root, options = {}, previous = null, forcePaths = /* @
1208
1325
  try {
1209
1326
  code = await readFile4(f.abs, "utf8");
1210
1327
  } catch {
1211
- 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
+ };
1212
1353
  }
1213
- const { symbols, imports, importDetails, calls } = await extractAll(code, lang, f.rel);
1354
+ const { symbols, imports, importDetails, calls, diagnostic } = extracted;
1214
1355
  return {
1215
1356
  path: f.rel,
1216
1357
  lang,
@@ -1218,7 +1359,8 @@ async function buildIndex(root, options = {}, previous = null, forcePaths = /* @
1218
1359
  symbols: symbols.map((s) => ({ ...s, file: f.rel })),
1219
1360
  imports,
1220
1361
  importDetails,
1221
- calls
1362
+ calls,
1363
+ diagnostic
1222
1364
  };
1223
1365
  })
1224
1366
  );
@@ -1266,11 +1408,12 @@ function indexesEqual(left, right) {
1266
1408
  return false;
1267
1409
  }
1268
1410
  if (file.symbols.length !== other.symbols.length) return false;
1411
+ if (JSON.stringify(file.diagnostic) !== JSON.stringify(other.diagnostic)) return false;
1269
1412
  if (!callsEqual(file.calls, other.calls)) return false;
1270
1413
  if (!importsEqual(file.importDetails, other.importDetails)) return false;
1271
1414
  return file.symbols.every((symbol, symbolIndex) => {
1272
1415
  const candidate = other.symbols[symbolIndex];
1273
- 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);
1274
1417
  });
1275
1418
  });
1276
1419
  }
@@ -1302,7 +1445,7 @@ function callsEqual(left, right) {
1302
1445
  if (a.length !== b.length) return false;
1303
1446
  return a.every((call, index) => {
1304
1447
  const other = b[index];
1305
- 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;
1306
1449
  });
1307
1450
  }
1308
1451
  async function findRepoRoot(startDir, maxLevels = 12) {
@@ -1320,6 +1463,21 @@ async function findRepoRoot(startDir, maxLevels = 12) {
1320
1463
  return null;
1321
1464
  }
1322
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
+
1323
1481
  // src/repomap.ts
1324
1482
  import path5 from "path";
1325
1483
  var KIND_WEIGHT = {
@@ -1334,7 +1492,7 @@ var KIND_WEIGHT = {
1334
1492
  import: 0.05,
1335
1493
  module: 0.3
1336
1494
  };
1337
- 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))$/;
1338
1496
  function scoreFile(file) {
1339
1497
  let score = 0;
1340
1498
  for (const sym of file.symbols) {
@@ -1426,6 +1584,7 @@ function referencePageRank(files, density) {
1426
1584
  }
1427
1585
  function resolveImportDetailed(spec, fromPath, fileSet) {
1428
1586
  if (!spec) return { target: null, suffixSkipped: false };
1587
+ if (/^@(ohos|kit)\./.test(spec)) return { target: null, suffixSkipped: false };
1429
1588
  if (spec.startsWith("./") || spec.startsWith("../")) {
1430
1589
  const base = path5.posix.normalize(path5.posix.join(path5.posix.dirname(fromPath), spec));
1431
1590
  return { target: candidateFor(base, fileSet), suffixSkipped: false };
@@ -1442,11 +1601,13 @@ function resolveImport(spec, fromPath, fileSet) {
1442
1601
  }
1443
1602
  function candidateFor(base, fileSet) {
1444
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;
1445
1605
  for (const ext of SUPPORTED_EXTS) {
1446
1606
  if (fileSet.has(`${base}${ext}`)) return `${base}${ext}`;
1447
1607
  }
1448
1608
  const aliased = tsSourceForJsSpecifier(base, fileSet);
1449
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;
1450
1611
  for (const ext of SUPPORTED_EXTS) {
1451
1612
  if (fileSet.has(`${base}/index${ext}`)) return `${base}/index${ext}`;
1452
1613
  if (fileSet.has(`${base}/__init__${ext}`)) return `${base}/__init__${ext}`;
@@ -1512,20 +1673,31 @@ function symbolRefs(index, symbol, limit = 200) {
1512
1673
  const defs = table.get(symbol) ?? [];
1513
1674
  const callers = [];
1514
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
+ }
1515
1685
  for (const file of index.files) {
1516
1686
  for (const call of file.calls ?? []) {
1517
- 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) {
1518
1689
  callers.push({ file: file.path, line: call.line, from: call.from });
1519
1690
  }
1520
1691
  if (call.from === symbol && !calleeFirstLine.has(call.name)) {
1521
1692
  calleeFirstLine.set(call.name, call.line);
1522
1693
  }
1694
+ if (call.from === symbol) arktsTargets.set(call.name, dedupeSymbols([...arktsTargets.get(call.name) ?? [], ...resolved ?? table.get(call.name) ?? []]));
1523
1695
  }
1524
1696
  }
1525
1697
  callers.sort(
1526
1698
  (a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.from.localeCompare(b.from)
1527
1699
  );
1528
- 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));
1529
1701
  return {
1530
1702
  defs,
1531
1703
  callers: callers.slice(0, limit),
@@ -1572,7 +1744,7 @@ function resolveCallTargetsFor(fileSet, file, call, symbolsByName, symbolsByFile
1572
1744
  const qualified = call.qualifier !== null;
1573
1745
  const imports = (file.importDetails ?? []).filter((info) => info.kind !== "reexport");
1574
1746
  if (!qualified) {
1575
- 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))));
1576
1748
  if (locals.length > 0) {
1577
1749
  const unique2 = locals.length === 1;
1578
1750
  const owned = unique2 && isStructuralOwner(call, locals[0]);
@@ -1602,7 +1774,7 @@ function resolveCallTargetsFor(fileSet, file, call, symbolsByName, symbolsByFile
1602
1774
  }
1603
1775
  if (!isExplicitBinding(binding)) continue;
1604
1776
  for (const def of symbolsByName.get(binding.imported) ?? []) {
1605
- 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);
1606
1778
  }
1607
1779
  }
1608
1780
  }
@@ -1610,6 +1782,7 @@ function resolveCallTargetsFor(fileSet, file, call, symbolsByName, symbolsByFile
1610
1782
  const label = bound.length === 1 && directBinding && !defaultBinding ? "exact" : "import-scoped";
1611
1783
  return toEdges(bound, call, file.path, "explicit-import-binding", label, maxCandidates);
1612
1784
  }
1785
+ if (call.context === "arkui") return [];
1613
1786
  const resolvedModules = /* @__PURE__ */ new Set();
1614
1787
  for (const info of imports) {
1615
1788
  const target = resolveImport(info.specifier, file.path, fileSet);
@@ -1824,7 +1997,7 @@ function findCycles(index) {
1824
1997
  return components;
1825
1998
  }
1826
1999
  var ENTRY_RE = /(^|\/)(index|main|cli|app|server|mod|lib|__init__)\.[A-Za-z0-9]+$|(^|\/)(bin|cmd|scripts)\/|\.config\.[A-Za-z0-9]+$/;
1827
- 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))$/;
1828
2001
  function findOrphanModules(index) {
1829
2002
  const { nodes, edges } = buildModuleGraph(index);
1830
2003
  const incoming = /* @__PURE__ */ new Map();
@@ -2136,7 +2309,7 @@ var LABEL_STRENGTH = {
2136
2309
  };
2137
2310
  var ENTRY_KINDS = /* @__PURE__ */ new Set(["function", "method", "class"]);
2138
2311
  var ENTRY_FILE_RE = /(^|\/)(main|index|cli|server|app)\.[^./]+$/;
2139
- 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))$/;
2140
2313
  function changeContextInputFromArgs(args, root) {
2141
2314
  const hasDiff = typeof args.diff === "string" && args.diff.trim().length > 0;
2142
2315
  const hasFiles = Array.isArray(args.files) && args.files.length > 0;
@@ -2186,7 +2359,7 @@ async function buildChangeContext(index, input, options = {}) {
2186
2359
  const fileSet = new Set(filesByPath.keys());
2187
2360
  const incomingCount = /* @__PURE__ */ new Map();
2188
2361
  for (const [id, edges] of graph.incoming) incomingCount.set(id, edges.length);
2189
- const warnings = [];
2362
+ const warnings = indexIntegrityWarnings(index);
2190
2363
  const changedByKey = /* @__PURE__ */ new Map();
2191
2364
  const unmapped = [];
2192
2365
  const changedFiles = /* @__PURE__ */ new Set();
@@ -2263,6 +2436,7 @@ async function buildChangeContext(index, input, options = {}) {
2263
2436
  const lang = languageForFile(filePath);
2264
2437
  if (!lang) return null;
2265
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`);
2266
2440
  return extracted.symbols;
2267
2441
  })();
2268
2442
  baseSymbolsCache.set(filePath, pending);
@@ -2928,7 +3102,7 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
2928
3102
  const graph = buildReferenceGraph(index);
2929
3103
  const candidates = [];
2930
3104
  const seen = /* @__PURE__ */ new Set();
2931
- const gaps = [];
3105
+ const gaps = [...context.warnings];
2932
3106
  const unavailable = /* @__PURE__ */ new Set();
2933
3107
  const source = async (symbol, kind, reason, extra = {}, focus) => {
2934
3108
  const side = extra.side ?? "current";
@@ -3044,7 +3218,7 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
3044
3218
  task: context.task.slice(0, 120),
3045
3219
  route: context.route.kind,
3046
3220
  items: [],
3047
- gaps: ["budget omitted some evidence"],
3221
+ gaps: context.warnings.some((warning) => warning.startsWith("index incomplete:")) ? ["index incomplete; inspect code_index"] : ["budget omitted some evidence"],
3048
3222
  budget: { budgetChars, usedChars: 0, packChars: 0, truncated: context.task.length > 120 }
3049
3223
  };
3050
3224
  const fits = () => {
@@ -3053,7 +3227,10 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
3053
3227
  };
3054
3228
  if (!fits()) {
3055
3229
  pack.task = "";
3056
- pack.gaps = [];
3230
+ fits();
3231
+ }
3232
+ if (!fits()) {
3233
+ pack.gaps = pack.gaps.filter((gap) => gap.startsWith("index incomplete"));
3057
3234
  fits();
3058
3235
  }
3059
3236
  if (!fits()) throw new Error("budget too small for repository identity");
@@ -3073,7 +3250,7 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
3073
3250
  }
3074
3251
  if (!added) pack.budget.truncated = true;
3075
3252
  }
3076
- if (!pack.budget.truncated) pack.gaps = [];
3253
+ if (!pack.budget.truncated) pack.gaps = pack.gaps.filter((gap) => gap.startsWith("index incomplete"));
3077
3254
  for (const item of pack.items) if (item.file && unavailable.has(`${item.side}:${item.file}`)) {
3078
3255
  gaps.push(`source unavailable or changed after indexing: ${item.file} (${item.side})`);
3079
3256
  }
@@ -3091,7 +3268,7 @@ async function selectContextPack(index, context, budgetChars, maxFiles = 12) {
3091
3268
  }
3092
3269
 
3093
3270
  // src/context.ts
3094
- 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;
3095
3272
  var FILE_TOKEN_RE = /[A-Za-z0-9_./\\-]+\.[A-Za-z0-9]+/g;
3096
3273
  var CODE_TOKEN_RE = /[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)?/g;
3097
3274
  var STOP_WORDS = /* @__PURE__ */ new Set(
@@ -3357,7 +3534,7 @@ async function buildTaskContext(index, task, options = {}) {
3357
3534
  const maxSymbols = clamp2(options.maxSymbols, 1, 50, 10);
3358
3535
  const route = routeTask(task, index);
3359
3536
  let changeContext = null;
3360
- const warnings = [];
3537
+ const warnings = indexIntegrityWarnings(index);
3361
3538
  if (route.includeChangeContext) {
3362
3539
  try {
3363
3540
  changeContext = await buildChangeContext(index, {
@@ -3645,9 +3822,9 @@ var RepoContextManager = class {
3645
3822
  attachWatcher(context) {
3646
3823
  if (!this.options.watch) return;
3647
3824
  const options = this.options.indexOptions();
3648
- const ignored = (absolute) => {
3825
+ const ignored = (absolute, stats) => {
3649
3826
  const rel = path9.relative(context.root, absolute);
3650
- return rel === ".." || rel.startsWith(`..${path9.sep}`) || path9.isAbsolute(rel) || ignoredRelativePath(rel, options) || isGitIgnoredPath(context.root, rel, context.ignoreMatcherCache);
3827
+ return rel === ".." || rel.startsWith(`..${path9.sep}`) || path9.isAbsolute(rel) || ignoredRelativePath(rel, options) || stats !== void 0 && isGitIgnoredPath(context.root, rel, context.ignoreMatcherCache, stats.isDirectory());
3651
3828
  };
3652
3829
  const watcher = chokidar.watch(context.root, {
3653
3830
  ignored,
@@ -3784,7 +3961,8 @@ var tools = [
3784
3961
  `files indexed: ${index.files.length}`,
3785
3962
  `symbols: ${total}`,
3786
3963
  `languages: ${[...langs].join(", ")}`,
3787
- 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)
3788
3966
  ].join("\n");
3789
3967
  } catch (error) {
3790
3968
  return `code_index: ${error.message ?? String(error)}`;
@@ -4322,6 +4500,7 @@ export {
4322
4500
  findCycles,
4323
4501
  findOrphanModules,
4324
4502
  findRepoRoot,
4503
+ indexIntegrityWarnings,
4325
4504
  inject,
4326
4505
  languageForFile,
4327
4506
  loadIndex,