@colbymchenry/codegraph 0.9.8 → 1.0.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 (49) hide show
  1. package/README.md +150 -70
  2. package/dist/bin/codegraph.d.ts +1 -0
  3. package/dist/context/index.d.ts +9 -0
  4. package/dist/context/markers.d.ts +19 -0
  5. package/dist/db/migrations.d.ts +1 -1
  6. package/dist/db/queries.d.ts +43 -0
  7. package/dist/db/sqlite-adapter.d.ts +7 -0
  8. package/dist/directory.d.ts +34 -2
  9. package/dist/extraction/astro-extractor.d.ts +79 -0
  10. package/dist/extraction/extraction-version.d.ts +25 -0
  11. package/dist/extraction/function-ref.d.ts +118 -0
  12. package/dist/extraction/grammars.d.ts +7 -1
  13. package/dist/extraction/index.d.ts +34 -0
  14. package/dist/extraction/languages/c-cpp.d.ts +8 -0
  15. package/dist/extraction/languages/csharp.d.ts +22 -0
  16. package/dist/extraction/languages/r.d.ts +3 -0
  17. package/dist/extraction/languages/typescript.d.ts +13 -0
  18. package/dist/extraction/liquid-extractor.d.ts +7 -0
  19. package/dist/extraction/razor-extractor.d.ts +42 -0
  20. package/dist/extraction/tree-sitter-types.d.ts +33 -0
  21. package/dist/extraction/tree-sitter.d.ts +237 -0
  22. package/dist/extraction/vue-extractor.d.ts +15 -0
  23. package/dist/index.d.ts +41 -3
  24. package/dist/installer/instructions-template.d.ts +34 -11
  25. package/dist/installer/targets/opencode.d.ts +9 -1
  26. package/dist/installer/targets/shared.d.ts +14 -0
  27. package/dist/mcp/daemon.d.ts +60 -1
  28. package/dist/mcp/dynamic-boundaries.d.ts +41 -0
  29. package/dist/mcp/ppid-watchdog.d.ts +44 -0
  30. package/dist/mcp/proxy.d.ts +6 -0
  31. package/dist/mcp/server-instructions.d.ts +12 -1
  32. package/dist/mcp/session.d.ts +2 -0
  33. package/dist/mcp/stdin-teardown.d.ts +27 -0
  34. package/dist/mcp/tools.d.ts +110 -49
  35. package/dist/resolution/callback-synthesizer.d.ts +3 -3
  36. package/dist/resolution/frameworks/astro.d.ts +9 -0
  37. package/dist/resolution/frameworks/index.d.ts +1 -0
  38. package/dist/resolution/import-resolver.d.ts +10 -0
  39. package/dist/resolution/index.d.ts +80 -0
  40. package/dist/resolution/name-matcher.d.ts +61 -0
  41. package/dist/resolution/types.d.ts +27 -3
  42. package/dist/resolution/workspace-packages.d.ts +48 -0
  43. package/dist/search/query-utils.d.ts +35 -1
  44. package/dist/sync/watcher.d.ts +124 -32
  45. package/dist/telemetry/index.d.ts +146 -0
  46. package/dist/types.d.ts +25 -2
  47. package/dist/upgrade/index.d.ts +132 -0
  48. package/dist/utils.d.ts +30 -24
  49. package/package.json +7 -7
