@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
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { readConfigString } from "../store/teamConfig.js";
|
|
3
|
+
/** Minimum secret length we will accept. Short enough not to be annoying,
|
|
4
|
+
* long enough that a token typed by hand isn't guessable in an afternoon. */
|
|
5
|
+
const MIN_TOKEN_LENGTH = 16;
|
|
6
|
+
/** Parses a token list: `name:token[:write]` entries separated by commas,
|
|
7
|
+
* whitespace, or newlines.
|
|
8
|
+
*
|
|
9
|
+
* Chosen over JSON because it has to survive being pasted into a shell
|
|
10
|
+
* export, a systemd `Environment=` line, and a TOML string without escaping.
|
|
11
|
+
* Anything unparseable is DROPPED with a reason rather than silently
|
|
12
|
+
* becoming a valid credential — a malformed entry must never widen access.
|
|
13
|
+
* Capability is opt-in: only the literal third field `write` grants it. */
|
|
14
|
+
export function parseServeTokens(spec) {
|
|
15
|
+
const tokens = [];
|
|
16
|
+
const errors = [];
|
|
17
|
+
const seenNames = new Set();
|
|
18
|
+
const seenSecrets = new Set();
|
|
19
|
+
for (const rawEntry of spec.split(/[,\s]+/)) {
|
|
20
|
+
const entry = rawEntry.trim();
|
|
21
|
+
if (entry === "")
|
|
22
|
+
continue;
|
|
23
|
+
const parts = entry.split(":");
|
|
24
|
+
if (parts.length < 2 || parts.length > 3) {
|
|
25
|
+
errors.push(`ignored a token entry: expected "name:token" or "name:token:write"`);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const [name, token, cap] = parts;
|
|
29
|
+
if (!/^[A-Za-z0-9._-]{1,40}$/.test(name)) {
|
|
30
|
+
errors.push(`ignored a token entry: name must be 1-40 chars of [A-Za-z0-9._-] (got ${JSON.stringify(name)})`);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (token.length < MIN_TOKEN_LENGTH) {
|
|
34
|
+
errors.push(`ignored token "${name}": secret shorter than ${MIN_TOKEN_LENGTH} characters`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (cap !== undefined && cap !== "write") {
|
|
38
|
+
errors.push(`ignored token "${name}": third field must be "write" (got ${JSON.stringify(cap)})`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (seenNames.has(name)) {
|
|
42
|
+
errors.push(`ignored a duplicate token name: "${name}"`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (seenSecrets.has(token)) {
|
|
46
|
+
errors.push(`ignored token "${name}": its secret is already used by another name`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
seenNames.add(name);
|
|
50
|
+
seenSecrets.add(token);
|
|
51
|
+
tokens.push({ name, token, write: cap === "write" });
|
|
52
|
+
}
|
|
53
|
+
return { tokens, errors };
|
|
54
|
+
}
|
|
55
|
+
/** Resolves the token list for `serve`: `REPOSKEIN_SERVE_TOKENS` wins over
|
|
56
|
+
* `[serve] tokens` in `.reposkein/config.toml`.
|
|
57
|
+
*
|
|
58
|
+
* Env-over-config because the config file is COMMITTED — an operator who
|
|
59
|
+
* puts real secrets there has leaked them to everyone with repo access, so
|
|
60
|
+
* the documented path is the env var and config is the convenience for a
|
|
61
|
+
* private deployment. Only one source is consulted (no union): merging them
|
|
62
|
+
* would make "why does this old token still work?" unanswerable. */
|
|
63
|
+
export function loadServeTokens(repoPath, env = process.env) {
|
|
64
|
+
const fromEnv = env.REPOSKEIN_SERVE_TOKENS?.trim();
|
|
65
|
+
if (fromEnv)
|
|
66
|
+
return { ...parseServeTokens(fromEnv), source: "env" };
|
|
67
|
+
const fromConfig = readConfigString(repoPath, "serve", "tokens");
|
|
68
|
+
if (fromConfig && fromConfig.trim()) {
|
|
69
|
+
return { ...parseServeTokens(fromConfig), source: "config" };
|
|
70
|
+
}
|
|
71
|
+
return { tokens: [], errors: [], source: "none" };
|
|
72
|
+
}
|
|
73
|
+
/** Extracts the credential from an `Authorization: Bearer <token>` header.
|
|
74
|
+
* Returns null for a missing, non-Bearer, or empty-value header. */
|
|
75
|
+
export function bearerFromAuthHeader(header) {
|
|
76
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
77
|
+
if (!value)
|
|
78
|
+
return null;
|
|
79
|
+
const m = /^Bearer[ \t]+(.+)$/i.exec(value.trim());
|
|
80
|
+
if (!m)
|
|
81
|
+
return null;
|
|
82
|
+
const token = m[1].trim();
|
|
83
|
+
return token === "" ? null : token;
|
|
84
|
+
}
|
|
85
|
+
/** Length-independent constant-time comparison: both sides are hashed to a
|
|
86
|
+
* fixed 32 bytes first, so `timingSafeEqual` never sees mismatched lengths
|
|
87
|
+
* (it throws on those, and the throw itself would leak the length). */
|
|
88
|
+
function secretEquals(a, b) {
|
|
89
|
+
const ha = createHash("sha256").update(a, "utf8").digest();
|
|
90
|
+
const hb = createHash("sha256").update(b, "utf8").digest();
|
|
91
|
+
return timingSafeEqual(ha, hb);
|
|
92
|
+
}
|
|
93
|
+
/** Finds the token a presented credential matches, or null.
|
|
94
|
+
*
|
|
95
|
+
* Scans the WHOLE list with no early exit so the response time doesn't
|
|
96
|
+
* reveal which position matched (or how many tokens the operator
|
|
97
|
+
* configured). */
|
|
98
|
+
export function matchServeToken(tokens, presented) {
|
|
99
|
+
if (presented === null)
|
|
100
|
+
return null;
|
|
101
|
+
let hit = null;
|
|
102
|
+
for (const t of tokens) {
|
|
103
|
+
if (secretEquals(t.token, presented))
|
|
104
|
+
hit = t;
|
|
105
|
+
}
|
|
106
|
+
return hit;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/serve/tokens.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** What one watch tick did. `unchanged` is the overwhelmingly common case and
|
|
2
|
+
* costs exactly one `git rev-parse` — the whole point of comparing against
|
|
3
|
+
* the indexed-at marker rather than re-indexing on a timer. `locked` means
|
|
4
|
+
* another indexer run (a write-capable tool call, or the previous tick) owns
|
|
5
|
+
* the index lock; the tick is skipped, not queued. */
|
|
6
|
+
export type WatchOutcome = "unchanged" | "reindexed" | "failed" | "no-head" | "locked";
|
|
7
|
+
export interface ReindexResult {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
nodes?: number;
|
|
10
|
+
edges?: number;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface WatchDeps {
|
|
14
|
+
/** Current git HEAD of the served checkout, or null (not a repo / no HEAD). */
|
|
15
|
+
headSha: () => string | null;
|
|
16
|
+
/** The SHA the graph was last built from (`.reposkein/local/indexed-at`). */
|
|
17
|
+
indexedSha: () => string | null;
|
|
18
|
+
/** Rebuild the graph. MUST also refresh the indexed-at marker on success. */
|
|
19
|
+
reindex: () => Promise<ReindexResult>;
|
|
20
|
+
/** Where the one-line-per-reindex summary goes. MUST NOT be stdout in stdio
|
|
21
|
+
* mode; `serve` has no stdio transport, but stderr stays the convention. */
|
|
22
|
+
log: (message: string) => void;
|
|
23
|
+
/** Whether an indexer run is already in progress or queued. Injectable so
|
|
24
|
+
* the skip path is testable without spawning anything. */
|
|
25
|
+
indexBusy: () => boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Reads HEAD of `repoPath`, or null. Never throws. */
|
|
28
|
+
export declare function gitHeadSha(repoPath: string): string | null;
|
|
29
|
+
/** One watch tick.
|
|
30
|
+
*
|
|
31
|
+
* Idempotence is structural, not statistical: the decision to do work is
|
|
32
|
+
* `HEAD !== indexed-at`, and the marker is written from HEAD by the index
|
|
33
|
+
* step itself. So a tick against an unchanged checkout does nothing at all
|
|
34
|
+
* (no indexer spawn, no log line), and a tick after `git pull` does exactly
|
|
35
|
+
* one re-index — including across a server restart, because the marker is on
|
|
36
|
+
* disk. If HEAD moves again DURING an index, the marker records the SHA that
|
|
37
|
+
* was current when the index finished and the next tick converges.
|
|
38
|
+
*
|
|
39
|
+
* Reusing REP-16's marker (rather than an in-memory "last seen" SHA) is what
|
|
40
|
+
* keeps `doctor --ci`'s `graph_stale` check agreeing with the watcher: both
|
|
41
|
+
* read the same file. */
|
|
42
|
+
export declare function watchTick(deps: WatchDeps): Promise<WatchOutcome>;
|
|
43
|
+
/** The default `reindex`: the same indexer invocation `reposkein-mcp index`
|
|
44
|
+
* uses, followed by the indexed-at marker write. */
|
|
45
|
+
export declare function makeDefaultReindex(repoPath: string, repoId: string): () => Promise<ReindexResult>;
|
|
46
|
+
export interface HeadWatcherOptions {
|
|
47
|
+
intervalMs: number;
|
|
48
|
+
log?: (message: string) => void;
|
|
49
|
+
/** Called after a successful re-index — `serve` uses it to rebuild the
|
|
50
|
+
* `/api/*` handler, whose node/edge counts and git metadata are computed
|
|
51
|
+
* once per handler. */
|
|
52
|
+
onReindexed?: () => void;
|
|
53
|
+
/** Test seam: replaces the git + indexer plumbing wholesale. */
|
|
54
|
+
deps?: Partial<WatchDeps>;
|
|
55
|
+
}
|
|
56
|
+
export interface HeadWatcher {
|
|
57
|
+
/** Runs one tick now (awaitable — used by the startup catch-up and tests). */
|
|
58
|
+
tick: () => Promise<WatchOutcome>;
|
|
59
|
+
stop: () => void;
|
|
60
|
+
}
|
|
61
|
+
/** Polls `git rev-parse HEAD` on an interval and re-indexes when it moves.
|
|
62
|
+
*
|
|
63
|
+
* A poll, not a filesystem watcher: the indexer's determinism guarantee is
|
|
64
|
+
* defined against committed state, and an fs watcher would fire on every
|
|
65
|
+
* editor save — re-indexing mid-edit, repeatedly, for a graph nobody asked
|
|
66
|
+
* for yet. A poll also covers the case that actually matters for a shared
|
|
67
|
+
* server (someone ran `git fetch && git reset --hard` in the checkout, or a
|
|
68
|
+
* deploy hook did) without requiring a hook to be installed at all.
|
|
69
|
+
*
|
|
70
|
+
* `intervalMs <= 0` disables polling entirely; `tick()` still works, so an
|
|
71
|
+
* operator can drive re-indexing from their own post-receive hook by
|
|
72
|
+
* restarting or by running `reposkein-mcp index`. */
|
|
73
|
+
export declare function startHeadWatcher(repoPath: string, repoId: string, opts: HeadWatcherOptions): HeadWatcher;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readIndexedAtSha, writeIndexedAtMarker } from "../store/indexedAt.js";
|
|
3
|
+
import { ensureIndexerBinary } from "../indexer/fetchBinary.js";
|
|
4
|
+
import { spawnIndexer, parseJsonStats } from "../indexer/runIndexer.js";
|
|
5
|
+
import { isIndexLockHeld } from "../indexer/indexLock.js";
|
|
6
|
+
/** Reads HEAD of `repoPath`, or null. Never throws. */
|
|
7
|
+
export function gitHeadSha(repoPath) {
|
|
8
|
+
try {
|
|
9
|
+
const sha = execFileSync("git", ["-C", repoPath, "rev-parse", "HEAD"], {
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
12
|
+
}).trim();
|
|
13
|
+
return sha || null;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** One watch tick.
|
|
20
|
+
*
|
|
21
|
+
* Idempotence is structural, not statistical: the decision to do work is
|
|
22
|
+
* `HEAD !== indexed-at`, and the marker is written from HEAD by the index
|
|
23
|
+
* step itself. So a tick against an unchanged checkout does nothing at all
|
|
24
|
+
* (no indexer spawn, no log line), and a tick after `git pull` does exactly
|
|
25
|
+
* one re-index — including across a server restart, because the marker is on
|
|
26
|
+
* disk. If HEAD moves again DURING an index, the marker records the SHA that
|
|
27
|
+
* was current when the index finished and the next tick converges.
|
|
28
|
+
*
|
|
29
|
+
* Reusing REP-16's marker (rather than an in-memory "last seen" SHA) is what
|
|
30
|
+
* keeps `doctor --ci`'s `graph_stale` check agreeing with the watcher: both
|
|
31
|
+
* read the same file. */
|
|
32
|
+
export async function watchTick(deps) {
|
|
33
|
+
const head = deps.headSha();
|
|
34
|
+
if (!head)
|
|
35
|
+
return "no-head";
|
|
36
|
+
if (head === deps.indexedSha())
|
|
37
|
+
return "unchanged";
|
|
38
|
+
// Someone else is indexing (a write tool call, or a still-running earlier
|
|
39
|
+
// tick). SKIP rather than queue: whatever they are building is being built
|
|
40
|
+
// from the same working tree, so by the time the lock frees, this tick's
|
|
41
|
+
// work is either done or the next tick will see it needs doing. Queueing
|
|
42
|
+
// here would fire a redundant full index behind every write tool call.
|
|
43
|
+
if (deps.indexBusy())
|
|
44
|
+
return "locked";
|
|
45
|
+
const started = Date.now();
|
|
46
|
+
const r = await deps.reindex();
|
|
47
|
+
const secs = ((Date.now() - started) / 1000).toFixed(1);
|
|
48
|
+
const short = head.slice(0, 12);
|
|
49
|
+
if (!r.ok) {
|
|
50
|
+
deps.log(`reposkein serve: reindex for HEAD ${short} FAILED after ${secs}s: ${r.error ?? "unknown error"} ` +
|
|
51
|
+
"(serving the previous graph; will retry on the next tick)");
|
|
52
|
+
return "failed";
|
|
53
|
+
}
|
|
54
|
+
deps.log(`reposkein serve: reindexed at HEAD ${short} in ${secs}s ` +
|
|
55
|
+
`(${r.nodes ?? "?"} nodes, ${r.edges ?? "?"} edges)`);
|
|
56
|
+
return "reindexed";
|
|
57
|
+
}
|
|
58
|
+
/** The default `reindex`: the same indexer invocation `reposkein-mcp index`
|
|
59
|
+
* uses, followed by the indexed-at marker write. */
|
|
60
|
+
export function makeDefaultReindex(repoPath, repoId) {
|
|
61
|
+
return async () => {
|
|
62
|
+
try {
|
|
63
|
+
const bin = await ensureIndexerBinary();
|
|
64
|
+
const r = await spawnIndexer(bin, ["index", "--json", "--repo-id", repoId, repoPath]);
|
|
65
|
+
if (r.code !== 0) {
|
|
66
|
+
return { ok: false, error: r.stderr.trim() || r.stdout.trim() || `exit ${r.code}` };
|
|
67
|
+
}
|
|
68
|
+
const stats = parseJsonStats(r.stdout);
|
|
69
|
+
writeIndexedAtMarker(repoPath);
|
|
70
|
+
return stats ? { ok: true, nodes: stats.nodes, edges: stats.edges } : { ok: true };
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Polls `git rev-parse HEAD` on an interval and re-indexes when it moves.
|
|
78
|
+
*
|
|
79
|
+
* A poll, not a filesystem watcher: the indexer's determinism guarantee is
|
|
80
|
+
* defined against committed state, and an fs watcher would fire on every
|
|
81
|
+
* editor save — re-indexing mid-edit, repeatedly, for a graph nobody asked
|
|
82
|
+
* for yet. A poll also covers the case that actually matters for a shared
|
|
83
|
+
* server (someone ran `git fetch && git reset --hard` in the checkout, or a
|
|
84
|
+
* deploy hook did) without requiring a hook to be installed at all.
|
|
85
|
+
*
|
|
86
|
+
* `intervalMs <= 0` disables polling entirely; `tick()` still works, so an
|
|
87
|
+
* operator can drive re-indexing from their own post-receive hook by
|
|
88
|
+
* restarting or by running `reposkein-mcp index`. */
|
|
89
|
+
export function startHeadWatcher(repoPath, repoId, opts) {
|
|
90
|
+
const log = opts.log ?? ((m) => void process.stderr.write(`${m}\n`));
|
|
91
|
+
const deps = {
|
|
92
|
+
headSha: opts.deps?.headSha ?? (() => gitHeadSha(repoPath)),
|
|
93
|
+
indexedSha: opts.deps?.indexedSha ?? (() => readIndexedAtSha(repoPath)),
|
|
94
|
+
reindex: opts.deps?.reindex ?? makeDefaultReindex(repoPath, repoId),
|
|
95
|
+
log: opts.deps?.log ?? log,
|
|
96
|
+
indexBusy: opts.deps?.indexBusy ?? isIndexLockHeld,
|
|
97
|
+
};
|
|
98
|
+
// Ticks never overlap: a slow index on a big repo must not stack up behind
|
|
99
|
+
// a fast interval, and two concurrent indexer runs on one checkout would
|
|
100
|
+
// race on the same output files.
|
|
101
|
+
let busy = false;
|
|
102
|
+
let stopped = false;
|
|
103
|
+
const tick = async () => {
|
|
104
|
+
if (stopped)
|
|
105
|
+
return "unchanged";
|
|
106
|
+
// A previous tick is still running (slow index on a big repo, fast
|
|
107
|
+
// interval). Same reasoning as the index-lock skip in `watchTick`.
|
|
108
|
+
if (busy)
|
|
109
|
+
return "locked";
|
|
110
|
+
busy = true;
|
|
111
|
+
try {
|
|
112
|
+
const outcome = await watchTick(deps);
|
|
113
|
+
if (outcome === "reindexed")
|
|
114
|
+
opts.onReindexed?.();
|
|
115
|
+
return outcome;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
busy = false;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const timer = opts.intervalMs > 0
|
|
122
|
+
? setInterval(() => {
|
|
123
|
+
void tick();
|
|
124
|
+
}, opts.intervalMs)
|
|
125
|
+
: null;
|
|
126
|
+
return {
|
|
127
|
+
tick,
|
|
128
|
+
stop: () => {
|
|
129
|
+
stopped = true;
|
|
130
|
+
if (timer)
|
|
131
|
+
clearInterval(timer);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/serve/watch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { GraphStore } from "../store/GraphStore.js";
|
|
4
|
+
import type { RepoResolution } from "../store/resolveRepoPath.js";
|
|
5
|
+
import { ensureGraph } from "../indexer/ensureGraph.js";
|
|
6
|
+
/** Selects the store backend.
|
|
7
|
+
* REPOSKEIN_STORE = "jsonl" | "neo4j" | "auto" (default "auto").
|
|
8
|
+
* - auto: JSONL if <repoPath>/.reposkein/nodes.jsonl exists, else Neo4j if
|
|
9
|
+
* NEO4J_PASSWORD is set, else Unconfigured.
|
|
10
|
+
* - jsonl: JSONL if available, else Unconfigured.
|
|
11
|
+
* - neo4j: Neo4j if configured, else Unconfigured.
|
|
12
|
+
*
|
|
13
|
+
* `agent` names the writer for sidecar selection (one summaries file per
|
|
14
|
+
* agent, see store/sidecar.ts); undefined keeps the REPOSKEIN_AGENT default. */
|
|
15
|
+
export declare function buildStore(repoPath: string | undefined, repoId: string | undefined, agent?: string): GraphStore;
|
|
16
|
+
/** Structured, actionable message for repo-scoped tool calls when no repo
|
|
17
|
+
* resolved (see `resolveRepoPath`). Names the discovered candidates when
|
|
18
|
+
* resolution failed due to workspace-mode ambiguity, and points at
|
|
19
|
+
* `list_repos` / `select_repo` so the agent can pick one instead of
|
|
20
|
+
* abandoning the tools. */
|
|
21
|
+
export declare function repoRequiredMessage(resolution: RepoResolution): string;
|
|
22
|
+
/** A repo resolved but has no committed `meta.json` (never indexed). */
|
|
23
|
+
export declare function repoUnindexedMessage(repoPath: string): string;
|
|
24
|
+
/** Whether a graph is available to READ at `repoPath` without building one.
|
|
25
|
+
*
|
|
26
|
+
* Mirrors `buildStore`'s backend choice exactly (same env, same order), so
|
|
27
|
+
* "we can serve reads" and "we picked a real store" can never disagree:
|
|
28
|
+
* explicit neo4j mode trusts the DB, otherwise the committed-derived JSONL
|
|
29
|
+
* decides, and `auto` falls back to Neo4j when a password is configured. */
|
|
30
|
+
export declare function graphAvailable(repoPath: string, env?: NodeJS.ProcessEnv): boolean;
|
|
31
|
+
/** A read-only connection resolved a repo whose derived graph isn't built.
|
|
32
|
+
*
|
|
33
|
+
* Building it means spawning the indexer and writing `.reposkein/*.jsonl` —
|
|
34
|
+
* a write, and the LAST write a read-only token could still trigger, since
|
|
35
|
+
* `ensureGraph` used to run unconditionally on first touch of any repo. So a
|
|
36
|
+
* read-only caller gets this instead: an explanation and the exact command
|
|
37
|
+
* for whoever does hold write access, with nothing changed on disk. */
|
|
38
|
+
export declare function readOnlyUnindexedMessage(repoPath: string): string;
|
|
39
|
+
/** Exported for schema tests. */
|
|
40
|
+
export declare const getContextProfileInputSchema: {
|
|
41
|
+
node_id: z.ZodOptional<z.ZodString>;
|
|
42
|
+
file_path: z.ZodOptional<z.ZodString>;
|
|
43
|
+
name: z.ZodOptional<z.ZodString>;
|
|
44
|
+
hops: z.ZodOptional<z.ZodNumber>;
|
|
45
|
+
federated: z.ZodOptional<z.ZodBoolean>;
|
|
46
|
+
};
|
|
47
|
+
/** Exported for schema tests. */
|
|
48
|
+
export declare const recordDecisionInputSchema: {
|
|
49
|
+
title: z.ZodString;
|
|
50
|
+
context: z.ZodString;
|
|
51
|
+
decision: z.ZodString;
|
|
52
|
+
consequences: z.ZodOptional<z.ZodString>;
|
|
53
|
+
alternatives: z.ZodOptional<z.ZodString>;
|
|
54
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
55
|
+
proposed: "proposed";
|
|
56
|
+
accepted: "accepted";
|
|
57
|
+
}>>;
|
|
58
|
+
anchor_node_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
59
|
+
anchor_paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
60
|
+
supersedes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
61
|
+
};
|
|
62
|
+
export declare const setDecisionStatusInputSchema: {
|
|
63
|
+
decision_id: z.ZodString;
|
|
64
|
+
status: z.ZodEnum<{
|
|
65
|
+
accepted: "accepted";
|
|
66
|
+
rejected: "rejected";
|
|
67
|
+
deprecated: "deprecated";
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
70
|
+
export declare const reaffirmDecisionInputSchema: {
|
|
71
|
+
decision_id: z.ZodString;
|
|
72
|
+
};
|
|
73
|
+
export declare const listDecisionsInputSchema: {
|
|
74
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
75
|
+
proposed: "proposed";
|
|
76
|
+
accepted: "accepted";
|
|
77
|
+
rejected: "rejected";
|
|
78
|
+
deprecated: "deprecated";
|
|
79
|
+
superseded: "superseded";
|
|
80
|
+
}>>;
|
|
81
|
+
anchor: z.ZodOptional<z.ZodString>;
|
|
82
|
+
q: z.ZodOptional<z.ZodString>;
|
|
83
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
84
|
+
};
|
|
85
|
+
export declare const getDecisionInputSchema: {
|
|
86
|
+
decision_id: z.ZodString;
|
|
87
|
+
};
|
|
88
|
+
/** The tools that MUTATE the repo (sidecar prose, decision records, the
|
|
89
|
+
* derived graph). Gated behind the `write` capability in `serve --http`
|
|
90
|
+
* (REP-17); always available over stdio, where the caller already owns the
|
|
91
|
+
* checkout.
|
|
92
|
+
*
|
|
93
|
+
* `select_repo` is deliberately NOT here: it mutates only this connection's
|
|
94
|
+
* own `RepoSession`, which since REP-17 is per-connection state, so a
|
|
95
|
+
* read-only caller pointing itself at a different discovered repo affects
|
|
96
|
+
* nobody else. */
|
|
97
|
+
export declare const WRITE_TOOLS: readonly ["write_semantic_summary", "record_decision", "set_decision_status", "reaffirm_decision", "reindex_file", "init_cpg_skeleton"];
|
|
98
|
+
export interface ToolCapabilities {
|
|
99
|
+
/** False = read-only connection: every WRITE_TOOLS call is refused. */
|
|
100
|
+
write: boolean;
|
|
101
|
+
}
|
|
102
|
+
export interface CreateMcpServerOptions {
|
|
103
|
+
/** Working directory repo discovery starts from. */
|
|
104
|
+
cwd: string;
|
|
105
|
+
/** REPOSKEIN_REPO_PATH, or undefined. */
|
|
106
|
+
envRepoPath: string | undefined;
|
|
107
|
+
/** Session-log id. Defaults to `resolveSessionId(process.env)` (the stdio
|
|
108
|
+
* behaviour: one id per server process). */
|
|
109
|
+
sessionId?: string;
|
|
110
|
+
/** Writer identity recorded as `summary_by`/`decided_by` and used to pick
|
|
111
|
+
* the per-agent sidecar file. Undefined keeps the historical
|
|
112
|
+
* `REPOSKEIN_AGENT ?? "agent"` fallback inside the tools. */
|
|
113
|
+
identity?: string;
|
|
114
|
+
/** Defaults to `{ write: true }` — the stdio behaviour. */
|
|
115
|
+
capabilities?: ToolCapabilities;
|
|
116
|
+
/** Test seam for the first-touch graph build. Defaults to the real
|
|
117
|
+
* `ensureGraph`. Injected in tests so the read-only build gate can be
|
|
118
|
+
* asserted (called / not called) without spawning an indexer. */
|
|
119
|
+
ensureGraph?: typeof ensureGraph;
|
|
120
|
+
}
|
|
121
|
+
/** Builds ONE MCP server with its own session state.
|
|
122
|
+
*
|
|
123
|
+
* Everything a caller can move — the active repo (`select_repo`), the
|
|
124
|
+
* per-repoPath store/context cache, the tool-call logger — lives in this
|
|
125
|
+
* closure, so two connections into the same process cannot see each other's
|
|
126
|
+
* selection. That is what makes `serve --http` (REP-17) safe; over stdio
|
|
127
|
+
* there is exactly one of these per process, which is byte-for-byte the
|
|
128
|
+
* behaviour that existed before the extraction. */
|
|
129
|
+
export declare function createMcpServer(opts: CreateMcpServerOptions): McpServer;
|