brainclaw 1.25.0 → 1.26.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.
Files changed (36) hide show
  1. package/dist/brainclaw-vscode.vsix +0 -0
  2. package/dist/commands/code-map.js +1 -4
  3. package/dist/commands/mcp.js +7 -7
  4. package/dist/commands/session-start.js +84 -13
  5. package/dist/core/bootstrap.js +28 -4
  6. package/dist/core/code-map/aggregate.js +36 -31
  7. package/dist/core/code-map/backend.js +4 -4
  8. package/dist/core/code-map/core.js +1 -0
  9. package/dist/core/code-map/export.js +4 -4
  10. package/dist/core/code-map/finalizer.js +57 -2
  11. package/dist/core/code-map/freshness.js +78 -13
  12. package/dist/core/code-map/impact.js +36 -4
  13. package/dist/core/code-map/indexes.js +37 -0
  14. package/dist/core/code-map/lang/python/index.js +4 -2
  15. package/dist/core/code-map/lang/query-runtime.js +2 -0
  16. package/dist/core/code-map/lang/typescript/index.js +4 -2
  17. package/dist/core/code-map/lang/usages.js +333 -0
  18. package/dist/core/code-map/memory-reader.js +15 -0
  19. package/dist/core/code-map/query.js +209 -58
  20. package/dist/core/code-map/refresh.js +0 -0
  21. package/dist/core/code-map/resolve.js +27 -2
  22. package/dist/core/code-map/store.js +1 -0
  23. package/dist/core/code-map/types.js +55 -9
  24. package/dist/core/code-map/vocabulary.js +6 -0
  25. package/dist/core/code-map/work-section.js +12 -14
  26. package/dist/core/context-diff.js +17 -3
  27. package/dist/core/entity-operations.js +14 -2
  28. package/dist/core/hint-aging.js +4 -1
  29. package/dist/core/identity.js +69 -17
  30. package/dist/core/io.js +27 -0
  31. package/dist/core/project-discovery.js +7 -1
  32. package/dist/core/runtime.js +23 -0
  33. package/dist/facts.js +12 -12
  34. package/dist/facts.json +11 -11
  35. package/docs/code-map.md +36 -27
  36. package/package.json +1 -1
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import path from 'node:path';
10
10
  import { fileId } from './ids.js';
11
+ import { makeFreshnessBadge } from './freshness.js';
11
12
  import { readManifest, readResolutionIndex, readShard, readSymbolsIndex, } from './store.js';
12
13
  import { deriveBadge, isTestPath, makeLazyChecker, newAccumulator, validateStoreEntry, } from './query.js';
13
14
  /** Direct results and each optional transitive layer are independently bounded. */
@@ -100,12 +101,14 @@ function causeKey(cause) {
100
101
  cause.target.kind,
101
102
  cause.target.path,
102
103
  cause.target.node_id ?? '',
104
+ cause.caller?.node_id ?? '',
103
105
  ].join('\u0001');
104
106
  }
