@colbymchenry/codegraph 0.9.9 → 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.
- package/README.md +100 -17
- package/dist/bin/codegraph.d.ts +1 -0
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/queries.d.ts +43 -0
- package/dist/db/sqlite-adapter.d.ts +7 -0
- package/dist/directory.d.ts +34 -2
- package/dist/extraction/astro-extractor.d.ts +79 -0
- package/dist/extraction/extraction-version.d.ts +25 -0
- package/dist/extraction/function-ref.d.ts +118 -0
- package/dist/extraction/grammars.d.ts +7 -1
- package/dist/extraction/index.d.ts +34 -0
- package/dist/extraction/languages/c-cpp.d.ts +8 -0
- package/dist/extraction/languages/csharp.d.ts +22 -0
- package/dist/extraction/languages/r.d.ts +3 -0
- package/dist/extraction/languages/typescript.d.ts +13 -0
- package/dist/extraction/liquid-extractor.d.ts +7 -0
- package/dist/extraction/razor-extractor.d.ts +42 -0
- package/dist/extraction/tree-sitter-types.d.ts +33 -0
- package/dist/extraction/tree-sitter.d.ts +211 -0
- package/dist/extraction/vue-extractor.d.ts +15 -0
- package/dist/index.d.ts +34 -2
- package/dist/installer/instructions-template.d.ts +34 -11
- package/dist/installer/targets/opencode.d.ts +9 -1
- package/dist/installer/targets/shared.d.ts +14 -0
- package/dist/mcp/daemon.d.ts +60 -1
- package/dist/mcp/dynamic-boundaries.d.ts +41 -0
- package/dist/mcp/ppid-watchdog.d.ts +44 -0
- package/dist/mcp/proxy.d.ts +6 -0
- package/dist/mcp/server-instructions.d.ts +12 -1
- package/dist/mcp/session.d.ts +2 -0
- package/dist/mcp/stdin-teardown.d.ts +27 -0
- package/dist/mcp/tools.d.ts +71 -0
- package/dist/resolution/callback-synthesizer.d.ts +3 -3
- package/dist/resolution/frameworks/astro.d.ts +9 -0
- package/dist/resolution/frameworks/index.d.ts +1 -0
- package/dist/resolution/import-resolver.d.ts +10 -0
- package/dist/resolution/index.d.ts +80 -0
- package/dist/resolution/name-matcher.d.ts +61 -0
- package/dist/resolution/types.d.ts +27 -3
- package/dist/resolution/workspace-packages.d.ts +48 -0
- package/dist/search/query-utils.d.ts +17 -1
- package/dist/sync/watcher.d.ts +124 -32
- package/dist/telemetry/index.d.ts +146 -0
- package/dist/types.d.ts +17 -2
- package/dist/upgrade/index.d.ts +132 -0
- package/dist/utils.d.ts +30 -24
- package/package.json +7 -7
|
@@ -9,6 +9,36 @@ import { UnresolvedRef, ResolvedRef, ResolutionContext } from './types';
|
|
|
9
9
|
* by matching the filename against file nodes.
|
|
10
10
|
*/
|
|
11
11
|
export declare function matchByFilePath(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
|
|
12
|
+
export declare function sameLanguageFamily(a: string, b: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* True when `lang` belongs to a known multi-language family (jvm/apple/web/c).
|
|
15
|
+
* Languages not listed (php, python, go, ruby, rust, dart, …) and config
|
|
16
|
+
* formats (yaml/xml/blade) form their own singleton families and return
|
|
17
|
+
* `false` — used to leave config↔code framework bridges (whose config side is
|
|
18
|
+
* never a known programming-language family) out of the cross-family gate.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isKnownLanguageFamily(lang: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* True when `a` and `b` are two DIFFERENT *known* language families — the
|
|
23
|
+
* signature of a coincidental cross-language name collision (a TS `import
|
|
24
|
+
* React` matching a Swift `import React`, a C++ `#include "X.h"` matching a
|
|
25
|
+
* same-named ObjC header on another platform). The both-*known* test is
|
|
26
|
+
* deliberately weaker than {@link sameLanguageFamily}'s negation: a
|
|
27
|
+
* single-file-component language that carries its own tag (`vue`/`svelte`)
|
|
28
|
+
* importing a `.ts` module, or any singleton-family language (php/go/ruby/…),
|
|
29
|
+
* returns `false` here and is left alone.
|
|
30
|
+
*/
|
|
31
|
+
export declare function crossesKnownFamily(a: string, b: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a function-as-value reference (#756) — a function name used as a
|
|
34
|
+
* callback/function-pointer value (`register(handler)`, `o->cb = handler`,
|
|
35
|
+
* `{ .cb = handler }`, `signal(SIGINT, handler)`). The ONLY strategy allowed
|
|
36
|
+
* for `function_ref` refs: exact name, function/method targets only, same
|
|
37
|
+
* language family, same-file first, and cross-file only when the match is
|
|
38
|
+
* UNIQUE. No fuzzy fallback, no qualified-name walking — a wrong callback
|
|
39
|
+
* edge is worse than none.
|
|
40
|
+
*/
|
|
41
|
+
export declare function matchFunctionRef(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
|
|
12
42
|
/**
|
|
13
43
|
* Try to resolve a reference by exact name match
|
|
14
44
|
*/
|
|
@@ -17,6 +47,37 @@ export declare function matchByExactName(ref: UnresolvedRef, context: Resolution
|
|
|
17
47
|
* Try to resolve by qualified name
|
|
18
48
|
*/
|
|
19
49
|
export declare function matchByQualifiedName(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a C++ chained call whose receiver is itself a call — encoded by the
|
|
52
|
+
* extractor as `<innerCallee>().<method>` (#645). The receiver's type is what
|
|
53
|
+
* the inner call returns; the outer method is then resolved and VALIDATED on it
|
|
54
|
+
* (resolveMethodOnType requires `cls::method` to exist), so a wrong inference
|
|
55
|
+
* produces no edge rather than a wrong one.
|
|
56
|
+
*/
|
|
57
|
+
export declare function matchCppCallChain(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a `::`-scoped factory chain whose receiver is a scoped/static call —
|
|
60
|
+
* PHP `Cls::for($x)->method()` (#608, the per-credential Laravel client idiom) or
|
|
61
|
+
* Rust `Foo::new().bar()` (an associated-function call) — both encoded by the
|
|
62
|
+
* extractor as `Cls::factory().method`. The receiver's type is what `Cls::factory`
|
|
63
|
+
* returns: a `self` marker (PHP `: self`/`: static`, Rust `-> Self`) resolves to
|
|
64
|
+
* the factory's own type, a concrete return type to that type. The outer method is
|
|
65
|
+
* then resolved and VALIDATED on it (resolveMethodOnType requires the method to
|
|
66
|
+
* exist on the type or a supertype it conforms to), so a wrong inference yields no
|
|
67
|
+
* edge rather than a wrong one. Shared by the `::`-receiver languages (PHP, Rust).
|
|
68
|
+
*/
|
|
69
|
+
export declare function matchScopedCallChain(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve a dotted chained call whose receiver is a static factory / fluent call —
|
|
72
|
+
* `Foo.getInstance().bar()`, encoded by the extractor as `Foo.getInstance().bar`
|
|
73
|
+
* (#645/#608 mechanism). The receiver's type is what `Foo.getInstance` returns
|
|
74
|
+
* (its declared return type); the outer method is then resolved and VALIDATED on
|
|
75
|
+
* it (resolveMethodOnType requires `Type::method` to exist), so a wrong inference
|
|
76
|
+
* yields no edge rather than a wrong one (e.g. a same-named `bar()` on an
|
|
77
|
+
* unrelated class is never matched). Shared by the dot-notation languages
|
|
78
|
+
* (Java, Kotlin, C#, Swift) — same receiver shape, same `Class::method` qualified names.
|
|
79
|
+
*/
|
|
80
|
+
export declare function matchDottedCallChain(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
|
|
20
81
|
/**
|
|
21
82
|
* Try to resolve by method name on a class/object
|
|
22
83
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Types for the reference resolution system.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { Language, Node, ReferenceKind } from '../types';
|
|
7
7
|
/**
|
|
8
8
|
* An unresolved reference from extraction
|
|
9
9
|
*/
|
|
@@ -13,7 +13,7 @@ export interface UnresolvedRef {
|
|
|
13
13
|
/** The name being referenced */
|
|
14
14
|
referenceName: string;
|
|
15
15
|
/** Type of reference */
|
|
16
|
-
referenceKind:
|
|
16
|
+
referenceKind: ReferenceKind;
|
|
17
17
|
/** Line where reference occurs */
|
|
18
18
|
line: number;
|
|
19
19
|
/** Column where reference occurs */
|
|
@@ -36,7 +36,7 @@ export interface ResolvedRef {
|
|
|
36
36
|
/** Confidence score (0-1) */
|
|
37
37
|
confidence: number;
|
|
38
38
|
/** How it was resolved */
|
|
39
|
-
resolvedBy: 'exact-match' | 'import' | 'qualified-name' | 'framework' | 'fuzzy' | 'instance-method' | 'file-path';
|
|
39
|
+
resolvedBy: 'exact-match' | 'import' | 'qualified-name' | 'framework' | 'fuzzy' | 'instance-method' | 'file-path' | 'function-ref';
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Result of resolution attempt
|
|
@@ -76,6 +76,23 @@ export interface ResolutionContext {
|
|
|
76
76
|
getAllFiles(): string[];
|
|
77
77
|
/** Get nodes by lowercase name (O(1) lookup for fuzzy matching) */
|
|
78
78
|
getNodesByLowerName(lowerName: string): Node[];
|
|
79
|
+
/**
|
|
80
|
+
* Direct supertypes of the type named `typeName` (same language): the classes
|
|
81
|
+
* it extends and the interfaces / protocols / traits it implements/conforms to,
|
|
82
|
+
* by simple name. Backed by the resolved `implements`/`extends` edges, so it is
|
|
83
|
+
* EMPTY during the first resolution pass (edges aren't built yet) and populated
|
|
84
|
+
* afterward — the conformance pass uses it to resolve a chained method defined
|
|
85
|
+
* on a supertype the receiver type conforms to (e.g. a protocol-extension
|
|
86
|
+
* method). Optional so external/test contexts compile without it.
|
|
87
|
+
*/
|
|
88
|
+
getSupertypes?(typeName: string, language: Language): string[];
|
|
89
|
+
/**
|
|
90
|
+
* Look up a node by its id. Lets matchers derive the FROM-symbol's
|
|
91
|
+
* enclosing-class scope (Swift implicit-self method scoping, `this.X`
|
|
92
|
+
* member resolution). Optional so external/test contexts compile
|
|
93
|
+
* without it.
|
|
94
|
+
*/
|
|
95
|
+
getNodeById?(id: string): Node | null;
|
|
79
96
|
/** Get cached import mappings for a file */
|
|
80
97
|
getImportMappings(filePath: string, language: Language): ImportMapping[];
|
|
81
98
|
/**
|
|
@@ -94,6 +111,13 @@ export interface ResolutionContext {
|
|
|
94
111
|
* cross-package imports from third-party packages.
|
|
95
112
|
*/
|
|
96
113
|
getGoModule?(): import('./go-module').GoModule | null;
|
|
114
|
+
/**
|
|
115
|
+
* Monorepo workspace member packages, keyed by declared package name.
|
|
116
|
+
* Returns `null` for single-package repos (no `workspaces` field).
|
|
117
|
+
* Lets the resolver treat `@scope/ui/sub` as a local import into the
|
|
118
|
+
* member's directory instead of an external npm package (#629).
|
|
119
|
+
*/
|
|
120
|
+
getWorkspacePackages?(): import('./workspace-packages').WorkspacePackages | null;
|
|
97
121
|
/**
|
|
98
122
|
* Re-exports declared by a file (`export { x } from './other'`,
|
|
99
123
|
* `export * from './other'`). Empty array when the file has none.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS/TS workspace (monorepo) package resolution.
|
|
3
|
+
*
|
|
4
|
+
* npm / yarn / bun read member packages from the root `package.json`
|
|
5
|
+
* `workspaces` field; pnpm from `pnpm-workspace.yaml`. A cross-package
|
|
6
|
+
* import like `@scope/ui/widgets` is LOCAL to the monorepo, but to a
|
|
7
|
+
* single-package resolver it looks exactly like a third-party npm
|
|
8
|
+
* specifier — so `isExternalImport` flags it external and the
|
|
9
|
+
* consumer↔definition edge is never created. For component barrels
|
|
10
|
+
* (`export { default as X } from './x.svelte'`) that surfaces as a false
|
|
11
|
+
* `0 callers` on a live component (issue #629).
|
|
12
|
+
*
|
|
13
|
+
* This module maps each member package's declared `name` to its
|
|
14
|
+
* directory so the resolver can rewrite `@scope/ui/widgets` →
|
|
15
|
+
* `packages/ui/widgets` and then run normal extension/index resolution.
|
|
16
|
+
*
|
|
17
|
+
* Scope deliberately small for v1 (mirrors path-aliases.ts):
|
|
18
|
+
* - reads `workspaces` (array OR `{ packages: [...] }`) from package.json,
|
|
19
|
+
* plus a minimal `pnpm-workspace.yaml` `packages:` list
|
|
20
|
+
* - expands one level of `*` / `**` globs (`packages/*`, `apps/*`)
|
|
21
|
+
* - subpath resolution is directory-based (`@scope/ui/sub` → `<ui>/sub`);
|
|
22
|
+
* it does NOT yet honour a member's `exports` map or `main` field
|
|
23
|
+
* - returns null when the project declares no workspaces, so single-
|
|
24
|
+
* package repos pay nothing and see no behaviour change.
|
|
25
|
+
*/
|
|
26
|
+
export interface WorkspacePackages {
|
|
27
|
+
/** Member package `name` → directory relative to projectRoot (posix). */
|
|
28
|
+
byName: Map<string, string>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Load workspace member packages for `projectRoot`. Returns `null` when
|
|
32
|
+
* the project declares no workspaces (the common single-package case) —
|
|
33
|
+
* callers then skip all workspace logic.
|
|
34
|
+
*
|
|
35
|
+
* Cheap to call repeatedly only via the resolver's per-instance cache;
|
|
36
|
+
* this function itself touches the filesystem, so the resolver memoises it
|
|
37
|
+
* the same way it does {@link loadProjectAliases} / {@link loadGoModule}.
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadWorkspacePackages(projectRoot: string): WorkspacePackages | null;
|
|
40
|
+
/**
|
|
41
|
+
* Rewrite a bare workspace import to a path relative to projectRoot,
|
|
42
|
+
* WITHOUT an extension — the caller applies the language's extension/index
|
|
43
|
+
* resolution. `@scope/ui/widgets` → `packages/ui/widgets`; the bare package
|
|
44
|
+
* name `@scope/ui` → its directory. Returns `null` when no member package
|
|
45
|
+
* name matches.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveWorkspaceImport(importPath: string, ws: WorkspacePackages): string | null;
|
|
48
|
+
//# sourceMappingURL=workspace-packages.d.ts.map
|
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
* Shared module for search term extraction and scoring.
|
|
5
5
|
*/
|
|
6
6
|
import { Node } from '../types';
|
|
7
|
+
/** Normalize a name to a comparable token: lowercase, alphanumerics only. */
|
|
8
|
+
export declare function normalizeNameToken(raw: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Tokens that name the PROJECT as a whole — its `go.mod` module, `package.json`
|
|
11
|
+
* name, or repo root directory — rather than any specific symbol. A user
|
|
12
|
+
* naturally puts the project name in a query as context ("MyApp backend
|
|
13
|
+
* routes"), but it carries no discriminative signal: when it's also a substring
|
|
14
|
+
* of a symbol or path on one stack (a `MyAppFrontend/` dir, a `MyAppApp` class)
|
|
15
|
+
* it lexically inflates that stack and buries the rest (#720).
|
|
16
|
+
*
|
|
17
|
+
* Returned normalized (lowercase, alphanumerics only) so a query word can be
|
|
18
|
+
* compared by its normalized form. Only names ≥5 chars are kept — short ones
|
|
19
|
+
* (`api`, `app`, `core`, `web`) collide with real query terms too often to
|
|
20
|
+
* safely down-weight.
|
|
21
|
+
*/
|
|
22
|
+
export declare function deriveProjectNameTokens(projectRoot: string): Set<string>;
|
|
7
23
|
/**
|
|
8
24
|
* Common stop words to filter from search queries.
|
|
9
25
|
* Includes generic English + code-specific noise words.
|
|
@@ -34,7 +50,7 @@ export declare function extractSearchTerms(query: string, options?: {
|
|
|
34
50
|
* Score path relevance to a query
|
|
35
51
|
* Higher score = more relevant path
|
|
36
52
|
*/
|
|
37
|
-
export declare function scorePathRelevance(filePath: string, query: string): number;
|
|
53
|
+
export declare function scorePathRelevance(filePath: string, query: string, projectNameTokens?: Set<string>): number;
|
|
38
54
|
/**
|
|
39
55
|
* Check if a file path looks like a test file
|
|
40
56
|
*/
|
package/dist/sync/watcher.d.ts
CHANGED
|
@@ -4,14 +4,31 @@
|
|
|
4
4
|
* Watches the project directory for file changes and triggers debounced sync
|
|
5
5
|
* operations to keep the code graph up-to-date.
|
|
6
6
|
*
|
|
7
|
-
* Uses
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* .
|
|
7
|
+
* Uses Node's built-in `fs.watch` directly (no third-party watcher, no native
|
|
8
|
+
* addon) with a per-platform strategy chosen to keep the open-descriptor /
|
|
9
|
+
* kernel-watch cost BOUNDED rather than growing with the number of files:
|
|
10
|
+
*
|
|
11
|
+
* - macOS / Windows: a SINGLE recursive `fs.watch(root, {recursive:true})`.
|
|
12
|
+
* libuv maps this to one FSEvents stream (macOS) / one
|
|
13
|
+
* ReadDirectoryChangesW handle (Windows), so it costs O(1) descriptors no
|
|
14
|
+
* matter how large the tree. This is the fix for the macOS file-table
|
|
15
|
+
* exhaustion (#644 / #496 / #555 / #628): the previous watcher held one
|
|
16
|
+
* open fd PER WATCHED FILE on macOS (tens of thousands of REG fds), which
|
|
17
|
+
* exhausted `kern.maxfiles` and crashed unrelated processes system-wide.
|
|
18
|
+
*
|
|
19
|
+
* - Linux: recursive `fs.watch` is unsupported, so we watch each (non-ignored)
|
|
20
|
+
* DIRECTORY with one inotify watch — O(directories), NOT O(files). New
|
|
21
|
+
* directories are picked up dynamically and an overall watch cap bounds
|
|
22
|
+
* inotify usage on pathological monorepos (#579). A single inotify watch on
|
|
23
|
+
* a directory already reports create/modify/delete for its children, so
|
|
24
|
+
* per-file watches are never needed.
|
|
25
|
+
*
|
|
26
|
+
* Excluded trees (node_modules/, dist/, .git/, …) are filtered via the
|
|
27
|
+
* indexer's `buildScopeIgnore` (built-in default-ignore dirs + the project's
|
|
28
|
+
* .gitignore) — on Linux they're never descended into (so they cost no watch),
|
|
29
|
+
* and on macOS/Windows the single recursive stream still covers them but their
|
|
30
|
+
* events are dropped before any sync is scheduled. Either way the watcher's
|
|
31
|
+
* scope matches the indexer's (#276 / #407).
|
|
15
32
|
*/
|
|
16
33
|
/**
|
|
17
34
|
* Options for the file watcher
|
|
@@ -34,6 +51,15 @@ export interface WatchOptions {
|
|
|
34
51
|
* Callback when a sync errors (for logging/diagnostics).
|
|
35
52
|
*/
|
|
36
53
|
onSyncError?: (error: Error) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Test-only. When true, `start()` installs NO OS-level fs.watch — the
|
|
56
|
+
* watcher is "inert" and only the {@link __emitWatchEventForTests} /
|
|
57
|
+
* {@link FileWatcher.ingestEventForTests} seam drives its pipeline. This
|
|
58
|
+
* restores the deterministic, OS-free behavior the unit tests need (real
|
|
59
|
+
* FSEvents/inotify delivery races under parallel vitest). Production never
|
|
60
|
+
* sets it.
|
|
61
|
+
*/
|
|
62
|
+
inertForTests?: boolean;
|
|
37
63
|
}
|
|
38
64
|
/**
|
|
39
65
|
* Thrown by a `syncFn` to signal that the underlying sync couldn't acquire
|
|
@@ -69,8 +95,9 @@ export interface PendingFile {
|
|
|
69
95
|
* debounced sync operations via a provided callback.
|
|
70
96
|
*
|
|
71
97
|
* Design goals:
|
|
72
|
-
* -
|
|
73
|
-
*
|
|
98
|
+
* - Bounded resource usage: O(1) descriptors on macOS/Windows (one recursive
|
|
99
|
+
* watch), O(directories) inotify watches on Linux — never O(files), which
|
|
100
|
+
* was the system-crashing fd leak on macOS (#644/#496/#555/#628).
|
|
74
101
|
* - Debounced to avoid thrashing on rapid saves
|
|
75
102
|
* - Filters to supported source files by extension
|
|
76
103
|
* - Ignores .codegraph/ and .git/ regardless of .gitignore
|
|
@@ -78,11 +105,18 @@ export interface PendingFile {
|
|
|
78
105
|
* without blocking on a sync (issue #403)
|
|
79
106
|
*/
|
|
80
107
|
export declare class FileWatcher {
|
|
81
|
-
|
|
108
|
+
/** macOS/Windows: the single recursive watcher. Null on Linux. */
|
|
109
|
+
private recursiveWatcher;
|
|
110
|
+
/** Linux: one watcher per watched directory (keyed by absolute path). */
|
|
111
|
+
private dirWatchers;
|
|
112
|
+
/** Set once the per-directory watch cap is hit, so we log only once. */
|
|
113
|
+
private dirCapWarned;
|
|
114
|
+
/** Test-only inert mode: started, but with no OS watcher installed. */
|
|
115
|
+
private inert;
|
|
82
116
|
private debounceTimer;
|
|
83
117
|
/**
|
|
84
118
|
* Files seen by the watcher since the last successful sync — populated on
|
|
85
|
-
* every
|
|
119
|
+
* every change event, cleared at the start of a sync, and re-populated by
|
|
86
120
|
* events that arrive mid-sync (or restored on sync failure). Keyed by the
|
|
87
121
|
* same project-relative POSIX path the rest of the codebase uses, so a
|
|
88
122
|
* caller can intersect tool-response file paths against this map cheaply.
|
|
@@ -98,17 +132,18 @@ export declare class FileWatcher {
|
|
|
98
132
|
private syncing;
|
|
99
133
|
private stopped;
|
|
100
134
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
135
|
+
* True once the initial watch set is established. Unlike the previous
|
|
136
|
+
* chokidar implementation there is no asynchronous initial "crawl" emitting
|
|
137
|
+
* an `add` per existing file — `fs.watch` only reports changes from the
|
|
138
|
+
* moment it's installed — so this flips to true synchronously at the end of
|
|
139
|
+
* `start()`. The startup reconcile against on-disk state is handled
|
|
140
|
+
* separately by the engine's catch-up sync, not by the watcher.
|
|
106
141
|
*/
|
|
107
|
-
private
|
|
142
|
+
private ready;
|
|
108
143
|
/**
|
|
109
|
-
* Callbacks that resolve when
|
|
110
|
-
* any production caller that cares about a clean baseline) to
|
|
111
|
-
* gate on
|
|
144
|
+
* Callbacks that resolve when the watch set is established. Used by tests
|
|
145
|
+
* (and any production caller that cares about a clean baseline) to
|
|
146
|
+
* deterministically gate on watcher readiness.
|
|
112
147
|
*/
|
|
113
148
|
private readyWaiters;
|
|
114
149
|
private ignoreMatcher;
|
|
@@ -117,6 +152,7 @@ export declare class FileWatcher {
|
|
|
117
152
|
private readonly syncFn;
|
|
118
153
|
private readonly onSyncComplete?;
|
|
119
154
|
private readonly onSyncError?;
|
|
155
|
+
private readonly inertForTests;
|
|
120
156
|
constructor(projectRoot: string, syncFn: () => Promise<{
|
|
121
157
|
filesChanged: number;
|
|
122
158
|
durationMs: number;
|
|
@@ -126,32 +162,79 @@ export declare class FileWatcher {
|
|
|
126
162
|
* Returns true if watching started successfully, false otherwise.
|
|
127
163
|
*/
|
|
128
164
|
start(): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* macOS/Windows: one recursive watcher for the whole tree. O(1) descriptors.
|
|
167
|
+
* `filename` arrives relative to the project root (with subdirectories), so
|
|
168
|
+
* it maps straight to a project-relative path.
|
|
169
|
+
*/
|
|
170
|
+
private startRecursive;
|
|
171
|
+
/**
|
|
172
|
+
* Linux: walk the (non-ignored) tree and watch each directory. One inotify
|
|
173
|
+
* watch per directory reports create/modify/delete for that directory's
|
|
174
|
+
* direct children, so we never watch individual files.
|
|
175
|
+
*/
|
|
176
|
+
private startPerDirectory;
|
|
177
|
+
/**
|
|
178
|
+
* Add an inotify watch for `dir` and recurse into its non-ignored
|
|
179
|
+
* subdirectories. When `markExisting` is true (a directory that appeared
|
|
180
|
+
* AFTER startup), the source files already inside it are recorded as pending
|
|
181
|
+
* — this closes the `mkdir + write` race where files created before the new
|
|
182
|
+
* directory's watch is installed would otherwise be missed until the next
|
|
183
|
+
* full sync. The initial startup walk passes false (the engine's catch-up
|
|
184
|
+
* sync owns the baseline).
|
|
185
|
+
*/
|
|
186
|
+
private watchTree;
|
|
187
|
+
/**
|
|
188
|
+
* Linux per-directory event handler. `filename` is relative to `dir`. A new
|
|
189
|
+
* sub-directory is picked up by extending the watch tree; everything else is
|
|
190
|
+
* routed through the shared change handler.
|
|
191
|
+
*/
|
|
192
|
+
private handleDirEvent;
|
|
193
|
+
/**
|
|
194
|
+
* Shared change handler for both watch strategies. `rel` is a
|
|
195
|
+
* project-relative POSIX path. Applies the ignore + source-file filters and,
|
|
196
|
+
* for a real source change, records it as pending (#403) and schedules a
|
|
197
|
+
* debounced sync.
|
|
198
|
+
*
|
|
199
|
+
* The recursive (macOS/Windows) watcher reports events for ignored trees too
|
|
200
|
+
* (one stream covers the whole repo), so the ignore check here is load-bearing
|
|
201
|
+
* — it drops node_modules/dist/.git churn before any sync is scheduled.
|
|
202
|
+
*/
|
|
203
|
+
private handleChange;
|
|
204
|
+
/** Close and forget the watch for a directory that errored/was removed. */
|
|
205
|
+
private unwatchDir;
|
|
129
206
|
/** Our own dirs are always ignored, regardless of .gitignore. */
|
|
130
207
|
private isAlwaysIgnored;
|
|
131
208
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* rule like `build/` matches
|
|
209
|
+
* True for any directory that should NOT be watched (used while building the
|
|
210
|
+
* Linux per-directory watch tree). Tests the directory form of the path so a
|
|
211
|
+
* dir-only ignore rule like `build/` matches.
|
|
135
212
|
*/
|
|
136
|
-
private
|
|
213
|
+
private shouldIgnoreDir;
|
|
137
214
|
/**
|
|
138
215
|
* Stop watching for file changes.
|
|
139
216
|
*/
|
|
140
217
|
stop(): void;
|
|
218
|
+
/**
|
|
219
|
+
* @internal Test-only: feed a synthetic project-relative change through the
|
|
220
|
+
* same filter → pendingFiles → debounced-sync path a real fs.watch event
|
|
221
|
+
* takes. Lets the watcher / staleness-banner suites stay deterministic
|
|
222
|
+
* instead of racing on OS watch-delivery latency. See
|
|
223
|
+
* {@link __emitWatchEventForTests}.
|
|
224
|
+
*/
|
|
225
|
+
ingestEventForTests(relPath: string): void;
|
|
141
226
|
/**
|
|
142
227
|
* Whether the watcher is currently active.
|
|
143
228
|
*/
|
|
144
229
|
isActive(): boolean;
|
|
145
230
|
/**
|
|
146
|
-
* Resolves once
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* is flaky under load because chokidar can take longer than expected to
|
|
150
|
-
* finish its initial crawl on slow filesystems / parallel test runs.
|
|
231
|
+
* Resolves once the watch set has been installed (or immediately if it
|
|
232
|
+
* already has). Useful for tests that need a deterministic boundary before
|
|
233
|
+
* asserting on `pendingFiles`.
|
|
151
234
|
*
|
|
152
235
|
* Production callers don't need this: `pendingFiles` is read continuously,
|
|
153
|
-
* the staleness banner is always correct (empty or populated), and
|
|
154
|
-
* initial-scan window
|
|
236
|
+
* the staleness banner is always correct (empty or populated), and there is
|
|
237
|
+
* no asynchronous initial-scan window with `fs.watch`.
|
|
155
238
|
*/
|
|
156
239
|
waitUntilReady(timeoutMs?: number): Promise<void>;
|
|
157
240
|
/**
|
|
@@ -188,4 +271,13 @@ export declare class FileWatcher {
|
|
|
188
271
|
*/
|
|
189
272
|
getPendingFiles(): PendingFile[];
|
|
190
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Test-only: synthesize a source-file change for the live watcher running at
|
|
276
|
+
* `projectRoot`, exercising the real filter → pendingFiles → debounced-sync
|
|
277
|
+
* logic without depending on fs.watch delivery timing (which races under
|
|
278
|
+
* parallel vitest). `relPath` is project-relative POSIX (e.g. "src/foo.ts").
|
|
279
|
+
* Returns false if no live watcher is registered for that root (e.g. outside a
|
|
280
|
+
* test runtime, where the registry is intentionally not populated).
|
|
281
|
+
*/
|
|
282
|
+
export declare function __emitWatchEventForTests(projectRoot: string, relPath: string): boolean;
|
|
191
283
|
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous usage telemetry — client side.
|
|
3
|
+
*
|
|
4
|
+
* The contract for what may be collected lives in docs/design/telemetry.md
|
|
5
|
+
* (and user-facing TELEMETRY.md); the ingest endpoint that enforces it is
|
|
6
|
+
* public at telemetry-worker/. This module honors four invariants:
|
|
7
|
+
*
|
|
8
|
+
* 1. Zero hot-path cost: recording is an in-memory increment. Disk writes are
|
|
9
|
+
* a tiny synchronous append at process exit (works under `process.exit()`,
|
|
10
|
+
* where `beforeExit` never fires); network sends happen opportunistically
|
|
11
|
+
* (startup of long-running commands, daemon interval, bounded await at the
|
|
12
|
+
* end of install/init) and are fire-and-forget everywhere else.
|
|
13
|
+
* 2. Zero stdout: stdio is the MCP protocol channel. Notices and debug output
|
|
14
|
+
* go to stderr only.
|
|
15
|
+
* 3. Off is off: when disabled, nothing is recorded, nothing is sent, and no
|
|
16
|
+
* socket is opened — there is no "opted out" ping. Turning telemetry off
|
|
17
|
+
* also deletes any buffered, unsent data.
|
|
18
|
+
* 4. Fail silent: offline, endpoint down, disk full — every failure mode is
|
|
19
|
+
* silence, never a retry loop, never an error surfaced to the user/agent.
|
|
20
|
+
*
|
|
21
|
+
* Usage counts aggregate locally into per-day rollups; only *completed* (UTC)
|
|
22
|
+
* days are sent, so volume scales with active machines, not with tool calls.
|
|
23
|
+
*/
|
|
24
|
+
export declare const TELEMETRY_ENDPOINT = "https://telemetry.getcodegraph.com/v1/events";
|
|
25
|
+
export declare const TELEMETRY_DOCS = "https://github.com/colbymchenry/codegraph/blob/main/TELEMETRY.md";
|
|
26
|
+
export type UsageKind = 'mcp_tool' | 'cli_command';
|
|
27
|
+
export type LifecycleEvent = 'install' | 'index' | 'uninstall';
|
|
28
|
+
/** Coarse buckets — exact counts are deliberately not collected. */
|
|
29
|
+
export declare function bucketFileCount(n: number): '<100' | '100-1k' | '1k-10k' | '10k+';
|
|
30
|
+
export declare function bucketDuration(ms: number): '<10s' | '10-60s' | '1-5m' | '5m+';
|
|
31
|
+
/** Collapse a backend identifier (e.g. `node-sqlite`) to the schema's enum. */
|
|
32
|
+
export declare function backendKind(backend: string): 'native' | 'wasm';
|
|
33
|
+
/**
|
|
34
|
+
* Shared "a full index completed" event (CLI init/index + installer local
|
|
35
|
+
* init): language names and coarse buckets only — never paths, file names,
|
|
36
|
+
* or exact counts. Structurally typed so callers don't need engine imports.
|
|
37
|
+
*/
|
|
38
|
+
export declare function recordIndexEvent(cg: {
|
|
39
|
+
getStats(): {
|
|
40
|
+
filesByLanguage: Record<string, number>;
|
|
41
|
+
};
|
|
42
|
+
getBackend(): string;
|
|
43
|
+
}, result: {
|
|
44
|
+
filesIndexed: number;
|
|
45
|
+
durationMs: number;
|
|
46
|
+
}): void;
|
|
47
|
+
export interface ClientInfo {
|
|
48
|
+
name?: string;
|
|
49
|
+
version?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface TelemetryStatus {
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
/** What decided the current state — mirrors the precedence order. */
|
|
54
|
+
decidedBy: 'DO_NOT_TRACK' | 'CODEGRAPH_TELEMETRY' | 'config' | 'default';
|
|
55
|
+
machineId: string | null;
|
|
56
|
+
configPath: string;
|
|
57
|
+
}
|
|
58
|
+
export interface TelemetryOptions {
|
|
59
|
+
/** Global state dir; defaults to ~/.codegraph. Tests inject a temp dir. */
|
|
60
|
+
dir?: string;
|
|
61
|
+
fetchImpl?: typeof globalThis.fetch;
|
|
62
|
+
now?: () => Date;
|
|
63
|
+
env?: NodeJS.ProcessEnv;
|
|
64
|
+
stderr?: (line: string) => void;
|
|
65
|
+
/** Tests opt out so short-lived instances don't pile onto process 'exit'. */
|
|
66
|
+
installExitHook?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export declare class Telemetry {
|
|
69
|
+
private readonly dir;
|
|
70
|
+
private readonly fetchImpl;
|
|
71
|
+
private readonly now;
|
|
72
|
+
private readonly env;
|
|
73
|
+
private readonly writeStderr;
|
|
74
|
+
private counts;
|
|
75
|
+
private events;
|
|
76
|
+
private readonly installExitHook;
|
|
77
|
+
private exitHookInstalled;
|
|
78
|
+
private configCache;
|
|
79
|
+
private intervalHandle;
|
|
80
|
+
constructor(opts?: TelemetryOptions);
|
|
81
|
+
get configPath(): string;
|
|
82
|
+
get queuePath(): string;
|
|
83
|
+
/**
|
|
84
|
+
* Resolution order (first match wins) — keep in sync with TELEMETRY.md:
|
|
85
|
+
* DO_NOT_TRACK=1 > CODEGRAPH_TELEMETRY=0|1 > stored config > default on.
|
|
86
|
+
*/
|
|
87
|
+
getStatus(): TelemetryStatus;
|
|
88
|
+
isEnabled(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Persist an explicit user choice (installer toggle or `codegraph
|
|
91
|
+
* telemetry on|off`). Turning telemetry off also deletes any buffered,
|
|
92
|
+
* unsent data — off means off.
|
|
93
|
+
*/
|
|
94
|
+
setEnabled(enabled: boolean, source: 'installer' | 'cli'): void;
|
|
95
|
+
/** True once any consent decision (or the first-run notice) is on disk. */
|
|
96
|
+
hasStoredChoice(): boolean;
|
|
97
|
+
/** In-memory increment — safe on the MCP tool-call hot path. */
|
|
98
|
+
recordUsage(kind: UsageKind, name: string, ok: boolean, client?: ClientInfo): void;
|
|
99
|
+
/** install / index / uninstall — buffered like everything else. */
|
|
100
|
+
recordLifecycle(event: LifecycleEvent, props: Record<string, unknown>): void;
|
|
101
|
+
/**
|
|
102
|
+
* Fire-and-forget send of everything sendable. Never throws, never logs
|
|
103
|
+
* above debug. Safe to call at startup of long-running commands.
|
|
104
|
+
*/
|
|
105
|
+
maybeFlush(): void;
|
|
106
|
+
/**
|
|
107
|
+
* Drain in-memory state to the buffer, then send completed-day rollups and
|
|
108
|
+
* lifecycle events. Bounded by `timeoutMs`; leftovers stay buffered for the
|
|
109
|
+
* next process. Awaited only where latency is invisible (install/init).
|
|
110
|
+
*/
|
|
111
|
+
flushNow(timeoutMs?: number): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Periodic flush for long-lived processes (MCP daemon / serve). Unref'd so
|
|
114
|
+
* it never keeps the process alive.
|
|
115
|
+
*/
|
|
116
|
+
startInterval(everyMs?: number): void;
|
|
117
|
+
stopInterval(): void;
|
|
118
|
+
private utcDay;
|
|
119
|
+
private readConfig;
|
|
120
|
+
private writeConfig;
|
|
121
|
+
/**
|
|
122
|
+
* Default-on consent is gated by a one-time stderr notice (interactive
|
|
123
|
+
* installs record their choice explicitly and never reach this).
|
|
124
|
+
*/
|
|
125
|
+
private firstRunNotice;
|
|
126
|
+
/**
|
|
127
|
+
* Synchronous, tiny, exit-safe: drain in-memory deltas to the JSONL queue.
|
|
128
|
+
* Runs on `process.on('exit')`, so it must never be async or slow.
|
|
129
|
+
*/
|
|
130
|
+
persistSync(): void;
|
|
131
|
+
private appendLines;
|
|
132
|
+
/**
|
|
133
|
+
* Atomically claim the queue for sending (rename). Concurrent processes
|
|
134
|
+
* can't double-send; a crash mid-send leaves a claim file that
|
|
135
|
+
* `recoverStaleClaims` merges back after an hour.
|
|
136
|
+
*/
|
|
137
|
+
private claimQueue;
|
|
138
|
+
private recoverStaleClaims;
|
|
139
|
+
/** Returns the lines that did NOT make it out (to be re-queued). */
|
|
140
|
+
private send;
|
|
141
|
+
private packageVersion;
|
|
142
|
+
private ensureExitHook;
|
|
143
|
+
private debug;
|
|
144
|
+
}
|
|
145
|
+
export declare function getTelemetry(): Telemetry;
|
|
146
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/types.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type EdgeKind = 'contains' | 'calls' | 'imports' | 'exports' | 'extends'
|
|
|
20
20
|
* Supported programming languages. See NODE_KINDS for why this is a
|
|
21
21
|
* runtime-iterable const array.
|
|
22
22
|
*/
|
|
23
|
-
export declare const LANGUAGES: readonly ["typescript", "javascript", "tsx", "jsx", "python", "go", "rust", "java", "c", "cpp", "csharp", "php", "ruby", "swift", "kotlin", "dart", "svelte", "vue", "liquid", "pascal", "scala", "lua", "luau", "objc", "yaml", "twig", "xml", "properties", "unknown"];
|
|
23
|
+
export declare const LANGUAGES: readonly ["typescript", "javascript", "tsx", "jsx", "python", "go", "rust", "java", "c", "cpp", "csharp", "razor", "php", "ruby", "swift", "kotlin", "dart", "svelte", "vue", "astro", "liquid", "pascal", "scala", "lua", "luau", "objc", "r", "yaml", "twig", "xml", "properties", "unknown"];
|
|
24
24
|
export type Language = (typeof LANGUAGES)[number];
|
|
25
25
|
/**
|
|
26
26
|
* A node in the knowledge graph representing a code symbol
|
|
@@ -64,6 +64,14 @@ export interface Node {
|
|
|
64
64
|
decorators?: string[];
|
|
65
65
|
/** Generic type parameters */
|
|
66
66
|
typeParameters?: string[];
|
|
67
|
+
/**
|
|
68
|
+
* Normalized return/result type name for a function/method (the bare class
|
|
69
|
+
* name, smart-pointer pointee unwrapped). Captured for C/C++ so resolution
|
|
70
|
+
* can infer a chained receiver's type from what the inner call returns —
|
|
71
|
+
* `Foo::instance().bar()` resolves `bar` on `Foo` (issue #645). Undefined for
|
|
72
|
+
* languages/symbols where it isn't captured.
|
|
73
|
+
*/
|
|
74
|
+
returnType?: string;
|
|
67
75
|
/** When the node was last updated */
|
|
68
76
|
updatedAt: number;
|
|
69
77
|
}
|
|
@@ -139,6 +147,13 @@ export interface ExtractionError {
|
|
|
139
147
|
/** Error code for categorization */
|
|
140
148
|
code?: string;
|
|
141
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Kinds an unresolved reference can carry. `function_ref` is internal-only —
|
|
152
|
+
* a function name used as a VALUE (callback registration, #756). It never
|
|
153
|
+
* becomes an edge kind: resolution maps it to a `references` edge targeting
|
|
154
|
+
* function/method nodes only (see `matchFunctionRef`).
|
|
155
|
+
*/
|
|
156
|
+
export type ReferenceKind = EdgeKind | 'function_ref';
|
|
142
157
|
/**
|
|
143
158
|
* A reference that couldn't be resolved during extraction
|
|
144
159
|
*/
|
|
@@ -148,7 +163,7 @@ export interface UnresolvedReference {
|
|
|
148
163
|
/** Name being referenced */
|
|
149
164
|
referenceName: string;
|
|
150
165
|
/** Type of reference (call, type, import, etc.) */
|
|
151
|
-
referenceKind:
|
|
166
|
+
referenceKind: ReferenceKind;
|
|
152
167
|
/** Location of the reference */
|
|
153
168
|
line: number;
|
|
154
169
|
column: number;
|