@aroman22/codegraph-vba 1.3.3
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 +855 -0
- package/dist/bin/codegraph.d.ts +26 -0
- package/dist/bin/command-supervision.d.ts +12 -0
- package/dist/bin/fatal-handler.d.ts +20 -0
- package/dist/bin/node-version-check.d.ts +37 -0
- package/dist/bin/uninstall.d.ts +14 -0
- package/dist/context/formatter.d.ts +30 -0
- package/dist/context/index.d.ts +119 -0
- package/dist/context/markers.d.ts +19 -0
- package/dist/db/index.d.ts +122 -0
- package/dist/db/migrations.d.ts +44 -0
- package/dist/db/queries.d.ts +402 -0
- package/dist/db/sqlite-adapter.d.ts +53 -0
- package/dist/directory.d.ts +179 -0
- package/dist/errors.d.ts +136 -0
- package/dist/extraction/astro-extractor.d.ts +79 -0
- package/dist/extraction/dfm-extractor.d.ts +31 -0
- package/dist/extraction/extraction-version.d.ts +25 -0
- package/dist/extraction/function-ref.d.ts +118 -0
- package/dist/extraction/generated-detection.d.ts +30 -0
- package/dist/extraction/grammars.d.ts +128 -0
- package/dist/extraction/index.d.ts +187 -0
- package/dist/extraction/languages/c-cpp.d.ts +12 -0
- package/dist/extraction/languages/csharp.d.ts +25 -0
- package/dist/extraction/languages/dart.d.ts +3 -0
- package/dist/extraction/languages/go.d.ts +3 -0
- package/dist/extraction/languages/index.d.ts +10 -0
- package/dist/extraction/languages/java.d.ts +3 -0
- package/dist/extraction/languages/javascript.d.ts +3 -0
- package/dist/extraction/languages/kotlin.d.ts +3 -0
- package/dist/extraction/languages/lua.d.ts +3 -0
- package/dist/extraction/languages/luau.d.ts +3 -0
- package/dist/extraction/languages/objc.d.ts +3 -0
- package/dist/extraction/languages/pascal.d.ts +3 -0
- package/dist/extraction/languages/php.d.ts +3 -0
- package/dist/extraction/languages/python.d.ts +3 -0
- package/dist/extraction/languages/r.d.ts +3 -0
- package/dist/extraction/languages/ruby.d.ts +3 -0
- package/dist/extraction/languages/rust.d.ts +3 -0
- package/dist/extraction/languages/scala.d.ts +3 -0
- package/dist/extraction/languages/swift.d.ts +3 -0
- package/dist/extraction/languages/typescript.d.ts +16 -0
- package/dist/extraction/liquid-extractor.d.ts +59 -0
- package/dist/extraction/mybatis-extractor.d.ts +48 -0
- package/dist/extraction/parse-pool.d.ts +126 -0
- package/dist/extraction/parse-worker.d.ts +8 -0
- package/dist/extraction/razor-extractor.d.ts +42 -0
- package/dist/extraction/sql-query-extractor.d.ts +25 -0
- package/dist/extraction/svelte-extractor.d.ts +56 -0
- package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
- package/dist/extraction/tree-sitter-types.d.ts +239 -0
- package/dist/extraction/tree-sitter.d.ts +647 -0
- package/dist/extraction/vba-extractor.d.ts +394 -0
- package/dist/extraction/vba-form-extractor.d.ts +89 -0
- package/dist/extraction/vba-preprocess.d.ts +81 -0
- package/dist/extraction/vue-extractor.d.ts +51 -0
- package/dist/extraction/wasm-runtime-flags.d.ts +38 -0
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/queries.d.ts +106 -0
- package/dist/graph/traversal.d.ts +127 -0
- package/dist/index.d.ts +563 -0
- package/dist/installer/config-writer.d.ts +28 -0
- package/dist/installer/index.d.ts +100 -0
- package/dist/installer/instructions-template.d.ts +41 -0
- package/dist/installer/targets/antigravity.d.ts +57 -0
- package/dist/installer/targets/claude.d.ts +62 -0
- package/dist/installer/targets/codex.d.ts +18 -0
- package/dist/installer/targets/cursor.d.ts +35 -0
- package/dist/installer/targets/gemini.d.ts +26 -0
- package/dist/installer/targets/hermes.d.ts +18 -0
- package/dist/installer/targets/kiro.d.ts +27 -0
- package/dist/installer/targets/opencode.d.ts +38 -0
- package/dist/installer/targets/registry.d.ts +35 -0
- package/dist/installer/targets/shared.d.ts +101 -0
- package/dist/installer/targets/toml.d.ts +52 -0
- package/dist/installer/targets/types.d.ts +108 -0
- package/dist/mcp/daemon-manager.d.ts +42 -0
- package/dist/mcp/daemon-paths.d.ts +73 -0
- package/dist/mcp/daemon-registry.d.ts +47 -0
- package/dist/mcp/daemon.d.ts +258 -0
- package/dist/mcp/dynamic-boundaries.d.ts +41 -0
- package/dist/mcp/engine.d.ts +122 -0
- package/dist/mcp/index.d.ts +113 -0
- package/dist/mcp/liveness-watchdog.d.ts +18 -0
- package/dist/mcp/ppid-watchdog.d.ts +62 -0
- package/dist/mcp/proxy.d.ts +87 -0
- package/dist/mcp/query-pool.d.ts +94 -0
- package/dist/mcp/query-worker.d.ts +24 -0
- package/dist/mcp/server-instructions.d.ts +34 -0
- package/dist/mcp/session.d.ts +79 -0
- package/dist/mcp/stdin-teardown.d.ts +27 -0
- package/dist/mcp/tools.d.ts +591 -0
- package/dist/mcp/transport.d.ts +188 -0
- package/dist/mcp/version.d.ts +19 -0
- package/dist/project-config.d.ts +56 -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 +5 -0
- package/dist/resolution/callback-synthesizer.d.ts +15 -0
- package/dist/resolution/frameworks/astro.d.ts +9 -0
- package/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
- package/dist/resolution/frameworks/csharp.d.ts +8 -0
- package/dist/resolution/frameworks/drupal.d.ts +51 -0
- package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
- package/dist/resolution/frameworks/express.d.ts +8 -0
- package/dist/resolution/frameworks/fabric.d.ts +3 -0
- package/dist/resolution/frameworks/go.d.ts +8 -0
- package/dist/resolution/frameworks/goframe.d.ts +41 -0
- package/dist/resolution/frameworks/index.d.ts +50 -0
- package/dist/resolution/frameworks/java.d.ts +8 -0
- package/dist/resolution/frameworks/laravel.d.ts +13 -0
- package/dist/resolution/frameworks/nestjs.d.ts +26 -0
- package/dist/resolution/frameworks/play.d.ts +19 -0
- package/dist/resolution/frameworks/python.d.ts +10 -0
- package/dist/resolution/frameworks/react-native.d.ts +3 -0
- package/dist/resolution/frameworks/react.d.ts +8 -0
- package/dist/resolution/frameworks/ruby.d.ts +8 -0
- package/dist/resolution/frameworks/rust.d.ts +8 -0
- package/dist/resolution/frameworks/svelte.d.ts +9 -0
- package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
- package/dist/resolution/frameworks/swift.d.ts +10 -0
- package/dist/resolution/frameworks/vue.d.ts +9 -0
- package/dist/resolution/go-module.d.ts +26 -0
- package/dist/resolution/goframe-synthesizer.d.ts +28 -0
- package/dist/resolution/import-resolver.d.ts +78 -0
- package/dist/resolution/index.d.ts +196 -0
- package/dist/resolution/lru-cache.d.ts +24 -0
- package/dist/resolution/name-matcher.d.ts +93 -0
- package/dist/resolution/path-aliases.d.ts +68 -0
- package/dist/resolution/strip-comments.d.ts +27 -0
- package/dist/resolution/swift-objc-bridge.d.ts +134 -0
- package/dist/resolution/types.d.ts +233 -0
- package/dist/resolution/workspace-packages.d.ts +48 -0
- package/dist/search/query-parser.d.ts +57 -0
- package/dist/search/query-utils.d.ts +87 -0
- package/dist/sync/git-hooks.d.ts +45 -0
- package/dist/sync/index.d.ts +19 -0
- package/dist/sync/watch-policy.d.ts +48 -0
- package/dist/sync/watcher.d.ts +358 -0
- package/dist/sync/worktree.d.ts +54 -0
- package/dist/telemetry/index.d.ts +143 -0
- package/dist/types.d.ts +409 -0
- package/dist/ui/glyphs.d.ts +42 -0
- package/dist/ui/shimmer-progress.d.ts +11 -0
- package/dist/ui/shimmer-worker.d.ts +2 -0
- package/dist/ui/types.d.ts +17 -0
- package/dist/upgrade/index.d.ts +132 -0
- package/dist/utils.d.ts +224 -0
- package/npm-sdk.js +75 -0
- package/npm-shim.js +246 -0
- package/package.json +32 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { DaemonRecord, StopResult } from './daemon-registry';
|
|
2
|
+
/** Sentinel option values (not real roots, so they can't collide with a project path). */
|
|
3
|
+
export declare const STOP_ALL = "__stop_all__";
|
|
4
|
+
export declare const CANCEL = "__cancel__";
|
|
5
|
+
export interface PickItem {
|
|
6
|
+
value: string;
|
|
7
|
+
label: string;
|
|
8
|
+
hint?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Compact uptime: `45s`, `12m`, `3h 5m`. */
|
|
11
|
+
export declare function formatUptime(ms: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* Build the ordered, UI-ready option list: the current project's daemon first
|
|
14
|
+
* (so it's the auto-selected default), the rest newest-first, then "Stop all"
|
|
15
|
+
* (only when there's more than one) and "Cancel".
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildPickItems(daemons: DaemonRecord[], cwdRoot: string | null, now: number): PickItem[];
|
|
18
|
+
export interface PickerDeps {
|
|
19
|
+
list: () => DaemonRecord[];
|
|
20
|
+
stop: (root: string) => Promise<StopResult>;
|
|
21
|
+
stopAll: () => Promise<StopResult[]>;
|
|
22
|
+
/** Realpath'd root of the current project's daemon, or null. */
|
|
23
|
+
cwdRoot: string | null;
|
|
24
|
+
now: () => number;
|
|
25
|
+
/** Render the picker; returns the chosen value or a cancel sentinel. */
|
|
26
|
+
select: (opts: {
|
|
27
|
+
message: string;
|
|
28
|
+
options: PickItem[];
|
|
29
|
+
initialValue: string;
|
|
30
|
+
}) => Promise<unknown>;
|
|
31
|
+
isCancel: (v: unknown) => boolean;
|
|
32
|
+
/** Per-action note (e.g. "Stopped daemon …"). */
|
|
33
|
+
note: (msg: string) => void;
|
|
34
|
+
/** Final line + teardown (clack outro). */
|
|
35
|
+
done: (msg: string) => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Pick a daemon → stop it → re-prompt with what's left, until the user cancels
|
|
39
|
+
* (Esc / Ctrl-C / "Cancel"), picks "Stop all", or nothing remains.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runDaemonPicker(deps: PickerDeps): Promise<void>;
|
|
42
|
+
//# sourceMappingURL=daemon-manager.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon socket + lockfile path helpers — issue #411.
|
|
3
|
+
*
|
|
4
|
+
* One shared `codegraph serve --mcp` daemon per project root means we need a
|
|
5
|
+
* stable, project-keyed rendezvous between cooperating processes. The IPC
|
|
6
|
+
* surface area is just two file paths:
|
|
7
|
+
*
|
|
8
|
+
* - `daemon.sock` — Unix domain socket / named pipe the daemon listens on.
|
|
9
|
+
* - `daemon.pid` — atomic-create lockfile holding the daemon's pid + version.
|
|
10
|
+
*
|
|
11
|
+
* Both live under `.codegraph/` so the project-scoped uninstall (`codegraph
|
|
12
|
+
* uninit`) sweeps them up for free.
|
|
13
|
+
*
|
|
14
|
+
* Special-case: Unix domain socket paths have a hard length limit (~104 on
|
|
15
|
+
* macOS, ~108 on Linux); when the in-project path exceeds it we fall back to
|
|
16
|
+
* an absolute-path hash under `os.tmpdir()`. The pidfile always stays in the
|
|
17
|
+
* project (it doesn't have a length limit) — and acts as the authoritative
|
|
18
|
+
* pointer to the socket path the daemon chose.
|
|
19
|
+
*
|
|
20
|
+
* Second special-case (#997, #974): some filesystems can't host an AF_UNIX node
|
|
21
|
+
* AT ALL — ExFAT/FAT external volumes, certain network mounts, WSL2 DrvFs — so
|
|
22
|
+
* `listen()` throws ENOTSUP/EACCES regardless of path length. We can't cheaply
|
|
23
|
+
* tell those apart from a normal volume up front, so instead of guessing we
|
|
24
|
+
* expose an ORDERED candidate list (`getDaemonSocketCandidates`): the in-project
|
|
25
|
+
* path first, the deterministic tmpdir path as the fallback of last resort. The
|
|
26
|
+
* daemon binds the first that works (relocating past a capability error); the
|
|
27
|
+
* proxy connects the first that answers. Both walk the SAME list, so they still
|
|
28
|
+
* converge on whichever the daemon bound with zero coordination.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Ordered socket / named-pipe path candidates the daemon should try to bind (and
|
|
32
|
+
* the proxy should try to connect) for `projectRoot`, most-preferred first.
|
|
33
|
+
* Deterministic given a project root, so independent processes converge without
|
|
34
|
+
* coordination — even when the preferred candidate is unusable and both fall
|
|
35
|
+
* through to the same fallback.
|
|
36
|
+
*
|
|
37
|
+
* - Windows: a single named pipe (lives in the kernel pipe namespace, not on
|
|
38
|
+
* the project FS, so neither the length nor the ExFAT hazard applies).
|
|
39
|
+
* - Short in-project path: `[ .codegraph/daemon.sock , <tmpdir> ]` — try the
|
|
40
|
+
* project first, fall back to tmpdir if its FS can't host a socket (#997).
|
|
41
|
+
* - Long in-project path (deep monorepos, Bazel out dirs): `[ <tmpdir> ]` only
|
|
42
|
+
* — bind would throw ENAMETOOLONG, so we skip straight to tmpdir.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getDaemonSocketCandidates(projectRoot: string): string[];
|
|
45
|
+
/**
|
|
46
|
+
* The PREFERRED (primary) socket path — candidate 0. Use this only where a
|
|
47
|
+
* single representative path is wanted (the lockfile's informational
|
|
48
|
+
* `socketPath` field, status display). For binding/connecting, walk the full
|
|
49
|
+
* {@link getDaemonSocketCandidates} list — the daemon may bind a fallback when
|
|
50
|
+
* candidate 0 is unusable.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getDaemonSocketPath(projectRoot: string): string;
|
|
53
|
+
/** Absolute path to the daemon pid lockfile for `projectRoot`. */
|
|
54
|
+
export declare function getDaemonPidPath(projectRoot: string): string;
|
|
55
|
+
/** Structured contents of the pid lockfile. */
|
|
56
|
+
export interface DaemonLockInfo {
|
|
57
|
+
pid: number;
|
|
58
|
+
version: string;
|
|
59
|
+
socketPath: string;
|
|
60
|
+
startedAt: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Serialize a {@link DaemonLockInfo} for writing to the pidfile. JSON for
|
|
64
|
+
* human readability — operators occasionally `cat` this when debugging.
|
|
65
|
+
*/
|
|
66
|
+
export declare function encodeLockInfo(info: DaemonLockInfo): string;
|
|
67
|
+
/**
|
|
68
|
+
* Parse a pidfile body. Tolerant of old-format pidfiles (plain decimal pid) so
|
|
69
|
+
* a 0.10.x daemon doesn't trip over a 0.9.x lockfile if that ever happens —
|
|
70
|
+
* we treat such a lockfile as "process is unknown version, refuse to share."
|
|
71
|
+
*/
|
|
72
|
+
export declare function decodeLockInfo(raw: string): DaemonLockInfo | null;
|
|
73
|
+
//# sourceMappingURL=daemon-paths.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface DaemonRecord {
|
|
2
|
+
/** Realpath'd project root the daemon serves. */
|
|
3
|
+
root: string;
|
|
4
|
+
pid: number;
|
|
5
|
+
version: string;
|
|
6
|
+
socketPath: string;
|
|
7
|
+
/** Epoch ms when the daemon bound its socket. */
|
|
8
|
+
startedAt: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* `~/.codegraph/daemons` — GLOBAL, keyed off the home install dir. (The
|
|
12
|
+
* `CODEGRAPH_DIR` env var only renames the per-project index dir, not this.)
|
|
13
|
+
*/
|
|
14
|
+
export declare function getRegistryDir(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Is `pid` a live process? `kill(pid, 0)` sends no signal — it just probes:
|
|
17
|
+
* ESRCH ⇒ dead, EPERM ⇒ alive but not ours (still alive). Same liveness check
|
|
18
|
+
* the PPID watchdog (#277) and daemon lock arbitration use.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
21
|
+
/** Best-effort: record this daemon so `list`/`stop --all` can find it. */
|
|
22
|
+
export declare function registerDaemon(rec: DaemonRecord): void;
|
|
23
|
+
/** Best-effort: drop this daemon's record on graceful shutdown. */
|
|
24
|
+
export declare function deregisterDaemon(root: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* All registered daemons whose process is still alive, newest first. Dead/garbage
|
|
27
|
+
* records are deleted as a side effect (self-healing) unless `prune` is false.
|
|
28
|
+
*/
|
|
29
|
+
export declare function listDaemons(opts?: {
|
|
30
|
+
prune?: boolean;
|
|
31
|
+
}): DaemonRecord[];
|
|
32
|
+
export interface StopResult {
|
|
33
|
+
root: string;
|
|
34
|
+
pid: number | null;
|
|
35
|
+
/** 'term' graceful, 'kill' force, 'not-running' stale lock, 'no-daemon' none found. */
|
|
36
|
+
outcome: 'term' | 'kill' | 'not-running' | 'no-daemon';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Stop the daemon serving `root`: SIGTERM, wait, then SIGKILL if it won't go,
|
|
40
|
+
* then sweep its artifacts. `root` must be realpath'd (match how the daemon
|
|
41
|
+
* keys its socket/lockfile). Resolves the pid from the authoritative lockfile,
|
|
42
|
+
* falling back to the registry.
|
|
43
|
+
*/
|
|
44
|
+
export declare function stopDaemonAt(root: string): Promise<StopResult>;
|
|
45
|
+
/** Stop every registered, live daemon. */
|
|
46
|
+
export declare function stopAllDaemons(): Promise<StopResult[]>;
|
|
47
|
+
//# sourceMappingURL=daemon-registry.d.ts.map
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared MCP daemon — issue #411.
|
|
3
|
+
*
|
|
4
|
+
* One detached `codegraph serve --mcp` daemon process per project root,
|
|
5
|
+
* accepting N concurrent MCP clients over a Unix-domain socket (or named pipe
|
|
6
|
+
* on Windows). Each incoming connection gets its own {@link MCPSession}; all
|
|
7
|
+
* sessions share a single {@link MCPEngine}, which means a single file watcher
|
|
8
|
+
* (one inotify set), a single SQLite connection (one WAL writer), and a single
|
|
9
|
+
* tree-sitter warm-up — paid once, amortized across every agent talking to the
|
|
10
|
+
* project.
|
|
11
|
+
*
|
|
12
|
+
* Lifecycle (see also `./index.ts` and `./proxy.ts`):
|
|
13
|
+
* - The daemon is spawned **detached** (its own session/process group, stdio
|
|
14
|
+
* decoupled) by the first launcher that finds no daemon running. It is NOT
|
|
15
|
+
* a child of any MCP host, so closing one terminal / Ctrl-C'ing one session
|
|
16
|
+
* can't take it down and sever the others. That's why this process has no
|
|
17
|
+
* PPID watchdog: it deliberately outlives every individual client.
|
|
18
|
+
* - Every MCP host talks to the daemon through a thin `proxy` process (the
|
|
19
|
+
* thing the host actually spawned). The proxy keeps the #277 PPID watchdog,
|
|
20
|
+
* so a SIGKILL'd host still reaps its proxy promptly; the proxy's socket
|
|
21
|
+
* close then decrements the daemon's refcount.
|
|
22
|
+
* - When the last client disconnects the daemon lingers for
|
|
23
|
+
* `CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS` (default 300s) so back-to-back agent
|
|
24
|
+
* runs in the same project don't repay startup, then exits cleanly. This is
|
|
25
|
+
* what keeps a single-agent session from leaking a daemon forever (#277).
|
|
26
|
+
*
|
|
27
|
+
* What this file owns:
|
|
28
|
+
* - Listening on the daemon socket and spawning per-connection sessions.
|
|
29
|
+
* - The handshake "hello" line that lets a proxy verify it found a
|
|
30
|
+
* same-version daemon before piping any JSON-RPC through it.
|
|
31
|
+
* - The lockfile (`.codegraph/daemon.pid`) competing daemons arbitrate
|
|
32
|
+
* against — atomic `O_EXCL` create with the full record written in the same
|
|
33
|
+
* breath (no empty-file window) + cleanup on exit.
|
|
34
|
+
* - Reference counting + idle timeout.
|
|
35
|
+
* - Graceful shutdown on SIGTERM/SIGINT and idle exit.
|
|
36
|
+
*
|
|
37
|
+
* What this file does NOT own:
|
|
38
|
+
* - The proxy side (`./proxy.ts`).
|
|
39
|
+
* - The decision of *whether* to run as daemon at all — that's `MCPServer`.
|
|
40
|
+
* - The MCP protocol state machine — that's `./session.ts`.
|
|
41
|
+
*/
|
|
42
|
+
import * as net from 'net';
|
|
43
|
+
import { DaemonLockInfo } from './daemon-paths';
|
|
44
|
+
/** Bytes/parse-window for an oversized hello line — bounded against a malicious peer. */
|
|
45
|
+
declare const MAX_HELLO_LINE_BYTES = 4096;
|
|
46
|
+
/**
|
|
47
|
+
* Wire format for the one-shot hello line the daemon emits on every new
|
|
48
|
+
* connection. Versioned with the package's own semver so a 0.9.x proxy never
|
|
49
|
+
* pipes through a 0.10.x daemon (or vice-versa) — the proxy falls back to
|
|
50
|
+
* direct mode on mismatch rather than risk subtle wire incompatibilities.
|
|
51
|
+
*/
|
|
52
|
+
export interface DaemonHello {
|
|
53
|
+
codegraph: string;
|
|
54
|
+
pid: number;
|
|
55
|
+
socketPath: string;
|
|
56
|
+
protocol: 1;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Optional reverse-handshake line a proxy sends right after it verifies the
|
|
60
|
+
* daemon hello, carrying its own pids so the daemon can reap the client if its
|
|
61
|
+
* process dies WITHOUT the socket ever signalling close (the Windows named-pipe
|
|
62
|
+
* hazard behind #692). Entirely optional and fail-safe: a connection that never
|
|
63
|
+
* sends it (a legacy/direct client) just falls back to the socket-close
|
|
64
|
+
* lifecycle. The `codegraph_client` marker is what tells it apart from the
|
|
65
|
+
* client's first JSON-RPC message.
|
|
66
|
+
*/
|
|
67
|
+
export interface DaemonClientHello {
|
|
68
|
+
codegraph_client: 1;
|
|
69
|
+
pid: number;
|
|
70
|
+
hostPid: number | null;
|
|
71
|
+
}
|
|
72
|
+
export interface DaemonStartResult {
|
|
73
|
+
/** Always-non-null for a successfully-started daemon. */
|
|
74
|
+
socketPath: string;
|
|
75
|
+
/** Lockfile contents as written. */
|
|
76
|
+
lock: DaemonLockInfo;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Run as the shared daemon for `projectRoot`. Resolves once the socket is
|
|
80
|
+
* listening. The Daemon owns the socket, the engine, and the lockfile until
|
|
81
|
+
* `stop()` is called or it exits on idle/signal.
|
|
82
|
+
*
|
|
83
|
+
* Race-safe: callers must first call `tryAcquireDaemonLock(projectRoot)` and
|
|
84
|
+
* only construct a Daemon if they got the lock (`kind: 'acquired'`). The atomic
|
|
85
|
+
* `O_EXCL` create inside the acquire helper — which now also writes the full
|
|
86
|
+
* record before returning — is the only synchronization between competing
|
|
87
|
+
* daemons.
|
|
88
|
+
*/
|
|
89
|
+
export declare class Daemon {
|
|
90
|
+
private projectRoot;
|
|
91
|
+
private server;
|
|
92
|
+
private clients;
|
|
93
|
+
/** Per-client peer pids from the optional client-hello, for the liveness sweep. */
|
|
94
|
+
private clientPeers;
|
|
95
|
+
private idleTimer;
|
|
96
|
+
private idleTimeoutMs;
|
|
97
|
+
private maxIdleMs;
|
|
98
|
+
private lastActivityAt;
|
|
99
|
+
private maxIdleTimer;
|
|
100
|
+
private clientSweepTimer;
|
|
101
|
+
private engine;
|
|
102
|
+
private stopping;
|
|
103
|
+
private socketPath;
|
|
104
|
+
private pidPath;
|
|
105
|
+
constructor(projectRoot: string, opts?: {
|
|
106
|
+
idleTimeoutMs?: number;
|
|
107
|
+
maxIdleMs?: number;
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Bind the socket, kick off engine init, and register signal handlers. The
|
|
111
|
+
* lockfile body was already written atomically by `tryAcquireDaemonLock`, so
|
|
112
|
+
* there is nothing to write here. The promise resolves once the server is
|
|
113
|
+
* listening — the daemon then sticks around until idle/shutdown.
|
|
114
|
+
*/
|
|
115
|
+
start(): Promise<DaemonStartResult>;
|
|
116
|
+
/** Currently-connected client count. Exposed for tests / status output. */
|
|
117
|
+
getClientCount(): number;
|
|
118
|
+
/** The socket path the daemon is (or will be) listening on. */
|
|
119
|
+
getSocketPath(): string;
|
|
120
|
+
/** Graceful shutdown: close all sessions, the engine, and clean up the lock. */
|
|
121
|
+
stop(reason?: string): Promise<void>;
|
|
122
|
+
private handleConnection;
|
|
123
|
+
private dropClient;
|
|
124
|
+
private armIdleTimer;
|
|
125
|
+
private disarmIdleTimer;
|
|
126
|
+
/**
|
|
127
|
+
* Defense-in-depth against a daemon that outlives its clients (#692), for the
|
|
128
|
+
* cases the refcount + idle timer miss because a socket close never arrives:
|
|
129
|
+
* - **Inactivity backstop:** exit if no inbound traffic for `maxIdleMs` while
|
|
130
|
+
* clients are still (nominally) connected. A phantom client sends nothing,
|
|
131
|
+
* so it can't pin the daemon past this window.
|
|
132
|
+
* - **Liveness sweep:** drop any client whose peer process has died (per the
|
|
133
|
+
* client-hello pids), which re-arms the idle timer once the last real
|
|
134
|
+
* client is gone. Catches a dead peer within one sweep instead of waiting
|
|
135
|
+
* out the whole backstop.
|
|
136
|
+
* Both timers are unref'd — the listening server keeps the loop alive, and
|
|
137
|
+
* neither should hold it open on its own.
|
|
138
|
+
*/
|
|
139
|
+
private startLivenessTimers;
|
|
140
|
+
/**
|
|
141
|
+
* Drop every connected client whose peer process is gone. Returns the count
|
|
142
|
+
* reaped. `isAlive` is injected for testing. Clients with unknown pids (no
|
|
143
|
+
* client-hello) are skipped — they rely on the socket-close path.
|
|
144
|
+
*/
|
|
145
|
+
reapDeadClients(isAlive: (pid: number) => boolean): number;
|
|
146
|
+
private cleanupLockfile;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Result of `tryAcquireDaemonLock`. Either we got the lockfile (caller becomes
|
|
150
|
+
* the daemon), or it already existed (caller should connect to the existing
|
|
151
|
+
* daemon as a proxy, or — if the holder is dead — clear it and retry).
|
|
152
|
+
*/
|
|
153
|
+
export type AcquireResult = {
|
|
154
|
+
kind: 'acquired';
|
|
155
|
+
pidPath: string;
|
|
156
|
+
info: DaemonLockInfo;
|
|
157
|
+
} | {
|
|
158
|
+
kind: 'taken';
|
|
159
|
+
existing: DaemonLockInfo | null;
|
|
160
|
+
pidPath: string;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Atomically create the daemon pidfile with its full record already in place.
|
|
164
|
+
* Returns either an `acquired` result (the caller is the daemon-elect and may
|
|
165
|
+
* construct a {@link Daemon}) or a `taken` result.
|
|
166
|
+
*
|
|
167
|
+
* must-fix 1 (issue #411 review): the lockfile must appear in ONE atomic step,
|
|
168
|
+
* already complete — never empty, even momentarily. The first attempt at this
|
|
169
|
+
* (`O_EXCL` create then a separate `writeSync`) left a microsecond window where
|
|
170
|
+
* the file existed but was empty; under concurrent daemon startup a third
|
|
171
|
+
* candidate could read that empty file, decode it as `null`, and `unlink` the
|
|
172
|
+
* winner's lock → two daemons (two watchers, two writers). The window was
|
|
173
|
+
* normally too small to hit, but the file watcher's extra startup time made
|
|
174
|
+
* concurrent daemons overlap enough to reproduce it reliably.
|
|
175
|
+
*
|
|
176
|
+
* The fix writes the complete record to a private temp file, then hard-links it
|
|
177
|
+
* into place: `link()` is atomic AND exclusive (EEXIST if the target exists), so
|
|
178
|
+
* the pidfile becomes visible in one step already containing a full record.
|
|
179
|
+
* Whoever links first wins; everyone else gets EEXIST and reads a complete file.
|
|
180
|
+
* There is no empty-file window at all.
|
|
181
|
+
*
|
|
182
|
+
* Filesystems without hard links (#997): ExFAT/FAT external volumes and some
|
|
183
|
+
* network mounts can't `link()` at all — it throws ENOTSUP/EPERM, which would
|
|
184
|
+
* otherwise kill the daemon before it ever reaches the socket bind. There we
|
|
185
|
+
* fall back to an O_EXCL create (`acquireLockViaExclusiveOpen`): still exclusive
|
|
186
|
+
* ("first writer wins"), but the full record is written through the fd in a
|
|
187
|
+
* second step, so the empty-file window the link approach removed is reopened —
|
|
188
|
+
* only on these filesystems, only for the microseconds between create and write
|
|
189
|
+
* (far narrower than the original bug, which the file watcher's startup latency
|
|
190
|
+
* widened). The race's worst case is two daemons briefly; on a single external
|
|
191
|
+
* drive that's strictly better than the daemon never starting at all.
|
|
192
|
+
*/
|
|
193
|
+
export declare function tryAcquireDaemonLock(projectRoot: string): AcquireResult;
|
|
194
|
+
/**
|
|
195
|
+
* Exclusive-create the pidfile (O_CREAT|O_EXCL via the `wx` flag) and write the
|
|
196
|
+
* full record through the same fd — the hard-link-free fallback used by
|
|
197
|
+
* {@link tryAcquireDaemonLock} on filesystems without `link()`. Returns true if
|
|
198
|
+
* we created it (acquired the lock), false on EEXIST (another candidate holds
|
|
199
|
+
* it). Any other error propagates. Still exclusive, so "first writer wins" holds
|
|
200
|
+
* exactly as the link path does; the only difference is the brief empty-file
|
|
201
|
+
* window between create and write. Exported for testing.
|
|
202
|
+
*/
|
|
203
|
+
export declare function acquireLockViaExclusiveOpen(pidPath: string, info: DaemonLockInfo): boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Remove a stale pidfile, but only if it still names a dead process. Re-reads
|
|
206
|
+
* the file immediately before unlinking so we never delete a lock that a live
|
|
207
|
+
* daemon (re)acquired in the meantime.
|
|
208
|
+
*
|
|
209
|
+
* must-fix 1 (issue #411 review): the original unconditionally `unlink`'d,
|
|
210
|
+
* which let a racing candidate delete a healthy daemon's lock. Passing
|
|
211
|
+
* `expectedDeadPid` (the pid the caller believed was dead) makes the clear a
|
|
212
|
+
* compare-and-delete: bail if the file now holds a different pid, or any live
|
|
213
|
+
* pid. Returns true when the stale lock is gone (or was already gone).
|
|
214
|
+
*/
|
|
215
|
+
export declare function clearStaleDaemonLock(pidPath: string, expectedDeadPid?: number): boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Probe whether `pid` is currently alive (signal-0). Treats EPERM as alive on
|
|
218
|
+
* every platform (the process exists, it's just not ours to signal) so we never
|
|
219
|
+
* mistake a live daemon for a dead one and clear its lock.
|
|
220
|
+
*/
|
|
221
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Bind the first usable socket from an ordered candidate list, relocating past
|
|
224
|
+
* any path that fails to bind for a non-conflict reason (see {@link
|
|
225
|
+
* SOCKET_BIND_CONFLICT_CODE}). The injected `listen` does the real
|
|
226
|
+
* `net.Server.listen` (and stale-socket clear); abstracted so the relocation
|
|
227
|
+
* policy is unit-testable without a real unsupported filesystem. Returns the
|
|
228
|
+
* server plus the path actually bound. An EADDRINUSE, or any error on the LAST
|
|
229
|
+
* candidate, propagates — the caller releases the lockfile and falls back to
|
|
230
|
+
* direct mode (#974). Exported for testing.
|
|
231
|
+
*/
|
|
232
|
+
export declare function bindFirstUsableSocket(candidates: string[], listen: (socketPath: string) => Promise<net.Server>, opts?: {
|
|
233
|
+
onRelocate?: (from: string, to: string, code: string) => void;
|
|
234
|
+
}): Promise<{
|
|
235
|
+
server: net.Server;
|
|
236
|
+
socketPath: string;
|
|
237
|
+
}>;
|
|
238
|
+
/**
|
|
239
|
+
* Parse one client-hello line. Returns the peer pids if `line` is a well-formed
|
|
240
|
+
* client-hello (carries the `codegraph_client` marker), or null otherwise — in
|
|
241
|
+
* which case the caller treats the bytes as ordinary JSON-RPC.
|
|
242
|
+
*/
|
|
243
|
+
export declare function parseClientHelloLine(line: string): {
|
|
244
|
+
pid: number;
|
|
245
|
+
hostPid: number | null;
|
|
246
|
+
} | null;
|
|
247
|
+
/**
|
|
248
|
+
* A client's peer is dead when its proxy process is gone, or when its known
|
|
249
|
+
* host process is gone. Unknown pid (no client-hello) is never "dead" on this
|
|
250
|
+
* basis — those clients rely on the socket-close path. Exported for testing.
|
|
251
|
+
*/
|
|
252
|
+
export declare function peerIsDead(peers: {
|
|
253
|
+
pid: number | null;
|
|
254
|
+
hostPid: number | null;
|
|
255
|
+
}, isAlive: (pid: number) => boolean): boolean;
|
|
256
|
+
/** Exported for test stubs that need to bound the hello-line read. */
|
|
257
|
+
export { MAX_HELLO_LINE_BYTES };
|
|
258
|
+
//# 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
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP shared engine — the heavyweight, *shared* state for an MCP server:
|
|
3
|
+
* the project's {@link CodeGraph} instance, file watcher, and the
|
|
4
|
+
* {@link ToolHandler} cache for cross-project queries.
|
|
5
|
+
*
|
|
6
|
+
* One engine, many sessions:
|
|
7
|
+
* - direct mode (single stdio session) instantiates one engine + one session;
|
|
8
|
+
* - daemon mode instantiates one engine and a new session per socket
|
|
9
|
+
* connection. Every session reads from the same SQLite WAL and the same
|
|
10
|
+
* inotify watch set — that's the entire point of issue #411.
|
|
11
|
+
*/
|
|
12
|
+
import { ToolHandler } from './tools';
|
|
13
|
+
export interface MCPEngineOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Whether to start the file watcher when initializing. Daemon and direct
|
|
16
|
+
* modes both want this true; tests may set it false to keep the engine
|
|
17
|
+
* cheap. Honors {@link watchDisabledReason} regardless.
|
|
18
|
+
*/
|
|
19
|
+
watch?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to off-load read-tool dispatch to a worker-thread pool. Only the
|
|
22
|
+
* SHARED daemon wants this — it serves many concurrent clients on one event
|
|
23
|
+
* loop, so without a pool concurrent explores serialize and starve the MCP
|
|
24
|
+
* transport. Direct mode (one stdio client, no concurrency) leaves it off so a
|
|
25
|
+
* single call never pays a worker round-trip. `CODEGRAPH_QUERY_POOL_SIZE=0`
|
|
26
|
+
* disables it even in daemon mode.
|
|
27
|
+
*/
|
|
28
|
+
queryPool?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Shared MCP engine. Thread-safe in the sense that multiple sessions can
|
|
32
|
+
* call its methods concurrently — internally it serializes initialization
|
|
33
|
+
* through a single promise so multiple sessions racing each other on first
|
|
34
|
+
* connect never double-open the SQLite file.
|
|
35
|
+
*/
|
|
36
|
+
export declare class MCPEngine {
|
|
37
|
+
private cg;
|
|
38
|
+
private toolHandler;
|
|
39
|
+
private projectPath;
|
|
40
|
+
private initPromise;
|
|
41
|
+
private watcherStarted;
|
|
42
|
+
private opts;
|
|
43
|
+
private closed;
|
|
44
|
+
private queryPool;
|
|
45
|
+
constructor(opts?: MCPEngineOptions);
|
|
46
|
+
/**
|
|
47
|
+
* Start the worker-thread query pool once a default project is open (daemon
|
|
48
|
+
* mode only; honors `CODEGRAPH_QUERY_POOL_SIZE`). Idempotent and best-effort:
|
|
49
|
+
* if workers can't spawn on this platform the ToolHandler keeps serving reads
|
|
50
|
+
* in-process, so the pool can only help, never break, tool calls.
|
|
51
|
+
*/
|
|
52
|
+
private maybeStartPool;
|
|
53
|
+
/**
|
|
54
|
+
* Convenience for {@link MCPServer} compatibility: pre-seed an explicit
|
|
55
|
+
* project path (from the `--path` CLI flag) without yet opening it. This
|
|
56
|
+
* keeps the synchronous constructor cheap; the actual open happens on the
|
|
57
|
+
* first `ensureInitialized` call.
|
|
58
|
+
*/
|
|
59
|
+
setProjectPathHint(projectPath: string): void;
|
|
60
|
+
/** Project root that the engine resolved on first init (null if none). */
|
|
61
|
+
getProjectPath(): string | null;
|
|
62
|
+
/** Shared ToolHandler — sessions delegate tool dispatch through this. */
|
|
63
|
+
getToolHandler(): ToolHandler;
|
|
64
|
+
/** Whether the default project's CodeGraph is open. */
|
|
65
|
+
hasDefaultCodeGraph(): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Walk up from `searchFrom` to find the nearest `.codegraph/` and open it.
|
|
68
|
+
* Idempotent: concurrent callers share one in-flight init; subsequent
|
|
69
|
+
* callers after success are no-ops.
|
|
70
|
+
*
|
|
71
|
+
* The original `MCPServer.tryInitializeDefault` carried the same retry-on-
|
|
72
|
+
* subsequent-tool-call semantics; we preserve them by NOT throwing when the
|
|
73
|
+
* search misses (just leaves `cg` null so the next call can retry).
|
|
74
|
+
*/
|
|
75
|
+
ensureInitialized(searchFrom: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Synchronous last-resort init used by the per-session retry loop when the
|
|
78
|
+
* background `ensureInitialized` already finished (or failed) and we need
|
|
79
|
+
* to pick up a project that appeared *after* the engine started.
|
|
80
|
+
*/
|
|
81
|
+
retryInitializeSync(searchFrom: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Close everything. Used on graceful daemon shutdown (SIGTERM/idle timeout)
|
|
84
|
+
* and on direct-mode stop. Idempotent.
|
|
85
|
+
*/
|
|
86
|
+
stop(): void;
|
|
87
|
+
private doInitialize;
|
|
88
|
+
/**
|
|
89
|
+
* Start file watching on the active CodeGraph instance. Idempotent — the
|
|
90
|
+
* watcher is per-engine, not per-session, which is why the daemon path
|
|
91
|
+
* collapses N inotify sets to one. The wording of the disabled-reason log
|
|
92
|
+
* exactly matches the prior in-tree implementation so log-driven dashboards
|
|
93
|
+
* keep working.
|
|
94
|
+
*/
|
|
95
|
+
private startWatching;
|
|
96
|
+
/**
|
|
97
|
+
* Reconcile the index with the current filesystem once, right after open —
|
|
98
|
+
* catches edits, adds, deletes, and `git pull`/`checkout` changes made while
|
|
99
|
+
* no watcher was running. Runs in the background, but the returned promise
|
|
100
|
+
* is pushed into the ToolHandler as a one-shot gate so the *first* tool
|
|
101
|
+
* call awaits completion before serving (without this, a tool call that
|
|
102
|
+
* races past sync returns rows for files that no longer exist on disk —
|
|
103
|
+
* and the per-file staleness banner can't help because `getPendingFiles()`
|
|
104
|
+
* is populated by the watcher, not by catch-up).
|
|
105
|
+
*/
|
|
106
|
+
private catchUpSync;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Parse and clamp the CODEGRAPH_WATCH_DEBOUNCE_MS env override.
|
|
110
|
+
*
|
|
111
|
+
* Issue #403: workspaces with bursty writes (formatter-on-save, multi-file
|
|
112
|
+
* refactors) sometimes want a longer quiet window before sync. Returns
|
|
113
|
+
* `undefined` for unset / empty / non-numeric / out-of-range values so the
|
|
114
|
+
* FileWatcher default (2000ms) takes over — never throws.
|
|
115
|
+
*
|
|
116
|
+
* Clamp range: 100ms (faster would mean a sync per keystroke) to 60s (longer
|
|
117
|
+
* and the watcher feels broken). Out-of-range values are treated as "ignore
|
|
118
|
+
* this misconfiguration" rather than capped, since silently capping a 0 or
|
|
119
|
+
* a typoed value would mask a real config bug.
|
|
120
|
+
*/
|
|
121
|
+
export declare function parseDebounceEnv(raw: string | undefined): number | undefined;
|
|
122
|
+
//# sourceMappingURL=engine.d.ts.map
|