@massu/core 1.6.0 → 1.6.2
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/dist/cli.js +730 -1686
- package/dist/hooks/session-start.js +10 -15
- package/docs/AUTHORING-ADAPTERS.md +41 -0
- package/docs/SECURITY.md +39 -0
- package/package.json +3 -3
- package/src/db.ts +24 -1
- package/src/security/registry-pubkey.generated.ts +1 -1
- package/src/server.ts +126 -25
- package/src/tool-db-needs.ts +226 -0
- package/src/tools.ts +110 -24
|
@@ -8690,25 +8690,20 @@ init_parse_guard();
|
|
|
8690
8690
|
import { Parser as Parser6 } from "web-tree-sitter";
|
|
8691
8691
|
init_parse_guard();
|
|
8692
8692
|
|
|
8693
|
-
//
|
|
8694
|
-
import {
|
|
8693
|
+
// src/detect/adapters/go-chi.ts
|
|
8694
|
+
import { goChiAdapter } from "@massu/adapter-go-chi";
|
|
8695
8695
|
|
|
8696
|
-
//
|
|
8697
|
-
import {
|
|
8698
|
-
import { Language as Language2, Parser as Parser7 } from "web-tree-sitter";
|
|
8699
|
-
var MAX_AST_FILE_BYTES2 = 1 * 1024 * 1024;
|
|
8696
|
+
// src/detect/adapters/rails.ts
|
|
8697
|
+
import { railsAdapter } from "@massu/adapter-rails";
|
|
8700
8698
|
|
|
8701
|
-
//
|
|
8702
|
-
import {
|
|
8699
|
+
// src/detect/adapters/phoenix.ts
|
|
8700
|
+
import { phoenixAdapter } from "@massu/adapter-phoenix";
|
|
8703
8701
|
|
|
8704
|
-
//
|
|
8705
|
-
import {
|
|
8702
|
+
// src/detect/adapters/aspnet.ts
|
|
8703
|
+
import { aspnetAdapter } from "@massu/adapter-aspnet";
|
|
8706
8704
|
|
|
8707
|
-
//
|
|
8708
|
-
import {
|
|
8709
|
-
|
|
8710
|
-
// ../adapter-spring/dist/index.js
|
|
8711
|
-
import { Parser as Parser12 } from "web-tree-sitter";
|
|
8705
|
+
// src/detect/adapters/spring.ts
|
|
8706
|
+
import { springAdapter } from "@massu/adapter-spring";
|
|
8712
8707
|
|
|
8713
8708
|
// src/detect/codebase-introspector.ts
|
|
8714
8709
|
function introspect(detection, projectRoot) {
|
|
@@ -200,6 +200,47 @@ adapter authors to opt-in to the new shape.
|
|
|
200
200
|
Additive changes (new optional fields on result types, new
|
|
201
201
|
TreeSitterLanguage enum entries) are minor-version compatible.
|
|
202
202
|
|
|
203
|
+
## Manifest sha256 round-trip — what to do when CI fails
|
|
204
|
+
|
|
205
|
+
> Plan 3c Phase 9b P-D-004 runbook excerpt.
|
|
206
|
+
|
|
207
|
+
The `tarball-e2e` CI job runs `adapter-manifest-roundtrip.test.ts` against the
|
|
208
|
+
live registry manifest at `https://registry.massu.ai/adapters/manifest.json`.
|
|
209
|
+
The test rebuilds every workspace adapter's `dist/`, computes the sha256, and
|
|
210
|
+
asserts it matches the manifest's `sha256` entry for that `{package, version}`
|
|
211
|
+
pair.
|
|
212
|
+
|
|
213
|
+
**If the round-trip fails after a workspace adapter source edit**, the
|
|
214
|
+
manifest must be re-signed BEFORE merge. The flow:
|
|
215
|
+
|
|
216
|
+
1. **Verify your edit is intentional.** Run `npm run build` from the repo
|
|
217
|
+
root and inspect `git diff packages/adapter-<f>/dist/`. If the diff is
|
|
218
|
+
non-trivial, the source change is real and needs a manifest re-sign.
|
|
219
|
+
2. **Bump the adapter version** in `packages/adapter-<f>/package.json` (e.g.
|
|
220
|
+
`1.0.0` → `1.0.1` for a bugfix; `1.1.0` for an additive feature). Manifest
|
|
221
|
+
entries are versioned, so re-signing without a version bump would break
|
|
222
|
+
reproducibility for users on the prior version.
|
|
223
|
+
3. **Compute the new sha256** via `node packages/core/scripts/compute-adapter-shasums.mjs`
|
|
224
|
+
(or equivalent) — this writes to `~/.massu/build-shasums.json`.
|
|
225
|
+
4. **Re-sign the manifest.** Run `bash scripts/provision/registry-publish.sh
|
|
226
|
+
path/to/manifest-body.json` — reads the Ed25519 private key from macOS
|
|
227
|
+
Keychain (`massu/registry/signing/private`), produces an envelope, deploys
|
|
228
|
+
to Vercel.
|
|
229
|
+
5. **Re-run the round-trip test locally**: `MASSU_MANIFEST_ROUNDTRIP=1 npm test
|
|
230
|
+
-- adapter-manifest-roundtrip` — should now PASS.
|
|
231
|
+
6. **Commit + open PR**. The CI gate will re-verify against the freshly-deployed
|
|
232
|
+
manifest.
|
|
233
|
+
|
|
234
|
+
If CI fails on a transient registry outage (5xx, DNS, CDN cache miss), the
|
|
235
|
+
test SKIPs cleanly with a console.warn — does NOT fail the job. Re-run the
|
|
236
|
+
job to recover.
|
|
237
|
+
|
|
238
|
+
**Non-monorepo adapter authors** (third-party packages NOT under `packages/adapter-*`):
|
|
239
|
+
the round-trip test SKIPs your package automatically (workspace dir absent in
|
|
240
|
+
the monorepo). Your install-time verification chain runs against the registry
|
|
241
|
+
sha256 directly via `discover.ts:295-360` — that path catches the same drift
|
|
242
|
+
class without requiring the test.
|
|
243
|
+
|
|
203
244
|
## See also
|
|
204
245
|
|
|
205
246
|
- [`SECURITY.md`](./SECURITY.md) — signing model, key rotation, supply-chain risks
|
package/docs/SECURITY.md
CHANGED
|
@@ -240,6 +240,45 @@ per the canonical plan). The maintainer will:
|
|
|
240
240
|
5. Add the affected adapter to the manifest's `unpublished: true` list
|
|
241
241
|
if applicable, so all consumers refuse to load on next refresh.
|
|
242
242
|
|
|
243
|
+
## Migration: 1.5.x → 1.6.0 (workspace adapter publish)
|
|
244
|
+
|
|
245
|
+
> Plan 3c Phase 9b shipped 2026-05-09. See root `CHANGELOG.md` `[1.6.0]`.
|
|
246
|
+
|
|
247
|
+
`1.6.0` is **additive** — end-users on `1.5.x` are unaffected. No
|
|
248
|
+
breaking changes. No config migration. The 5 first-party AST adapters
|
|
249
|
+
(`rails`, `phoenix`, `aspnet`, `spring`, `go-chi`) continue to ship
|
|
250
|
+
CORE-BUNDLED in `@massu/core` itself; zero-config detection still works
|
|
251
|
+
out of the box.
|
|
252
|
+
|
|
253
|
+
What's new for users who want REGISTRY-VERIFIED trust:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm install @massu/core@^1.6.0 @massu/adapter-rails@^1.0.0
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
After install, `npx massu adapters list` will show TWO entries for
|
|
260
|
+
`rails`:
|
|
261
|
+
|
|
262
|
+
- `rails` — CORE-BUNDLED (from `@massu/core`'s bundled `dist/detect/adapters/rails.js`).
|
|
263
|
+
- `@massu/adapter-rails` — REGISTRY-VERIFIED (from `node_modules/@massu/adapter-rails/dist/`,
|
|
264
|
+
sha256-cross-checked against the signed manifest at
|
|
265
|
+
`https://registry.massu.ai/adapters/manifest.json`).
|
|
266
|
+
|
|
267
|
+
The two co-exist. Discovery prefers REGISTRY-VERIFIED when present
|
|
268
|
+
(the standalone package opts the user into the more-verified path);
|
|
269
|
+
CORE-BUNDLED remains the fallback. There is no "elevation" — they are
|
|
270
|
+
two distinct trust-class entries.
|
|
271
|
+
|
|
272
|
+
### peerDependency note
|
|
273
|
+
|
|
274
|
+
`@massu/adapter-*@1.0.0` declares `peerDependencies: { "@massu/core": "^1.6.0" }`.
|
|
275
|
+
Users pinning `@massu/core@1.5.x` who install a standalone adapter will
|
|
276
|
+
see an npm peerDep warning (non-fatal). For cleanest UX, upgrade
|
|
277
|
+
`@massu/core` to `^1.6.0` before installing standalone adapters. The
|
|
278
|
+
adapter source is binary-identical between CORE-BUNDLED and
|
|
279
|
+
REGISTRY-VERIFIED — the warning is informational, not a runtime
|
|
280
|
+
incompatibility.
|
|
281
|
+
|
|
243
282
|
## See also
|
|
244
283
|
|
|
245
284
|
- [`AUTHORING-ADAPTERS.md`](./AUTHORING-ADAPTERS.md) — how to write a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 72 total), 55+ workflow commands, 11 agents, 20+ patterns",
|
|
6
6
|
"main": "src/server.ts",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"build:adapter-types": "tsc -p tsconfig.adapter-types.json",
|
|
23
23
|
"build:adapter-subpath": "tsx scripts/bundle-adapters.ts --subpath-only",
|
|
24
24
|
"build:bundle-adapters": "tsx scripts/bundle-adapters.ts",
|
|
25
|
-
"build:cli": "esbuild --bundle --platform=node --format=esm --outfile=dist/cli.js src/cli.ts --external:better-sqlite3 --external:yaml --external:zod --external:chokidar --external:proper-lockfile --external:fsevents --external:web-tree-sitter --external:tweetnacl --external:tar --external:smol-toml --external:vscode-languageserver-protocol --banner:js='#!/usr/bin/env node\nimport{createRequire as __cr}from\"module\";const require=__cr(import.meta.url);'",
|
|
26
|
-
"build:hooks": "esbuild --bundle --platform=node --format=esm --outdir=dist/hooks src/hooks/*.ts --external:better-sqlite3 --external:yaml --external:zod --external:chokidar --external:proper-lockfile --external:fsevents --external:web-tree-sitter --external:tweetnacl --external:tar --external:smol-toml --external:vscode-languageserver-protocol --banner:js='import{createRequire as __cr}from\"module\";const require=__cr(import.meta.url);'",
|
|
25
|
+
"build:cli": "esbuild --bundle --platform=node --format=esm --outfile=dist/cli.js src/cli.ts --external:better-sqlite3 --external:yaml --external:zod --external:chokidar --external:proper-lockfile --external:fsevents --external:web-tree-sitter --external:tweetnacl --external:tar --external:smol-toml --external:vscode-languageserver-protocol --external:@massu/adapter-rails --external:@massu/adapter-phoenix --external:@massu/adapter-aspnet --external:@massu/adapter-spring --external:@massu/adapter-go-chi --banner:js='#!/usr/bin/env node\nimport{createRequire as __cr}from\"module\";const require=__cr(import.meta.url);'",
|
|
26
|
+
"build:hooks": "esbuild --bundle --platform=node --format=esm --outdir=dist/hooks src/hooks/*.ts --external:better-sqlite3 --external:yaml --external:zod --external:chokidar --external:proper-lockfile --external:fsevents --external:web-tree-sitter --external:tweetnacl --external:tar --external:smol-toml --external:vscode-languageserver-protocol --external:@massu/adapter-rails --external:@massu/adapter-phoenix --external:@massu/adapter-aspnet --external:@massu/adapter-spring --external:@massu/adapter-go-chi --banner:js='import{createRequire as __cr}from\"module\";const require=__cr(import.meta.url);'",
|
|
27
27
|
"prepublishOnly": "bash ../../scripts/prepublish-check.sh && node ../../scripts/bundle-pubkey.mjs && npm run build",
|
|
28
28
|
"bench:watch": "tsx test/perf/watch-benchmark.ts"
|
|
29
29
|
},
|
package/src/db.ts
CHANGED
|
@@ -6,14 +6,37 @@ import { dirname, join } from 'path';
|
|
|
6
6
|
import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
7
7
|
import { getResolvedPaths } from './config.ts';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Thrown by `getCodeGraphDb()` when `.codegraph/codegraph.db` is missing.
|
|
11
|
+
*
|
|
12
|
+
* Caught at the JSON-RPC dispatch layer (server.ts) and translated to a
|
|
13
|
+
* structured `-32001` error response carrying a remedy hint and the resolved
|
|
14
|
+
* DB path. The thrown error is INTERNAL; user-facing copy lives in the
|
|
15
|
+
* dispatcher's error envelope.
|
|
16
|
+
*
|
|
17
|
+
* @see `docs/plans/2026-05-10-server-lazy-db-deps.md` P-C-001 + P-A-004
|
|
18
|
+
*/
|
|
19
|
+
export class CodegraphDbNotInitializedError extends Error {
|
|
20
|
+
readonly dbPath: string;
|
|
21
|
+
constructor(dbPath: string) {
|
|
22
|
+
super(`CodeGraph database not found at ${dbPath}`);
|
|
23
|
+
this.name = 'CodegraphDbNotInitializedError';
|
|
24
|
+
this.dbPath = dbPath;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
/**
|
|
10
29
|
* Connection to CodeGraph's read-only SQLite database.
|
|
11
30
|
* We NEVER write to this DB - it belongs to vanilla CodeGraph.
|
|
31
|
+
*
|
|
32
|
+
* Throws `CodegraphDbNotInitializedError` (internal signal) when the DB is
|
|
33
|
+
* missing. The MCP dispatcher catches and translates to a structured
|
|
34
|
+
* JSON-RPC error pointing at `npx @colbymchenry/codegraph init`.
|
|
12
35
|
*/
|
|
13
36
|
export function getCodeGraphDb(): Database.Database {
|
|
14
37
|
const dbPath = getResolvedPaths().codegraphDbPath;
|
|
15
38
|
if (!existsSync(dbPath)) {
|
|
16
|
-
throw new
|
|
39
|
+
throw new CodegraphDbNotInitializedError(dbPath);
|
|
17
40
|
}
|
|
18
41
|
const db = new Database(dbPath, { readonly: true });
|
|
19
42
|
db.pragma('journal_mode = WAL');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-
|
|
1
|
+
// AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-11T05:57:55.925Z.
|
|
2
2
|
// Source pem: packages/core/security/registry-pubkey.pem
|
|
3
3
|
// RAW-bytes sha256: 3b6226d036c472e533110d11a7d0cd2773ce1d7d4f1003517d5bd69c5418ed4c
|
|
4
4
|
// DO NOT EDIT — regenerate via `node scripts/bundle-pubkey.mjs` or
|
package/src/server.ts
CHANGED
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
import { readFileSync } from 'fs';
|
|
15
15
|
import { resolve, dirname } from 'path';
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
17
|
+
import type Database from 'better-sqlite3';
|
|
18
|
+
import { getCodeGraphDb, getDataDb, CodegraphDbNotInitializedError } from './db.ts';
|
|
19
|
+
import { getConfig, getResolvedPaths } from './config.ts';
|
|
19
20
|
import { getToolDefinitions, handleToolCall } from './tools.ts';
|
|
20
21
|
import { getMemoryDb, pruneOldConversationTurns, pruneOldObservations } from './memory-db.ts';
|
|
21
22
|
import { getCurrentTier } from './license.ts';
|
|
23
|
+
import { getToolDbNeeds, UnknownToolError, type DbNeed } from './tool-db-needs.ts';
|
|
22
24
|
|
|
23
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
26
|
const PKG_VERSION = (() => {
|
|
@@ -44,14 +46,53 @@ interface JsonRpcResponse {
|
|
|
44
46
|
error?: { code: number; message: string; data?: unknown };
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
// Server state
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
// === Server state: lazy per-tool DB resolution ===
|
|
50
|
+
//
|
|
51
|
+
// Per plan-1.6.2-server-lazy-db-deps: DBs are opened ONLY when the
|
|
52
|
+
// currently-dispatched tool declares it needs them in `TOOL_DB_NEEDS`.
|
|
53
|
+
// Connections are cached at module scope so subsequent tool calls reuse
|
|
54
|
+
// the open handle without re-opening (CodeGraph is read-only — safe to
|
|
55
|
+
// share; Data DB has WAL journal — single-writer is fine).
|
|
56
|
+
//
|
|
57
|
+
// PRIOR DESIGN (eliminated 2026-05-10): `getDb()` eagerly opened BOTH
|
|
58
|
+
// CodeGraph + Data on every `tools/call`, even for memory/audit/knowledge
|
|
59
|
+
// tools that don't need codegraph. Missing `.codegraph/codegraph.db`
|
|
60
|
+
// broke ALL tools. See `docs/plans/2026-05-10-server-lazy-db-deps.md`.
|
|
50
61
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
let codegraphDbCache: Database.Database | null = null;
|
|
63
|
+
let dataDbCache: Database.Database | null = null;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the SQLite connections a tool needs, opening cached singletons
|
|
67
|
+
* lazily. Memory DB and Knowledge DB are opened per-call by their routed
|
|
68
|
+
* handlers (existing pattern in tools.ts) — only CodeGraph + Data are
|
|
69
|
+
* cached here.
|
|
70
|
+
*
|
|
71
|
+
* @throws {CodegraphDbNotInitializedError} when tool needs codegraph but
|
|
72
|
+
* `.codegraph/codegraph.db` is missing. Caller (handleRequest) catches
|
|
73
|
+
* and translates to a structured `-32001` JSON-RPC error.
|
|
74
|
+
*/
|
|
75
|
+
function resolveDbsForTool(toolName: string): {
|
|
76
|
+
needs: readonly DbNeed[];
|
|
77
|
+
dataDb?: Database.Database;
|
|
78
|
+
codegraphDb?: Database.Database;
|
|
79
|
+
} {
|
|
80
|
+
const needs = getToolDbNeeds(toolName, getConfig().toolPrefix);
|
|
81
|
+
|
|
82
|
+
let dataDbResolved: Database.Database | undefined;
|
|
83
|
+
let codegraphDbResolved: Database.Database | undefined;
|
|
84
|
+
|
|
85
|
+
if (needs.includes('data')) {
|
|
86
|
+
if (!dataDbCache) dataDbCache = getDataDb();
|
|
87
|
+
dataDbResolved = dataDbCache;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (needs.includes('codegraph')) {
|
|
91
|
+
if (!codegraphDbCache) codegraphDbCache = getCodeGraphDb(); // throws CodegraphDbNotInitializedError on missing
|
|
92
|
+
codegraphDbResolved = codegraphDbCache;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { needs, dataDb: dataDbResolved, codegraphDb: codegraphDbResolved };
|
|
55
96
|
}
|
|
56
97
|
|
|
57
98
|
async function handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
|
|
@@ -93,14 +134,50 @@ async function handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse>
|
|
|
93
134
|
const toolName = (params as { name: string })?.name;
|
|
94
135
|
const toolArgs = (params as { arguments?: Record<string, unknown> })?.arguments ?? {};
|
|
95
136
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
137
|
+
// Lazy per-tool DB resolution. Throws if tool needs codegraph and
|
|
138
|
+
// .codegraph/codegraph.db is missing; caught below and translated
|
|
139
|
+
// to a structured -32001 error preserving the request id.
|
|
140
|
+
try {
|
|
141
|
+
const { dataDb: lDb, codegraphDb: cgDb } = resolveDbsForTool(toolName);
|
|
142
|
+
const result = await handleToolCall(toolName, toolArgs, lDb, cgDb);
|
|
143
|
+
return {
|
|
144
|
+
jsonrpc: '2.0',
|
|
145
|
+
id: id ?? null,
|
|
146
|
+
result,
|
|
147
|
+
};
|
|
148
|
+
} catch (err) {
|
|
149
|
+
if (err instanceof CodegraphDbNotInitializedError) {
|
|
150
|
+
return {
|
|
151
|
+
jsonrpc: '2.0',
|
|
152
|
+
id: id ?? null,
|
|
153
|
+
error: {
|
|
154
|
+
code: -32001,
|
|
155
|
+
message: `Tool requires CodeGraph database which is not initialized for this repo`,
|
|
156
|
+
data: {
|
|
157
|
+
remedy: 'npx @colbymchenry/codegraph@0.7.4 init . && npx @colbymchenry/codegraph@0.7.4 index .',
|
|
158
|
+
codegraphDbPath: err.dbPath,
|
|
159
|
+
tool: toolName,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (err instanceof UnknownToolError) {
|
|
165
|
+
return {
|
|
166
|
+
jsonrpc: '2.0',
|
|
167
|
+
id: id ?? null,
|
|
168
|
+
error: {
|
|
169
|
+
code: -32602,
|
|
170
|
+
message: `Unknown tool: ${err.toolName}`,
|
|
171
|
+
data: {
|
|
172
|
+
remedy: 'Tool not registered in TOOL_DB_NEEDS manifest. See packages/core/src/tool-db-needs.ts.',
|
|
173
|
+
tool: toolName,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// Other errors propagate to the outer catch in the stdio handler
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
104
181
|
}
|
|
105
182
|
|
|
106
183
|
case 'ping': {
|
|
@@ -172,22 +249,45 @@ process.stdin.on('data', async (chunk: string) => {
|
|
|
172
249
|
|
|
173
250
|
if (!line) continue;
|
|
174
251
|
|
|
252
|
+
// Two-phase error handling: separate JSON-parse failures (genuine
|
|
253
|
+
// -32700) from request-processing failures (-32603 Internal error,
|
|
254
|
+
// preserving the request id when parseable).
|
|
255
|
+
let request: JsonRpcRequest | null = null;
|
|
175
256
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
257
|
+
request = JSON.parse(line) as JsonRpcRequest;
|
|
258
|
+
} catch (parseError) {
|
|
259
|
+
// Real JSON parse failure — -32700 per JSON-RPC §5.1, id MUST be null
|
|
260
|
+
// because we couldn't extract one.
|
|
261
|
+
const errorResponse: JsonRpcResponse = {
|
|
262
|
+
jsonrpc: '2.0',
|
|
263
|
+
id: null,
|
|
264
|
+
error: {
|
|
265
|
+
code: -32700,
|
|
266
|
+
message: `Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
178
272
|
|
|
273
|
+
try {
|
|
274
|
+
const response = await handleRequest(request);
|
|
179
275
|
// Don't send responses for notifications (no id)
|
|
180
276
|
if (request.id !== undefined) {
|
|
181
277
|
const responseStr = JSON.stringify(response);
|
|
182
278
|
process.stdout.write(responseStr + '\n');
|
|
183
279
|
}
|
|
184
280
|
} catch (error) {
|
|
281
|
+
// Request-processing failure — propagate the request id (not null).
|
|
282
|
+
// -32603 Internal error per JSON-RPC §5.1. Specific subclasses
|
|
283
|
+
// (codegraph-not-init, unknown-tool) are caught earlier in the
|
|
284
|
+
// tools/call handler and translated to structured -32001/-32602.
|
|
185
285
|
const errorResponse: JsonRpcResponse = {
|
|
186
286
|
jsonrpc: '2.0',
|
|
187
|
-
id: null,
|
|
287
|
+
id: request.id ?? null,
|
|
188
288
|
error: {
|
|
189
|
-
code: -
|
|
190
|
-
message: `
|
|
289
|
+
code: -32603,
|
|
290
|
+
message: `Internal error: ${error instanceof Error ? error.message : String(error)}`,
|
|
191
291
|
},
|
|
192
292
|
};
|
|
193
293
|
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
|
@@ -196,9 +296,10 @@ process.stdin.on('data', async (chunk: string) => {
|
|
|
196
296
|
});
|
|
197
297
|
|
|
198
298
|
process.stdin.on('end', () => {
|
|
199
|
-
// Clean up
|
|
200
|
-
|
|
201
|
-
if (
|
|
299
|
+
// Clean up cached DB connections (Memory + Knowledge are per-call,
|
|
300
|
+
// already closed in their routing branches).
|
|
301
|
+
if (codegraphDbCache) codegraphDbCache.close();
|
|
302
|
+
if (dataDbCache) dataDbCache.close();
|
|
202
303
|
process.exit(0);
|
|
203
304
|
});
|
|
204
305
|
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
+
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Per-tool SQLite database dependency manifest.
|
|
6
|
+
*
|
|
7
|
+
* **Role**: SOLE source of truth declaring which SQLite connections each MCP
|
|
8
|
+
* tool needs. The dispatcher (`server.ts` → `tools.ts:handleToolCall`) reads
|
|
9
|
+
* this map to lazy-resolve connections, opening ONLY the DBs a tool requires.
|
|
10
|
+
*
|
|
11
|
+
* **Why this exists**:
|
|
12
|
+
* Before plan `plan-1.6.2-server-lazy-db-deps`, the dispatcher eagerly opened
|
|
13
|
+
* BOTH CodeGraph + Data DBs on every tool/call (see legacy `server.ts:51-55,96`
|
|
14
|
+
* and `tools.ts:279`). When `.codegraph/codegraph.db` was missing, EVERY tool
|
|
15
|
+
* call failed — even memory/audit/knowledge tools that have no codegraph
|
|
16
|
+
* dependency. This manifest makes that bug class structurally impossible:
|
|
17
|
+
* a missing peripheral DB only blocks the tools that need it.
|
|
18
|
+
*
|
|
19
|
+
* **Structural drift-prevention (3 layers)**:
|
|
20
|
+
* - L1: TypeScript compile time — exhaustiveness check via `keyof TOOL_DB_NEEDS`.
|
|
21
|
+
* - L2: `tool-db-needs-completeness.test.ts` — TypeScript AST walk of every
|
|
22
|
+
* tool module verifies declared needs match actual DB access pattern.
|
|
23
|
+
* Aliasing/destructuring renames cannot bypass the AST walk.
|
|
24
|
+
* - L3: `scripts/massu-pattern-scanner.sh` Check 14 — grep-level enforcement
|
|
25
|
+
* that every tool in `getToolDefinitions()` has a manifest entry.
|
|
26
|
+
*
|
|
27
|
+
* **Adding a new MCP tool**:
|
|
28
|
+
* 1. Register in `tools.ts` (CR-11).
|
|
29
|
+
* 2. Add an entry here. Missing entries throw `UnknownToolError` at first
|
|
30
|
+
* dispatch AND fail L2 + L3 above.
|
|
31
|
+
*
|
|
32
|
+
* @see `docs/plans/2026-05-10-server-lazy-db-deps.md` (`plan-1.6.2-server-lazy-db-deps`)
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** SQLite connections the MCP server can resolve for a tool call. */
|
|
36
|
+
export type DbNeed = 'codegraph' | 'data' | 'memory' | 'knowledge';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Custom error thrown when `getToolDbNeeds()` is called with a tool name that
|
|
40
|
+
* isn't in the manifest. Caught at the JSON-RPC layer and translated to a
|
|
41
|
+
* structured `-32602` (Invalid params) error to the client.
|
|
42
|
+
*/
|
|
43
|
+
export class UnknownToolError extends Error {
|
|
44
|
+
readonly toolName: string;
|
|
45
|
+
constructor(toolName: string) {
|
|
46
|
+
super(`Tool not registered in TOOL_DB_NEEDS manifest: ${toolName}. Add an entry to packages/core/src/tool-db-needs.ts.`);
|
|
47
|
+
this.name = 'UnknownToolError';
|
|
48
|
+
this.toolName = toolName;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Per-tool DB-need declarations. Keys are tool SHORT-NAMES (without the
|
|
54
|
+
* `${toolPrefix}_` prefix). Values are the SQLite connections the handler
|
|
55
|
+
* (or its routed module) actually accesses.
|
|
56
|
+
*
|
|
57
|
+
* Sourced from exhaustive grep of `packages/core/src/{*-tools,analytics,
|
|
58
|
+
* cost-tracker,prompt-analyzer,audit-trail,validation-engine,adr-generator,
|
|
59
|
+
* security-scorer,dependency-scorer,team-knowledge,regression-detector,
|
|
60
|
+
* python-tools,license}.ts` on 2026-05-10. Verified line citations in
|
|
61
|
+
* `docs/plans/2026-05-10-server-lazy-db-deps.md §1.4`.
|
|
62
|
+
*/
|
|
63
|
+
export const TOOL_DB_NEEDS = {
|
|
64
|
+
// === Core code-intel tools (tools.ts:393-406) ===
|
|
65
|
+
// Use CodeGraph DB (read-only AST) + Data DB (Massu's import/trpc/sentinel
|
|
66
|
+
// tables). All call `ensureIndexes` directly or via shared infrastructure.
|
|
67
|
+
sync: ['codegraph', 'data'],
|
|
68
|
+
context: ['codegraph', 'data'],
|
|
69
|
+
coupling_check: ['codegraph', 'data'],
|
|
70
|
+
impact: ['codegraph', 'data'],
|
|
71
|
+
domains: ['codegraph', 'data'],
|
|
72
|
+
|
|
73
|
+
// `trpc_map` reads only Data DB (tRPC index lives there); no CodeGraph access.
|
|
74
|
+
trpc_map: ['data'],
|
|
75
|
+
|
|
76
|
+
// `schema` reads filesystem (Prisma schema files); no DB access at all.
|
|
77
|
+
schema: [],
|
|
78
|
+
|
|
79
|
+
// === Memory tools (memory-tools.ts) ===
|
|
80
|
+
// Routed via `name.startsWith(pfx + '_memory_')` at tools.ts:284-290.
|
|
81
|
+
// Handler opens memory DB per-call (with try/finally close).
|
|
82
|
+
memory_search: ['memory'],
|
|
83
|
+
memory_timeline: ['memory'],
|
|
84
|
+
memory_detail: ['memory'],
|
|
85
|
+
memory_sessions: ['memory'],
|
|
86
|
+
memory_failures: ['memory'],
|
|
87
|
+
memory_ingest: ['memory'],
|
|
88
|
+
memory_backfill: ['memory'],
|
|
89
|
+
|
|
90
|
+
// === Observability tools (observability-tools.ts) ===
|
|
91
|
+
// Routed via `isObservabilityTool(name)` at tools.ts:294-300. Memory DB only.
|
|
92
|
+
session_replay: ['memory'],
|
|
93
|
+
session_stats: ['memory'],
|
|
94
|
+
tool_patterns: ['memory'],
|
|
95
|
+
prompt_analysis: ['memory'],
|
|
96
|
+
|
|
97
|
+
// === Docs tools (docs-tools.ts) ===
|
|
98
|
+
// Routed via `name.startsWith(pfx + '_docs_')` at tools.ts:303-306.
|
|
99
|
+
// No DB access — pure filesystem traversal.
|
|
100
|
+
docs_audit: [],
|
|
101
|
+
docs_coverage: [],
|
|
102
|
+
|
|
103
|
+
// === Sentinel registry tools (sentinel-tools.ts:180-184) ===
|
|
104
|
+
// Routed via `name.startsWith(pfx + '_sentinel_')` at tools.ts:308.
|
|
105
|
+
// Handler signature: `(name, args, dataDb)` — Data DB only.
|
|
106
|
+
sentinel_register: ['data'],
|
|
107
|
+
sentinel_validate: ['data'],
|
|
108
|
+
sentinel_search: ['data'],
|
|
109
|
+
sentinel_detail: ['data'],
|
|
110
|
+
sentinel_impact: ['data'],
|
|
111
|
+
sentinel_parity: ['data'],
|
|
112
|
+
|
|
113
|
+
// === Knowledge layer tools (knowledge-tools.ts) ===
|
|
114
|
+
// Routed via `isKnowledgeTool(name)` at tools.ts:372-376. Primary DB is
|
|
115
|
+
// `knowledgeDb` (separate SQLite file). Handlers ALSO call `getDataDb()`
|
|
116
|
+
// (knowledge-tools.ts:1187,1275) and `getMemoryDb()` (knowledge-tools.ts:1332)
|
|
117
|
+
// for cross-DB joins — declare all three so the AST completeness test
|
|
118
|
+
// (P-B-002) verifies the full access pattern.
|
|
119
|
+
knowledge_search: ['knowledge', 'data', 'memory'],
|
|
120
|
+
knowledge_pattern: ['knowledge', 'data', 'memory'],
|
|
121
|
+
knowledge_rule: ['knowledge', 'data', 'memory'],
|
|
122
|
+
knowledge_correct: ['knowledge', 'data', 'memory'],
|
|
123
|
+
knowledge_incident: ['knowledge', 'data', 'memory'],
|
|
124
|
+
knowledge_plan: ['knowledge', 'data', 'memory'],
|
|
125
|
+
knowledge_command: ['knowledge', 'data', 'memory'],
|
|
126
|
+
knowledge_gaps: ['knowledge', 'data', 'memory'],
|
|
127
|
+
knowledge_verification: ['knowledge', 'data', 'memory'],
|
|
128
|
+
knowledge_effectiveness: ['knowledge', 'data', 'memory'],
|
|
129
|
+
knowledge_graph: ['knowledge', 'data', 'memory'],
|
|
130
|
+
knowledge_schema_check: ['knowledge', 'data', 'memory'],
|
|
131
|
+
|
|
132
|
+
// === Analytics / quality (analytics.ts) ===
|
|
133
|
+
// Routed via `isAnalyticsTool(name)`. Memory DB only.
|
|
134
|
+
quality_score: ['memory'],
|
|
135
|
+
quality_report: ['memory'],
|
|
136
|
+
quality_trend: ['memory'],
|
|
137
|
+
|
|
138
|
+
// === Cost tracker (cost-tracker.ts) ===
|
|
139
|
+
cost_session: ['memory'],
|
|
140
|
+
cost_feature: ['memory'],
|
|
141
|
+
cost_trend: ['memory'],
|
|
142
|
+
|
|
143
|
+
// === Prompt analyzer (prompt-analyzer.ts) ===
|
|
144
|
+
prompt_effectiveness: ['memory'],
|
|
145
|
+
prompt_suggestions: ['memory'],
|
|
146
|
+
|
|
147
|
+
// === Audit trail (audit-trail.ts) ===
|
|
148
|
+
audit_chain: ['memory'],
|
|
149
|
+
audit_log: ['memory'],
|
|
150
|
+
audit_report: ['memory'],
|
|
151
|
+
|
|
152
|
+
// === Validation engine (validation-engine.ts) ===
|
|
153
|
+
validation_check: ['memory'],
|
|
154
|
+
validation_report: ['memory'],
|
|
155
|
+
|
|
156
|
+
// === ADR generator (adr-generator.ts) ===
|
|
157
|
+
adr_create: ['memory'],
|
|
158
|
+
adr_list: ['memory'],
|
|
159
|
+
adr_detail: ['memory'],
|
|
160
|
+
|
|
161
|
+
// === Security scorer (security-scorer.ts) ===
|
|
162
|
+
security_score: ['memory'],
|
|
163
|
+
security_heatmap: ['memory'],
|
|
164
|
+
security_trend: ['memory'],
|
|
165
|
+
|
|
166
|
+
// === Dependency scorer (dependency-scorer.ts) ===
|
|
167
|
+
dep_score: ['memory'],
|
|
168
|
+
dep_alternatives: ['memory'],
|
|
169
|
+
|
|
170
|
+
// === Team knowledge (team-knowledge.ts) ===
|
|
171
|
+
team_expertise: ['memory'],
|
|
172
|
+
team_conflicts: ['memory'],
|
|
173
|
+
team_search: ['memory'],
|
|
174
|
+
|
|
175
|
+
// === Regression detector (regression-detector.ts) ===
|
|
176
|
+
regression_risk: ['memory'],
|
|
177
|
+
feature_health: ['memory'],
|
|
178
|
+
|
|
179
|
+
// === Python code-intel tools (python-tools.ts) ===
|
|
180
|
+
// Routed via `isPythonTool(name)` at tools.ts:379-381. Data DB only.
|
|
181
|
+
py_imports: ['data'],
|
|
182
|
+
py_routes: ['data'],
|
|
183
|
+
py_models: ['data'],
|
|
184
|
+
py_migrations: ['data'],
|
|
185
|
+
py_coupling: ['data'],
|
|
186
|
+
py_context: ['data'],
|
|
187
|
+
py_impact: ['data'],
|
|
188
|
+
py_domains: ['data'],
|
|
189
|
+
|
|
190
|
+
// === License tool (license.ts) ===
|
|
191
|
+
license_status: ['memory'],
|
|
192
|
+
} as const satisfies Readonly<Record<string, readonly DbNeed[]>>;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Configured tool-prefix-stripping helper. Pulled from the runtime config
|
|
196
|
+
* so this module stays project-prefix-agnostic.
|
|
197
|
+
*/
|
|
198
|
+
function stripConfiguredPrefix(toolName: string, prefix: string): string {
|
|
199
|
+
const pfx = `${prefix}_`;
|
|
200
|
+
return toolName.startsWith(pfx) ? toolName.slice(pfx.length) : toolName;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Look up the DB needs for a tool by its full name (with prefix). Strips the
|
|
205
|
+
* configured prefix and consults `TOOL_DB_NEEDS`. Throws `UnknownToolError`
|
|
206
|
+
* for tool names not in the manifest — the dispatcher MUST catch this and
|
|
207
|
+
* translate to a structured JSON-RPC error.
|
|
208
|
+
*
|
|
209
|
+
* @param toolName Full tool name including prefix (e.g., `"massu_memory_search"`)
|
|
210
|
+
* @param prefix Tool prefix (e.g., `"massu"`) — read from config at dispatch time
|
|
211
|
+
* @returns Array of DB connections the tool requires (may be empty)
|
|
212
|
+
* @throws {UnknownToolError} if `stripPrefix(toolName)` not in the manifest
|
|
213
|
+
*/
|
|
214
|
+
export function getToolDbNeeds(toolName: string, prefix: string): readonly DbNeed[] {
|
|
215
|
+
const shortName = stripConfiguredPrefix(toolName, prefix);
|
|
216
|
+
const needs = (TOOL_DB_NEEDS as Record<string, readonly DbNeed[]>)[shortName];
|
|
217
|
+
if (needs === undefined) {
|
|
218
|
+
throw new UnknownToolError(toolName);
|
|
219
|
+
}
|
|
220
|
+
return needs;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Convenience predicate: does a tool need CodeGraph DB? */
|
|
224
|
+
export function toolNeedsCodegraph(toolName: string, prefix: string): boolean {
|
|
225
|
+
return getToolDbNeeds(toolName, prefix).includes('codegraph');
|
|
226
|
+
}
|