@reposkein/mcp 0.3.0 → 0.5.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 +76 -1
- package/binary-digests.json +4 -4
- package/dist/SKILL.md +19 -0
- package/dist/cli/agentAdapters.d.ts +68 -0
- package/dist/cli/agentAdapters.js +223 -0
- package/dist/cli/agentAdapters.js.map +1 -0
- package/dist/cli/ansi.d.ts +24 -0
- package/dist/cli/ansi.js +40 -0
- package/dist/cli/ansi.js.map +1 -0
- package/dist/cli/doctor.d.ts +34 -2
- package/dist/cli/doctor.js +51 -6
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/doctorFreshness.d.ts +43 -0
- package/dist/cli/doctorFreshness.js +111 -0
- package/dist/cli/doctorFreshness.js.map +1 -0
- package/dist/cli/doctorSummaries.d.ts +2 -0
- package/dist/cli/doctorSummaries.js +95 -0
- package/dist/cli/doctorSummaries.js.map +1 -0
- package/dist/cli/federatedDiscovery.d.ts +16 -0
- package/dist/cli/federatedDiscovery.js +75 -0
- package/dist/cli/federatedDiscovery.js.map +1 -0
- package/dist/cli/init.d.ts +69 -6
- package/dist/cli/init.js +194 -28
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/sourceSlices.d.ts +17 -0
- package/dist/cli/sourceSlices.js +59 -0
- package/dist/cli/sourceSlices.js.map +1 -0
- package/dist/cli/stats.d.ts +29 -0
- package/dist/cli/stats.js +180 -0
- package/dist/cli/stats.js.map +1 -0
- package/dist/cli/view.d.ts +71 -3
- package/dist/cli/view.js +170 -14
- package/dist/cli/view.js.map +1 -1
- package/dist/index.d.ts +8 -48
- package/dist/index.js +94 -295
- package/dist/index.js.map +1 -1
- package/dist/indexer/fetchBinary.d.ts +14 -0
- package/dist/indexer/fetchBinary.js +81 -40
- package/dist/indexer/fetchBinary.js.map +1 -1
- package/dist/indexer/indexLock.d.ts +13 -0
- package/dist/indexer/indexLock.js +66 -0
- package/dist/indexer/indexLock.js.map +1 -0
- package/dist/indexer/runIndexer.d.ts +8 -2
- package/dist/indexer/runIndexer.js +20 -4
- package/dist/indexer/runIndexer.js.map +1 -1
- package/dist/serve/serve.d.ts +95 -0
- package/dist/serve/serve.js +581 -0
- package/dist/serve/serve.js.map +1 -0
- package/dist/serve/tokens.d.ts +49 -0
- package/dist/serve/tokens.js +108 -0
- package/dist/serve/tokens.js.map +1 -0
- package/dist/serve/watch.d.ts +73 -0
- package/dist/serve/watch.js +135 -0
- package/dist/serve/watch.js.map +1 -0
- package/dist/server/createMcpServer.d.ts +129 -0
- package/dist/server/createMcpServer.js +464 -0
- package/dist/server/createMcpServer.js.map +1 -0
- package/dist/store/JsonlGraphStore.d.ts +18 -4
- package/dist/store/JsonlGraphStore.js +61 -19
- package/dist/store/JsonlGraphStore.js.map +1 -1
- package/dist/store/decisions.d.ts +20 -2
- package/dist/store/decisions.js +35 -2
- package/dist/store/decisions.js.map +1 -1
- package/dist/store/indexedAt.d.ts +18 -0
- package/dist/store/indexedAt.js +56 -0
- package/dist/store/indexedAt.js.map +1 -0
- package/dist/store/instrumentTool.d.ts +40 -0
- package/dist/store/instrumentTool.js +82 -0
- package/dist/store/instrumentTool.js.map +1 -0
- package/dist/store/repoContextCache.d.ts +15 -0
- package/dist/store/repoContextCache.js +26 -0
- package/dist/store/repoContextCache.js.map +1 -0
- package/dist/store/repoSession.d.ts +63 -0
- package/dist/store/repoSession.js +123 -0
- package/dist/store/repoSession.js.map +1 -0
- package/dist/store/resolveRepoPath.d.ts +31 -0
- package/dist/store/resolveRepoPath.js +85 -0
- package/dist/store/resolveRepoPath.js.map +1 -0
- package/dist/store/sessionLog.d.ts +98 -0
- package/dist/store/sessionLog.js +216 -0
- package/dist/store/sessionLog.js.map +1 -0
- package/dist/store/sessionStats.d.ts +55 -0
- package/dist/store/sessionStats.js +140 -0
- package/dist/store/sessionStats.js.map +1 -0
- package/dist/store/sidecar.d.ts +39 -3
- package/dist/store/sidecar.js +89 -5
- package/dist/store/sidecar.js.map +1 -1
- package/dist/store/summaryShards.d.ts +117 -0
- package/dist/store/summaryShards.js +291 -0
- package/dist/store/summaryShards.js.map +1 -0
- package/dist/store/teamConfig.d.ts +17 -0
- package/dist/store/teamConfig.js +55 -0
- package/dist/store/teamConfig.js.map +1 -0
- package/dist/tools/indexerTools.d.ts +14 -2
- package/dist/tools/indexerTools.js +17 -5
- package/dist/tools/indexerTools.js.map +1 -1
- package/dist/tools/recordDecision.d.ts +3 -0
- package/dist/tools/recordDecision.js +7 -2
- package/dist/tools/recordDecision.js.map +1 -1
- package/dist/tools/writeSemanticSummary.d.ts +4 -1
- package/dist/tools/writeSemanticSummary.js +5 -2
- package/dist/tools/writeSemanticSummary.js.map +1 -1
- package/dist/viz/assets/{graph.worker-CC7BeE-8.js → graph.worker-DkH6-lMU.js} +1 -1
- package/dist/viz/assets/index-BnqIVYFE.css +1 -0
- package/dist/viz/assets/index-IhtszTp0.js +5 -0
- package/dist/viz/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/viz/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/viz/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
- package/dist/viz/assets/r3f-Bn9htB92.js +336 -0
- package/dist/viz/assets/tanstack-CSzx6NDA.js +62 -0
- package/dist/viz/assets/three-Cpu5RUEM.js +4116 -0
- package/dist/viz/index.html +5 -4
- package/package.json +5 -4
- package/dist/viz/assets/index-C_8UkiJ8.js +0 -9
- package/dist/viz/assets/r3f-Cku1OhB-.js +0 -277
- package/dist/viz/assets/tanstack-DyXcNRWk.js +0 -77
- package/dist/viz/assets/three-Z8XaLGmf.js +0 -3839
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createWriteStream, existsSync, mkdirSync, chmodSync, statSync, readFileSync, unlinkSync, renameSync } from "node:fs";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
-
import { get } from "node:https";
|
|
4
3
|
import { dirname, join } from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { pipeline } from "node:stream/promises";
|
|
6
|
+
import { request, EnvHttpProxyAgent } from "undici";
|
|
6
7
|
/** Supported platform keys → must match the release asset suffixes (D2).
|
|
7
8
|
* Intel macOS (darwin-x64) is intentionally unsupported: all Macs have shipped
|
|
8
9
|
* Apple Silicon since 2020, and GitHub's macos-13 Intel runners are scarce and
|
|
@@ -28,6 +29,48 @@ export function platformKey(platform = process.platform, arch = process.arch) {
|
|
|
28
29
|
export function isWindows(platform = process.platform) {
|
|
29
30
|
return platform === "win32";
|
|
30
31
|
}
|
|
32
|
+
/** Whether this host has a prebuilt indexer release asset. Convenience
|
|
33
|
+
* wrapper over `platformKey()` for call sites that just need a boolean
|
|
34
|
+
* (e.g. `init`'s pre-flight check, before attempting any fetch). */
|
|
35
|
+
export function isSupportedPlatform(platform = process.platform, arch = process.arch) {
|
|
36
|
+
return platformKey(platform, arch) !== null;
|
|
37
|
+
}
|
|
38
|
+
/** Printed when a host has no prebuilt indexer (darwin-x64, win32-arm64, …):
|
|
39
|
+
* the from-source fallback plus the offline override env var, so `init` can
|
|
40
|
+
* continue (write agent config, skip only the binary-dependent steps)
|
|
41
|
+
* instead of crashing. See docs/INSTALL.md §2 "Platform support". */
|
|
42
|
+
export function cargoInstallFallbackMessage(platform = process.platform, arch = process.arch) {
|
|
43
|
+
return (`reposkein: no prebuilt indexer for ${platform}-${arch}. Build from source:\n` +
|
|
44
|
+
" git clone https://github.com/reposkein/reposkein.git && cd reposkein\n" +
|
|
45
|
+
" cargo install --path indexer/crates/cli --root ./cargo-out\n" +
|
|
46
|
+
" export REPOSKEIN_INDEXER_BIN=$PWD/cargo-out/bin/reposkein-indexer\n" +
|
|
47
|
+
"Then re-run `reposkein-mcp init` (or `doctor`/`index`) with that env var set — " +
|
|
48
|
+
"REPOSKEIN_INDEXER_BIN is also the offline fallback on supported platforms " +
|
|
49
|
+
"when the GitHub Releases fetch is unreachable.");
|
|
50
|
+
}
|
|
51
|
+
/** Reads HTTP(S)_PROXY / NO_PROXY (either case) via undici's
|
|
52
|
+
* `EnvHttpProxyAgent` — the standard way to proxy `undici.request()` once
|
|
53
|
+
* undici is a direct dependency (the global `fetch`'s own undici instance
|
|
54
|
+
* isn't importable to attach a dispatcher to). Returns `undefined` (use
|
|
55
|
+
* undici's default, non-proxying dispatcher) when no proxy env var is set,
|
|
56
|
+
* so the common case pays zero extra cost. Cached per-process — proxy env
|
|
57
|
+
* vars don't change mid-run. */
|
|
58
|
+
let _proxyDispatcher;
|
|
59
|
+
export function proxyDispatcher() {
|
|
60
|
+
if (_proxyDispatcher !== undefined)
|
|
61
|
+
return _proxyDispatcher ?? undefined;
|
|
62
|
+
const hasProxyEnv = !!(process.env.HTTPS_PROXY ||
|
|
63
|
+
process.env.https_proxy ||
|
|
64
|
+
process.env.HTTP_PROXY ||
|
|
65
|
+
process.env.http_proxy);
|
|
66
|
+
_proxyDispatcher = hasProxyEnv ? new EnvHttpProxyAgent() : null;
|
|
67
|
+
return _proxyDispatcher ?? undefined;
|
|
68
|
+
}
|
|
69
|
+
/** For testing only: reset the cached proxy dispatcher so env var changes
|
|
70
|
+
* take effect on the next call. */
|
|
71
|
+
export function _resetProxyDispatcherCache() {
|
|
72
|
+
_proxyDispatcher = undefined;
|
|
73
|
+
}
|
|
31
74
|
/** Release asset name for a platform key, e.g. reposkein-indexer-darwin-arm64. */
|
|
32
75
|
export function assetName(key) {
|
|
33
76
|
return `reposkein-indexer-${key}${key.startsWith("win32") ? ".exe" : ""}`;
|
|
@@ -96,45 +139,43 @@ export function verifyDigest(tempPath, expected) {
|
|
|
96
139
|
}
|
|
97
140
|
/** Follows redirects (GitHub release assets redirect to a CDN) and streams the
|
|
98
141
|
* body to `dest`. Rejects on non-2xx (after redirects) or network error.
|
|
99
|
-
* Redirect hardening: only follows https:// redirects to github.com or
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}).on("error", reject);
|
|
137
|
-
});
|
|
142
|
+
* Redirect hardening: only follows https:// redirects to github.com or
|
|
143
|
+
* *.githubusercontent.com.
|
|
144
|
+
*
|
|
145
|
+
* Uses undici's `request()` (not node:https) so a `proxyDispatcher()` can be
|
|
146
|
+
* attached — that's the only way to honor HTTPS_PROXY/NO_PROXY for this
|
|
147
|
+
* fetch, since the global `fetch()`'s own undici instance isn't reachable to
|
|
148
|
+
* set a dispatcher on. `request()` never auto-follows redirects (no redirect
|
|
149
|
+
* interceptor configured), so they're handled manually below to keep the
|
|
150
|
+
* host allowlist in the loop. */
|
|
151
|
+
async function downloadTo(url, dest, redirects = 5) {
|
|
152
|
+
const res = await request(url, { dispatcher: proxyDispatcher() });
|
|
153
|
+
const status = res.statusCode;
|
|
154
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
155
|
+
await res.body.dump().catch(() => { });
|
|
156
|
+
if (redirects <= 0) {
|
|
157
|
+
throw new Error("too many redirects");
|
|
158
|
+
}
|
|
159
|
+
const locationHeader = res.headers.location;
|
|
160
|
+
const location = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
|
|
161
|
+
const next = new URL(location, url);
|
|
162
|
+
if (next.protocol !== "https:") {
|
|
163
|
+
throw new Error(`reposkein: redirect to non-https URL rejected: ${next.href}`);
|
|
164
|
+
}
|
|
165
|
+
const host = next.hostname;
|
|
166
|
+
if (host !== "github.com" &&
|
|
167
|
+
host !== "objects.githubusercontent.com" &&
|
|
168
|
+
!host.endsWith(".githubusercontent.com")) {
|
|
169
|
+
throw new Error(`reposkein: redirect to non-allowlisted host rejected: ${host}`);
|
|
170
|
+
}
|
|
171
|
+
return downloadTo(next.href, dest, redirects - 1);
|
|
172
|
+
}
|
|
173
|
+
if (status !== 200) {
|
|
174
|
+
await res.body.dump().catch(() => { });
|
|
175
|
+
throw new Error(`download failed: HTTP ${status} for ${url}`);
|
|
176
|
+
}
|
|
177
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
178
|
+
await pipeline(res.body, createWriteStream(dest));
|
|
138
179
|
}
|
|
139
180
|
/** Downloads the indexer binary for this host into the package cache, verifies
|
|
140
181
|
* its integrity, makes it executable, and atomically renames it into place.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchBinary.js","sourceRoot":"","sources":["../../src/indexer/fetchBinary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC9H,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"fetchBinary.js","sourceRoot":"","sources":["../../src/indexer/fetchBinary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** True when an indexer run is in progress OR queued.
|
|
2
|
+
*
|
|
3
|
+
* Deliberately counts waiters, not just the active holder: the HEAD watcher
|
|
4
|
+
* uses this to SKIP a tick rather than queue behind a tool call, and a tick
|
|
5
|
+
* that queued would fire a redundant index the moment the tool's run
|
|
6
|
+
* finished — for a HEAD the tool's own run has by then already indexed. */
|
|
7
|
+
export declare function isIndexLockHeld(): boolean;
|
|
8
|
+
/** Runs `fn` with exclusive access to the indexer. */
|
|
9
|
+
export declare function withIndexLock<T>(fn: () => Promise<T>): Promise<T>;
|
|
10
|
+
/** Test-only: drains the queue so one suite's leftovers can't wedge the next.
|
|
11
|
+
* Never call this from production code — it does not cancel a running
|
|
12
|
+
* holder, it only waits for the queue to empty. */
|
|
13
|
+
export declare function drainIndexLock(): Promise<void>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
/** Process-wide mutual exclusion for indexer invocations.
|
|
3
|
+
*
|
|
4
|
+
* Every `reposkein-indexer` run writes into the served checkout's
|
|
5
|
+
* `.reposkein/` — `nodes.jsonl`, `edges.jsonl`, the committed summary shards,
|
|
6
|
+
* the pending-drift delta. Two runs at once on one checkout interleave those
|
|
7
|
+
* writes, and the loser's output is whatever the filesystem happened to
|
|
8
|
+
* order last. That was survivable while the server was one stdio process
|
|
9
|
+
* serving one agent: index runs were driven by one caller, one at a time.
|
|
10
|
+
*
|
|
11
|
+
* `serve --http` (REP-17) removes that accident. A write-capable tool call
|
|
12
|
+
* (`reindex_file`, `init_cpg_skeleton`, `record_decision`'s pre-stamp
|
|
13
|
+
* refresh) can now land while the HEAD watcher is mid-reindex, from a
|
|
14
|
+
* different connection, with no shared call stack to serialize them. So the
|
|
15
|
+
* serialization has to be explicit and it has to be somewhere no future
|
|
16
|
+
* caller can forget it — which is inside `spawnIndexer` itself, the single
|
|
17
|
+
* choke point every invocation passes through.
|
|
18
|
+
*
|
|
19
|
+
* Reentrancy-safe: a lock holder that (now or later) reaches `spawnIndexer`
|
|
20
|
+
* again runs straight through instead of deadlocking the process against
|
|
21
|
+
* itself. FIFO: waiters run in arrival order, so a queued tool call can't be
|
|
22
|
+
* starved by a fast-polling watcher. */
|
|
23
|
+
const reentrant = new AsyncLocalStorage();
|
|
24
|
+
/** Tail of the FIFO queue: resolves when every already-queued holder is done. */
|
|
25
|
+
let tail = Promise.resolve();
|
|
26
|
+
/** Holders plus waiters. Drives `isIndexLockHeld` (see its doc comment). */
|
|
27
|
+
let outstanding = 0;
|
|
28
|
+
/** True when an indexer run is in progress OR queued.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately counts waiters, not just the active holder: the HEAD watcher
|
|
31
|
+
* uses this to SKIP a tick rather than queue behind a tool call, and a tick
|
|
32
|
+
* that queued would fire a redundant index the moment the tool's run
|
|
33
|
+
* finished — for a HEAD the tool's own run has by then already indexed. */
|
|
34
|
+
export function isIndexLockHeld() {
|
|
35
|
+
return outstanding > 0;
|
|
36
|
+
}
|
|
37
|
+
/** Runs `fn` with exclusive access to the indexer. */
|
|
38
|
+
export async function withIndexLock(fn) {
|
|
39
|
+
// Already ours (a nested spawn inside a lock-holding operation): proceed.
|
|
40
|
+
if (reentrant.getStore())
|
|
41
|
+
return fn();
|
|
42
|
+
outstanding++;
|
|
43
|
+
let release;
|
|
44
|
+
const mine = new Promise((r) => {
|
|
45
|
+
release = r;
|
|
46
|
+
});
|
|
47
|
+
const prior = tail;
|
|
48
|
+
// A rejection can't come from `mine` (release never throws), but chain
|
|
49
|
+
// defensively: a broken tail would wedge every future acquisition.
|
|
50
|
+
tail = prior.then(() => mine).catch(() => undefined);
|
|
51
|
+
try {
|
|
52
|
+
await prior;
|
|
53
|
+
return await reentrant.run(true, fn);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
outstanding--;
|
|
57
|
+
release();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Test-only: drains the queue so one suite's leftovers can't wedge the next.
|
|
61
|
+
* Never call this from production code — it does not cancel a running
|
|
62
|
+
* holder, it only waits for the queue to empty. */
|
|
63
|
+
export async function drainIndexLock() {
|
|
64
|
+
await tail;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=indexLock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexLock.js","sourceRoot":"","sources":["../../src/indexer/indexLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;yCAoByC;AAEzC,MAAM,SAAS,GAAG,IAAI,iBAAiB,EAAQ,CAAC;AAEhD,iFAAiF;AACjF,IAAI,IAAI,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;AAC5C,4EAA4E;AAC5E,IAAI,WAAW,GAAG,CAAC,CAAC;AAEpB;;;;;4EAK4E;AAC5E,MAAM,UAAU,eAAe;IAC7B,OAAO,WAAW,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,sDAAsD;AACtD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAI,EAAoB;IACzD,0EAA0E;IAC1E,IAAI,SAAS,CAAC,QAAQ,EAAE;QAAE,OAAO,EAAE,EAAE,CAAC;IAEtC,WAAW,EAAE,CAAC;IACd,IAAI,OAAoB,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE;QACnC,OAAO,GAAG,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,CAAC;IACnB,uEAAuE;IACvE,mEAAmE;IACnE,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACrD,IAAI,CAAC;QACH,MAAM,KAAK,CAAC;QACZ,OAAO,MAAM,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACvC,CAAC;YAAS,CAAC;QACT,WAAW,EAAE,CAAC;QACd,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;oDAEoD;AACpD,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,IAAI,CAAC;AACb,CAAC"}
|
|
@@ -3,8 +3,14 @@ export interface SpawnResult {
|
|
|
3
3
|
stdout: string;
|
|
4
4
|
stderr: string;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/** Runs the indexer binary, capped and timed out.
|
|
7
|
+
*
|
|
8
|
+
* Serialized process-wide by `withIndexLock`: every invocation writes into
|
|
9
|
+
* the target checkout's `.reposkein/`, and since REP-17 two callers (a
|
|
10
|
+
* write-capable tool call on one connection, the HEAD watcher on another) can
|
|
11
|
+
* arrive concurrently with no shared call stack to order them. The lock lives
|
|
12
|
+
* HERE rather than at each call site so a new call site cannot forget it —
|
|
13
|
+
* see `indexLock.ts`. */
|
|
8
14
|
export declare function spawnIndexer(bin: string, args: string[]): Promise<SpawnResult>;
|
|
9
15
|
export interface IndexJsonStats {
|
|
10
16
|
repo_id: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { withIndexLock } from "./indexLock.js";
|
|
2
3
|
const ALLOWED_KEYS = new Set(["PATH", "HOME", "TMPDIR", "LANG", "LC_ALL", "NEO4J_URI", "NEO4J_USER", "NEO4J_PASSWORD"]);
|
|
3
4
|
/** Indexer spawn timeout (~10 minutes). Large repos can be slow but shouldn't hang forever. */
|
|
4
5
|
const INDEXER_TIMEOUT_MS = 600_000;
|
|
@@ -13,11 +14,26 @@ function buildChildEnv() {
|
|
|
13
14
|
}
|
|
14
15
|
return env;
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// NOTE: there used to be a `repoPath()` here (REPOSKEIN_REPO_PATH ?? cwd),
|
|
18
|
+
// an independent repo-path resolver `tools/indexerTools.ts` called instead
|
|
19
|
+
// of using its caller's already-resolved active repo. That let init/reindex
|
|
20
|
+
// bypass session.select_repo entirely and index the wrong repo in workspace
|
|
21
|
+
// mode — removed; every caller now takes an explicit `repoPath` param
|
|
22
|
+
// sourced from mcp/src/store/repoSession.ts. Don't reintroduce an
|
|
23
|
+
// env/cwd-based resolver here — see mcp/src/store/resolveRepoPath.ts for the
|
|
24
|
+
// one central resolution algorithm.
|
|
25
|
+
/** Runs the indexer binary, capped and timed out.
|
|
26
|
+
*
|
|
27
|
+
* Serialized process-wide by `withIndexLock`: every invocation writes into
|
|
28
|
+
* the target checkout's `.reposkein/`, and since REP-17 two callers (a
|
|
29
|
+
* write-capable tool call on one connection, the HEAD watcher on another) can
|
|
30
|
+
* arrive concurrently with no shared call stack to order them. The lock lives
|
|
31
|
+
* HERE rather than at each call site so a new call site cannot forget it —
|
|
32
|
+
* see `indexLock.ts`. */
|
|
20
33
|
export function spawnIndexer(bin, args) {
|
|
34
|
+
return withIndexLock(() => spawnIndexerUnlocked(bin, args));
|
|
35
|
+
}
|
|
36
|
+
function spawnIndexerUnlocked(bin, args) {
|
|
21
37
|
return new Promise((resolve, reject) => {
|
|
22
38
|
const child = spawn(bin, args, { env: buildChildEnv() });
|
|
23
39
|
let stdout = "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runIndexer.js","sourceRoot":"","sources":["../../src/indexer/runIndexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"runIndexer.js","sourceRoot":"","sources":["../../src/indexer/runIndexer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
import { type CreateMcpServerOptions } from "../server/createMcpServer.js";
|
|
3
|
+
import { type ServeToken } from "./tokens.js";
|
|
4
|
+
/** The one path the MCP Streamable HTTP transport answers on. Everything else
|
|
5
|
+
* is the viewer + `/api/*`, served by view.ts's own handler. */
|
|
6
|
+
export declare const MCP_PATH = "/mcp";
|
|
7
|
+
export interface ServeOptions {
|
|
8
|
+
port: number;
|
|
9
|
+
host: string;
|
|
10
|
+
/** `git rev-parse HEAD` poll interval. 0 disables the watcher. */
|
|
11
|
+
watchIntervalMs: number;
|
|
12
|
+
}
|
|
13
|
+
/** Default bind host. NOT 0.0.0.0: even in the mode that exists to be
|
|
14
|
+
* reachable, exposure is an explicit `--host` decision by the operator. */
|
|
15
|
+
export declare const DEFAULT_SERVE_HOST = "127.0.0.1";
|
|
16
|
+
export declare const DEFAULT_SERVE_PORT = 4318;
|
|
17
|
+
export declare const DEFAULT_WATCH_INTERVAL_MS = 30000;
|
|
18
|
+
export declare function parseServeArgs(argv: string[]): {
|
|
19
|
+
repoPath: string;
|
|
20
|
+
opts: ServeOptions;
|
|
21
|
+
http: boolean;
|
|
22
|
+
};
|
|
23
|
+
type NodeHandler = (req: IncomingMessage, res: ServerResponse) => void;
|
|
24
|
+
export interface CreateServeAppOptions {
|
|
25
|
+
repoPath: string;
|
|
26
|
+
repoId: string;
|
|
27
|
+
tokens: readonly ServeToken[];
|
|
28
|
+
log?: (message: string) => void;
|
|
29
|
+
/** Test seam: replaces `makeViewHandler` (which reads the repo's JSONL and
|
|
30
|
+
* shells out to git at construction time). */
|
|
31
|
+
viewHandlerFactory?: (repoPath: string, repoId: string) => NodeHandler;
|
|
32
|
+
/** Test seam: passed through to every per-connection `createMcpServer`. */
|
|
33
|
+
ensureGraph?: CreateMcpServerOptions["ensureGraph"];
|
|
34
|
+
/** Concurrent-session cap. Defaults to `MAX_SESSIONS` (64); lowered in
|
|
35
|
+
* tests so the cap and the reaper-then-retry path are cheap to exercise. */
|
|
36
|
+
maxSessions?: number;
|
|
37
|
+
/** Idle-session reap threshold. Defaults to `SESSION_IDLE_MS` (5 min). */
|
|
38
|
+
idleMs?: number;
|
|
39
|
+
/** Clock. Injectable so the reaper can be tested without waiting minutes. */
|
|
40
|
+
now?: () => number;
|
|
41
|
+
}
|
|
42
|
+
export interface ServeApp {
|
|
43
|
+
handler: NodeHandler;
|
|
44
|
+
/** Rebuilds the `/api/*` handler. The view handler snapshots node/edge
|
|
45
|
+
* counts and git metadata once per construction, so after a re-index the
|
|
46
|
+
* hosted viewer would otherwise keep reporting the old graph — this is the
|
|
47
|
+
* watcher's `onReindexed` hook, and it is why the MCP tools and the viewer
|
|
48
|
+
* cannot drift apart: they are refreshed by the same event. */
|
|
49
|
+
refresh: () => void;
|
|
50
|
+
/** Closes every session idle past `idleMs` with no request in flight, and
|
|
51
|
+
* returns how many it closed. Called automatically before each new-session
|
|
52
|
+
* attempt (so a server wedged at the cap by dead clients frees itself on
|
|
53
|
+
* the next connect); exposed for tests. */
|
|
54
|
+
sweepIdleSessions: () => number;
|
|
55
|
+
close: () => Promise<void>;
|
|
56
|
+
sessionCount: () => number;
|
|
57
|
+
}
|
|
58
|
+
/** Builds the one request handler that answers both the MCP endpoint and the
|
|
59
|
+
* viewer/`/api/*` surface (REP-17 requirement: one process, one graph).
|
|
60
|
+
*
|
|
61
|
+
* URL layout:
|
|
62
|
+
* POST/GET/DELETE `/mcp` — MCP Streamable HTTP transport
|
|
63
|
+
* GET `/api/graph`, `/api/jsonl/*`, `/api/source`, `/api/temporal`
|
|
64
|
+
* — byte-identical to `reposkein-mcp view`, because
|
|
65
|
+
* it IS view.ts's handler, not a copy
|
|
66
|
+
* GET everything else — the prebuilt viewer SPA
|
|
67
|
+
*
|
|
68
|
+
* Auth applies to every route. `/mcp` accepts ONLY
|
|
69
|
+
* `Authorization: Bearer <token>`; the viewer routes additionally accept
|
|
70
|
+
* `?token=` (which is immediately traded for an HttpOnly cookie and
|
|
71
|
+
* redirected away, so it appears once) because a static SPA in a browser
|
|
72
|
+
* cannot attach an Authorization header to its own page load. */
|
|
73
|
+
export declare function createServeApp(opts: CreateServeAppOptions): ServeApp;
|
|
74
|
+
/** `git status --porcelain` for the served checkout, or null when git is
|
|
75
|
+
* unavailable / the path isn't a repo. Never throws. */
|
|
76
|
+
export declare function gitPorcelainStatus(repoPath: string): string[] | null;
|
|
77
|
+
/** The startup warning for serving a checkout with local modifications, or
|
|
78
|
+
* null when the tree is clean (or git can't tell us).
|
|
79
|
+
*
|
|
80
|
+
* Serving a dirty checkout is legal and sometimes exactly what an operator
|
|
81
|
+
* wants, so this WARNS and never refuses. But it is worth saying loudly:
|
|
82
|
+
* `serve`'s own re-index writes into `.reposkein/` (the committed summary
|
|
83
|
+
* shards absorb every `local/summaries-*.jsonl` sidecar), so a checkout that
|
|
84
|
+
* someone also works in will accumulate diffs that look like the server's
|
|
85
|
+
* fault — and a re-index landing on top of half-finished local edits indexes
|
|
86
|
+
* those edits. A dedicated deploy clone has neither problem. */
|
|
87
|
+
export declare function dirtyCheckoutWarning(repoPath: string, statusFn?: (p: string) => string[] | null): string | null;
|
|
88
|
+
/** `reposkein-mcp serve --http [path] [--port N] [--host H] [--watch-interval S]`.
|
|
89
|
+
*
|
|
90
|
+
* Strictly optional (REP-17 / adr:2026-08-21-optional-shared-remote-mcp-server-…):
|
|
91
|
+
* stdio is still the default transport and nothing binds a socket unless this
|
|
92
|
+
* runs. Refuses to start without at least one configured token — an
|
|
93
|
+
* unauthenticated remote MCP server would hand write access to the network. */
|
|
94
|
+
export declare function runServe(repoPath: string, repoId: string, opts: ServeOptions): Promise<number>;
|
|
95
|
+
export {};
|