@colbymchenry/codegraph 1.2.0 → 1.3.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 (44) hide show
  1. package/README.md +94 -1
  2. package/dist/bin/codegraph.d.ts +1 -1
  3. package/dist/db/migrations.d.ts +1 -1
  4. package/dist/db/queries.d.ts +51 -0
  5. package/dist/directory.d.ts +9 -5
  6. package/dist/extraction/cfml-extractor.d.ts +107 -0
  7. package/dist/extraction/grammars.d.ts +9 -0
  8. package/dist/extraction/index.d.ts +46 -1
  9. package/dist/extraction/languages/arkts.d.ts +3 -0
  10. package/dist/extraction/languages/c-cpp.d.ts +42 -0
  11. package/dist/extraction/languages/cfquery.d.ts +12 -0
  12. package/dist/extraction/languages/cfscript.d.ts +3 -0
  13. package/dist/extraction/languages/cobol.d.ts +33 -0
  14. package/dist/extraction/languages/erlang.d.ts +3 -0
  15. package/dist/extraction/languages/nix.d.ts +3 -0
  16. package/dist/extraction/languages/solidity.d.ts +3 -0
  17. package/dist/extraction/languages/terraform.d.ts +3 -0
  18. package/dist/extraction/languages/vbnet.d.ts +11 -0
  19. package/dist/extraction/mybatis-extractor.d.ts +30 -10
  20. package/dist/extraction/tree-sitter-types.d.ts +3 -1
  21. package/dist/extraction/tree-sitter.d.ts +38 -0
  22. package/dist/index.d.ts +63 -1
  23. package/dist/mcp/daemon.d.ts +25 -3
  24. package/dist/mcp/early-ppid.d.ts +26 -0
  25. package/dist/mcp/startup-handshake.d.ts +44 -0
  26. package/dist/mcp/tools.d.ts +22 -0
  27. package/dist/project-config.d.ts +38 -0
  28. package/dist/resolution/frameworks/cics.d.ts +20 -0
  29. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  30. package/dist/resolution/import-resolver.d.ts +7 -0
  31. package/dist/resolution/index.d.ts +39 -6
  32. package/dist/resolution/name-matcher.d.ts +0 -3
  33. package/dist/resolution/strip-comments.d.ts +1 -1
  34. package/dist/resolution/types.d.ts +20 -0
  35. package/dist/resolution/workspace-packages.d.ts +10 -0
  36. package/dist/search/identifier-segments.d.ts +60 -0
  37. package/dist/sync/watcher.d.ts +10 -5
  38. package/dist/types.d.ts +19 -1
  39. package/npm-shim.js +8 -1
  40. package/package.json +7 -7
  41. package/dist/reasoning/config.d.ts +0 -45
  42. package/dist/reasoning/credentials.d.ts +0 -5
  43. package/dist/reasoning/login.d.ts +0 -21
  44. package/dist/reasoning/reasoner.d.ts +0 -43
@@ -12,15 +12,21 @@ import { ExtractionResult } from '../types';
12
12
  *
13
13
  * This extractor emits one method-shaped node per `<select|insert|update|
14
14
  * delete>` and per `<sql>` fragment, qualified as `<namespace>::<id>` so the
15
- * MyBatis framework synthesizer (`src/resolution/frameworks/mybatis.ts`) can
16
- * link the matching Java method → XML statement by suffix-matching qualified
17
- * names. `<include refid="...">` inside a statement yields an unresolved
18
- * reference to the SQL fragment, also keyed by `<namespace>::<refid>`.
15
+ * MyBatis framework synthesizer can link the matching Java method → XML
16
+ * statement by suffix-matching qualified names. `<include refid="...">` inside
17
+ * a statement yields an unresolved reference to the SQL fragment, also keyed
18
+ * by `<namespace>::<refid>`.
19
+ *
20
+ * Both dialects are covered: MyBatis 3 `<mapper namespace="...">` and the
21
+ * legacy iBatis 2 `<sqlMap>` (namespaced, or namespace-less with `Map.stmt`
22
+ * ids, plus its extra `<statement>`/`<procedure>` verbs). Attribute values may
23
+ * use either quote style, and statements commented out with `<!-- ... -->` are
24
+ * ignored (see the constructor's comment-stripping pre-pass).
19
25
  *
20
26
  * Non-mapper XML (Maven `pom.xml`, Spring beans XML, `web.xml`, log4j config,
21
- * etc.) is detected by the absence of a `<mapper namespace="...">` root and
22
- * returns just a file node — we still need the file row so the watcher can
23
- * track it, but we emit no symbols.
27
+ * etc.) is detected by the absence of a `<mapper namespace="...">` /
28
+ * `<sqlMap>` root and returns just a file node — we still need the file row so
29
+ * the watcher can track it, but we emit no symbols.
24
30
  */