@@ -20,11 +20,45 @@ export declare class TreeSitterExtractor {
20
20
  private extractor;
21
21
  private nodeStack;
22
22
  private methodIndex;
23
+ private fnRefSpec;
24
+ private fnRefCandidates;
23
25
  constructor(filePath: string, source: string, language?: Language);
24
26
  /**
25
27
  * Parse and extract from the source code
26
28
  */
27
29
  extract(): ExtractionResult;
30
+ /**
31
+ * Function-as-value capture (#756): if this node is one of the language's
32
+ * value-position containers (call arguments, assignment RHS, struct/object
33
+ * initializer, array/table literal), collect candidate function names from
34
+ * it. Candidates are gated & flushed at end-of-file (flushFnRefCandidates).
35
+ */
36
+ private maybeCaptureFnRefs;
37
+ /**
38
+ * Candidates-only scan of a subtree the main walkers won't traverse
39
+ * (top-level variable initializers). No extraction side effects. Halts at
40
+ * nested function definitions: their bodies are walked — and their
41
+ * candidates attributed — by extractFunction's own body walk.
42
+ */
43
+ private scanFnRefSubtree;
44
+ /**
45
+ * Gate captured function-as-value candidates and push survivors as
46
+ * `function_ref` unresolved references.
47
+ *
48
+ * The gate bounds volume and protects precision: a candidate survives only
49
+ * if its name matches a function/method DEFINED IN THIS FILE or a name this
50
+ * file imports/references. Everything else (locals, params, fields passed
51
+ * as arguments) is dropped before it ever reaches the database. Resolution
52
+ * then matches survivors against function/method nodes only
53
+ * (matchFunctionRef) and emits `references` edges — which callers/impact
54
+ * already traverse.
55
+ *
56
+ * Known v1 limit, deliberate: a C/C++ callback registered in a DIFFERENT
57
+ * translation unit than its definition (extern, no symbol imports to match)
58
+ * is not captured. Same-file registration — the dominant C pattern (static
59
+ * callback + same-file ops struct) — is.
60
+ */
61
+ private flushFnRefCandidates;
28
62
  /**
29
63
  * Visit a node and extract information
30
64
  */
@@ -97,6 +131,32 @@ export declare class TreeSitterExtractor {
97
131
  * Extracts each declarator as a 'field' kind node inside the owning class.
98
132
  */
99
133
  private extractField;
134
+ /**
135
+ * Extract function-valued properties of an object literal as named function
136
+ * nodes (named by their property key). Shared by the two object-of-functions
137
+ * shapes in extractVariable: the object as a direct const value, and the
138
+ * object returned by a store-initializer call. Handles both `key: () => {}` /
139
+ * `key: function() {}` pairs and method shorthand `key() {}`.
140
+ */
141
+ private extractObjectLiteralFunctions;
142
+ /** Property-key text with surrounding quotes stripped (`'foo'` → `foo`). */
143
+ private objectKeyName;
144
+ /**
145
+ * Given a `call_expression` initializer (`create((set, get) => ({...}))`),
146
+ * find the object literal RETURNED by a function argument — descending through
147
+ * nested call_expression arguments so middleware wrappers are unwrapped
148
+ * (`create(persist((set, get) => ({...}), {...}))`, devtools, immer,
149
+ * subscribeWithSelector). Returns null when no such object is found — the
150
+ * common case for ordinary call initializers — so this stays cheap and silent
151
+ * rather than guessing. Keyed purely on AST shape; no library names.
152
+ */
153
+ private findInitializerReturnedObject;
154
+ /**
155
+ * The object literal a function expression returns — either the `=> ({...})`
156
+ * arrow form (a parenthesized_expression wrapping an object) or a
157
+ * `=> { return {...} }` block. Returns null for any other body shape.
158
+ */
159
+ private functionReturnedObject;
100
160
  /**
101
161
  * Extract a variable declaration (const, let, var, etc.)
102
162
  *
@@ -111,6 +171,14 @@ export declare class TreeSitterExtractor {
111
171
  * Returns true if children should be skipped (struct/interface handled body visiting).
112
172
  */
113
173
  private extractTypeAlias;
174
+ /**
175
+ * Extract the method specs of a Go `interface_type` body as `method` nodes
176
+ * contained by the interface (e.g. `Marshal`, `Unmarshal` of a `Core`
177
+ * interface). tree-sitter-go names these `method_elem` (newer) or
178
+ * `method_spec` (older). Embedded interfaces (`Reader` inside `ReadWriter`)
179
+ * are `type_identifier`s, not methods, and are left to inheritance extraction.
180
+ */
181
+ private extractGoInterfaceMethods;
114
182
  /**
115
183
  * Surface the members of a TypeScript `type X = { ... }` (or intersection
116
184
  * thereof) as `property` / `method` nodes under the type-alias node. Only
@@ -119,6 +187,34 @@ export declare class TreeSitterExtractor {
119
187
  * don't produce phantom members.
120
188
  */
121
189
  private extractTsTypeAliasMembers;
190
+ /**
191
+ * Surface the string-literal "names" of a TypeScript service/contract
192
+ * registry written as a tuple of generic instantiations:
193
+ *
194
+ * type MyServiceList = [
195
+ * Service<'query_apply_record', Req, Resp>,
196
+ * Service<'apply_confirm', Req, Resp>,
197
+ * ];
198
+ *
199
+ * Each `Service<'name', …>` tags an entry with a string-literal name that a
200
+ * dynamic factory (`createService<MyServiceList>()`) turns into a callable
201
+ * property (`api.query_apply_record(…)`). Static extraction otherwise never
202
+ * sees that name — it's a type argument, not a declaration — so
203
+ * `codegraph query query_apply_record` returned nothing (issue #634). We emit
204
+ * each name as a `method` node under the type alias (qualifiedName
205
+ * `MyServiceList::query_apply_record`) so it's searchable and resolvable as a
206
+ * symbol. (A call through the proxy, `api.query_apply_record(…)`, still
207
+ * resolves to the imported `api` binding — the receiver's type isn't known —
208
+ * so this fixes discoverability, not the per-method call edge.)
209
+ *
210
+ * Scope is deliberately narrow to avoid noise: only a string literal that is
211
+ * a DIRECT type argument of a `generic_type` that is itself a DIRECT element
212
+ * of a `tuple_type`. This excludes utility types (`Pick`/`Omit`/`Record` are
213
+ * never written as tuples) and string args nested deeper
214
+ * (`Service<'a', Pick<U, 'id'>>` yields only `a`, never `id`). Names must be
215
+ * valid identifiers, which also rules out route paths / arbitrary strings.
216
+ */
217
+ private extractTsTupleContractNames;
122
218
  /**
123
219
  * `foo: () => T` → property_signature whose type_annotation contains a
124
220
  * `function_type`. Treat that as a method-shaped contract member, since
@@ -132,6 +228,83 @@ export declare class TreeSitterExtractor {
132
228
  * Also creates unresolved references for resolution purposes.
133
229
  */
134
230
  private extractImport;
231
+ /**
232
+ * Emit one `imports` reference per named/default import binding (TS/JS family),
233
+ * attributed to the file node — so the resolver links each imported symbol to
234
+ * the file that DEFINES it.
235
+ *
236
+ * Importing a symbol IS a dependency, but extraction only emits references for
237
+ * calls, instantiations, type annotations, and inheritance. A symbol that's
238
+ * imported and then only re-exported (`export { X } from './x'`), placed in a
239
+ * registry array (`[expressResolver, …]`), passed as an argument, or used in
240
+ * JSX produced NO cross-file edge at all — so the providing file showed a
241
+ * false "0 dependents" and was invisible to blast-radius / `affected`. The
242
+ * resolver maps the local name (alias-aware) to the provider's definition and
243
+ * creates a cross-file `imports` edge; `getFileDependents` picks it up, while
244
+ * `getImpactRadius` keeps it as a bounded leaf (the importing file node).
245
+ *
246
+ * Namespace imports (`import * as NS`) bind a whole module: `NS.member` calls
247
+ * resolve on their own, but a namespace used ONLY via a value-member read
248
+ * (`NS.SOME_CONST`) would leave no edge — so we also emit the namespace local
249
+ * name, which the resolver links to the module FILE as a dependency backstop.
250
+ */
251
+ private emitImportBindingRefs;
252
+ /**
253
+ * Emit one `imports` reference per re-exported binding of a
254
+ * `export { A, B as C } from './y'` statement, attributed to the file node —
255
+ * so a barrel that re-exports from another module records a dependency on it.
256
+ *
257
+ * Links the SOURCE-side name (`A`, the `name` field — not the local alias
258
+ * `C`), since that is what the source module defines. `export * from './y'`
259
+ * has no named bindings to attribute and `export { default as X }` can't be
260
+ * name-matched, so both are skipped.
261
+ */
262
+ private emitReExportRefs;
263
+ /**
264
+ * Emit one `imports` reference per binding of a Rust `use` declaration —
265
+ * `use crate::m::Item`, `use crate::m::{A, B as C}`, `pub use self::sub::Item`.
266
+ * Emits the FULL path (e.g. `self::sub::Item`, not just `Item`) so the resolver
267
+ * can resolve the module prefix to a file and find the leaf symbol there —
268
+ * disambiguating common-name re-exports (`pub use self::read::read`, where the
269
+ * leaf `read` collides with many same-named symbols). Falls back to name-match
270
+ * on the leaf when the path can't be resolved. `use ...::*` has no leaf binding.
271
+ */
272
+ private emitRustUseBindingRefs;
273
+ /**
274
+ * Emit an `imports` reference for a single PHP `use Foo\Bar\Baz;` (grouped
275
+ * imports `use Foo\{A, B}` are handled where their per-item nodes are created).
276
+ * The reference targets the namespace-qualified `Foo\Bar::Baz` form classes are
277
+ * stored under (see the PHP `namespace` capture), so it resolves to the RIGHT
278
+ * definition — Laravel has many same-named contracts (`Factory`, `Dispatcher`,
279
+ * `Guard`) across namespaces that a bare-name match can't disambiguate.
280
+ */
281
+ private emitPhpUseRefs;
282
+ /**
283
+ * Ruby `require`/`require_relative` → an `imports` ref to the required FILE.
284
+ * `require "sidekiq/fetch"` is load-path-relative (matched by file-path suffix
285
+ * via {@link matchByFilePath}); `require_relative "../foo"` is resolved against
286
+ * this file's directory. Bare gem/stdlib requires (`require "json"`, no slash)
287
+ * are skipped — they're external. The path form (a `/` + `.rb`) makes the ref
288
+ * resolve to the file node, so a file pulled in only by `require` — not by a
289
+ * resolved constant/call — still records a cross-file dependency.
290
+ */
291
+ private emitRubyRequireRefs;
292
+ /** Convert a PHP FQN `Foo\Bar\Baz` to the stored `Foo\Bar::Baz` and emit an `imports` ref. */
293
+ private pushPhpUseRef;
294
+ /**
295
+ * Emit one `imports` reference per name imported in a Python
296
+ * `from module import A, B as C` statement, attributed to the file node — so
297
+ * the resolver links each imported name to the module that DEFINES it.
298
+ *
299
+ * Same recall gap as TS: extraction only emitted references for calls,
300
+ * instantiations, and inheritance, so a name imported and then used in a
301
+ * non-call position (a list/dict literal, a default argument, a decorator
302
+ * target, or simply re-exported through an `__init__.py` barrel) produced no
303
+ * cross-file edge — the providing module showed a false "0 dependents". Links
304
+ * the LOCAL name (alias when present, since that's what the resolver's import
305
+ * mapping keys on); `from module import *` has no names to attribute.
306
+ */
307
+ private emitPyFromImportRefs;
135
308
  /**
136
309
  * Extract a function call
137
310
  */
@@ -146,6 +319,18 @@ export declare class TreeSitterExtractor {
146
319
  * arguments (`new Foo(bar())`) get their own `calls` references.
147
320
  */
148
321
  private extractInstantiation;
322
+ /**
323
+ * Static-member / value-read pass. A type/enum/class used only via a member
324
+ * VALUE — `Enum.value`, `Type.CONST`, `Colors.red`, `Foo::BAR` — recorded no
325
+ * edge, because the body walker only handled CALLS (`Type.method()`). So a
326
+ * type referenced only by an enum value or a static field looked like nothing
327
+ * depended on it (the residual frontier across Dart/Java/C#/Swift/Kotlin/PHP).
328
+ * Emit a `references` edge to the capitalized receiver. Gated to languages
329
+ * where types are Capitalized by convention, and skipped when the access is a
330
+ * call's callee (the call extractor already links the method).
331
+ */
332
+ private extractStaticMemberRef;
333
+ private pushStaticMemberRef;
149
334
  /**
150
335
  * Find a `class_body` child of an `object_creation_expression` — the
151
336
  * marker for an anonymous class (`new T() { ... }`). Returns the body
@@ -190,6 +375,19 @@ export declare class TreeSitterExtractor {
190
375
  * tree-sitter to interpret the namespace block as a function_definition,
191
376
  * hiding real class/struct/enum nodes inside the "function body".
192
377
  */
378
+ /**
379
+ * Rocket route-registration macros — `routes![a::b::handler, c::d::other]`
380
+ * and `catchers![not_found]`. Tree-sitter leaves a macro body as a flat
381
+ * `token_tree` of raw tokens (`identifier`, `::`, `,`), so the handler paths
382
+ * are never seen as references and each handler fn looks like it has no caller
383
+ * — it's mounted by Rocket at runtime, not called by in-repo code, so its file
384
+ * shows 0 dependents. Walk the token tree, reconstruct each comma-separated
385
+ * path, and emit a `references` edge; the Rust path resolver
386
+ * (`resolveRustPathReference`) then links it to the handler fn. The handler
387
+ * names are explicit in source, so this is precise static extraction, not a
388
+ * heuristic — no false edges (resolution still validates each path).
389
+ */
390
+ private extractRustRouteMacro;
193
391
  private visitFunctionBody;
194
392
  /**
195
393
  * Extract inheritance relationships
@@ -208,6 +406,11 @@ export declare class TreeSitterExtractor {
208
406
  * Languages that support type annotations (TypeScript, etc.)
209
407
  */
210
408
  private readonly TYPE_ANNOTATION_LANGUAGES;
409
+ /**
410
+ * PHP pseudo-types and `self`/`static`/`parent` that aren't project symbols.
411
+ * (Scalar primitives parse as `primitive_type` and are skipped structurally.)
412
+ */
413
+ private readonly PHP_PSEUDO_TYPES;
211
414
  /**
212
415
  * Built-in/primitive type names that shouldn't create references
213
416
  */
@@ -230,12 +433,35 @@ export declare class TreeSitterExtractor {
230
433
  * `tuple_type`, …) — none of which are `type_identifier`. Closes #381.
231
434
  */
232
435
  private extractCsharpTypeRefs;
436
+ /**
437
+ * Record the dependencies declared by a C# PRIMARY CONSTRUCTOR
438
+ * (`class Svc(IRepo repo, [FromKeyedServices("k")] ICache cache) { … }`,
439
+ * C# 12+). The parameter list hangs off the class/struct/record declaration
440
+ * as an unnamed-field `parameter_list` child (not the `parameters` field a
441
+ * method uses), so it's found by node type. Each parameter's declared type
442
+ * becomes a `references` edge from the owning type — these are exactly the
443
+ * services a DI-registered type depends on, so impact/blast-radius and
444
+ * "who depends on this contract" now see them. No-op when there's no primary
445
+ * constructor. (#237)
446
+ */
447
+ private extractCsharpPrimaryCtorParamRefs;
233
448
  /**
234
449
  * Walk a C# subtree that is KNOWN to be in a type position
235
450
  * (return type, parameter type, property type, field type, generic
236
451
  * argument). Identifiers here are type names, not parameter names.
237
452
  */
238
453
  private walkCsharpTypePosition;
454
+ /**
455
+ * Extract PHP type references from a method/function/property declaration.
456
+ * Walks ONLY type positions: each parameter's type child (inside
457
+ * `formal_parameters`), the return type, and a property's type — all
458
+ * `named_type` / `optional_type` / `union_type` / … direct children. Parameter
459
+ * and property NAMES are `variable_name` (`$x`), never type nodes, so they
460
+ * can't be mis-emitted.
461
+ */
462
+ private extractPhpTypeRefs;
463
+ /** Walk a PHP subtree KNOWN to be in a type position; emit class/interface refs. */
464
+ private walkPhpTypePosition;
239
465
  /**
240
466
  * Extract type references from a variable's type annotation.
241
467
  */
@@ -275,6 +501,17 @@ export declare class TreeSitterExtractor {
275
501
  * Extract function calls from a Pascal expression
276
502
  */
277
503
  private extractPascalCall;
504
+ /**
505
+ * Extract a PAREN-LESS Pascal method/procedure call (`Obj.Method;`,
506
+ * `TFoo.GetInstance.DoIt;`). Pascal lets a no-arg method drop its parens, so it
507
+ * parses as a bare `exprDot` (not an `exprCall`). A bare `exprDot` is
508
+ * syntactically identical to a field/property access, so this is only ever
509
+ * called for a STATEMENT-level exprDot (caller-gated): a bare `Obj.Field;`
510
+ * statement is a no-op, so a statement-level dot expression is a call. (An
511
+ * exprDot in assignment LHS/RHS or a condition is left alone — there it really
512
+ * can be a field/property read.)
513
+ */
514
+ private extractPascalParenlessCall;
278
515
  /**
279
516
  * Recursively visit a Pascal block/statement tree for call expressions
280
517
  */
@@ -32,5 +32,20 @@ export declare class VueExtractor {
32
32
  * Process a script block by delegating to TreeSitterExtractor
33
33
  */
34
34
  private processScriptBlock;
35
+ /**
36
+ * Extract component usages from the Vue `<template>`.
37
+ *
38
+ * PascalCase tags (`<Modal>`, `<Button />`) and kebab-case tags
39
+ * (`<my-button>`) both represent component instantiations — analogous to
40
+ * function calls in imperative code. Capturing them creates parent→child
41
+ * component edges and lets `callers` / `impact` see a component that is
42
+ * only ever used in markup. Vue's extractor previously parsed only the
43
+ * `<script>` block, so these usages produced no edge at all (#629).
44
+ *
45
+ * HTML elements (lowercase, no hyphen) and Vue built-ins are skipped.
46
+ * Unmatched names create no edge during resolution, so converting
47
+ * kebab-case is safe even for native custom elements.
48
+ */
49
+ private extractTemplateComponents;
35
50
  }
36
51
  //# sourceMappingURL=vue-extractor.d.ts.map
package/dist/index.d.ts CHANGED
@@ -158,8 +158,8 @@ export declare class CodeGraph {
158
158
  */
159
159
  getPendingFiles(): PendingFile[];
160
160
  /**
161
- * Resolves once the file watcher has finished its initial chokidar scan.
162
- * Useful for tests that need a deterministic boundary before asserting on
161
+ * Resolves once the file watcher has installed its watch set. Useful for
162
+ * tests that need a deterministic boundary before asserting on
163
163
  * `getPendingFiles()`. Resolves immediately when no watcher is active.
164
164
  */
165
165
  waitUntilWatcherReady(timeoutMs?: number): Promise<void>;
@@ -171,6 +171,30 @@ export declare class CodeGraph {
171
171
  modified: string[];
172
172
  removed: string[];
173
173
  };
174
+ /**
175
+ * Most recent index timestamp (ms since epoch) across all tracked files, or
176
+ * null when nothing is indexed yet. Lets library consumers check index
177
+ * freshness without shelling out to `codegraph status --json`. (#329)
178
+ */
179
+ getLastIndexedAt(): number | null;
180
+ /**
181
+ * Which engine built the current index: the package version + extraction
182
+ * version stamped at the last full `indexAll`. Either field is null for an
183
+ * index built before stamping existed (treated as stale). See
184
+ * `extraction-version.ts` and `isIndexStale()`.
185
+ */
186
+ getIndexBuildInfo(): {
187
+ version: string | null;
188
+ extractionVersion: number | null;
189
+ };
190
+ /**
191
+ * True when the on-disk index was built by an engine whose extraction is
192
+ * older than the one now running — i.e. a re-index would add data a migration
193
+ * can't backfill. False when there's no index yet (nothing to refresh) or the
194
+ * stamp is current. This is the signal behind `codegraph status`'s re-index
195
+ * hint and `codegraph upgrade`'s reminder.
196
+ */
197
+ isIndexStale(): boolean;
174
198
  /**
175
199
  * Extract nodes and edges from source code (without storing)
176
200
  */
@@ -227,15 +251,29 @@ export declare class CodeGraph {
227
251
  * Get all nodes of a specific kind
228
252
  */
229
253
  getNodesByKind(kind: Node['kind']): Node[];
254
+ /**
255
+ * Get ALL nodes with an exact name (direct index lookup, not FTS-ranked/capped).
256
+ * Used to enumerate every overload of a heavily-overloaded name so the specific
257
+ * definition the caller wants is never dropped below a search cut.
258
+ */
259
+ getNodesByName(name: string): Node[];
230
260
  /**
231
261
  * Search nodes by text
232
262
  */
233
263
  searchNodes(query: string, options?: SearchOptions): SearchResult[];
264
+ /**
265
+ * Normalized project-name tokens (go.mod / package.json / repo dir) used to
266
+ * down-weight the non-discriminative project name in search ranking (#720).
267
+ * Exposed so explore can exclude it from the PascalCase type-disambiguation
268
+ * bias, which would otherwise pull overloaded tokens toward whichever stack
269
+ * embeds the project name.
270
+ */
271
+ getProjectNameTokens(): Set<string>;
234
272
  /**
235
273
  * Find the project's "primary route file" — the file with the densest
236
274
  * concentration of framework-emitted `route` nodes (≥3 routes, ≥30%
237
275
  * of all non-test routes). Used to inline the routing config in
238
- * `codegraph_context` responses on small realworld template repos
276
+ * `codegraph_explore` responses on small realworld template repos
239
277
  * (rails-realworld, laravel-realworld, drupal-admintoolbar, …) where
240
278
  * Glob+Read of `routes.rb`/`urls.py`/etc. otherwise beats codegraph.
241
279
  */
@@ -1,18 +1,41 @@
1
1
  /**
2
- * Marker constants for the legacy agent-instructions block.
2
+ * The marker-fenced agent-instructions block the installer writes into each
3
+ * agent's instructions file (CLAUDE.md / AGENTS.md / GEMINI.md).
3
4
  *
4
- * Codegraph used to write a `## CodeGraph` usage guide into each
5
- * agent's instructions file (CLAUDE.md / AGENTS.md / GEMINI.md /
6
- * codegraph.mdc / Kiro steering doc). That duplicated the guidance the
7
- * MCP server already emits in its `initialize` response every agent
8
- * read the same playbook twice each turn (issue #529). The installer no
9
- * longer writes an instructions file; the MCP server instructions in
10
- * `mcp/server-instructions.ts` are the single source of truth.
5
+ * History: pre-#529 the installer wrote a full usage playbook here, which
6
+ * duplicated the MCP `initialize` instructions for the main agent so it
7
+ * was removed and `mcp/server-instructions.ts` became the single source of
8
+ * truth. A much smaller block returned for #704, because the MCP
9
+ * instructions cannot reach two audiences that the instructions FILE does
10
+ * reach:
11
11
  *
12
- * These markers are retained so install (self-heal on upgrade) and
13
- * uninstall can find and strip the block a previous install wrote.
12
+ * - **Task-tool subagents** they receive the project instructions file
13
+ * in their context but NOT the MCP initialize instructions. They hold
14
+ * the codegraph MCP tools only as deferred names and rarely think to
15
+ * load them: measured on a forced-delegation flow question (excalidraw,
16
+ * sonnet, high effort), subagents loaded + used codegraph in ~1 of 9
17
+ * runs without this block, and consistently with it — including runs
18
+ * with zero Read/grep fallback.
19
+ * - **Non-MCP harnesses** — agents with no MCP client at all can still
20
+ * run the `codegraph explore` / `codegraph node` CLI, which prints the
21
+ * same output as the MCP tools.
22
+ *
23
+ * Keep this block SHORT. The main agent reads it every turn on top of the
24
+ * server instructions — the #529 duplication-cost argument still bounds
25
+ * its size. Command names and the two surfaces, nothing more.
14
26
  */
15
- /** Markers used by the marker-based section removal. */
27
+ /** Markers used by the marker-based section write/removal. */
16
28
  export declare const CODEGRAPH_SECTION_START = "<!-- CODEGRAPH_START -->";
17
29
  export declare const CODEGRAPH_SECTION_END = "<!-- CODEGRAPH_END -->";
30
+ /**
31
+ * The full block, markers included, exactly as written to disk.
32
+ *
33
+ * The wording is deliberately CONDITIONAL ("in repositories indexed by…"):
34
+ * a global install writes this into a user-scope file (~/.claude/CLAUDE.md,
35
+ * ~/.codex/AGENTS.md) that applies to every project the user opens —
36
+ * including unindexed ones, where an unconditional "this repository is
37
+ * indexed" claim would send subagents into failing codegraph calls (the
38
+ * noise the unindexed-session policy exists to prevent).
39
+ */
40
+ export declare const CODEGRAPH_INSTRUCTIONS_BLOCK = "<!-- CODEGRAPH_START -->\n## CodeGraph\n\nIn repositories indexed by CodeGraph (a `.codegraph/` directory exists at the repo root), reach for it BEFORE grep/find or reading files when you need to understand or locate code:\n\n- **MCP tools** (when available): `codegraph_explore` answers most code questions in one call \u2014 the relevant symbols' verbatim source plus the call paths between them. `codegraph_node` returns one symbol's source + callers, or reads a whole file with line numbers. If the tools are listed but deferred, load them by name via tool search.\n- **Shell** (always works): `codegraph explore \"<symbol names or question>\"` and `codegraph node <symbol-or-file>` print the same output.\n\nIf there is no `.codegraph/` directory, skip CodeGraph entirely \u2014 indexing is the user's decision.\n<!-- CODEGRAPH_END -->";
18
41
  //# sourceMappingURL=instructions-template.d.ts.map
@@ -2,10 +2,18 @@
2
2
  * opencode target.
3
3
  *
4
4
  * - MCP server entry to `~/.config/opencode/opencode.jsonc` (global,
5
- * XDG-style; `%APPDATA%/opencode/opencode.jsonc` on Windows) or
5
+ * XDG-style on EVERY platform, Windows included — see below) or
6
6
  * `./opencode.jsonc` (local). Falls back to `opencode.json` when a
7
7
  * `.json` file already exists; defaults new installs to `.jsonc`
8
8
  * because that's what opencode itself creates on first run.
9
+ *
10
+ * opencode resolves its config dir with the `xdg-basedir` package
11
+ * (sst/opencode `packages/core/src/global.ts`): `XDG_CONFIG_HOME`
12
+ * if set, else `~/.config` — unconditionally, on all platforms. It
13
+ * never reads `%APPDATA%`; that layout belonged to the discontinued
14
+ * Go fork. We previously wrote there on Windows, so opencode never
15
+ * saw the entry (#535) — install/uninstall now also sweep a stale
16
+ * codegraph entry out of the legacy `%APPDATA%/opencode` location.
9
17
  * - Instructions to `~/.config/opencode/AGENTS.md` (global) or
10
18
  * `./AGENTS.md` (local). opencode reads AGENTS.md for agent
11
19
  * instructions — same convention Codex CLI uses.
@@ -64,6 +64,20 @@ export declare function jsonDeepEqual(a: unknown, b: unknown): boolean;
64
64
  * existing block already matches `body`.
65
65
  */
66
66
  export declare function replaceOrAppendMarkedSection(filePath: string, body: string, startMarker: string, endMarker: string): 'created' | 'updated' | 'appended' | 'unchanged';
67
+ /**
68
+ * Upsert the CodeGraph instructions block into an agent instructions
69
+ * file (CLAUDE.md / AGENTS.md / GEMINI.md). The one write shared by
70
+ * every target: self-heals a stale pre-#529 long block (markers match →
71
+ * replaced by the current short one), appends after existing user
72
+ * content otherwise, and reports `unchanged` on byte-equal re-runs so
73
+ * install stays idempotent. See `instructions-template.ts` for why this
74
+ * block exists (#704: subagents + non-MCP harnesses never see the MCP
75
+ * initialize instructions).
76
+ */
77
+ export declare function upsertInstructionsEntry(file: string): {
78
+ path: string;
79
+ action: 'created' | 'updated' | 'unchanged';
80
+ };
67
81
  /**
68
82
  * Inverse of `replaceOrAppendMarkedSection`. Strips the marker
69
83
  * block from `filePath` if present. If the file becomes empty after
@@ -54,6 +54,20 @@ export interface DaemonHello {
54
54
  socketPath: string;
55
55
  protocol: 1;
56
56
  }
57
+ /**
58
+ * Optional reverse-handshake line a proxy sends right after it verifies the
59
+ * daemon hello, carrying its own pids so the daemon can reap the client if its
60
+ * process dies WITHOUT the socket ever signalling close (the Windows named-pipe
61
+ * hazard behind #692). Entirely optional and fail-safe: a connection that never
62
+ * sends it (a legacy/direct client) just falls back to the socket-close
63
+ * lifecycle. The `codegraph_client` marker is what tells it apart from the
64
+ * client's first JSON-RPC message.
65
+ */
66
+ export interface DaemonClientHello {
67
+ codegraph_client: 1;
68
+ pid: number;
69
+ hostPid: number | null;
70
+ }
57
71
  export interface DaemonStartResult {
58
72
  /** Always-non-null for a successfully-started daemon. */
59
73
  socketPath: string;
@@ -75,14 +89,21 @@ export declare class Daemon {
75
89
  private projectRoot;
76
90
  private server;
77
91
  private clients;
92
+ /** Per-client peer pids from the optional client-hello, for the liveness sweep. */
93
+ private clientPeers;
78
94
  private idleTimer;
79
95
  private idleTimeoutMs;
96
+ private maxIdleMs;
97
+ private lastActivityAt;
98
+ private maxIdleTimer;
99
+ private clientSweepTimer;
80
100
  private engine;
81
101
  private stopping;
82
102
  private socketPath;
83
103
  private pidPath;
84
104
  constructor(projectRoot: string, opts?: {
85
105
  idleTimeoutMs?: number;
106
+ maxIdleMs?: number;
86
107
  });
87
108
  /**
88
109
  * Bind the socket, kick off engine init, and register signal handlers. The
@@ -101,6 +122,26 @@ export declare class Daemon {
101
122
  private dropClient;
102
123
  private armIdleTimer;
103
124
  private disarmIdleTimer;
125
+ /**
126
+ * Defense-in-depth against a daemon that outlives its clients (#692), for the
127
+ * cases the refcount + idle timer miss because a socket close never arrives:
128
+ * - **Inactivity backstop:** exit if no inbound traffic for `maxIdleMs` while
129
+ * clients are still (nominally) connected. A phantom client sends nothing,
130
+ * so it can't pin the daemon past this window.
131
+ * - **Liveness sweep:** drop any client whose peer process has died (per the
132
+ * client-hello pids), which re-arms the idle timer once the last real
133
+ * client is gone. Catches a dead peer within one sweep instead of waiting
134
+ * out the whole backstop.
135
+ * Both timers are unref'd — the listening server keeps the loop alive, and
136
+ * neither should hold it open on its own.
137
+ */
138
+ private startLivenessTimers;
139
+ /**
140
+ * Drop every connected client whose peer process is gone. Returns the count
141
+ * reaped. `isAlive` is injected for testing. Clients with unknown pids (no
142
+ * client-hello) are skipped — they rely on the socket-close path.
143
+ */
144
+ reapDeadClients(isAlive: (pid: number) => boolean): number;
104
145
  private cleanupLockfile;
105
146
  }
106
147
  /**
@@ -128,7 +169,7 @@ export type AcquireResult = {
128
169
  * the file existed but was empty; under concurrent daemon startup a third
129
170
  * candidate could read that empty file, decode it as `null`, and `unlink` the
130
171
  * winner's lock → two daemons (two watchers, two writers). The window was
131
- * normally too small to hit, but the chokidar watcher's extra startup time made
172
+ * normally too small to hit, but the file watcher's extra startup time made
132
173
  * concurrent daemons overlap enough to reproduce it reliably.
133
174
  *
134
175
  * The fix writes the complete record to a private temp file, then hard-links it
@@ -156,6 +197,24 @@ export declare function clearStaleDaemonLock(pidPath: string, expectedDeadPid?:
156
197
  * mistake a live daemon for a dead one and clear its lock.
157
198
  */
158
199
  export declare function isProcessAlive(pid: number): boolean;
200
+ /**
201
+ * Parse one client-hello line. Returns the peer pids if `line` is a well-formed
202
+ * client-hello (carries the `codegraph_client` marker), or null otherwise — in
203
+ * which case the caller treats the bytes as ordinary JSON-RPC.
204
+ */
205
+ export declare function parseClientHelloLine(line: string): {
206
+ pid: number;
207
+ hostPid: number | null;
208
+ } | null;
209
+ /**
210
+ * A client's peer is dead when its proxy process is gone, or when its known
211
+ * host process is gone. Unknown pid (no client-hello) is never "dead" on this
212
+ * basis — those clients rely on the socket-close path. Exported for testing.
213
+ */
214
+ export declare function peerIsDead(peers: {
215
+ pid: number | null;
216
+ hostPid: number | null;
217
+ }, isAlive: (pid: number) => boolean): boolean;
159
218
  /** Exported for test stubs that need to bound the hello-line read. */
160
219
  export { MAX_HELLO_LINE_BYTES };
161
220
  //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1,41 @@
1
+ export interface BoundaryMatch {
2
+ /** Stable form id, e.g. 'computed-call' — used for per-form dedupe. */
3
+ form: string;
4
+ /** Human label for the dispatch form, e.g. 'computed member call'. */
5
+ label: string;
6
+ /** One-line source snippet of the site (from the original, untrimmed text). */
7
+ snippet: string;
8
+ /** 1-based line within the scanned body's FILE (absolute, ready to print). */
9
+ line: number;
10
+ /**
11
+ * Statically-visible dispatch key, when one exists: the string literal in
12
+ * `handlers['save']`, the `:symbol` in ruby `send`, the type name in
13
+ * `Send(new CreateCmd(...))`. Drives candidate lookup. Undefined when the
14
+ * key is a runtime value (variable, computed expression).
15
+ */
16
+ key?: string;
17
+ /** For typed-bus matches the key is a TYPE name (candidates ~ `${key}Handler`). */
18
+ keyIsType?: boolean;
19
+ /** Additional sites of the same form+key in this body beyond the reported one. */
20
+ moreSites?: number;
21
+ }
22
+ /**
23
+ * Blank the CONTENTS of string literals (quotes preserved, offsets preserved)
24
+ * so dispatch-shaped prose — docs, error messages, template text — can't fire
25
+ * a matcher. Run AFTER comment stripping (comments are already spaces).
26
+ * Backslash escapes are honored; `'`/`"` strings end at a newline (treated as
27
+ * unterminated, matching the comment stripper); backticks span lines, and
28
+ * `${...}` interpolations inside them are blanked too — missing a dispatch
29
+ * inside a template literal is acceptable, false-firing on prose is not.
30
+ */
31
+ export declare function blankStringContents(text: string): string;
32
+ /**
33
+ * Scan one symbol's body for dynamic-dispatch sites.
34
+ *
35
+ * @param body the symbol's source text (sliced from the file)
36
+ * @param language Node.language of the symbol
37
+ * @param fileStartLine 1-based line where `body` starts in its file — returned
38
+ * line numbers are absolute file lines.
39
+ */
40
+ export declare function scanDynamicDispatch(body: string, language: string, fileStartLine: number): BoundaryMatch[];
41
+ //# sourceMappingURL=dynamic-boundaries.d.ts.map