@zuvia-software-solutions/code-mapper 2.6.4 → 2.7.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.
@@ -147,6 +147,113 @@ function extractParamNames(content) {
147
147
  .map(p => expandIdentifier(p))
148
148
  .join(', ');
149
149
  }
150
+ /**
151
+ * Extract domain-relevant content from node source code.
152
+ * Pulls string literals, object keys, numbers, and identifiers that carry
153
+ * semantic meaning — DI tokens, event names, FSM states, permissions,
154
+ * error codes, route paths, feature flags, etc.
155
+ */
156
+ function extractContentKeywords(content) {
157
+ if (!content)
158
+ return '';
159
+ const keywords = new Set();
160
+ // 1. String literals: 'booking.created', 'leads.create', '/api/bookings', 'INVOICE_NOT_FOUND'
161
+ // Also match template literal parts before ${}: `Invoice ${id} not found`
162
+ const stringLiterals = content.match(/['"`]([^'"`\n]{2,120}?)['"`$]/g);
163
+ if (stringLiterals) {
164
+ for (const lit of stringLiterals) {
165
+ const val = lit.slice(1, -1);
166
+ // Skip imports and actual file paths (must have / or start with ./)
167
+ if (val.includes('from ') || (/\.\w{1,4}$/.test(val) && val.includes('/')))
168
+ continue;
169
+ // Split on dots, underscores, hyphens, slashes, spaces → individual words
170
+ const words = val.split(/[._\-/\s]/).map(w => w.trim().toLowerCase()).filter(w => w.length >= 2 && !/^\d+$/.test(w));
171
+ for (const w of words)
172
+ keywords.add(w);
173
+ }
174
+ }
175
+ // 2. Object keys (identifier: value patterns) — expand camelCase/PascalCase
176
+ const objKeys = content.match(/(?:^|[{,\n])\s*(\w{2,})\s*[=:]/gm);
177
+ if (objKeys) {
178
+ for (const k of objKeys) {
179
+ const name = k.replace(/^[{,\n\s]+/, '').replace(/\s*[=:]$/, '').trim();
180
+ if (name.length >= 2 && !/^(const|export|return|type|let|var|if|else|new|async|await|function|class|interface)$/.test(name)) {
181
+ const expanded = expandIdentifier(name);
182
+ for (const w of expanded.split(' '))
183
+ if (w.length >= 2)
184
+ keywords.add(w);
185
+ }
186
+ }
187
+ }
188
+ // 3. Member access targets: TOKENS.BookingsRepository, S.PENDING → extract after dot
189
+ const memberAccess = content.match(/\w+\.([A-Z]\w{2,})/g);
190
+ if (memberAccess) {
191
+ for (const m of memberAccess) {
192
+ const target = m.split('.').pop();
193
+ const expanded = expandIdentifier(target);
194
+ for (const w of expanded.split(' '))
195
+ if (w.length >= 2)
196
+ keywords.add(w);
197
+ }
198
+ }
199
+ // 4. Numeric literals that look like HTTP codes or business constants
200
+ const numbers = content.match(/\b([2-5]\d{2})\b/g);
201
+ if (numbers) {
202
+ for (const n of numbers)
203
+ keywords.add(n);
204
+ }
205
+ // 5. UPPER_CASE constants (error codes, permission names, status names)
206
+ // Keep both the full constant (e.g. "invoice_not_found") and individual words
207
+ const upperConsts = content.match(/\b[A-Z][A-Z0-9_]{2,}\b/g);
208
+ if (upperConsts) {
209
+ for (const c of upperConsts) {
210
+ if (/^(GET|POST|PUT|DELETE|NULL|TRUE|FALSE|AND|NOT|SQL)$/.test(c))
211
+ continue;
212
+ // Full constant name as one token (for exact matching)
213
+ keywords.add(c.toLowerCase().replace(/_/g, '_'));
214
+ // Also expanded (for word matching)
215
+ const expanded = c.toLowerCase().replace(/_/g, ' ');
216
+ for (const w of expanded.split(' '))
217
+ if (w.length >= 2)
218
+ keywords.add(w);
219
+ }
220
+ }
221
+ // Common English stop words that appear in code as string literals (e.g. stop word lists)
222
+ const EMBED_STOP = new Set([
223
+ 'the', 'and', 'for', 'from', 'with', 'this', 'that', 'have', 'has', 'not', 'are', 'was',
224
+ 'were', 'been', 'being', 'will', 'would', 'could', 'should', 'may', 'might', 'can',
225
+ 'does', 'did', 'let', 'var', 'new', 'return', 'else', 'case', 'break', 'while',
226
+ 'throw', 'catch', 'try', 'finally', 'typeof', 'instanceof', 'void', 'null',
227
+ 'undefined', 'true', 'false', 'require', 'string', 'number', 'boolean', 'object',
228
+ 'any', 'never', 'unknown', 'symbol', 'import', 'export', 'default', 'const',
229
+ 'function', 'class', 'interface', 'type', 'enum', 'extends', 'implements',
230
+ 'static', 'private', 'public', 'protected', 'abstract', 'readonly', 'async',
231
+ 'await', 'yield', 'delete', 'switch', 'continue', 'declare', 'module',
232
+ 'namespace', 'override',
233
+ ]);
234
+ // Clean: remove noise + stop words, dedupe, cap at 30
235
+ const cleaned = [];
236
+ const seen = new Set();
237
+ for (const kw of keywords) {
238
+ if (kw.length < 2 || kw.length > 40)
239
+ continue;
240
+ if (seen.has(kw))
241
+ continue;
242
+ if (EMBED_STOP.has(kw))
243
+ continue;
244
+ if (/[\x00-\x1f\\{}$`\u2588\u2591]/.test(kw))
245
+ continue;
246
+ if (/^[^a-z0-9]+$/.test(kw))
247
+ continue;
248
+ if (/[(\[<>)\]]/.test(kw))
249
+ continue;
250
+ if (/^\d+$/.test(kw) && !/^[2-5]\d{2}$/.test(kw))
251
+ continue;
252
+ seen.add(kw);
253
+ cleaned.push(kw);
254
+ }
255
+ return cleaned.slice(0, 30).join(' ');
256
+ }
150
257
  /** Strip noise tokens that waste tokenizer budget without adding semantic value */
