@colbymchenry/codegraph 1.0.0 → 1.1.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 +87 -51
- package/dist/bin/fatal-handler.d.ts +20 -0
- package/dist/db/index.d.ts +22 -1
- package/dist/db/queries.d.ts +14 -0
- package/dist/directory.d.ts +58 -0
- package/dist/extraction/grammars.d.ts +11 -3
- package/dist/extraction/tree-sitter-types.d.ts +13 -0
- package/dist/extraction/tree-sitter.d.ts +119 -0
- package/dist/index.d.ts +33 -0
- package/dist/installer/instructions-template.d.ts +3 -3
- package/dist/installer/targets/claude.d.ts +18 -12
- package/dist/installer/targets/shared.d.ts +12 -2
- package/dist/installer/targets/types.d.ts +7 -0
- package/dist/mcp/daemon-manager.d.ts +42 -0
- package/dist/mcp/daemon-registry.d.ts +47 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/liveness-watchdog.d.ts +18 -0
- package/dist/mcp/server-instructions.d.ts +18 -14
- package/dist/mcp/tools.d.ts +51 -1
- package/dist/project-config.d.ts +19 -0
- package/dist/reasoning/config.d.ts +45 -0
- package/dist/reasoning/credentials.d.ts +5 -0
- package/dist/reasoning/login.d.ts +21 -0
- package/dist/reasoning/reasoner.d.ts +43 -0
- package/dist/resolution/c-fnptr-synthesizer.d.ts +33 -0
- package/dist/resolution/callback-synthesizer.d.ts +6 -1
- package/dist/resolution/frameworks/goframe.d.ts +41 -0
- package/dist/resolution/frameworks/index.d.ts +1 -0
- package/dist/resolution/goframe-synthesizer.d.ts +28 -0
- package/dist/resolution/strip-comments.d.ts +1 -1
- package/dist/sync/watcher.d.ts +68 -1
- package/dist/telemetry/index.d.ts +0 -3
- package/dist/utils.d.ts +14 -1
- package/package.json +7 -7
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Managed tier ("CodeGraph AI") — the metered gateway used when logged in. */
|
|
2
|
+
export declare const MANAGED_DEFAULT_URL = "https://ai.getcodegraph.com/v1";
|
|
3
|
+
/** The gateway's public model id (it translates this to the upstream provider id). */
|
|
4
|
+
export declare const MANAGED_DEFAULT_MODEL = "openai/gpt-oss-120b";
|
|
5
|
+
export interface OffloadConfig {
|
|
6
|
+
/** Managed tier: route through CodeGraph AI (metered) with the logged-in org token. */
|
|
7
|
+
managed?: boolean;
|
|
8
|
+
/** OpenAI-compatible base URL ending in `/v1` (e.g. https://api.cerebras.ai/v1). */
|
|
9
|
+
url?: string;
|
|
10
|
+
/** Model id to request (default `gpt-oss-120b` BYO, `openai/gpt-oss-120b` managed). */
|
|
11
|
+
model?: string;
|
|
12
|
+
/** Name of the env var holding the provider API key (never persisted). BYO only. */
|
|
13
|
+
keyEnv?: string;
|
|
14
|
+
/** reasoning_effort: low | medium | high (default `low`). */
|
|
15
|
+
effort?: string;
|
|
16
|
+
/** Output style: plain | report (default `plain`). */
|
|
17
|
+
style?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ResolvedOffload {
|
|
20
|
+
/** True when the offload is usable (endpoint present; for managed, a token too). */
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
/** Managed tier (CodeGraph AI, metered) vs BYO endpoint. */
|
|
23
|
+
managed: boolean;
|
|
24
|
+
url?: string;
|
|
25
|
+
model: string;
|
|
26
|
+
/** Resolved API key / org token (from env, the configured `keyEnv`, or login), if any. */
|
|
27
|
+
apiKey?: string;
|
|
28
|
+
/** Where the key/token came from (for `status` display) — never the secret itself. */
|
|
29
|
+
keySource?: string;
|
|
30
|
+
effort: string;
|
|
31
|
+
style: string;
|
|
32
|
+
timeoutMs: number;
|
|
33
|
+
maxTokens: number;
|
|
34
|
+
strip: boolean;
|
|
35
|
+
debug: boolean;
|
|
36
|
+
/** Where the endpoint came from — drives `codegraph offload status`. */
|
|
37
|
+
origin: 'env' | 'config' | 'none';
|
|
38
|
+
}
|
|
39
|
+
/** The persisted offload block (empty object if none). */
|
|
40
|
+
export declare function readOffloadConfig(): OffloadConfig;
|
|
41
|
+
/** Persist (or, with `null`, clear) the offload block, leaving other config keys intact. */
|
|
42
|
+
export declare function writeOffloadConfig(offload: OffloadConfig | null): void;
|
|
43
|
+
/** Merge the persisted config with `CODEGRAPH_OFFLOAD_*` env overrides (env wins). */
|
|
44
|
+
export declare function resolveOffload(env?: NodeJS.ProcessEnv): ResolvedOffload;
|
|
45
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The stored managed-offload org token, if the machine is logged in. */
|
|
2
|
+
export declare function readOffloadToken(): string | undefined;
|
|
3
|
+
/** Persist (or, with `null`, clear) the managed-offload org token at `0600`. */
|
|
4
|
+
export declare function writeOffloadToken(token: string | null): void;
|
|
5
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Dashboard base for the device-login endpoints; override for testing via CODEGRAPH_LOGIN_URL. */
|
|
2
|
+
export declare function loginBaseUrl(): string;
|
|
3
|
+
/** The dashboard's response to a device-authorization start request. */
|
|
4
|
+
export interface DeviceStart {
|
|
5
|
+
device_code: string;
|
|
6
|
+
user_code: string;
|
|
7
|
+
verification_uri: string;
|
|
8
|
+
/** Same URL with the code prefilled, for one-click open. */
|
|
9
|
+
verification_uri_complete?: string;
|
|
10
|
+
/** Seconds the CLI should wait between polls. */
|
|
11
|
+
interval?: number;
|
|
12
|
+
/** Seconds until the request expires. */
|
|
13
|
+
expires_in?: number;
|
|
14
|
+
}
|
|
15
|
+
/** Begin a device-authorization request. */
|
|
16
|
+
export declare function startDeviceLogin(): Promise<DeviceStart>;
|
|
17
|
+
/** Poll until the user approves in the browser; resolves with the org token. */
|
|
18
|
+
export declare function pollForToken(deviceCode: string, intervalSec: number, expiresInSec: number): Promise<string>;
|
|
19
|
+
/** Best-effort: open a URL in the default browser. Never throws — the URL is also printed. */
|
|
20
|
+
export declare function openBrowser(url: string): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=login.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
interface SynthArgs {
|
|
2
|
+
query: string;
|
|
3
|
+
context: string;
|
|
4
|
+
}
|
|
5
|
+
/** True when a reasoning offload endpoint is configured (env or `~/.codegraph/config.json`). */
|
|
6
|
+
export declare function isOffloadEnabled(): boolean;
|
|
7
|
+
export interface OffloadUsage {
|
|
8
|
+
plan?: string;
|
|
9
|
+
allowance?: number;
|
|
10
|
+
used?: number;
|
|
11
|
+
overage?: number;
|
|
12
|
+
remaining?: number;
|
|
13
|
+
periodEnd?: number;
|
|
14
|
+
unlimited?: boolean;
|
|
15
|
+
banned?: boolean;
|
|
16
|
+
tokensLast30?: number;
|
|
17
|
+
callsLast30?: number;
|
|
18
|
+
creditsLast30?: number;
|
|
19
|
+
models?: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* GET `/v1/usage` from the configured (managed) endpoint → the org's credit
|
|
23
|
+
* balance/usage, or null on any failure. Drives `codegraph offload status`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function fetchUsage(): Promise<OffloadUsage | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Strip sections of the explore output addressed to the AGENT (not useful to a
|
|
28
|
+
* reasoning model): the "Not shown above" pointer list, the completeness signal,
|
|
29
|
+
* the explore-budget note, the trimmed/truncation notices, and the redundant
|
|
30
|
+
* "## Exploration:/Found N symbols" header (the query is sent separately). Left
|
|
31
|
+
* in, some models regurgitate them ("We have 2 explore calls. Let's explore…")
|
|
32
|
+
* and they add noise. Source code, blast radius, relationships, and flow stay.
|
|
33
|
+
* Opt-in (`CODEGRAPH_OFFLOAD_STRIP=1`) — default off (it also removes the "Not
|
|
34
|
+
* shown above" pointers, which can be useful navigation).
|
|
35
|
+
*/
|
|
36
|
+
export declare function stripAgentDirectives(context: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Offload reasoning over the retrieved `context` to the configured model and
|
|
39
|
+
* return its synthesized answer, or null to signal "fall back to local source".
|
|
40
|
+
*/
|
|
41
|
+
export declare function synthesizeOffload({ query, context }: SynthArgs): Promise<string | null>;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=reasoner.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C/C++ function-pointer dispatch synthesis (#932).
|
|
3
|
+
*
|
|
4
|
+
* C/C++ polymorphism is the function pointer: a struct carries a fn-pointer
|
|
5
|
+
* field (`int (*fn)(int)`, or a fn-pointer-typedef field `hook_func func`),
|
|
6
|
+
* concrete functions are *registered* into it through a table
|
|
7
|
+
* (`static struct cmd cmds[] = {{"add", cmd_add}, …}`, a designated
|
|
8
|
+
* `.fn = cmd_add`, or `x->fn = cmd_add`), and the dispatcher calls through it
|
|
9
|
+
* indirectly (`p->fn(argv)`). Static extraction captures neither the
|
|
10
|
+
* registration→field binding nor the indirect call, so the dispatcher→handler
|
|
11
|
+
* edge is missing and `git`'s `run_builtin` looks like it calls nothing, the
|
|
12
|
+
* hooks in `hook_demo.c` are unreachable, etc.
|
|
13
|
+
*
|
|
14
|
+
* This bridges it, keyed by **(struct type, fn-pointer field)**:
|
|
15
|
+
* • registrations — a function bound to `S.field` via a positional
|
|
16
|
+
* initializer (matched by field index), a designated `.field = fn`, or a
|
|
17
|
+
* direct `x.field = fn` / `x->field = fn` assignment;
|
|
18
|
+
* • dispatch — `recv->field(…)` / `recv.field(…)` where `recv` resolves to a
|
|
19
|
+
* value of struct type `S` (from the enclosing function's params / locals),
|
|
20
|
+
* falling back to the field name when it is unique to one struct;
|
|
21
|
+
* • field←field propagation — `a->f = b->g` merges `B.g`'s handlers into
|
|
22
|
+
* `A.f`, so a generic single-slot hook that is reassigned from a registry
|
|
23
|
+
* (the `hook_demo.c` shape: `h->func = found->fn`) still resolves.
|
|
24
|
+
*
|
|
25
|
+
* Whole-graph pass after base resolution; all edges are `provenance:'heuristic'`
|
|
26
|
+
* (`synthesizedBy:'fn-pointer-dispatch'`). High precision via the (type, field)
|
|
27
|
+
* key + a real-function gate; a project with no fn-pointer dispatch is a no-op.
|
|
28
|
+
*/
|
|
29
|
+
import type { Edge } from '../types';
|
|
30
|
+
import type { QueryBuilder } from '../db/queries';
|
|
31
|
+
import type { ResolutionContext } from './types';
|
|
32
|
+
export declare function cFnPointerDispatchEdges(queries: QueryBuilder, ctx: ResolutionContext): Edge[];
|
|
33
|
+
//# sourceMappingURL=c-fnptr-synthesizer.d.ts.map
|
|
@@ -3,7 +3,12 @@ import type { ResolutionContext } from './types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Synthesize dispatcher→callback edges (field observers + EventEmitters +
|
|
5
5
|
* React re-render + JSX children + Vue templates + SvelteKit load + RN event
|
|
6
|
-
* channel + Fabric native-impl + MyBatis Java↔XML + Gin middleware chain
|
|
6
|
+
* channel + Fabric native-impl + MyBatis Java↔XML + Gin middleware chain +
|
|
7
|
+
* Redux-thunk dispatch chain + object-literal registry dispatch + RTK Query
|
|
8
|
+
* generated-hook → endpoint + Pinia useStore().action() + Vuex string dispatch +
|
|
9
|
+
* Celery task .delay()/.apply_async() → task body + Spring publishEvent → @EventListener +
|
|
10
|
+
* MediatR Send/Publish → IRequestHandler/INotificationHandler +
|
|
11
|
+
* Sidekiq Worker.perform_async → #perform + Laravel event(new X) → listener handle).
|
|
7
12
|
* Returns the count added. Never throws into indexing — callers wrap in try/catch.
|
|
8
13
|
*/
|
|
9
14
|
export declare function synthesizeCallbackEdges(queries: QueryBuilder, ctx: ResolutionContext): number;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GoFrame Framework Resolver (route metadata) — issue #747.
|
|
3
|
+
*
|
|
4
|
+
* GoFrame's "standard router" binds routes reflectively, so there is no literal
|
|
5
|
+
* path string at a `.GET("/x", handler)` call site and no static edge from a
|
|
6
|
+
* route to the controller method that serves it. The structural facts live in
|
|
7
|
+
* two places, joined only at runtime by GoFrame:
|
|
8
|
+
*
|
|
9
|
+
* // api/user/v1/user_sign_in.go — the route lives in a struct tag on the request type
|
|
10
|
+
* type SignInReq struct {
|
|
11
|
+
* g.Meta `path:"/user/sign-in" method:"post" tags:"UserService" summary:"…"`
|
|
12
|
+
* …
|
|
13
|
+
* }
|
|
14
|
+
* // internal/controller/user/user_v1_sign_in.go — the handler takes *that* request type
|
|
15
|
+
* func (c *ControllerV1) SignIn(ctx context.Context, req *v1.SignInReq) (res *v1.SignInRes, err error)
|
|
16
|
+
* // internal/cmd/cmd.go — reflective binding (no path, no handler name)
|
|
17
|
+
* group.Bind(user.NewV1())
|
|
18
|
+
*
|
|
19
|
+
* This resolver handles the FIRST half: it reads the `g.Meta` struct tag on a
|
|
20
|
+
* request type into a `route` node (`POST /user/sign-in`). The route → handler
|
|
21
|
+
* EDGE is the genuinely reflective part — the method name is NOT derivable from
|
|
22
|
+
* the request type (`DeptSearchReq` is served by `List`, `DeptAddReq` by `Add`),
|
|
23
|
+
* so the only reliable join is the request type appearing in the method's
|
|
24
|
+
* parameter signature. That whole-graph join is done by the companion
|
|
25
|
+
* `goframeRouteEdges` synthesizer, which reads the request type back out of the
|
|
26
|
+
* route node's qualifiedName.
|
|
27
|
+
*
|
|
28
|
+
* Honesty note: the route node carries the `g.Meta` path verbatim. The group
|
|
29
|
+
* prefix from `s.Group("/api", …)` / nested `group.Group("/v1", …)` is applied
|
|
30
|
+
* by reflective `Bind` at runtime and is deliberately NOT reconstructed here —
|
|
31
|
+
* the discriminating, structural part is the per-route path + method.
|
|
32
|
+
*/
|
|
33
|
+
import { FrameworkResolver } from '../types';
|
|
34
|
+
/** Marker embedded in a route node's qualifiedName so the synthesizer can read
|
|
35
|
+
* back the request type to join on. The value after it is the package-qualified
|
|
36
|
+
* request type (`cash.ListReq`) — the package disambiguates the many identical
|
|
37
|
+
* bare names (`ListReq`, `GetReq`) a large app defines, one per module. Falls
|
|
38
|
+
* back to the bare type when no `package` declaration is found. */
|
|
39
|
+
export declare const GOFRAME_ROUTE_MARKER = "::goframe-route:";
|
|
40
|
+
export declare const goframeResolver: FrameworkResolver;
|
|
41
|
+
//# sourceMappingURL=goframe.d.ts.map
|
|
@@ -39,6 +39,7 @@ export { railsResolver } from './ruby';
|
|
|
39
39
|
export { springResolver } from './java';
|
|
40
40
|
export { playResolver } from './play';
|
|
41
41
|
export { goResolver } from './go';
|
|
42
|
+
export { goframeResolver } from './goframe';
|
|
42
43
|
export { rustResolver } from './rust';
|
|
43
44
|
export { aspnetResolver } from './csharp';
|
|
44
45
|
export { swiftUIResolver, uikitResolver, vaporResolver } from './swift';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GoFrame route → controller-method dispatch synthesis (#747).
|
|
3
|
+
*
|
|
4
|
+
* GoFrame binds routes reflectively (`group.Bind(user.NewV1())`), so the route
|
|
5
|
+
* declared in a request type's `g.Meta` tag has no static edge to the method
|
|
6
|
+
* that serves it. The `goframeResolver` extract pass turns each `g.Meta` into a
|
|
7
|
+
* `route` node carrying its request type in the qualifiedName; this whole-graph
|
|
8
|
+
* pass closes the loop by joining each route to its handler.
|
|
9
|
+
*
|
|
10
|
+
* The join key is the REQUEST TYPE, not the method name — GoFrame method names
|
|
11
|
+
* are free (`DeptSearchReq` is served by `List`, `DeptAddReq` by `Add`), so the
|
|
12
|
+
* only reliable link is the request type appearing in the handler's parameter
|
|
13
|
+
* signature:
|
|
14
|
+
*
|
|
15
|
+
* func (c *sysDeptController) Add(ctx context.Context, req *system.DeptAddReq) (…)
|
|
16
|
+
* ^^^^^^^^^^^^^^^^ the join
|
|
17
|
+
*
|
|
18
|
+
* Go method nodes already carry that signature, so no source re-read is needed.
|
|
19
|
+
* Each synthesized edge is `kind:'calls'`, `provenance:'heuristic'`,
|
|
20
|
+
* `metadata.synthesizedBy:'goframe-route'` — a reflective-dispatch bridge, so
|
|
21
|
+
* `codegraph_explore` surfaces it as a dynamic hop rather than a literal call,
|
|
22
|
+
* and the handler's callers list the route that reaches it. A project with no
|
|
23
|
+
* GoFrame routes is a no-op.
|
|
24
|
+
*/
|
|
25
|
+
import type { Edge } from '../types';
|
|
26
|
+
import type { ResolutionContext } from './types';
|
|
27
|
+
export declare function goframeRouteEdges(ctx: ResolutionContext): Edge[];
|
|
28
|
+
//# sourceMappingURL=goframe-synthesizer.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';
|
|
25
|
+
export type CommentLang = 'python' | 'javascript' | 'typescript' | 'php' | 'ruby' | 'java' | 'csharp' | 'swift' | 'go' | 'rust' | 'c' | 'cpp';
|
|
26
26
|
export declare function stripCommentsForRegex(content: string, lang: CommentLang): string;
|
|
27
27
|
//# sourceMappingURL=strip-comments.d.ts.map
|
package/dist/sync/watcher.d.ts
CHANGED
|
@@ -30,6 +30,16 @@
|
|
|
30
30
|
* events are dropped before any sync is scheduled. Either way the watcher's
|
|
31
31
|
* scope matches the indexer's (#276 / #407).
|
|
32
32
|
*/
|
|
33
|
+
import * as fs from 'fs';
|
|
34
|
+
/**
|
|
35
|
+
* Indirection over `fs.watch` so tests can inject a fake that throws or emits
|
|
36
|
+
* `EMFILE`/`ENFILE` deterministically (real watch-resource exhaustion can't be
|
|
37
|
+
* provoked reliably, and `fs.watch` is a non-configurable property so it can't
|
|
38
|
+
* be spied). Production always uses the real `fs.watch`.
|
|
39
|
+
*/
|
|
40
|
+
type WatchFn = typeof fs.watch;
|
|
41
|
+
/** @internal Test-only seam to inject a fake fs.watch implementation. */
|
|
42
|
+
export declare function __setFsWatchForTests(fn: WatchFn | null): void;
|
|
33
43
|
/**
|
|
34
44
|
* Options for the file watcher
|
|
35
45
|
*/
|
|
@@ -51,6 +61,14 @@ export interface WatchOptions {
|
|
|
51
61
|
* Callback when a sync errors (for logging/diagnostics).
|
|
52
62
|
*/
|
|
53
63
|
onSyncError?: (error: Error) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Callback fired ONCE when live watching degrades permanently and auto-sync
|
|
66
|
+
* is disabled — OS watch-resource exhaustion (EMFILE/ENFILE), or a write lock
|
|
67
|
+
* held past the retry budget. The string is an actionable, human-readable
|
|
68
|
+
* reason. Lets a host (MCP server, daemon, CLI) tell the user that the index
|
|
69
|
+
* will no longer auto-update instead of silently serving stale results.
|
|
70
|
+
*/
|
|
71
|
+
onDegraded?: (reason: string) => void;
|
|
54
72
|
/**
|
|
55
73
|
* Test-only. When true, `start()` installs NO OS-level fs.watch — the
|
|
56
74
|
* watcher is "inert" and only the {@link __emitWatchEventForTests} /
|
|
@@ -111,6 +129,22 @@ export declare class FileWatcher {
|
|
|
111
129
|
private dirWatchers;
|
|
112
130
|
/** Set once the per-directory watch cap is hit, so we log only once. */
|
|
113
131
|
private dirCapWarned;
|
|
132
|
+
/**
|
|
133
|
+
* Set once the Linux inotify watch limit (ENOSPC) is hit. Double duty: we
|
|
134
|
+
* warn only once, AND we stop attempting new directory watches for the rest
|
|
135
|
+
* of the session — once the kernel budget is exhausted every further
|
|
136
|
+
* `inotify_add_watch` fails too, so trying the rest of the tree is pure
|
|
137
|
+
* waste. NON-fatal (does not degrade): installed watches keep working.
|
|
138
|
+
*/
|
|
139
|
+
private inotifyLimitWarned;
|
|
140
|
+
/**
|
|
141
|
+
* One-way latch: the reason live watching was permanently disabled at runtime
|
|
142
|
+
* (watch-resource exhaustion, or lock contention past the retry budget), or
|
|
143
|
+
* null while healthy. Set by {@link degrade}; cleared only by a fresh start().
|
|
144
|
+
*/
|
|
145
|
+
private degradedReason;
|
|
146
|
+
/** Consecutive lock-contention retries for watcher-triggered syncs. */
|
|
147
|
+
private lockRetryCount;
|
|
114
148
|
/** Test-only inert mode: started, but with no OS watcher installed. */
|
|
115
149
|
private inert;
|
|
116
150
|
private debounceTimer;
|
|
@@ -152,6 +186,7 @@ export declare class FileWatcher {
|
|
|
152
186
|
private readonly syncFn;
|
|
153
187
|
private readonly onSyncComplete?;
|
|
154
188
|
private readonly onSyncError?;
|
|
189
|
+
private readonly onDegraded?;
|
|
155
190
|
private readonly inertForTests;
|
|
156
191
|
constructor(projectRoot: string, syncFn: () => Promise<{
|
|
157
192
|
filesChanged: number;
|
|
@@ -211,6 +246,31 @@ export declare class FileWatcher {
|
|
|
211
246
|
* dir-only ignore rule like `build/` matches.
|
|
212
247
|
*/
|
|
213
248
|
private shouldIgnoreDir;
|
|
249
|
+
/**
|
|
250
|
+
* Permanently disable live watching after a terminal runtime failure
|
|
251
|
+
* (watch-resource exhaustion, or lock contention past the retry budget).
|
|
252
|
+
* Idempotent: logs one actionable warning, fires {@link WatchOptions.onDegraded}
|
|
253
|
+
* once, and stops the watcher. A subsequent start() clears the latch.
|
|
254
|
+
*/
|
|
255
|
+
private degrade;
|
|
256
|
+
/**
|
|
257
|
+
* Warn ONCE that the Linux inotify watch budget is exhausted (ENOSPC), and
|
|
258
|
+
* stop adding new watches for the rest of this session — every further
|
|
259
|
+
* `inotify_add_watch` would fail too, so walking the rest of the tree is
|
|
260
|
+
* waste. Unlike {@link degrade} this is NON-fatal: the watches already
|
|
261
|
+
* installed keep firing, and `codegraph sync` covers the unwatched remainder.
|
|
262
|
+
* The message names the kernel knob to raise (`fs.inotify.max_user_watches`).
|
|
263
|
+
*/
|
|
264
|
+
private warnInotifyLimit;
|
|
265
|
+
/**
|
|
266
|
+
* Whether live watching has degraded permanently (until the next start()).
|
|
267
|
+
* Distinct from {@link isActive}: a degraded watcher is inactive, but an
|
|
268
|
+
* inactive watcher is not necessarily degraded (it may simply be stopped or
|
|
269
|
+
* never started). Hosts use this to tell the user auto-sync is off.
|
|
270
|
+
*/
|
|
271
|
+
isDegraded(): boolean;
|
|
272
|
+
/** The reason live watching degraded, or null if it is healthy. */
|
|
273
|
+
getDegradedReason(): string | null;
|
|
214
274
|
/**
|
|
215
275
|
* Stop watching for file changes.
|
|
216
276
|
*/
|
|
@@ -238,9 +298,15 @@ export declare class FileWatcher {
|
|
|
238
298
|
*/
|
|
239
299
|
waitUntilReady(timeoutMs?: number): Promise<void>;
|
|
240
300
|
/**
|
|
241
|
-
* Schedule a debounced sync.
|
|
301
|
+
* Schedule a normal debounced sync after a source edit.
|
|
242
302
|
*/
|
|
243
303
|
private scheduleSync;
|
|
304
|
+
/**
|
|
305
|
+
* Schedule a retry after a recoverable sync failure (lock contention). Kept
|
|
306
|
+
* separate from {@link scheduleSync} so prolonged contention backs off
|
|
307
|
+
* exponentially instead of hammering the lock every debounce cycle.
|
|
308
|
+
*/
|
|
309
|
+
private scheduleRetrySync;
|
|
244
310
|
/**
|
|
245
311
|
* Flush pending changes by running sync.
|
|
246
312
|
*
|
|
@@ -280,4 +346,5 @@ export declare class FileWatcher {
|
|
|
280
346
|
* test runtime, where the registry is intentionally not populated).
|
|
281
347
|
*/
|
|
282
348
|
export declare function __emitWatchEventForTests(projectRoot: string, relPath: string): boolean;
|
|
349
|
+
export {};
|
|
283
350
|
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -28,8 +28,6 @@ export type LifecycleEvent = 'install' | 'index' | 'uninstall';
|
|
|
28
28
|
/** Coarse buckets — exact counts are deliberately not collected. */
|
|
29
29
|
export declare function bucketFileCount(n: number): '<100' | '100-1k' | '1k-10k' | '10k+';
|
|
30
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
31
|
/**
|
|
34
32
|
* Shared "a full index completed" event (CLI init/index + installer local
|
|
35
33
|
* init): language names and coarse buckets only — never paths, file names,
|
|
@@ -39,7 +37,6 @@ export declare function recordIndexEvent(cg: {
|
|
|
39
37
|
getStats(): {
|
|
40
38
|
filesByLanguage: Record<string, number>;
|
|
41
39
|
};
|
|
42
|
-
getBackend(): string;
|
|
43
40
|
}, result: {
|
|
44
41
|
filesIndexed: number;
|
|
45
42
|
durationMs: number;
|
package/dist/utils.d.ts
CHANGED
|
@@ -57,12 +57,25 @@ export declare function isConfigLeafNode(node: {
|
|
|
57
57
|
* (codegraph_node `includeCode`, codegraph_explore source) go through here, so
|
|
58
58
|
* this is the chokepoint that keeps out-of-root file contents from leaking.
|
|
59
59
|
*
|
|
60
|
+
* `allowSymlinkEscape` waives **only** the realpath-escape rejection (the
|
|
61
|
+
* lexical `../` guard still applies) for the INDEXING read path. The directory
|
|
62
|
+
* walk deliberately descends into in-root symlinks whose targets live outside
|
|
63
|
+
* the root (e.g. a `game/` symlink in a Dota custom-game tree, #935); discovery
|
|
64
|
+
* and the reader must agree, or every file the walk enumerated fails to index.
|
|
65
|
+
* Indexing only reads paths it just discovered, into a local index — it never
|
|
66
|
+
* serves them to an agent, so this does not widen the #527 leak surface. The
|
|
67
|
+
* content-serving sinks must never pass this flag.
|
|
68
|
+
*
|
|
60
69
|
* @param projectRoot - The project root directory
|
|
61
70
|
* @param filePath - The (relative or absolute) file path to validate
|
|
71
|
+
* @param options.allowSymlinkEscape - Follow in-root symlinks out of the root
|
|
72
|
+
* (indexing read path only); defaults to the strict, leak-safe behavior.
|
|
62
73
|
* @returns The resolved absolute path (realpath when it exists), or null if it
|
|
63
74
|
* escapes the root
|
|
64
75
|
*/
|
|
65
|
-
export declare function validatePathWithinRoot(projectRoot: string, filePath: string
|
|
76
|
+
export declare function validatePathWithinRoot(projectRoot: string, filePath: string, options?: {
|
|
77
|
+
allowSymlinkEscape?: boolean;
|
|
78
|
+
}): string | null;
|
|
66
79
|
/**
|
|
67
80
|
* Validate that a path is a safe project root directory.
|
|
68
81
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colbymchenry/codegraph",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Local-first code intelligence for AI agents (MCP). Self-contained — bundles its own runtime.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codegraph": "npm-shim.js"
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
"./package.json": "./package.json"
|
|
16
16
|
},
|
|
17
17
|
"optionalDependencies": {
|
|
18
|
-
"@colbymchenry/codegraph-darwin-arm64": "1.
|
|
19
|
-
"@colbymchenry/codegraph-darwin-x64": "1.
|
|
20
|
-
"@colbymchenry/codegraph-linux-arm64": "1.
|
|
21
|
-
"@colbymchenry/codegraph-linux-x64": "1.
|
|
22
|
-
"@colbymchenry/codegraph-win32-arm64": "1.
|
|
23
|
-
"@colbymchenry/codegraph-win32-x64": "1.
|
|
18
|
+
"@colbymchenry/codegraph-darwin-arm64": "1.1.0",
|
|
19
|
+
"@colbymchenry/codegraph-darwin-x64": "1.1.0",
|
|
20
|
+
"@colbymchenry/codegraph-linux-arm64": "1.1.0",
|
|
21
|
+
"@colbymchenry/codegraph-linux-x64": "1.1.0",
|
|
22
|
+
"@colbymchenry/codegraph-win32-arm64": "1.1.0",
|
|
23
|
+
"@colbymchenry/codegraph-win32-x64": "1.1.0"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"npm-shim.js",
|