25
31
  export declare class MyBatisExtractor {
26
32
  private filePath;
@@ -31,16 +37,30 @@ export declare class MyBatisExtractor {
31
37
  private errors;
32
38
  private lineStarts;
33
39
  constructor(filePath: string, source: string);
40
+ private static stripXmlComments;
34
41
  extract(): ExtractionResult;
35
42
  private createFileNode;
36
43
  /**
37
- * Find the `<mapper namespace="X">` opening tag. Returns the namespace and
38
- * the byte offsets of the body (between the opening and closing tag) so
39
- * statement extraction can be scoped to mapper contents.
44
+ * Find the mapper root and its dialect. Two shapes are recognized:
45
+ * - MyBatis 3: `<mapper namespace="com.foo.Bar">` namespace required.
46
+ * - iBatis 2: `<sqlMap namespace="Account">`, or a namespace-less
47
+ * `<sqlMap>` whose statement ids carry the qualifier as `Map.statement`.
48
+ * Returns the namespace, the dialect, and the byte offsets of the body
49
+ * (between the opening and closing tag) so statement extraction is scoped to
50
+ * the root's contents. Either quote style is accepted for the namespace
51
+ * (`namespace='X'` is legal XML and common in older mappers).
40
52
  */
41
53
  private findMapperRoot;
42
54
  private extractMapper;
43
55
  private buildSignature;
56
+ /**
57
+ * Build the `<namespace>::<id>` qualified name the MyBatis synthesizer
58
+ * suffix-matches against a Java `<Class>::<method>`, and the display name.
59
+ * For a namespace-less iBatis `<sqlMap>`, the statement id carries the
60
+ * qualifier as `Map.statement`, so split on the last dot to reach the same
61
+ * shape (`Account.getById` → `Account::getById`, name `getById`).
62
+ */
63
+ private qualifyStatement;
44
64
  private previewSql;
45
65
  private computeLineStarts;
46
66
  private getLineNumber;
@@ -76,8 +76,10 @@ export interface LanguageExtractor {
76
76
  * grammar mis-parses inside enum bodies). MUST preserve byte offsets (replace
77
77
  * removed text with spaces, keep newlines) so node positions and getNodeText
78
78
  * stay correct; the returned string is used for both parsing and extraction.
79
+ * `filePath` lets a transform key off the concrete file extension when one
80
+ * language id serves several dialects (C++ also parses `.metal` shaders).
79
81
  */
80
- preParse?: (source: string) => string;
82
+ preParse?: (source: string, filePath?: string) => string;
81
83
  /** Node types that represent functions */
82
84
  functionTypes: string[];
83
85
  /** Node types that represent classes */
@@ -25,6 +25,8 @@ export declare class TreeSitterExtractor {
25
25
  private errors;
26
26
  private extractor;
27
27
  private nodeStack;
28
+ private namespacePrefix;
29
+ private cppLocalFnPtrs;
28
30
  private methodIndex;
29
31
  private fnRefSpec;
30
32
  private fnRefCandidates;
@@ -427,6 +429,25 @@ export declare class TreeSitterExtractor {
427
429
  /**
428
430
  * Extract a function call
429
431
  */
432
+ /**
433
+ * The module an Erlang gen_server target expression statically names, or
434
+ * null when it's dynamic (pid/var/tuple form). Static shapes:
435
+ * - a bare atom — either this module or another one; OTP's dominant
436
+ * registration convention (`{local, ?MODULE}`) names a server process
437
+ * after its module, so `gen_server:call(other_mod, …)` reaches
438
+ * `other_mod`'s handlers. A registered name that matches no module
439
+ * resolves to nothing downstream (the qualified ref just drops).
440
+ * - `?MODULE`, or a macro the file defines as `?MODULE`
441
+ * (`-define(SERVER, ?MODULE)` — the standard self idiom)
442
+ * - a macro the file defines as a bare atom
443
+ * (`-define(STORE, kv_store)` — the cross-module variant)
444
+ * The macro tables are memoized per file (single entry — extraction is
445
+ * file-sequential).
446
+ */
447
+ private erlangServerMacroFile;
448
+ private erlangSelfMacros;
449
+ private erlangAtomMacros;
450
+ private resolveErlangGenServerTarget;
430
451
  private extractCall;
431
452
  /**
432
453
  * `new Foo(...)` / `Foo::new(...)` / object_creation_expression —
@@ -437,6 +458,15 @@ export declare class TreeSitterExtractor {
437
458
  * Children are still walked so nested calls inside the constructor
438
459
  * arguments (`new Foo(bar())`) get their own `calls` references.
439
460
  */
461
+ /**
462
+ * VB.NET `New Invoice(1)` is syntactically ambiguous between constructing
463
+ * Invoice with an argument and allocating an Invoice array of bound 1; the
464
+ * grammar parses the parenthesized form as array_creation_expression. A
465
+ * user-defined type with no `{...}` array initializer is overwhelmingly a
466
+ * constructor call, so treat it as an instantiation. Predefined element
467
+ * types (`New Byte(1023)`) and brace-initialized forms stay arrays.
468
+ */
469
+ private isVbnetConstructorShapedArrayCreation;
440
470
  private extractInstantiation;
441
471
  /**
442
472
  * Is this C++ `declaration` a stack/direct-initialization object construction
@@ -528,6 +558,14 @@ export declare class TreeSitterExtractor {
528
558
  * heuristic — no false edges (resolution still validates each path).
529
559
  */
530
560
  private extractRustRouteMacro;
561
+ /**
562
+ * Record a C++ local function-pointer binding (`local = &fn` / `&fn<…>` /
563
+ * `&ns::fn<…>`) for the CURRENT enclosing symbol, so calls through the local
564
+ * resolve to the real target (see cppLocalFnPtrs). Only the address-of shape
565
+ * is accepted — a bare-identifier RHS (`auto x = y;`) is any value copy, and
566
+ * linking through it would guess.
567
+ */
568
+ private recordCppFnPtrBinding;
531
569
  private visitFunctionBody;
532
570
  /**
533
571
  * Extract inheritance relationships
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * A local-first code intelligence system that builds a semantic
5
5
  * knowledge graph from any codebase.
6
6
  */
7
- import { Node, Edge, FileRecord, ExtractionResult, Subgraph, TraversalOptions, SearchOptions, SearchResult, Context, GraphStats, TaskInput, TaskContext, BuildContextOptions, FindRelevantContextOptions } from './types';
7
+ import { Node, Edge, FileRecord, ExtractionResult, Subgraph, TraversalOptions, SearchOptions, SearchResult, SegmentMatch, Context, GraphStats, TaskInput, TaskContext, BuildContextOptions, FindRelevantContextOptions } from './types';
8
8
  import { IndexProgress, IndexResult, SyncResult } from './extraction';
9
9
  import { ResolutionResult } from './resolution';
10
10
  import { WatchOptions, PendingFile } from './sync';
@@ -227,6 +227,16 @@ export declare class CodeGraph {
227
227
  * freshness without shelling out to `codegraph status --json`. (#329)
228
228
  */
229
229
  getLastIndexedAt(): number | null;
230
+ /**
231
+ * Completeness of the last full index run. `'complete'` is the only good
232
+ * state. `'indexing'` after the fact means a run was killed mid-index (OOM,
233
+ * SIGKILL, liveness watchdog) and the on-disk index is truncated;
234
+ * `'partial'` means the run finished but silently dropped files
235
+ * (discovered > indexed+skipped+errored); `'failed'` means it reported
236
+ * failure. `null` = index predates this marker. Surfaced by
237
+ * `codegraph status`.
238
+ */
239
+ getIndexState(): 'indexing' | 'complete' | 'partial' | 'failed' | null;
230
240
  /**
231
241
  * Which engine built the current index: the package version + extraction
232
242
  * version stamped at the last full `indexAll`. Either field is null for an
@@ -264,6 +274,13 @@ export declare class CodeGraph {
264
274
  * Processes chunks of unresolved refs, persisting results after each batch.
265
275
  */
266
276
  resolveReferencesBatched(onProgress?: (current: number, total: number) => void): Promise<ResolutionResult>;
277
+ /**
278
+ * References extracted but not yet resolved into edges. Zero on a healthy
279
+ * index — a completed resolution pass consumes every row. Non-zero at rest
280
+ * means a pass was interrupted mid-run (killed indexer, crash — #1187), so
281
+ * some files' call edges are missing; the next `sync` sweeps them.
282
+ */
283
+ getPendingReferenceCount(): number;
267
284
  /**
268
285
  * Get detected frameworks in the project
269
286
  */
@@ -307,10 +324,55 @@ export declare class CodeGraph {
307
324
  * definition the caller wants is never dropped below a search cut.
308
325
  */
309
326
  getNodesByName(name: string): Node[];
327
+ /** Nodes whose name starts with `prefix` (index range scan, capped). */
328
+ getNodesByNamePrefix(prefix: string, limit?: number): Node[];
310
329
  /**
311
330
  * Search nodes by text
312
331
  */
313
332
  searchNodes(query: string, options?: SearchOptions): SearchResult[];
333
+ /**
334
+ * Graph-derived prompt matching for the front-load hook's MEDIUM tier:
335
+ * which indexed symbols do these prose words name? "state machine des
336
+ * commandes" → `OrderStateMachine`, in any human language whose technical
337
+ * nouns are Latin script — no keyword list involved.
338
+ *
339
+ * Precision comes from the repo's own naming statistics, not vocabulary:
340
+ * - CO-OCCURRENCE: ≥2 words that are segments of the SAME name ("state" +
341
+ * "machine" → OrderStateMachine) is strong evidence and always qualifies.
342
+ * - RARITY: a single matched word qualifies only when its segment is
343
+ * discriminative here (≤ {@link SEGMENT_RARITY_CEILING} distinct names) —
344
+ * "checkout" in a shop backend yes, "state" in a react app no.
345
+ * Every candidate is re-verified against `nodes` before being returned
346
+ * (vocab rows are proposals; deletions leave orphans by design), so a
347
+ * returned symbol is guaranteed to exist right now.
348
+ */
349
+ getSegmentMatches(words: string[], limit?: number): SegmentMatch[];
350
+ /** A single word ("state") can match hundreds of names in a big repo — that
351
+ * is noise, not signal. Ceiling for the single-word tier; co-occurrence is
352
+ * exempt because two words on one name is already discriminative. */
353
+ private static readonly SEGMENT_RARITY_CEILING;
354
+ /** Which of the prompt's original words match `name`'s segments (via
355
+ * variants). Segments are recomputed in JS — a name-keyed vocab lookup
356
+ * would scan the (segment, name) primary key. */
357
+ private wordsMatchingName;
358
+ /**
359
+ * One-shot upgrade heal for callers that open the graph WITHOUT syncing —
360
+ * concretely the prompt hook, whose MEDIUM tier reads the segment
361
+ * vocabulary: a database migrated from before the vocab table existed
362
+ * starts with it empty, and the only other backfill lives inside `sync()`,
363
+ * which such callers never run (#1142). Returns true when the vocab is
364
+ * usable (already populated — the overwhelmingly common one-SELECT case —
365
+ * or healed here); false when it isn't (empty graph, or another process
366
+ * holds the index lock — that process's own sync heals it).
367
+ */
368
+ healSegmentVocabIfEmpty(): Promise<boolean>;
369
+ /**
370
+ * Rebuild the segment vocabulary from the current graph, batched and
371
+ * yielding — the upgrade-heal path for indexes built before the vocab table
372
+ * existed. Runs inside the index mutex/lock (sync and
373
+ * healSegmentVocabIfEmpty hold them).
374
+ */
375
+ private rebuildNameSegmentVocab;
314
376
  /**
315
377
  * Normalized project-name tokens (go.mod / package.json / repo dir) used to
316
378
  * down-weight the non-discriminative project name in search ranking (#720).
@@ -136,9 +136,11 @@ export declare class Daemon {
136
136
  /**
137
137
  * Defense-in-depth against a daemon that outlives its clients (#692), for the
138
138
  * cases the refcount + idle timer miss because a socket close never arrives:
139
- * - **Inactivity backstop:** exit if no inbound traffic for `maxIdleMs` while
140
- * clients are still (nominally) connected. A phantom client sends nothing,
141
- * so it can't pin the daemon past this window.
139
+ * - **Inactivity backstop:** after `maxIdleMs` with no inbound traffic, reap
140
+ * the daemon but ONLY if no connected client can be proven alive (see
141
+ * {@link backstopShouldExit}). This is the sole phantom class the sweep
142
+ * below can't catch: a client whose client-hello never arrived, so we have
143
+ * no pid to check.
142
144
  * - **Liveness sweep:** drop any client whose peer process has died (per the
143
145
  * client-hello pids), which re-arms the idle timer once the last real
144
146
  * client is gone. Catches a dead peer within one sweep instead of waiting
@@ -147,6 +149,26 @@ export declare class Daemon {
147
149
  * neither should hold it open on its own.
148
150
  */
149
151
  private startLivenessTimers;
152
+ /**
153
+ * Decide whether the inactivity backstop should reap the daemon right now.
154
+ * Public + `isAlive`-injected for deterministic tests; the timer calls it each
155
+ * tick with the real liveness probe.
156
+ *
157
+ * The backstop exists ONLY to catch a **phantom** client (#692) — one counted
158
+ * but actually gone, whose socket-close was never delivered. It must never
159
+ * reap a **live-but-quiet** session (connected, alive peer, just not querying):
160
+ * doing so silently severed the shared daemon and degraded that session — and
161
+ * any others sharing it — to an in-process engine. `lastActivityAt` only tracks
162
+ * inbound query bytes, and MCP has no keepalive, so a genuinely-live session
163
+ * trips the raw inactivity window after ~30 min of not being queried.
164
+ *
165
+ * So: once the inactivity window elapses, drop provably-dead peers (the same
166
+ * check the periodic sweep runs), then reap the daemon only when NOT ONE
167
+ * remaining client can be proven alive — i.e. every client left is an
168
+ * unknown-pid connection the sweep can't verify. A single provably-alive
169
+ * client keeps the daemon up. Has the sweep's side effect (drops dead peers).
170
+ */
171
+ backstopShouldExit(isAlive: (pid: number) => boolean): boolean;
150
172
  /**
151
173
  * Drop every connected client whose peer process is gone. Returns the count
152
174
  * reaped. `isAlive` is injected for testing. Clients with unknown pids (no
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Parent-pid baseline captured as early as possible in process life (#1185).
3
+ *
4
+ * The PPID watchdog's POSIX signal is "`process.ppid` CHANGED since startup" —
5
+ * but a launcher killed within the first ~100ms of our boot (an MCP host's
6
+ * config probe, an instant user cancel, an initialize-timeout teardown) can
7
+ * reparent this process to init BEFORE the serve/proxy code captured its
8
+ * baseline. The baseline then reads `1`, never diverges, and the watchdog is
9
+ * permanently blind — the orphaned-server accumulation reported in #1185.
10
+ * Reproduced on macOS: SIGKILL the launcher 50ms after spawn while the host
11
+ * holds the stdio pipes open, and the server survived indefinitely; at 150ms
12
+ * the old capture had already run and the watchdog reaped it.
13
+ *
14
+ * The CLI entry imports this module before anything else, so the capture runs
15
+ * within the first few ms of JS execution — the earliest a Node process can
16
+ * observe its parent. A kill landing in the remaining pre-JS window (process
17
+ * spawn → first require) still captures `1`; that residual case is covered by
18
+ * the startup-handshake timeout (see ./startup-handshake.ts), which reaps a
19
+ * server that never receives any MCP traffic.
20
+ *
21
+ * Library consumers don't load the CLI entry; for them the capture runs at
22
+ * first import of the MCP layer — no worse than the previous per-call-site
23
+ * capture, and identical once the module cache warms.
24
+ */
25
+ export declare const EARLY_PPID: number;
26
+ //# sourceMappingURL=early-ppid.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Never-initialized backstop for `serve --mcp` (#1185).
3
+ *
4
+ * Every real MCP host sends `initialize` immediately after spawning a server.
5
+ * A server that has received NO bytes at all for many minutes is not serving
6
+ * anyone — it is the residue of an abandoned launch: the host killed the
7
+ * launcher chain during startup (config probe, instant cancel, initialize
8
+ * timeout) but kept our stdio pipe fds open, so stdin never EOFs. If the kill
9
+ * landed before {@link ../mcp/early-ppid} could observe the real parent, the
10
+ * PPID watchdog is blind too (baseline `1`), and — pre-#1185 — the orphan
11
+ * lived until the HOST process exited, accumulating one ~30MB node process
12
+ * per occurrence.
13
+ *
14
+ * This backstop closes that last hole: arm a one-shot timer at serve start
15
+ * and disarm it on the first byte of client traffic. If the timer fires, the
16
+ * caller shuts the server down. The default is deliberately generous (15
17
+ * minutes) — hosts initialize within milliseconds, so the only processes this
18
+ * ever reaps are ones nobody is talking to. It never affects a session that
19
+ * spoke even once: after the first byte the timer is gone for good (a
20
+ * quiet-but-live session is the PPID watchdog's / stdin teardown's job).
21
+ *
22
+ * IMPORTANT (callers): attaching a `'data'` listener switches the stream into
23
+ * flowing mode. Arm this AFTER the real stdin consumer is attached, in the
24
+ * same synchronous block, so no early bytes are emitted while only our
25
+ * listener exists. The detached daemon must never arm this — its stdin is
26
+ * `'ignore'` and its lifecycle is refcount/idle-based.
27
+ *
28
+ * Tune with `CODEGRAPH_STARTUP_HANDSHAKE_TIMEOUT_MS`; `0` disables.
29
+ */
30
+ /** Default wait for the first byte of MCP traffic before assuming orphaned. */
31
+ export declare const DEFAULT_STARTUP_HANDSHAKE_TIMEOUT_MS = 900000;
32
+ export declare const STARTUP_HANDSHAKE_TIMEOUT_ENV = "CODEGRAPH_STARTUP_HANDSHAKE_TIMEOUT_MS";
33
+ /**
34
+ * Parse the timeout env override. Missing/invalid → default; `<= 0` → `0`
35
+ * (disabled), the same disable convention as `CODEGRAPH_PPID_POLL_MS`.
36
+ */
37
+ export declare function parseStartupHandshakeTimeoutMs(raw: string | undefined): number;
38
+ /**
39
+ * Arm the backstop. `onAbandoned` runs at most once, only if no `'data'` event
40
+ * arrives on `stream` within the timeout. Returns a disarm function (idempotent;
41
+ * also detaches the listener). `stream`/`timeoutMs` are injectable for tests.
42
+ */
43
+ export declare function armStartupHandshakeTimeout(onAbandoned: () => void, stream?: NodeJS.ReadableStream, timeoutMs?: number): () => void;
44
+ //# sourceMappingURL=startup-handshake.d.ts.map
@@ -25,6 +25,28 @@ export declare class NotIndexedError extends Error {
25
25
  */
26
26
  export declare class PathRefusalError extends Error {
27
27
  }
28
+ /**
29
+ * Normalize Erlang-native symbol spellings in an explore query into the shapes
30
+ * the rest of the pipeline already understands. Agents working Erlang code
31
+ * name symbols the way the language spells them — `mod:fn/3`, `init/2` — and
32
+ * those tokens previously died in both consumers: the flow-builder's token
33
+ * filter rejects `:` and `/arity` outright, and the search-side field parser
34
+ * eats `mod:fn` as an unknown `field:value`. Measured on cowboy: the agent
35
+ * named `cowboy_stream_h:request_process/3` in two queries, got no body back
36
+ * either time, and fell back to Read.
37
+ *
38
+ * - `fn/3` → `fn` (arity tail after an identifier; a path segment like
39
+ * `src/2fa` doesn't match because the tail must be all digits)
40
+ * - `mod:fn` → `mod.fn` (exactly one colon between identifiers, so it rides
41
+ * the existing Class.method qualified handling; `::`, URLs, drive letters,
42
+ * and times don't match, and the query language's own field prefixes —
43
+ * kind:/lang:/language:/path:/name: — are left alone)
44
+ *
45
+ * Safe cross-language: Lua's `t:m` spelling maps to the same `t.m` its
46
+ * qualified names use, and no other supported spelling contains a bare
47
+ * single-colon identifier pair.
48
+ */
49
+ export declare function normalizeQuerySpelling(query: string): string;
28
50
  /**
29
51
  * Calculate the recommended number of codegraph_explore calls based on project size.
30
52
  * Larger codebases need more exploration calls to cover their surface area,
@@ -23,6 +23,20 @@ export interface ProjectConfig {
23
23
  * and your `.gitignore`.
24
24
  */
25
25
  exclude?: string[];
26
+ /**
27
+ * Gitignore-style patterns for first-party source to force INTO the index even
28
+ * when `.gitignore` would drop it — the general whitelist `includeIgnored`
29
+ * never was (that one only revives *embedded git repos* inside ignored dirs).
30
+ * The case this exists for: a project under a second VCS (SVN, Perforce, …)
31
+ * deliberately `.gitignore`s its own real source so it never lands in Git, yet
32
+ * that source must still be indexed. Matched against project-root-relative
33
+ * paths, so `"Tools/"`, a recursive `"Tools/**"` glob, or `"Local/typescript"`
34
+ * all work.
35
+ * Built-in default-ignored dirs (`node_modules`, `dist`, …), `.git`, and
36
+ * CodeGraph's own data dir are never resurfaced; an explicit `exclude` still
37
+ * wins. Absent/empty (the default) forces nothing in.
38
+ */
39
+ include?: string[];
26
40
  }
27
41
  /**
28
42
  * Load the validated extension overrides for a project, mtime-cached.
@@ -51,6 +65,30 @@ export declare function loadIncludeIgnoredPatterns(rootDir: string): string[];
51
65
  * the built-in defaults and the project's `.gitignore`.
52
66
  */
53
67
  export declare function loadExcludePatterns(rootDir: string): string[];
68
+ /**
69
+ * Load the validated `include` patterns for a project, mtime-cached.
70
+ *
71
+ * These name first-party source to force INTO the index even when `.gitignore`
72
+ * would drop it — the whitelist for SVN/Perforce-only source a project
73
+ * gitignores out of Git. An empty result — the zero-config default — forces
74
+ * nothing in. Built-in default-ignored dirs, `.git`, and CodeGraph's data dir
75
+ * are never resurfaced, and an explicit `exclude` still wins.
76
+ */
77
+ export declare function loadIncludePatterns(rootDir: string): string[];
54
78
  /** Test/maintenance hook: forget cached config (e.g. after rewriting it in a test). */
55
79
  export declare function clearProjectConfigCache(): void;
80
+ /**
81
+ * Add gitignore-style patterns to a project's `codegraph.json` `includeIgnored`
82
+ * list, creating the file if absent and preserving every other key. Used by the
83
+ * CLI to opt a "super-repo of gitignored child repos" (#1156) into the index on
84
+ * the user's say-so. Returns the count of patterns actually ADDED (ones already
85
+ * present are skipped, so a re-run is idempotent).
86
+ *
87
+ * A plain-JSON round-trip: a `codegraph.json` carrying comments (not valid JSON)
88
+ * already fails to load with a warning, so rather than silently clobber such a
89
+ * file this throws when an existing config won't parse — the caller falls back
90
+ * to printing the manual snippet. Invalidates the config cache so a subsequent
91
+ * index in the same process sees the new patterns.
92
+ */
93
+ export declare function addIncludeIgnoredPatterns(rootDir: string, patterns: string[]): number;
56
94
  //# sourceMappingURL=project-config.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * CICS Framework Resolver (COBOL)
3
+ *
4
+ * Resolves the pseudo-conversational transaction hop: a program ends with
5
+ * `EXEC CICS RETURN TRANSID('CB00')` (or START), and CICS re-invokes the
6
+ * program that OWNS transaction CB00 on the next attention key. The
7
+ * transaction→program mapping lives in the CICS CSD, which is never in the
8
+ * repo — but by near-universal convention each program declares its own
9
+ * transaction id as a working-storage constant:
10
+ *
11
+ * 05 WS-TRANID PIC X(04) VALUE 'CB00'.
12
+ *
13
+ * The COBOL extractor emits `cics-transid:CB00` call references for literal
14
+ * (or same-file-dereferenced) TRANSID options; this resolver maps the id to
15
+ * the program module whose TRAN*-named data item declares that VALUE. No
16
+ * match (an id owned by a program outside the repo) stays unresolved.
17
+ */
18
+ import { FrameworkResolver } from '../types';
19
+ export declare const cicsResolver: FrameworkResolver;
20
+ //# sourceMappingURL=cics.d.ts.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Terraform Framework Resolver
3
+ *
4
+ * Terraform's scoping rule is narrow and directory-shaped: `var.X`,
5
+ * `local.X`, `module.M`, and resource/data references resolve ONLY inside
6
+ * the same module directory as the reference site. The generic name matcher
7
+ * resolves by qualified-name alone, so a reference to `var.project_id` from
8
+ * `modules/net-vpc/main.tf` could bind to a `variable "project_id"` declared
9
+ * in an unrelated module — a wrong cross-module edge that poisons impact
10
+ * analysis. This resolver enforces the real semantics:
11
+ *
12
+ * 1. Same directory as the reference site → resolve (highest confidence).
13
+ * 2. `.tfvars` files additionally walk UP to the nearest ancestor
14
+ * directory declaring the variable (`terraform apply -var-file=envs/prod.tfvars`
15
+ * sets ROOT module variables from a subdirectory).
16
+ * 3. Otherwise: no edge. Terraform cannot reference across sibling module
17
+ * directories, so a non-local candidate is never a correct target.
18
+ *
19
+ * It also bridges the module boundary through `:`-scoped references that
20
+ * only this resolver understands (see the extractor's emitModuleWiring):
21
+ *
22
+ * - `module.M:file` → the entry file of the module's local source
23
+ * directory (an `imports` edge, so a module call connects to the code
24
+ * it instantiates).
25
+ * - `module.M:var.<in>` → the child module's `variable "<in>"` node —
26
+ * the module block sets that variable, so "what depends on the child's
27
+ * var.cidr" reaches every caller.
28
+ * - `module.M:output.<o>` → the child module's `output "<o>"` node —
29
+ * `module.M.o` uses flow through to the output's definition instead of
30
+ * dead-ending at the module declaration.
31
+ *
32
+ * The module's `source` is re-read from the declaration's file (cached
33
+ * lines); only local `./`/`../` sources bridge. Registry/git sources stay
34
+ * unresolved — an out-of-repo module is a visible boundary, never a guess.
35
+ */
36
+ import type { FrameworkResolver } from '../types';
37
+ export declare const terraformResolver: FrameworkResolver;
38
+ //# sourceMappingURL=terraform.d.ts.map
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { Language } from '../types';
7
7
  import { UnresolvedRef, ResolvedRef, ResolutionContext, ImportMapping, ReExport } from './types';
8
+ export declare function isNixPathImportRef(ref: UnresolvedRef): boolean;
8
9
  /**
9
10
  * Resolve an import path to an actual file
10
11
  */
@@ -37,6 +38,12 @@ export declare function loadCppIncludeDirs(projectRoot: string): string[];
37
38
  * to a same-named symbol — so callers must not fall back to the name-matcher.
38
39
  */
39
40
  export declare function isPhpIncludePathRef(ref: UnresolvedRef): boolean;
41
+ /**
42
+ * Is this a COBOL COPY / EXEC SQL INCLUDE copybook reference? These resolve
43
+ * to files only (or stay unresolved for compiler-supplied members) — never
44
+ * to a same-named symbol via the name-matcher.
45
+ */
46
+ export declare function isCobolCopybookRef(ref: UnresolvedRef): boolean;
40
47
  /**
41
48
  * Extract import mappings from a file
42
49
  */
@@ -27,6 +27,9 @@ export declare class ReferenceResolver {
27
27
  private nameCache;
28
28
  private lowerNameCache;
29
29
  private qualifiedNameCache;
30
+ private fileLinesCache;
31
+ private methodMatchCache;
32
+ private nodesByKindCache;
30
33
  private knownNames;
31
34
  private knownFiles;
32
35
  private cachesWarmed;
@@ -58,6 +61,8 @@ export declare class ReferenceResolver {
58
61
  * Clear internal caches
59
62
  */
60
63
  clearCaches(): void;
64
+ /** `readFile` through the LRU content cache (null = read failed, also cached). */
65
+ private readFileCached;
61
66
  /**
62
67
  * Create the resolution context
63
68
  */
@@ -105,12 +110,17 @@ export declare class ReferenceResolver {
105
110
  */
106
111
  resolveChainedCallsViaConformance(): Promise<number>;
107
112
  /**
108
- * Resolve one batch in smaller sub-chunks, yielding to the event loop between
109
- * them so the #850 liveness heartbeat can fire on a slow/dense batch (#1091).
110
- * Behaviourally identical to a single `resolveAll(batch)`: `warmCaches()` is
111
- * idempotent (guarded) and `resolveOne` is independent per ref, so splitting
112
- * and re-merging changes only timing, never which edges get created. Falls
113
- * through to a plain `resolveAll` when the batch is already small.
113
+ * Resolve one batch with a yield checkpoint between EVERY ref so the #850
114
+ * liveness heartbeat can fire on a slow/dense batch (#1091). The checkpoint
115
+ * granularity is per-ref not per-N-refs because per-ref cost is unbounded
116
+ * in the worst case (a collision-heavy method name whose candidate set misses
117
+ * the LRU re-fetches tens of thousands of rows): any fixed N multiplies that
118
+ * worst case into the watchdog window, which is how v1.2.0 still got killed
119
+ * at "Resolving refs" on large Java monorepos (#1122). `maybeYield()` is a
120
+ * ~ns time check when under budget, so per-ref checkpoints cost nothing.
121
+ * Behaviourally identical to `resolveAll(batch)`: `warmCaches()` is
122
+ * idempotent (guarded) and `resolveOne` is independent per ref, so yielding
123
+ * between refs changes only timing, never which edges get created.
114
124
  */
115
125
  private resolveBatchYielding;
116
126
  /**
@@ -161,6 +171,29 @@ export declare class ReferenceResolver {
161
171
  * through to name-matching).
162
172
  */
163
173
  private resolveRazorUsing;
174
+ /**
175
+ * Resolve a CFML inheritance reference written as a component path (#1152).
176
+ * Two forms exist in real code:
177
+ *
178
+ * - Dotted: `extends="coldbox.system.web.Controller"` — dots are directory
179
+ * separators from the webroot or a CFML mapping. Mappings live in server
180
+ * config / Application.cfc, so the leading segments may not exist in the
181
+ * repo at all (in the coldbox repo itself the path is `system/web/
182
+ * Controller.cfc` — the `coldbox.` root IS the repo). Matched by final
183
+ * segment (the class), corroborated right-to-left against the candidate's
184
+ * parent directories.
185
+ * - Relative: `extends="../base"` / `extends="./base"` (the FW/1 style) —
186
+ * resolved against the referencing file's own directory.
187
+ *
188
+ * Conservative by design: a candidate needs at least one corroborating
189
+ * directory segment (a dotted path whose only same-named class sits in an
190
+ * unrelated directory is almost always an out-of-repo library supertype —
191
+ * mxunit/testbox/coldbox-as-dependency), and a corroboration tie yields no
192
+ * edge. Directory comparison is case-insensitive (CFML path resolution is);
193
+ * the class segment itself is matched exactly, which real code satisfies —
194
+ * dotted paths are written to match the on-disk file name.
195
+ */
196
+ private resolveCfmlComponentPath;
164
197
  /**
165
198
  * Resolve a `this.<member>` function-as-value reference (#756/#808) to the
166
199
  * ENCLOSING CLASS's own member — never a same-named symbol elsewhere. The
@@ -108,8 +108,5 @@ export declare function matchMethodCall(ref: UnresolvedRef, context: ResolutionC
108
108
  * Fuzzy match - last resort with lower confidence
109
109
  */
110
110
  export declare function matchFuzzy(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
111
- /**
112
- * Match all strategies in order of confidence
113
- */
114
111
  export declare function matchReference(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
115
112
  //# sourceMappingURL=name-matcher.d.ts.map
@@ -22,6 +22,6 @@
22
22
  * `path(...)`, `Route::get(...)`, `app.get(...)` style patterns that
23
23
  * framework extractors scan for.
24
24
  */
25
- export type CommentLang = 'python' | 'javascript' | 'typescript' | 'php' | 'ruby' | 'java' | 'csharp' | 'swift' | 'go' | 'rust' | 'c' | 'cpp';
25
+ export type CommentLang = 'python' | 'javascript' | 'typescript' | 'php' | 'ruby' | 'java' | 'csharp' | 'swift' | 'go' | 'rust' | 'c' | 'cpp' | 'erlang';
26
26
  export declare function stripCommentsForRegex(content: string, lang: CommentLang): string;
27
27
  //# sourceMappingURL=strip-comments.d.ts.map
@@ -70,6 +70,26 @@ export interface ResolutionContext {
70
70
  fileExists(filePath: string): boolean;
71
71
  /** Read file content */
72
72
  readFile(filePath: string): string | null;
73
+ /**
74
+ * `readFile(filePath)` split into lines, LRU-cached per file. Receiver-type
75
+ * inference scans source lines for EVERY `receiver.method()` ref; splitting
76
+ * the whole file per ref made that O(refs-in-file × file-size) — ~20% of
77
+ * total index CPU on a Java-heavy repo and a driver of the #1122 watchdog
78
+ * kill on large ones. Optional so external/test contexts compile without it;
79
+ * callers fall back to splitting `readFile` themselves.
80
+ */
81
+ getFileLines?(filePath: string): string[] | null;
82
+ /**
83
+ * The method-definition nodes matching `typeName::methodName` in `language` —
84
+ * exactly `resolveMethodOnType`'s kind/language/qualifiedName-suffix filter,
85
+ * LRU-cached per (language, type, method). The uncached path re-fetches every
86
+ * node sharing the METHOD name (unbounded — tens of thousands on a collision-
87
+ * heavy Java repo) and re-scans it per ref, the dominant term in the #1122
88
+ * watchdog kill. Cached entries hold only the small filtered result; per-ref
89
+ * disambiguation (import FQN, call-site file) stays in the caller so a cached
90
+ * entry is valid from any call site. Optional for external/test contexts.
91
+ */
92
+ getMethodMatches?(typeName: string, methodName: string, language: Language): Node[];
73
93
  /** Get project root */
74
94
  getProjectRoot(): string;
75
95
  /** Get all files */