151
258
  function condense(text) {
152
259
  return text
@@ -154,28 +261,47 @@ function condense(text) {
154
261
  .replace(/[{}[\]()'",;:]/g, '') // punctuation
155
262
  .replace(/\. /g, ' ') // sentence separators
156
263
  .replace(/\s{2,}/g, ' ') // collapse whitespace
264
+ .split(' ')
265
+ .filter(w => !CONDENSE_STOP.has(w.toLowerCase()))
266
+ .join(' ')
157
267
  .trim();
158
268
  }
269
+ /** Words to strip from condensed embedding text — TS/JS keywords and common English stop words */
270
+ const CONDENSE_STOP = new Set([
271
+ 'the', 'and', 'for', 'from', 'with', 'this', 'that', 'have', 'has', 'not', 'are', 'was',
272
+ 'were', 'been', 'being', 'will', 'would', 'could', 'should', 'may', 'might', 'can',
273
+ 'does', 'did', 'let', 'var', 'new', 'return', 'else', 'case', 'break', 'while',
274
+ 'throw', 'catch', 'try', 'finally', 'typeof', 'instanceof', 'void', 'null',
275
+ 'undefined', 'true', 'false', 'require', 'string', 'number', 'boolean', 'object',
276
+ 'any', 'never', 'unknown', 'symbol', 'import', 'export', 'default', 'const',
277
+ 'function', 'class', 'interface', 'type', 'enum', 'extends', 'implements',
278
+ 'static', 'private', 'public', 'protected', 'abstract', 'readonly', 'async',
279
+ 'await', 'yield', 'delete', 'switch', 'continue', 'declare', 'module',
280
+ 'namespace', 'override',
281
+ ]);
159
282
  /** Build NL documents from a node — keyword-dense, minimal tokens */
160
283
  export function extractNlTexts(node) {
161
284
  const docs = [];
162
285
  const expandedName = expandIdentifier(node.name);
163
286
  const dir = node.filePath.split('/').slice(-3, -1).join('/');
287
+ const contentKw = extractContentKeywords(node.content);
164
288
  // 1. Comment-based NL text (primary)
165
289
  const comment = extractFullComment(node.content);
166
290
  if (comment) {
167
291
  docs.push({
168
292
  nodeId: node.id,
169
293
  source: 'comment',
170
- text: condense(`${expandedName} ${comment} ${dir}`),
294
+ text: condense(`${expandedName} ${comment} ${contentKw} ${dir}`),
171
295
  });
172
296
  }
173
- // 2. Name + params (always available)
297
+ // 2. Name + params + content keywords (always available)
174
298
  const params = extractParamNames(node.content);
175
299
  if (!comment) {
176
300
  const parts = [expandedName];
177
301
  if (params)
178
302
  parts.push(params);
303
+ if (contentKw)
304
+ parts.push(contentKw);
179
305
  if (dir)
180
306
  parts.push(dir);
181
307
  docs.push({
@@ -216,10 +342,10 @@ export async function buildNlEmbeddings(db, onProgress) {
216
342
  const labels = ['Function', 'Class', 'Method', 'Interface', 'Const', 'Enum', 'TypeAlias', 'Namespace', 'Module', 'Struct'];
217
343
  const placeholders = labels.map(() => '?').join(',');
218
344
  const rows = db.prepare(`SELECT id, name, label, filePath, content, startLine, description FROM nodes WHERE label IN (${placeholders})`).all(...labels);
219
- // NL embeddings include ALL files (including tests) test names describe
220
- // functionality in natural language which helps conceptual search.
221
- // The bge-small model is fast enough (~6ms/doc) that the cost is trivial.
222
- const filteredRows = rows;
345
+ // Skip test/fixture files BM25 keyword search already covers them well.
346
+ // Saves ~40% embedding time on test-heavy codebases.
347
+ const { isTestFile } = await import('./types.js');
348
+ const filteredRows = rows.filter(r => !isTestFile(r.filePath));
223
349
  // Extract NL documents
224
350
  const allDocs = [];
225
351
  for (const row of filteredRows) {
@@ -440,3 +566,4 @@ export async function buildNlEmbeddings(db, onProgress) {
440
566
  }
441
567
  return { embedded, skipped, durationMs: Date.now() - t0 };
442
568
  }
569
+ // touch
@@ -311,6 +311,99 @@ export async function refreshFiles(db, repoPath, dirtyFiles) {
311
311
  }
312
312
  }
313
313
  // FTS5 auto-updates via triggers — no manual rebuild needed
314
+ // Phase 5: Rebuild graph-level analyses (communities, processes, interface dispatch)
315
+ // These are cheap (<300ms) but critical — stale communities/processes mislead agents.
316
+ // Load full graph from SQLite, re-run analyses, write results back.
317
+ try {
318
+ const { createKnowledgeGraph } = await import('../graph/graph.js');
319
+ const { processCommunities } = await import('../ingestion/community-processor.js');
320
+ const { processProcesses } = await import('../ingestion/process-processor.js');
321
+ const { insertNodesBatch, insertEdgesBatch } = await import('../db/adapter.js');
322
+ const { toNodeId, toEdgeId } = await import('../db/schema.js');
323
+ const graph = createKnowledgeGraph();
324
+ // Load all non-community/process nodes and edges into in-memory graph
325
+ const allNodes = db.prepare('SELECT * FROM nodes WHERE label NOT IN (\'Community\', \'Process\')').all();
326
+ const allEdges = db.prepare('SELECT * FROM edges WHERE type NOT IN (\'MEMBER_OF\', \'STEP_IN_PROCESS\')').all();
327
+ for (const row of allNodes) {
328
+ graph.addNode({
329
+ id: toNodeId(row.id),
330
+ label: row.label,
331
+ properties: {
332
+ name: row.name ?? '', filePath: row.filePath ?? '',
333
+ startLine: row.startLine ?? undefined, endLine: row.endLine ?? undefined,
334
+ isExported: Boolean(row.isExported),
335
+ description: row.description ?? undefined,
336
+ parameterCount: row.parameterCount ?? undefined,
337
+ returnType: row.returnType ?? undefined,
338
+ },
339
+ });
340
+ }
341
+ for (const row of allEdges) {
342
+ graph.addRelationship({
343
+ id: toEdgeId(row.id),
344
+ sourceId: toNodeId(row.sourceId),
345
+ targetId: toNodeId(row.targetId),
346
+ type: row.type,
347
+ confidence: row.confidence ?? 1.0,
348
+ reason: row.reason ?? '',
349
+ });
350
+ }
351
+ // Delete old community/process data from SQLite
352
+ db.exec('BEGIN');
353
+ db.prepare("DELETE FROM edges WHERE type IN ('MEMBER_OF', 'STEP_IN_PROCESS')").run();
354
+ db.prepare("DELETE FROM nodes WHERE label IN ('Community', 'Process')").run();
355
+ // Re-run community detection + process detection
356
+ const communityResult = await processCommunities(graph, () => { });
357
+ const processResult = await processProcesses(graph, communityResult.memberships, () => { });
358
+ // Write new community/process nodes + edges back to SQLite
359
+ const newNodes = [];
360
+ const newEdges = [];
361
+ for (const node of graph.iterNodes()) {
362
+ if (node.label === 'Community' || node.label === 'Process') {
363
+ newNodes.push({
364
+ id: node.id,
365
+ label: node.label,
366
+ name: node.properties.name ?? '',
367
+ filePath: node.properties.filePath ?? '',
368
+ heuristicLabel: node.properties.heuristicLabel ?? null,
369
+ cohesion: node.properties.cohesion ?? null,
370
+ symbolCount: node.properties.symbolCount ?? null,
371
+ keywords: Array.isArray(node.properties.keywords) ? node.properties.keywords.join(', ') : node.properties.keywords ?? null,
372
+ processType: node.properties.processType ?? null,
373
+ stepCount: node.properties.stepCount ?? null,
374
+ communities: node.properties.communities ?? null,
375
+ entryPointId: node.properties.entryPointId ?? null,
376
+ terminalId: node.properties.terminalId ?? null,
377
+ });
378
+ }
379
+ }
380
+ for (const rel of graph.iterRelationships()) {
381
+ if (rel.type === 'MEMBER_OF' || rel.type === 'STEP_IN_PROCESS') {
382
+ newEdges.push({
383
+ id: rel.id,
384
+ sourceId: rel.sourceId,
385
+ targetId: rel.targetId,
386
+ type: rel.type,
387
+ confidence: rel.confidence,
388
+ reason: rel.reason,
389
+ step: rel.step,
390
+ });
391
+ }
392
+ }
393
+ if (newNodes.length > 0)
394
+ insertNodesBatch(db, newNodes);
395
+ if (newEdges.length > 0)
396
+ insertEdgesBatch(db, newEdges);
397
+ db.exec('COMMIT');
398
+ console.error(`Code Mapper: refresh Phase 5 — ${communityResult.communities.length} communities, ${processResult.stats.totalProcesses} processes rebuilt`);
399
+ }
400
+ catch (err) {
401
+ try {
402
+ db.exec('ROLLBACK');
403
+ }
404
+ catch { }
405
+ console.error(`Code Mapper: Phase 5 graph rebuild failed: ${err instanceof Error ? err.message : err}`);
406
+ }
314
407
  return {
315
408
  filesProcessed: filesToProcess.length, filesSkipped,
316
409
  nodesDeleted, nodesInserted, edgesInserted,
@@ -4,7 +4,7 @@
4
4
  * some grammars (typescript vs tsx vs javascript) have slightly different node types
5
5
  */
6
6
  import { SupportedLanguages } from '../../config/supported-languages.js';
7
- export declare const TYPESCRIPT_QUERIES = "\n(class_declaration\n name: (type_identifier) @name) @definition.class\n\n(abstract_class_declaration\n name: (type_identifier) @name) @definition.class\n\n(interface_declaration\n name: (type_identifier) @name) @definition.interface\n\n(type_alias_declaration\n name: (type_identifier) @name) @definition.type\n\n(enum_declaration\n name: (identifier) @name) @definition.enum\n\n(function_declaration\n name: (identifier) @name) @definition.function\n\n(method_definition\n name: (property_identifier) @name) @definition.method\n\n; Interface method signatures: interface Foo { method(): void }\n(method_signature\n name: (property_identifier) @name) @definition.method\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function)))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression)))) @definition.function\n\n; Namespace / internal module\n(internal_module\n name: (identifier) @name) @definition.namespace\n\n; Ambient declarations: declare function foo(): void\n(ambient_declaration\n (function_signature\n name: (identifier) @name)) @definition.function\n\n(import_statement\n source: (string) @import.source) @import\n\n; Re-export statements: export { X } from './y'\n(export_statement\n source: (string) @import.source) @import\n\n(call_expression\n function: (identifier) @call.name) @call\n\n(call_expression\n function: (member_expression\n property: (property_identifier) @call.name)) @call\n\n; Await calls: await foo() \u2014 tree-sitter wraps await inside the function position\n(call_expression\n function: (await_expression\n (identifier) @call.name)) @call\n\n; Await member calls: await obj.method()\n(call_expression\n function: (await_expression\n (member_expression\n property: (property_identifier) @call.name))) @call\n\n; B3: Dynamic import calls: const { foo } = await import('./module.js')\n(call_expression\n function: (import) @call.name) @call\n\n; Constructor calls: new Foo()\n(new_expression\n constructor: (identifier) @call.name) @call\n\n; Exported const from call: export const Schema = z.object({...})\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression)))) @definition.const\n\n; Exported const from new: export const client = new PrismaClient()\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (new_expression)))) @definition.const\n\n; Module-level const from new: const prisma = new PrismaClient()\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (new_expression))) @definition.const\n\n; Exported const from array: export const EDGE_TYPES = [...] as const\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (as_expression)))) @definition.const\n\n; Exported const from array literal: export const FOO = [1, 2, 3]\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (array)))) @definition.const\n\n; Exported const from object literal: export const CONFIG = { port: 3000 }\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (object)))) @definition.const\n\n; Exported const from number/string: export const MAX_DEPTH = 10\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (number)))) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (string)))) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (template_string)))) @definition.const\n\n; Object literal arrow function methods: const x = { method: () => {} }\n(pair\n key: (property_identifier) @name\n value: (arrow_function)) @definition.function\n\n; Object literal function expression methods: const x = { method: function() {} }\n(pair\n key: (property_identifier) @name\n value: (function_expression)) @definition.function\n\n; Heritage queries - class extends\n(class_declaration\n name: (type_identifier) @heritage.class\n (class_heritage\n (extends_clause\n value: (identifier) @heritage.extends))) @heritage\n\n; Heritage queries - class implements interface\n(class_declaration\n name: (type_identifier) @heritage.class\n (class_heritage\n (implements_clause\n (type_identifier) @heritage.implements))) @heritage.impl\n";
7
+ export declare const TYPESCRIPT_QUERIES = "\n(class_declaration\n name: (type_identifier) @name) @definition.class\n\n(abstract_class_declaration\n name: (type_identifier) @name) @definition.class\n\n(interface_declaration\n name: (type_identifier) @name) @definition.interface\n\n(type_alias_declaration\n name: (type_identifier) @name) @definition.type\n\n(enum_declaration\n name: (identifier) @name) @definition.enum\n\n(function_declaration\n name: (identifier) @name) @definition.function\n\n(method_definition\n name: (property_identifier) @name) @definition.method\n\n; Interface method signatures: interface Foo { method(): void }\n(method_signature\n name: (property_identifier) @name) @definition.method\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function)))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression)))) @definition.function\n\n; Namespace / internal module\n(internal_module\n name: (identifier) @name) @definition.namespace\n\n; Ambient declarations: declare function foo(): void\n(ambient_declaration\n (function_signature\n name: (identifier) @name)) @definition.function\n\n(import_statement\n source: (string) @import.source) @import\n\n; Re-export statements: export { X } from './y'\n(export_statement\n source: (string) @import.source) @import\n\n(call_expression\n function: (identifier) @call.name) @call\n\n(call_expression\n function: (member_expression\n property: (property_identifier) @call.name)) @call\n\n; Await calls: await foo() \u2014 tree-sitter wraps await inside the function position\n(call_expression\n function: (await_expression\n (identifier) @call.name)) @call\n\n; Await member calls: await obj.method()\n(call_expression\n function: (await_expression\n (member_expression\n property: (property_identifier) @call.name))) @call\n\n; B3: Dynamic import calls: const { foo } = await import('./module.js')\n(call_expression\n function: (import) @call.name) @call\n\n; Constructor calls: new Foo()\n(new_expression\n constructor: (identifier) @call.name) @call\n\n; Exported const from call: export const Schema = z.object({...})\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression)))) @definition.const\n\n; Exported const from new: export const client = new PrismaClient()\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (new_expression)))) @definition.const\n\n; Module-level const from new: const prisma = new PrismaClient()\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (new_expression))) @definition.const\n\n; Exported const from as_expression: export const EDGE_TYPES = [...] as const\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (as_expression)))) @definition.const\n\n; Exported const from satisfies_expression: export const X = {...} as const satisfies Record<...>\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (satisfies_expression)))) @definition.const\n\n; Exported const from array literal: export const FOO = [1, 2, 3]\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (array)))) @definition.const\n\n; Exported const from object literal: export const CONFIG = { port: 3000 }\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (object)))) @definition.const\n\n; Exported const from number/string: export const MAX_DEPTH = 10\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (number)))) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (string)))) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (template_string)))) @definition.const\n\n; Object literal arrow function methods: const x = { method: () => {} }\n(pair\n key: (property_identifier) @name\n value: (arrow_function)) @definition.function\n\n; Object literal function expression methods: const x = { method: function() {} }\n(pair\n key: (property_identifier) @name\n value: (function_expression)) @definition.function\n\n; Heritage queries - class extends\n(class_declaration\n name: (type_identifier) @heritage.class\n (class_heritage\n (extends_clause\n value: (identifier) @heritage.extends))) @heritage\n\n; Heritage queries - class implements interface\n(class_declaration\n name: (type_identifier) @heritage.class\n (class_heritage\n (implements_clause\n (type_identifier) @heritage.implements))) @heritage.impl\n";
8
8
  export declare const JAVASCRIPT_QUERIES = "\n(class_declaration\n name: (identifier) @name) @definition.class\n\n(function_declaration\n name: (identifier) @name) @definition.function\n\n(method_definition\n name: (property_identifier) @name) @definition.method\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function)))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression)))) @definition.function\n\n(import_statement\n source: (string) @import.source) @import\n\n; Re-export statements: export { X } from './y'\n(export_statement\n source: (string) @import.source) @import\n\n(call_expression\n function: (identifier) @call.name) @call\n\n(call_expression\n function: (member_expression\n property: (property_identifier) @call.name)) @call\n\n; Await calls: await foo()\n(call_expression\n function: (await_expression\n (identifier) @call.name)) @call\n\n; Await member calls: await obj.method()\n(call_expression\n function: (await_expression\n (member_expression\n property: (property_identifier) @call.name))) @call\n\n; B3: Dynamic import calls: const { foo } = await import('./module.js')\n(call_expression\n function: (import) @call.name) @call\n\n; Constructor calls: new Foo()\n(new_expression\n constructor: (identifier) @call.name) @call\n\n; Exported const from call: export const schema = z.object({...})\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression)))) @definition.const\n\n; Exported const from new: export const client = new Client()\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (new_expression)))) @definition.const\n\n; Module-level const from new: const instance = new Class()\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (new_expression))) @definition.const\n\n; Exported const from array: export const ITEMS = [...]\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (array)))) @definition.const\n\n; Exported const from object: export const CONFIG = { ... }\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (object)))) @definition.const\n\n; Exported const from number/string\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (number)))) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (string)))) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (template_string)))) @definition.const\n\n; Object literal arrow function methods: const x = { method: () => {} }\n(pair\n key: (property_identifier) @name\n value: (arrow_function)) @definition.function\n\n; Object literal function expression methods: const x = { method: function() {} }\n(pair\n key: (property_identifier) @name\n value: (function_expression)) @definition.function\n\n; Heritage queries - class extends (JavaScript uses different AST than TypeScript)\n; In tree-sitter-javascript, class_heritage directly contains the parent identifier\n(class_declaration\n name: (identifier) @heritage.class\n (class_heritage\n (identifier) @heritage.extends)) @heritage\n";
9
9
  export declare const PYTHON_QUERIES = "\n(class_definition\n name: (identifier) @name) @definition.class\n\n(function_definition\n name: (identifier) @name) @definition.function\n\n(import_statement\n name: (dotted_name) @import.source) @import\n\n(import_from_statement\n module_name: (dotted_name) @import.source) @import\n\n(import_from_statement\n module_name: (relative_import) @import.source) @import\n\n(call\n function: (identifier) @call.name) @call\n\n(call\n function: (attribute\n attribute: (identifier) @call.name)) @call\n\n; Heritage queries - Python class inheritance\n(class_definition\n name: (identifier) @heritage.class\n superclasses: (argument_list\n (identifier) @heritage.extends)) @heritage\n";
10
10
  export declare const JAVA_QUERIES = "\n; Classes, Interfaces, Enums, Annotations\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(enum_declaration name: (identifier) @name) @definition.enum\n(annotation_type_declaration name: (identifier) @name) @definition.annotation\n\n; Methods & Constructors\n(method_declaration name: (identifier) @name) @definition.method\n(constructor_declaration name: (identifier) @name) @definition.constructor\n\n; Imports - capture any import declaration child as source\n(import_declaration (_) @import.source) @import\n\n; Calls\n(method_invocation name: (identifier) @call.name) @call\n(method_invocation object: (_) name: (identifier) @call.name) @call\n\n; Constructor calls: new Foo()\n(object_creation_expression type: (type_identifier) @call.name) @call\n\n; Heritage - extends class\n(class_declaration name: (identifier) @heritage.class\n (superclass (type_identifier) @heritage.extends)) @heritage\n\n; Heritage - implements interfaces\n(class_declaration name: (identifier) @heritage.class\n (super_interfaces (type_list (type_identifier) @heritage.implements))) @heritage.impl\n";
@@ -116,13 +116,20 @@ export const TYPESCRIPT_QUERIES = `
116
116
  name: (identifier) @name
117
117
  value: (new_expression))) @definition.const
118
118
 
119
- ; Exported const from array: export const EDGE_TYPES = [...] as const
119
+ ; Exported const from as_expression: export const EDGE_TYPES = [...] as const
120
120
  (export_statement
121
121
  declaration: (lexical_declaration
122
122
  (variable_declarator
123
123
  name: (identifier) @name
124
124
  value: (as_expression)))) @definition.const
125
125
 
126
+ ; Exported const from satisfies_expression: export const X = {...} as const satisfies Record<...>
127
+ (export_statement
128
+ declaration: (lexical_declaration
129
+ (variable_declarator
130
+ name: (identifier) @name
131
+ value: (satisfies_expression)))) @definition.const
132
+
126
133
  ; Exported const from array literal: export const FOO = [1, 2, 3]
127
134
  (export_statement
128
135
  declaration: (lexical_declaration
@@ -923,6 +923,30 @@ export class LocalBackend {
923
923
  lines.push(` ${this.shortPath(fp)}${statSuffix} — ${syms.length} symbols: ${names}${more}`);
924
924
  }
925
925
  }
926
+ // Deleted files (in git diff but no longer on disk)
927
+ const deleted = result.deleted_files || [];
928
+ if (deleted.length > 0) {
929
+ lines.push('');
930
+ lines.push(`### Deleted files (${deleted.length})`);
931
+ for (const fp of deleted) {
932
+ const basename = fp.split('/').slice(-1)[0] ?? '';
933
+ const stat = diffStats[fp] || diffStats[basename] || '';
934
+ const statSuffix = stat ? ` (${stat})` : '';
935
+ lines.push(` ${this.shortPath(fp)}${statSuffix}`);
936
+ }
937
+ }
938
+ // Changed files with no indexed symbols (e.g. config, assets)
939
+ const unindexed = result.unindexed_files || [];
940
+ if (unindexed.length > 0) {
941
+ lines.push('');
942
+ lines.push(`### Other changed files (${unindexed.length})`);
943
+ for (const fp of unindexed) {
944
+ const basename = fp.split('/').slice(-1)[0] ?? '';
945
+ const stat = diffStats[fp] || diffStats[basename] || '';
946
+ const statSuffix = stat ? ` (${stat})` : '';
947
+ lines.push(` ${this.shortPath(fp)}${statSuffix}`);
948
+ }
949
+ }
926
950
  // Affected processes
927
951
  const procs = result.affected_processes || [];
928
952
  if (procs.length > 0) {
@@ -2237,9 +2261,12 @@ export class LocalBackend {
2237
2261
  // Map changed files to indexed symbols
2238
2262
  const db = this.getDb(repo.id);
2239
2263
  const changedSymbols = [];
2264
+ const filesWithSymbols = new Set();
2240
2265
  for (const file of changedFiles) {
2241
2266
  const normalizedFile = file.replace(/\\/g, '/');
2242
2267
  const nodes = findNodesByFile(db, normalizedFile);
2268
+ if (nodes.length > 0)
2269
+ filesWithSymbols.add(normalizedFile);
2243
2270
  for (const node of nodes) {
2244
2271
  changedSymbols.push({
2245
2272
  id: node.id, name: node.name, type: node.label,
@@ -2247,6 +2274,22 @@ export class LocalBackend {
2247
2274
  });
2248
2275
  }
2249
2276
  }
2277
+ // Categorize changed files that have no indexed symbols
2278
+ const deletedFiles = [];
2279
+ const unindexedFiles = [];
2280
+ for (const file of changedFiles) {
2281
+ const normalizedFile = file.replace(/\\/g, '/');
2282
+ if (!filesWithSymbols.has(normalizedFile)) {
2283
+ const fullPath = path.resolve(repo.repoPath, normalizedFile);
2284
+ try {
2285
+ await fs.access(fullPath);
2286
+ unindexedFiles.push(normalizedFile);
2287
+ }
2288
+ catch {
2289
+ deletedFiles.push(normalizedFile);
2290
+ }
2291
+ }
2292
+ }
2250
2293
  // Fix 7: Detect REAL interface changes by comparing signature text
2251
2294
  // Read each changed file from disk and compare the declaration line against
2252
2295
  // the stored n.content signature. Only flags symbols whose actual signature changed,
@@ -2338,6 +2381,8 @@ export class LocalBackend {
2338
2381
  changed_symbols: changedSymbols,
2339
2382
  affected_processes: Array.from(affectedProcesses.values()),
2340
2383
  diff_stats: Object.fromEntries(diffStatMap), // F3: line stats per file
2384
+ deleted_files: deletedFiles,
2385
+ unindexed_files: unindexedFiles,
2341
2386
  };
2342
2387
  }
2343
2388
  /** Rename tool: multi-file coordinated rename using graph (high confidence) + text search */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuvia-software-solutions/code-mapper",
3
- "version": "2.6.4",
3
+ "version": "2.7.0",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",