105
107
  function compareCause(a, b) {
106
108
  return a.kind.localeCompare(b.kind)
107
109
  || a.target.path.localeCompare(b.target.path)
108
110
  || (a.target.node_id ?? '').localeCompare(b.target.node_id ?? '')
111
+ || (a.caller?.node_id ?? '').localeCompare(b.caller?.node_id ?? '')
109
112
  || (a.module ?? '').localeCompare(b.module ?? '')
110
113
  || (a.source_line ?? -1) - (b.source_line ?? -1)
111
114
  || a.imported.join('\u0000').localeCompare(b.imported.join('\u0000'))
@@ -138,6 +141,33 @@ function addRelation(rows, entry, depth, target, kind) {
138
141
  current.causes.sort(compareCause);
139
142
  rows.set(entry.path, current);
140
143
  }
144
+ function addUsageRelation(rows, entry, depth, target) {
145
+ const current = rows.get(entry.path) ?? {
146
+ path: entry.path,
147
+ file_id: entry.file_id,
148
+ depth,
149
+ causes: [],
150
+ causeKeys: new Set(),
151
+ };
152
+ current.depth = Math.min(current.depth, depth);
153
+ for (const reason of entry.reasons) {
154
+ const cause = {
155
+ kind: reason.kind,
156
+ imported: [],
157
+ confidence: reason.confidence,
158
+ ...(reason.source_line !== undefined ? { source_line: reason.source_line } : {}),
159
+ caller: { node_id: reason.caller_node_id },
160
+ target,
161
+ };
162
+ const key = causeKey(cause);
163
+ if (!current.causeKeys.has(key)) {
164
+ current.causeKeys.add(key);
165
+ current.causes.push(cause);
166
+ }
167
+ }
168
+ current.causes.sort(compareCause);
169
+ rows.set(entry.path, current);
170
+ }
141
171
  function publicRelation(row) {
142
172
  return {
143
173
  path: row.path,
@@ -205,7 +235,7 @@ export function impact(target, options, ctx) {
205
235
  freshness_badge: freshness,
206
236
  });
207
237
  if (!symbolsIndex || !manifest) {
208
- return empty({ status: 'missing_index', coarse: 'missing', details: { hint: 'run refresh' } });
238
+ return empty(makeFreshnessBadge('missing_index', { extra: { hint: 'run refresh' } }));
209
239
  }
210
240
  let matchKind = 'none';
211
241
  let rawDefinitions = [];
@@ -257,10 +287,12 @@ export function impact(target, options, ctx) {
257
287
  const directRows = new Map();
258
288
  if (resolution) {
259
289
  for (const definition of definitionByNodeId.values()) {
290
+ const target = { kind: 'symbol', path: definition.path, node_id: definition.node_id, name: definition.name };
260
291
  for (const dependent of resolution.dependents_by_symbol[definition.node_id] ?? []) {
261
- addRelation(directRows, dependent, 1, {
262
- kind: 'symbol', path: definition.path, node_id: definition.node_id, name: definition.name,
263
- }, 'imports_symbol');
292
+ addRelation(directRows, dependent, 1, target, 'imports_symbol');
293
+ }
294
+ for (const dependent of resolution.usages_by_symbol[definition.node_id] ?? []) {
295
+ addUsageRelation(directRows, dependent, 1, target);
264
296
  }
265
297
  }
266
298
  for (const definitionPath of definitionPaths) {
@@ -130,6 +130,29 @@ export function buildResolutionIndex(projectId, shards) {
130
130
  // target key -> (importer path -> merged entry)
131
131
  const byFile = new Map();
132
132
  const bySymbol = new Map();
133
+ const byUsageSymbol = new Map();
134
+ const addUsage = (targetSymbolId, importerPath, importerFileId, edge) => {
135
+ const perImporter = byUsageSymbol.get(targetSymbolId) ?? new Map();
136
+ const entry = perImporter.get(importerPath) ?? { path: importerPath, file_id: importerFileId, reasons: [] };
137
+ const reason = {
138
+ kind: edge.kind,
139
+ caller_node_id: edge.from,
140
+ confidence: edge.confidence,
141
+ ...(edge.source?.line !== undefined ? { source_line: edge.source.line } : {}),
142
+ };
143
+ if (!entry.reasons.some((existing) => existing.kind === reason.kind
144
+ && existing.caller_node_id === reason.caller_node_id
145
+ && existing.confidence === reason.confidence
146
+ && existing.source_line === reason.source_line)) {
147
+ entry.reasons.push(reason);
148
+ entry.reasons.sort((a, b) => a.kind.localeCompare(b.kind)
149
+ || a.caller_node_id.localeCompare(b.caller_node_id)
150
+ || (a.source_line ?? -1) - (b.source_line ?? -1)
151
+ || a.confidence - b.confidence);
152
+ }
153
+ perImporter.set(importerPath, entry);
154
+ byUsageSymbol.set(targetSymbolId, perImporter);
155
+ };
133
156
  const addDependent = (bucket, targetKey, importerPath, importerFileId, module, imported, confidence, kind, sourceLine) => {
134
157
  const perImporter = bucket.get(targetKey) ?? new Map();
135
158
  const prev = perImporter.get(importerPath);
@@ -189,6 +212,12 @@ export function buildResolutionIndex(projectId, shards) {
189
212
  moduleById.set(n.id, { name: n.name, imported: n.imported_names ?? [] });
190
213
  }
191
214
  for (const e of shard.edges) {
215
+ if (e.kind === 'calls' || e.kind === 'references') {
216
+ // `possible_textual_match` remains deliberately absent: it is a hint on
217
+ // the shard, never an impact dependency.
218
+ addUsage(e.to, shard.path, shard.file_id, { ...e, kind: e.kind });
219
+ continue;
220
+ }
192
221
  if (e.kind !== 'resolves_to' && e.kind !== 'imports_symbol')
193
222
  continue;
194
223
  const mod = moduleById.get(e.from);
@@ -210,12 +239,20 @@ export function buildResolutionIndex(projectId, shards) {
210
239
  }
211
240
  return out;
212
241
  };
242
+ const finalizeUsages = (bucket) => {
243
+ const out = Object.create(null);
244
+ for (const key of [...bucket.keys()].sort()) {
245
+ out[key] = [...bucket.get(key).values()].sort((a, b) => a.path.localeCompare(b.path));
246
+ }
247
+ return out;
248
+ };
213
249
  return {
214
250
  schema_version: CODE_MAP_SCHEMA_VERSION,
215
251
  project_id: projectId,
216
252
  updated_at: new Date().toISOString(),
217
253
  dependents_by_file: finalize(byFile),
218
254
  dependents_by_symbol: finalize(bySymbol),
255
+ usages_by_symbol: finalizeUsages(byUsageSymbol),
219
256
  };
220
257
  }
221
258
  //# sourceMappingURL=indexes.js.map
@@ -30,6 +30,7 @@ import path from 'node:path';
30
30
  import { fileURLToPath } from 'node:url';
31
31
  import { loadGrammarWasm, grammarHashForWasm } from '../../wasm-loader.js';
32
32
  import { extractWithQueries } from '../query-runtime.js';
33
+ import { extractLexicalUsages } from '../usages.js';
33
34
  const HERE = path.dirname(fileURLToPath(import.meta.url));
34
35
  /** The python grammar .wasm: dist basename + node_modules devDep fallback spec. */
35
36
  const PY_WASM_BASENAME = 'tree-sitter-python.wasm';
@@ -106,7 +107,7 @@ const queries = {
106
107
  };
107
108
  const vocabulary = {
108
109
  nodeSubtypes: ['function', 'method', 'class', 'variable', 'constant', 'property'],
109
- edgeKinds: ['contains', 'defines', 'imports'],
110
+ edgeKinds: ['contains', 'defines', 'imports', 'calls', 'references', 'possible_textual_match'],
110
111
  captureMap: queries.captureMap,
111
112
  };
112
113
  const capabilities = {
@@ -284,7 +285,8 @@ export class PythonProvider {
284
285
  }
285
286
  return d;
286
287
  });
287
- return { ...draft, definitions };
288
+ const tree = draft.attributes?.__tree;
289
+ return { ...draft, definitions, usages: extractLexicalUsages(tree?.rootNode, definitions, 'python') };
288
290
  }
289
291
  /**
290
292
  * P1c file-level import resolution (T3). Returns at most one resolution per import:
@@ -121,6 +121,7 @@ export async function extractWithQueries(input) {
121
121
  imports: [],
122
122
  exports: [],
123
123
  tests: [],
124
+ usages: [],
124
125
  facts,
125
126
  attributes: { parseStatus, __tree: tree },
126
127
  });
@@ -367,6 +368,7 @@ export async function extractWithQueries(input) {
367
368
  imports,
368
369
  exports,
369
370
  tests: [],
371
+ usages: [],
370
372
  facts,
371
373
  attributes: { parseStatus, __tree: tree },
372
374
  };
@@ -26,6 +26,7 @@ import path from 'node:path';
26
26
  import { fileURLToPath } from 'node:url';
27
27
  import { grammarHash, grammarName, loadGrammar } from '../../wasm-loader.js';
28
28
  import { extractWithQueries } from '../query-runtime.js';
29
+ import { extractLexicalUsages } from '../usages.js';
29
30
  import { isTypeScriptResolutionConfig, typeScriptSpecifierBases, } from './config.js';
30
31
  const HERE = path.dirname(fileURLToPath(import.meta.url));
31
32
  /** Resolve a vendored `.scm` next to this module (dist) or from the source tree. */
@@ -150,7 +151,7 @@ const queries = {
150
151
  };
151
152
  const vocabulary = {
152
153
  nodeSubtypes: ['function', 'class', 'type', 'interface', 'variable', 'component', 'hook', 'export'],
153
- edgeKinds: ['contains', 'defines', 'imports', 'exports'],
154
+ edgeKinds: ['contains', 'defines', 'imports', 'exports', 'calls', 'references', 'possible_textual_match'],
154
155
  captureMap: queries.captureMap,
155
156
  };
156
157
  const capabilities = {
@@ -305,7 +306,8 @@ export class TypeScriptProvider {
305
306
  }
306
307
  return d;
307
308
  });
308
- return { ...draft, definitions };
309
+ const tree = draft.attributes?.__tree;
310
+ return { ...draft, definitions, usages: extractLexicalUsages(tree?.rootNode, definitions, 'js-ts') };
309
311
  }
310
312
  /**
311
313
  * P1c file-level import resolution (intra-project, relative specifiers). Returns
@@ -0,0 +1,333 @@
1
+ const FUNCTION_SUBTYPES = new Set(['function', 'component', 'hook']);
2
+ const LOW_CONFIDENCE_TEXTUAL_MATCH = 0.2;
3
+ function sourceOf(definition) {
4
+ const value = definition.sourceNode;
5
+ if (!value || typeof value !== 'object' || !('node' in value) || !('nameNode' in value))
6
+ return null;
7
+ const candidate = value;
8
+ return candidate.node && candidate.nameNode ? candidate : null;
9
+ }
10
+ function spanOf(node) {
11
+ return {
12
+ start_line: node.startPosition.row + 1,
13
+ start_col: node.startPosition.column + 1,
14
+ end_line: node.endPosition.row + 1,
15
+ end_col: node.endPosition.column + 1,
16
+ };
17
+ }
18
+ function nodeKey(node) {
19
+ return `${node.type}:${node.startIndex}:${node.endIndex}`;
20
+ }
21
+ function sameNode(a, b) {
22
+ return !!a && a.type === b.type && a.startIndex === b.startIndex && a.endIndex === b.endIndex;
23
+ }
24
+ function field(node, name) {
25
+ try {
26
+ return node.childForFieldName(name);
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ function walk(node, visit) {
33
+ visit(node);
34
+ for (let i = 0; i < node.namedChildCount; i++) {
35
+ const child = node.namedChild(i);
36
+ if (child)
37
+ walk(child, visit);
38
+ }
39
+ }
40
+ function walkScope(node, rootScope, language, visit) {
41
+ visit(node);
42
+ for (let i = 0; i < node.namedChildCount; i++) {
43
+ const child = node.namedChild(i);
44
+ if (!child)
45
+ continue;
46
+ // Bindings in a nested function/class/lambda cannot shadow a use in this
47
+ // scope. Skipping them avoids false abstention between sibling functions.
48
+ if (child !== rootScope && isScope(child, language))
49
+ continue;
50
+ walkScope(child, rootScope, language, visit);
51
+ }
52
+ }
53
+ function hasAncestor(node, types) {
54
+ for (let parent = node.parent; parent; parent = parent.parent) {
55
+ if (types.has(parent.type))
56
+ return true;
57
+ }
58
+ return false;
59
+ }
60
+ function isTopLevelPythonFunction(definition) {
61
+ const source = sourceOf(definition);
62
+ if (!source)
63
+ return false;
64
+ let parent = source.node.parent;
65
+ if (parent?.type === 'decorated_definition')
66
+ parent = parent.parent;
67
+ return parent?.type === 'module';
68
+ }
69
+ function isLocalFunctionTarget(definition, language) {
70
+ if (!FUNCTION_SUBTYPES.has(definition.subtype))
71
+ return false;
72
+ // JS/TS tags are already program-anchored. Python captures nested functions too,
73
+ // so retain only module bindings: nested/method names need a real scope engine.
74
+ return language === 'js-ts' || isTopLevelPythonFunction(definition);
75
+ }
76
+ function isCallerDefinition(definition) {
77
+ return FUNCTION_SUBTYPES.has(definition.subtype);
78
+ }
79
+ function definitionRange(definition) {
80
+ const source = sourceOf(definition);
81
+ if (!source)
82
+ return null;
83
+ // A lexical declaration can carry several declarators. The declarator, not the
84
+ // shared statement span, is the smallest safe caller container for an arrow.
85
+ return source.nameNode.parent ?? source.node;
86
+ }
87
+ function contains(container, node) {
88
+ return container.startIndex <= node.startIndex && container.endIndex >= node.endIndex;
89
+ }
90
+ function callerFor(node, definitions) {
91
+ let winner;
92
+ for (const definition of definitions) {
93
+ if (!isCallerDefinition(definition))
94
+ continue;
95
+ const range = definitionRange(definition);
96
+ if (!range || !contains(range, node))
97
+ continue;
98
+ const width = range.endIndex - range.startIndex;
99
+ if (!winner || width < winner.width || (width === winner.width && definition.ordinal > winner.ordinal)) {
100
+ winner = { ordinal: definition.ordinal, width };
101
+ }
102
+ }
103
+ return winner?.ordinal;
104
+ }
105
+ function isScope(node, language) {
106
+ if (language === 'python')
107
+ return ['module', 'function_definition', 'lambda', 'class_definition'].includes(node.type);
108
+ return ['program', 'function_declaration', 'function_expression', 'arrow_function', 'method_definition'].includes(node.type);
109
+ }
110
+ function isParameterIdentifier(node, language) {
111
+ const parameterTypes = language === 'python'
112
+ ? new Set(['parameters', 'lambda_parameters'])
113
+ : new Set(['formal_parameters', 'required_parameter', 'optional_parameter', 'rest_pattern']);
114
+ return hasAncestor(node, parameterTypes);
115
+ }
116
+ function isBindingIdentifier(node, language) {
117
+ if (node.type !== 'identifier')
118
+ return false;
119
+ const parent = node.parent;
120
+ if (!parent)
121
+ return false;
122
+ if (sameNode(field(parent, 'name'), node) && [
123
+ 'variable_declarator', 'function_declaration', 'generator_function_declaration',
124
+ 'function_definition', 'class_declaration', 'class_definition', 'aliased_import',
125
+ ].includes(parent.type))
126
+ return true;
127
+ if (sameNode(field(parent, 'left'), node) && [
128
+ 'assignment', 'augmented_assignment', 'for_statement', 'with_item',
129
+ ].includes(parent.type))
130
+ return true;
131
+ return isParameterIdentifier(node, language);
132
+ }
133
+ function scopeBinds(scope, name, language, allowed) {
134
+ let found = false;
135
+ walkScope(scope, scope, language, (candidate) => {
136
+ if (found || candidate.text !== name || !isBindingIdentifier(candidate, language))
137
+ return;
138
+ if (!allowed.has(nodeKey(candidate)))
139
+ found = true;
140
+ });
141
+ return found;
142
+ }
143
+ function isShadowed(node, name, language, allowed) {
144
+ for (let parent = node.parent; parent; parent = parent.parent) {
145
+ if (isScope(parent, language) && scopeBinds(parent, name, language, allowed))
146
+ return true;
147
+ }
148
+ return false;
149
+ }
150
+ function propertyOf(node) {
151
+ return field(node, 'property') ?? field(node, 'attribute');
152
+ }
153
+ function isProperty(node) {
154
+ const parent = node.parent;
155
+ return !!parent && sameNode(propertyOf(parent), node)
156
+ && ['member_expression', 'optional_member_expression', 'attribute'].includes(parent.type);
157
+ }
158
+ function isValueReference(node, language) {
159
+ if (node.type !== 'identifier' || isBindingIdentifier(node, language))
160
+ return false;
161
+ const parent = node.parent;
162
+ if (!parent || isProperty(node))
163
+ return false;
164
+ if (hasAncestor(node, new Set(['import_statement', 'import_from_statement'])))
165
+ return false;
166
+ if (sameNode(field(parent, 'key'), node) || ['export_specifier', 'export_clause'].includes(parent.type))
167
+ return false;
168
+ // Type positions have distinct namespaces in TS and do not prove a value-level use.
169
+ if (hasAncestor(node, new Set(['type_annotation', 'type_alias_declaration', 'interface_declaration', 'type_identifier'])))
170
+ return false;
171
+ return true;
172
+ }
173
+ function addBinding(bindings, localName, binding) {
174
+ const previous = bindings.get(localName);
175
+ bindings.set(localName, previous === undefined ? binding : null);
176
+ }
177
+ function stripQuotes(text) {
178
+ return text.replace(/^['"`]|['"`]$/g, '');
179
+ }
180
+ function jsTsBindings(root) {
181
+ const bindings = new Map();
182
+ walk(root, (statement) => {
183
+ if (statement.type !== 'import_statement')
184
+ return;
185
+ const source = field(statement, 'source');
186
+ if (!source)
187
+ return;
188
+ const module = stripQuotes(source.text);
189
+ let clause = null;
190
+ for (let i = 0; i < statement.namedChildCount; i++) {
191
+ const child = statement.namedChild(i);
192
+ if (child?.type === 'import_clause') {
193
+ clause = child;
194
+ break;
195
+ }
196
+ }
197
+ if (!clause)
198
+ return;
199
+ walk(clause, (candidate) => {
200
+ if (candidate.type !== 'import_specifier')
201
+ return;
202
+ const imported = field(candidate, 'name');
203
+ if (!imported || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(imported.text))
204
+ return;
205
+ const alias = field(candidate, 'alias');
206
+ addBinding(bindings, (alias ?? imported).text, {
207
+ module,
208
+ importedName: imported.text,
209
+ bindingNode: alias ?? imported,
210
+ });
211
+ });
212
+ });
213
+ return bindings;
214
+ }
215
+ function pythonBindings(root) {
216
+ const bindings = new Map();
217
+ walk(root, (statement) => {
218
+ if (statement.type !== 'import_from_statement')
219
+ return;
220
+ const source = field(statement, 'module_name');
221
+ if (!source)
222
+ return;
223
+ const module = source.text;
224
+ for (let i = 0; i < statement.namedChildCount; i++) {
225
+ const child = statement.namedChild(i);
226
+ if (!child || sameNode(child, source))
227
+ continue;
228
+ if (child.type === 'dotted_name' && /^[A-Za-z_][A-Za-z0-9_]*$/.test(child.text)) {
229
+ addBinding(bindings, child.text, { module, importedName: child.text, bindingNode: child });
230
+ }
231
+ else if (child.type === 'aliased_import') {
232
+ const imported = field(child, 'name');
233
+ const alias = field(child, 'alias');
234
+ if (imported && alias && /^[A-Za-z_][A-Za-z0-9_]*$/.test(imported.text)) {
235
+ addBinding(bindings, alias.text, { module, importedName: imported.text, bindingNode: alias });
236
+ }
237
+ }
238
+ }
239
+ });
240
+ return bindings;
241
+ }
242
+ /** Extract provider-local, soundness-first usage drafts from an already parsed tree. */
243
+ export function extractLexicalUsages(root, definitions, language) {
244
+ if (!root)
245
+ return [];
246
+ const localTargets = new Map();
247
+ const allowedBindings = new Set();
248
+ for (const definition of definitions) {
249
+ if (!isLocalFunctionTarget(definition, language))
250
+ continue;
251
+ const previous = localTargets.get(definition.name);
252
+ localTargets.set(definition.name, previous === undefined ? definition : null);
253
+ const source = sourceOf(definition);
254
+ if (source)
255
+ allowedBindings.add(nodeKey(source.nameNode));
256
+ }
257
+ const bindings = language === 'js-ts' ? jsTsBindings(root) : pythonBindings(root);
258
+ for (const binding of bindings.values())
259
+ if (binding)
260
+ allowedBindings.add(nodeKey(binding.bindingNode));
261
+ const usages = [];
262
+ const seen = new Set();
263
+ const handledCallIdentifiers = new Set();
264
+ const handledProperties = new Set();
265
+ const add = (node, kind, target) => {
266
+ const caller = callerFor(node, definitions);
267
+ const targetKey = target.kind === 'local'
268
+ ? `local:${target.definitionOrdinal}`
269
+ : `import:${target.module}:${target.importedName}`;
270
+ const key = `${nodeKey(node)}:${kind}:${caller ?? 'file'}:${targetKey}`;
271
+ if (seen.has(key))
272
+ return;
273
+ seen.add(key);
274
+ usages.push({
275
+ kind,
276
+ ...(caller === undefined ? {} : { fromDefinitionOrdinal: caller }),
277
+ target,
278
+ span: spanOf(node),
279
+ confidence: kind === 'possible_textual_match' ? LOW_CONFIDENCE_TEXTUAL_MATCH : 1.0,
280
+ });
281
+ };
282
+ const directUse = (node, kind) => {
283
+ const name = node.text;
284
+ const local = localTargets.get(name);
285
+ const imported = bindings.get(name);
286
+ if ((local === undefined || local === null) && (!imported || imported === null))
287
+ return;
288
+ if (isShadowed(node, name, language, allowedBindings))
289
+ return;
290
+ if (local && local !== null) {
291
+ add(node, kind, { kind: 'local', definitionOrdinal: local.ordinal });
292
+ }
293
+ else if (imported) {
294
+ add(node, kind, { kind: 'import', module: imported.module, importedName: imported.importedName });
295
+ }
296
+ };
297
+ walk(root, (node) => {
298
+ if (node.type !== 'call_expression' && node.type !== 'call')
299
+ return;
300
+ const callee = field(node, 'function');
301
+ if (callee?.type === 'identifier') {
302
+ handledCallIdentifiers.add(nodeKey(callee));
303
+ directUse(callee, 'calls');
304
+ return;
305
+ }
306
+ if (!callee)
307
+ return;
308
+ const property = propertyOf(callee);
309
+ const local = property ? localTargets.get(property.text) : undefined;
310
+ if (property && local && local !== null) {
311
+ handledProperties.add(nodeKey(property));
312
+ add(property, 'possible_textual_match', { kind: 'local', definitionOrdinal: local.ordinal });
313
+ }
314
+ });
315
+ walk(root, (node) => {
316
+ if (node.type !== 'identifier')
317
+ return;
318
+ if (handledCallIdentifiers.has(nodeKey(node)) || handledProperties.has(nodeKey(node)))
319
+ return;
320
+ if (isProperty(node)) {
321
+ const local = localTargets.get(node.text);
322
+ if (local && local !== null)
323
+ add(node, 'possible_textual_match', { kind: 'local', definitionOrdinal: local.ordinal });
324
+ return;
325
+ }
326
+ if (isValueReference(node, language))
327
+ directUse(node, 'references');
328
+ });
329
+ return usages.sort((a, b) => a.span.start_line - b.span.start_line
330
+ || a.span.start_col - b.span.start_col
331
+ || a.kind.localeCompare(b.kind));
332
+ }
333
+ //# sourceMappingURL=usages.js.map
@@ -26,6 +26,12 @@ function coercePaths(raw) {
26
26
  return raw.filter((p) => typeof p === 'string');
27
27
  return [];
28
28
  }
29
+ function optionalString(raw) {
30
+ return typeof raw === 'string' ? raw : undefined;
31
+ }
32
+ function optionalCount(raw) {
33
+ return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;
34
+ }
29
35
  function toRelatedMemoryItem(kind, item) {
30
36
  return {
31
37
  id: typeof item.id === 'string' ? item.id : '',
@@ -33,6 +39,15 @@ function toRelatedMemoryItem(kind, item) {
33
39
  text: typeof item.text === 'string' ? item.text : '',
34
40
  tags: coerceTags(item.tags),
35
41
  related_paths: coercePaths(item.related_paths),
42
+ created_at: optionalString(item.created_at),
43
+ last_confirmed_at: optionalString(item.last_confirmed_at),
44
+ last_infirmed_at: optionalString(item.last_infirmed_at),
45
+ confirmation_count: optionalCount(item.confirmation_count),
46
+ infirmation_count: optionalCount(item.infirmation_count),
47
+ saved_me_count: optionalCount(item.saved_me_count),
48
+ misled_me_count: optionalCount(item.misled_me_count),
49
+ verified_at: optionalString(item.verified_at),
50
+ verify_cmd: optionalString(item.verify_cmd),
36
51
  };
37
52
  }
38
53
  /**