@massu/core 1.6.2 → 1.7.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/dist/cli.js +184 -142
- package/dist/hooks/session-start.js +28 -8
- package/package.json +1 -1
- package/src/detect/domain-inferrer.ts +67 -12
- package/src/license.ts +20 -0
- package/src/security/registry-pubkey.generated.ts +1 -1
- package/src/server-dispatch.ts +225 -0
- package/src/server.ts +9 -215
- package/src/tools.ts +6 -3
package/dist/cli.js
CHANGED
|
@@ -9542,14 +9542,33 @@ function domainFromWorkspace(pkg) {
|
|
|
9542
9542
|
allowedImportsFrom: []
|
|
9543
9543
|
};
|
|
9544
9544
|
}
|
|
9545
|
-
function topLevelSrcSubdirs(root) {
|
|
9546
|
-
const
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9545
|
+
function topLevelSrcSubdirs(root, sourceDirs) {
|
|
9546
|
+
const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
|
|
9547
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9548
|
+
for (const rel of effective) {
|
|
9549
|
+
const abs = join5(root, rel);
|
|
9550
|
+
if (!existsSync7(abs)) continue;
|
|
9551
|
+
try {
|
|
9552
|
+
for (const e2 of readdirSync5(abs, { withFileTypes: true })) {
|
|
9553
|
+
if (!e2.isDirectory()) continue;
|
|
9554
|
+
if (IGNORED_SUBDIRS.has(e2.name)) continue;
|
|
9555
|
+
seen.add(e2.name);
|
|
9556
|
+
}
|
|
9557
|
+
} catch {
|
|
9558
|
+
}
|
|
9559
|
+
}
|
|
9560
|
+
return Array.from(seen).sort();
|
|
9561
|
+
}
|
|
9562
|
+
function flattenSourceDirs(sourceDirs) {
|
|
9563
|
+
const flat = /* @__PURE__ */ new Set();
|
|
9564
|
+
for (const entry of Object.values(sourceDirs)) {
|
|
9565
|
+
if (!entry) continue;
|
|
9566
|
+
for (const dir of entry.source_dirs) {
|
|
9567
|
+
if (dir === "." || dir === "") continue;
|
|
9568
|
+
flat.add(dir);
|
|
9569
|
+
}
|
|
9552
9570
|
}
|
|
9571
|
+
return Array.from(flat);
|
|
9553
9572
|
}
|
|
9554
9573
|
function inferDomains(projectRoot, monorepo, sourceDirs) {
|
|
9555
9574
|
const domains = [];
|
|
@@ -9558,7 +9577,8 @@ function inferDomains(projectRoot, monorepo, sourceDirs) {
|
|
|
9558
9577
|
domains.push(domainFromWorkspace(pkg));
|
|
9559
9578
|
}
|
|
9560
9579
|
} else {
|
|
9561
|
-
const
|
|
9580
|
+
const flat = flattenSourceDirs(sourceDirs);
|
|
9581
|
+
const subdirs = topLevelSrcSubdirs(projectRoot, flat);
|
|
9562
9582
|
for (const s of subdirs) {
|
|
9563
9583
|
domains.push({
|
|
9564
9584
|
name: titleCase(s),
|
|
@@ -13780,6 +13800,9 @@ function annotateToolDefinitions(defs) {
|
|
|
13780
13800
|
};
|
|
13781
13801
|
});
|
|
13782
13802
|
}
|
|
13803
|
+
function isCloudFeatureAvailable() {
|
|
13804
|
+
return getConfig().cloud?.enabled === true;
|
|
13805
|
+
}
|
|
13783
13806
|
async function validateLicense(apiKey) {
|
|
13784
13807
|
const keyHash = createHash4("sha256").update(apiKey).digest("hex");
|
|
13785
13808
|
const memDb = getMemoryDb();
|
|
@@ -26723,7 +26746,8 @@ function getToolDefinitions() {
|
|
|
26723
26746
|
...getSecurityToolDefinitions(),
|
|
26724
26747
|
...getDependencyToolDefinitions(),
|
|
26725
26748
|
// Enterprise layer (team knowledge, regression detection)
|
|
26726
|
-
|
|
26749
|
+
// P-A-003: team tools are cloud-gated — only listed when cloud.enabled is true.
|
|
26750
|
+
...isCloudFeatureAvailable() ? getTeamToolDefinitions() : [],
|
|
26727
26751
|
...getRegressionToolDefinitions(),
|
|
26728
26752
|
// Knowledge layer (indexed .claude/ knowledge — rules, patterns, incidents)
|
|
26729
26753
|
...getKnowledgeToolDefinitions(),
|
|
@@ -26927,7 +26951,7 @@ async function handleToolCall(name, args2, dataDb, codegraphDb) {
|
|
|
26927
26951
|
memDb.close();
|
|
26928
26952
|
}
|
|
26929
26953
|
}
|
|
26930
|
-
if (isTeamTool(name)) {
|
|
26954
|
+
if (isTeamTool(name) && isCloudFeatureAvailable()) {
|
|
26931
26955
|
const memDb = getMemoryDb();
|
|
26932
26956
|
try {
|
|
26933
26957
|
return handleTeamToolCall(name, args2, memDb);
|
|
@@ -27759,111 +27783,163 @@ var init_tool_db_needs = __esm({
|
|
|
27759
27783
|
}
|
|
27760
27784
|
});
|
|
27761
27785
|
|
|
27762
|
-
// src/server.ts
|
|
27763
|
-
|
|
27764
|
-
|
|
27765
|
-
|
|
27766
|
-
|
|
27767
|
-
|
|
27768
|
-
|
|
27769
|
-
|
|
27770
|
-
|
|
27771
|
-
|
|
27772
|
-
|
|
27773
|
-
|
|
27774
|
-
|
|
27775
|
-
|
|
27776
|
-
|
|
27777
|
-
|
|
27778
|
-
|
|
27779
|
-
|
|
27780
|
-
|
|
27781
|
-
|
|
27782
|
-
|
|
27783
|
-
|
|
27784
|
-
case "initialize": {
|
|
27785
|
-
return {
|
|
27786
|
-
jsonrpc: "2.0",
|
|
27787
|
-
id: id ?? null,
|
|
27788
|
-
result: {
|
|
27789
|
-
protocolVersion: "2024-11-05",
|
|
27790
|
-
capabilities: {
|
|
27791
|
-
tools: {}
|
|
27792
|
-
},
|
|
27793
|
-
serverInfo: {
|
|
27794
|
-
name: getConfig().toolPrefix || "massu",
|
|
27795
|
-
version: PKG_VERSION
|
|
27796
|
-
}
|
|
27797
|
-
}
|
|
27798
|
-
};
|
|
27799
|
-
}
|
|
27800
|
-
case "notifications/initialized": {
|
|
27801
|
-
return { jsonrpc: "2.0", id: id ?? null, result: {} };
|
|
27802
|
-
}
|
|
27803
|
-
case "tools/list": {
|
|
27804
|
-
const tools = getToolDefinitions();
|
|
27805
|
-
return {
|
|
27806
|
-
jsonrpc: "2.0",
|
|
27807
|
-
id: id ?? null,
|
|
27808
|
-
result: { tools }
|
|
27809
|
-
};
|
|
27810
|
-
}
|
|
27811
|
-
case "tools/call": {
|
|
27812
|
-
const toolName = params?.name;
|
|
27813
|
-
const toolArgs = params?.arguments ?? {};
|
|
27814
|
-
try {
|
|
27815
|
-
const { dataDb: lDb, codegraphDb: cgDb } = resolveDbsForTool(toolName);
|
|
27816
|
-
const result = await handleToolCall(toolName, toolArgs, lDb, cgDb);
|
|
27786
|
+
// src/server-dispatch.ts
|
|
27787
|
+
function createDispatcher(options) {
|
|
27788
|
+
let codegraphDbCache = null;
|
|
27789
|
+
let dataDbCache = null;
|
|
27790
|
+
function resolveDbsForTool(toolName) {
|
|
27791
|
+
const needs = getToolDbNeeds(toolName, getConfig().toolPrefix);
|
|
27792
|
+
let dataDbResolved;
|
|
27793
|
+
let codegraphDbResolved;
|
|
27794
|
+
if (needs.includes("data")) {
|
|
27795
|
+
if (!dataDbCache) dataDbCache = getDataDb();
|
|
27796
|
+
dataDbResolved = dataDbCache;
|
|
27797
|
+
}
|
|
27798
|
+
if (needs.includes("codegraph")) {
|
|
27799
|
+
if (!codegraphDbCache) codegraphDbCache = getCodeGraphDb();
|
|
27800
|
+
codegraphDbResolved = codegraphDbCache;
|
|
27801
|
+
}
|
|
27802
|
+
return { needs, dataDb: dataDbResolved, codegraphDb: codegraphDbResolved };
|
|
27803
|
+
}
|
|
27804
|
+
async function handleRequest(request) {
|
|
27805
|
+
const { method, params, id } = request;
|
|
27806
|
+
switch (method) {
|
|
27807
|
+
case "initialize": {
|
|
27817
27808
|
return {
|
|
27818
27809
|
jsonrpc: "2.0",
|
|
27819
27810
|
id: id ?? null,
|
|
27820
|
-
result
|
|
27811
|
+
result: {
|
|
27812
|
+
protocolVersion: "2024-11-05",
|
|
27813
|
+
capabilities: { tools: {} },
|
|
27814
|
+
serverInfo: {
|
|
27815
|
+
name: getConfig().toolPrefix || "massu",
|
|
27816
|
+
version: options.serverInfoVersion
|
|
27817
|
+
}
|
|
27818
|
+
}
|
|
27821
27819
|
};
|
|
27822
|
-
}
|
|
27823
|
-
|
|
27824
|
-
|
|
27825
|
-
|
|
27826
|
-
|
|
27827
|
-
|
|
27828
|
-
|
|
27829
|
-
|
|
27830
|
-
|
|
27831
|
-
|
|
27832
|
-
|
|
27833
|
-
|
|
27820
|
+
}
|
|
27821
|
+
case "notifications/initialized": {
|
|
27822
|
+
return { jsonrpc: "2.0", id: id ?? null, result: {} };
|
|
27823
|
+
}
|
|
27824
|
+
case "tools/list": {
|
|
27825
|
+
const tools = getToolDefinitions();
|
|
27826
|
+
return { jsonrpc: "2.0", id: id ?? null, result: { tools } };
|
|
27827
|
+
}
|
|
27828
|
+
case "tools/call": {
|
|
27829
|
+
const toolName = params?.name ?? "";
|
|
27830
|
+
const toolArgs = params?.arguments ?? {};
|
|
27831
|
+
try {
|
|
27832
|
+
const { dataDb, codegraphDb } = resolveDbsForTool(toolName);
|
|
27833
|
+
const result = await handleToolCall(toolName, toolArgs, dataDb, codegraphDb);
|
|
27834
|
+
return { jsonrpc: "2.0", id: id ?? null, result };
|
|
27835
|
+
} catch (err) {
|
|
27836
|
+
if (err instanceof CodegraphDbNotInitializedError) {
|
|
27837
|
+
return {
|
|
27838
|
+
jsonrpc: "2.0",
|
|
27839
|
+
id: id ?? null,
|
|
27840
|
+
error: {
|
|
27841
|
+
code: -32001,
|
|
27842
|
+
message: "Tool requires CodeGraph database which is not initialized for this repo",
|
|
27843
|
+
data: {
|
|
27844
|
+
remedy: "npx @colbymchenry/codegraph@0.7.4 init . && npx @colbymchenry/codegraph@0.7.4 index .",
|
|
27845
|
+
codegraphDbPath: err.dbPath,
|
|
27846
|
+
tool: toolName
|
|
27847
|
+
}
|
|
27834
27848
|
}
|
|
27835
|
-
}
|
|
27836
|
-
}
|
|
27837
|
-
|
|
27838
|
-
|
|
27839
|
-
|
|
27840
|
-
|
|
27841
|
-
|
|
27842
|
-
|
|
27843
|
-
|
|
27844
|
-
|
|
27845
|
-
|
|
27846
|
-
|
|
27847
|
-
|
|
27849
|
+
};
|
|
27850
|
+
}
|
|
27851
|
+
if (err instanceof UnknownToolError) {
|
|
27852
|
+
return {
|
|
27853
|
+
jsonrpc: "2.0",
|
|
27854
|
+
id: id ?? null,
|
|
27855
|
+
error: {
|
|
27856
|
+
code: -32602,
|
|
27857
|
+
message: `Unknown tool: ${err.toolName}`,
|
|
27858
|
+
data: {
|
|
27859
|
+
remedy: "Tool not registered in TOOL_DB_NEEDS manifest. See packages/core/src/tool-db-needs.ts.",
|
|
27860
|
+
tool: toolName
|
|
27861
|
+
}
|
|
27848
27862
|
}
|
|
27849
|
-
}
|
|
27850
|
-
}
|
|
27863
|
+
};
|
|
27864
|
+
}
|
|
27865
|
+
throw err;
|
|
27851
27866
|
}
|
|
27852
|
-
|
|
27867
|
+
}
|
|
27868
|
+
case "ping": {
|
|
27869
|
+
return { jsonrpc: "2.0", id: id ?? null, result: {} };
|
|
27870
|
+
}
|
|
27871
|
+
default: {
|
|
27872
|
+
return {
|
|
27873
|
+
jsonrpc: "2.0",
|
|
27874
|
+
id: id ?? null,
|
|
27875
|
+
error: { code: -32601, message: `Method not found: ${method}` }
|
|
27876
|
+
};
|
|
27853
27877
|
}
|
|
27854
27878
|
}
|
|
27855
|
-
|
|
27856
|
-
|
|
27879
|
+
}
|
|
27880
|
+
async function processLine(line) {
|
|
27881
|
+
const trimmed = line.trim();
|
|
27882
|
+
if (!trimmed) return null;
|
|
27883
|
+
let request;
|
|
27884
|
+
try {
|
|
27885
|
+
request = JSON.parse(trimmed);
|
|
27886
|
+
} catch (parseError) {
|
|
27887
|
+
return {
|
|
27888
|
+
response: {
|
|
27889
|
+
jsonrpc: "2.0",
|
|
27890
|
+
id: null,
|
|
27891
|
+
error: {
|
|
27892
|
+
code: -32700,
|
|
27893
|
+
message: `Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`
|
|
27894
|
+
}
|
|
27895
|
+
},
|
|
27896
|
+
emit: true
|
|
27897
|
+
};
|
|
27857
27898
|
}
|
|
27858
|
-
|
|
27899
|
+
try {
|
|
27900
|
+
const response = await handleRequest(request);
|
|
27901
|
+
return { response, emit: request.id !== void 0 };
|
|
27902
|
+
} catch (error) {
|
|
27859
27903
|
return {
|
|
27860
|
-
|
|
27861
|
-
|
|
27862
|
-
|
|
27904
|
+
response: {
|
|
27905
|
+
jsonrpc: "2.0",
|
|
27906
|
+
id: request.id ?? null,
|
|
27907
|
+
error: {
|
|
27908
|
+
code: -32603,
|
|
27909
|
+
message: `Internal error: ${error instanceof Error ? error.message : String(error)}`
|
|
27910
|
+
}
|
|
27911
|
+
},
|
|
27912
|
+
emit: true
|
|
27863
27913
|
};
|
|
27864
27914
|
}
|
|
27865
27915
|
}
|
|
27916
|
+
function closeCachedDbs() {
|
|
27917
|
+
if (codegraphDbCache) {
|
|
27918
|
+
codegraphDbCache.close();
|
|
27919
|
+
codegraphDbCache = null;
|
|
27920
|
+
}
|
|
27921
|
+
if (dataDbCache) {
|
|
27922
|
+
dataDbCache.close();
|
|
27923
|
+
dataDbCache = null;
|
|
27924
|
+
}
|
|
27925
|
+
}
|
|
27926
|
+
return { handleRequest, processLine, closeCachedDbs };
|
|
27866
27927
|
}
|
|
27928
|
+
var init_server_dispatch = __esm({
|
|
27929
|
+
"src/server-dispatch.ts"() {
|
|
27930
|
+
"use strict";
|
|
27931
|
+
init_db();
|
|
27932
|
+
init_config();
|
|
27933
|
+
init_tools();
|
|
27934
|
+
init_tool_db_needs();
|
|
27935
|
+
}
|
|
27936
|
+
});
|
|
27937
|
+
|
|
27938
|
+
// src/server.ts
|
|
27939
|
+
var server_exports = {};
|
|
27940
|
+
import { readFileSync as readFileSync39 } from "fs";
|
|
27941
|
+
import { resolve as resolve32, dirname as dirname16 } from "path";
|
|
27942
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
27867
27943
|
function pruneMemoryOnStartup() {
|
|
27868
27944
|
try {
|
|
27869
27945
|
const memDb = getMemoryDb();
|
|
@@ -27887,16 +27963,13 @@ function pruneMemoryOnStartup() {
|
|
|
27887
27963
|
);
|
|
27888
27964
|
}
|
|
27889
27965
|
}
|
|
27890
|
-
var __dirname4, PKG_VERSION,
|
|
27966
|
+
var __dirname4, PKG_VERSION, dispatcher, buffer;
|
|
27891
27967
|
var init_server = __esm({
|
|
27892
27968
|
"src/server.ts"() {
|
|
27893
27969
|
"use strict";
|
|
27894
|
-
init_db();
|
|
27895
|
-
init_config();
|
|
27896
|
-
init_tools();
|
|
27897
27970
|
init_memory_db();
|
|
27898
27971
|
init_license();
|
|
27899
|
-
|
|
27972
|
+
init_server_dispatch();
|
|
27900
27973
|
__dirname4 = dirname16(fileURLToPath4(import.meta.url));
|
|
27901
27974
|
PKG_VERSION = (() => {
|
|
27902
27975
|
try {
|
|
@@ -27906,8 +27979,7 @@ var init_server = __esm({
|
|
|
27906
27979
|
return "0.0.0";
|
|
27907
27980
|
}
|
|
27908
27981
|
})();
|
|
27909
|
-
|
|
27910
|
-
dataDbCache = null;
|
|
27982
|
+
dispatcher = createDispatcher({ serverInfoVersion: PKG_VERSION });
|
|
27911
27983
|
pruneMemoryOnStartup();
|
|
27912
27984
|
getCurrentTier().then((tier) => {
|
|
27913
27985
|
process.stderr.write(`massu: License tier: ${tier}
|
|
@@ -27924,46 +27996,16 @@ var init_server = __esm({
|
|
|
27924
27996
|
buffer += chunk;
|
|
27925
27997
|
let newlineIndex;
|
|
27926
27998
|
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
27927
|
-
const line = buffer.slice(0, newlineIndex)
|
|
27999
|
+
const line = buffer.slice(0, newlineIndex);
|
|
27928
28000
|
buffer = buffer.slice(newlineIndex + 1);
|
|
27929
|
-
|
|
27930
|
-
|
|
27931
|
-
|
|
27932
|
-
request = JSON.parse(line);
|
|
27933
|
-
} catch (parseError) {
|
|
27934
|
-
const errorResponse = {
|
|
27935
|
-
jsonrpc: "2.0",
|
|
27936
|
-
id: null,
|
|
27937
|
-
error: {
|
|
27938
|
-
code: -32700,
|
|
27939
|
-
message: `Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`
|
|
27940
|
-
}
|
|
27941
|
-
};
|
|
27942
|
-
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
27943
|
-
continue;
|
|
27944
|
-
}
|
|
27945
|
-
try {
|
|
27946
|
-
const response = await handleRequest(request);
|
|
27947
|
-
if (request.id !== void 0) {
|
|
27948
|
-
const responseStr = JSON.stringify(response);
|
|
27949
|
-
process.stdout.write(responseStr + "\n");
|
|
27950
|
-
}
|
|
27951
|
-
} catch (error) {
|
|
27952
|
-
const errorResponse = {
|
|
27953
|
-
jsonrpc: "2.0",
|
|
27954
|
-
id: request.id ?? null,
|
|
27955
|
-
error: {
|
|
27956
|
-
code: -32603,
|
|
27957
|
-
message: `Internal error: ${error instanceof Error ? error.message : String(error)}`
|
|
27958
|
-
}
|
|
27959
|
-
};
|
|
27960
|
-
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
28001
|
+
const result = await dispatcher.processLine(line);
|
|
28002
|
+
if (result && result.emit) {
|
|
28003
|
+
process.stdout.write(JSON.stringify(result.response) + "\n");
|
|
27961
28004
|
}
|
|
27962
28005
|
}
|
|
27963
28006
|
});
|
|
27964
28007
|
process.stdin.on("end", () => {
|
|
27965
|
-
|
|
27966
|
-
if (dataDbCache) dataDbCache.close();
|
|
28008
|
+
dispatcher.closeCachedDbs();
|
|
27967
28009
|
process.exit(0);
|
|
27968
28010
|
});
|
|
27969
28011
|
process.on("uncaughtException", (error) => {
|
|
@@ -8360,14 +8360,33 @@ function domainFromWorkspace(pkg) {
|
|
|
8360
8360
|
allowedImportsFrom: []
|
|
8361
8361
|
};
|
|
8362
8362
|
}
|
|
8363
|
-
function topLevelSrcSubdirs(root) {
|
|
8364
|
-
const
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8363
|
+
function topLevelSrcSubdirs(root, sourceDirs) {
|
|
8364
|
+
const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
|
|
8365
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8366
|
+
for (const rel of effective) {
|
|
8367
|
+
const abs = join3(root, rel);
|
|
8368
|
+
if (!existsSync5(abs)) continue;
|
|
8369
|
+
try {
|
|
8370
|
+
for (const e of readdirSync3(abs, { withFileTypes: true })) {
|
|
8371
|
+
if (!e.isDirectory()) continue;
|
|
8372
|
+
if (IGNORED_SUBDIRS.has(e.name)) continue;
|
|
8373
|
+
seen.add(e.name);
|
|
8374
|
+
}
|
|
8375
|
+
} catch {
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8378
|
+
return Array.from(seen).sort();
|
|
8379
|
+
}
|
|
8380
|
+
function flattenSourceDirs(sourceDirs) {
|
|
8381
|
+
const flat = /* @__PURE__ */ new Set();
|
|
8382
|
+
for (const entry of Object.values(sourceDirs)) {
|
|
8383
|
+
if (!entry) continue;
|
|
8384
|
+
for (const dir of entry.source_dirs) {
|
|
8385
|
+
if (dir === "." || dir === "") continue;
|
|
8386
|
+
flat.add(dir);
|
|
8387
|
+
}
|
|
8370
8388
|
}
|
|
8389
|
+
return Array.from(flat);
|
|
8371
8390
|
}
|
|
8372
8391
|
function inferDomains(projectRoot, monorepo, sourceDirs) {
|
|
8373
8392
|
const domains = [];
|
|
@@ -8376,7 +8395,8 @@ function inferDomains(projectRoot, monorepo, sourceDirs) {
|
|
|
8376
8395
|
domains.push(domainFromWorkspace(pkg));
|
|
8377
8396
|
}
|
|
8378
8397
|
} else {
|
|
8379
|
-
const
|
|
8398
|
+
const flat = flattenSourceDirs(sourceDirs);
|
|
8399
|
+
const subdirs = topLevelSrcSubdirs(projectRoot, flat);
|
|
8380
8400
|
for (const s of subdirs) {
|
|
8381
8401
|
domains.push({
|
|
8382
8402
|
name: titleCase(s),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
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",
|
|
@@ -67,17 +67,69 @@ function domainFromWorkspace(pkg: WorkspacePackage): DomainConfig {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Enumerate domain candidates under each detected source directory.
|
|
72
|
+
*
|
|
73
|
+
* The `sourceDirs` argument is the flattened, unique list of relative
|
|
74
|
+
* source paths produced upstream by the source-dir detector
|
|
75
|
+
* (`detectSourceDirs` in `source-dir-detector.ts`). For each path that
|
|
76
|
+
* exists under `root`, this function lists immediate subdirectories as
|
|
77
|
+
* candidate domain names. Hardcoded `src/` lookup was removed (plan
|
|
78
|
+
* `plan-1.7.0-cohesive-cleanup` P-B-002) — the function now consumes
|
|
79
|
+
* the detection pipeline's output verbatim, so projects whose source
|
|
80
|
+
* lives at non-`src/` paths (e.g. `lib/`, `apps/<x>/src/`) are no
|
|
81
|
+
* longer silently dropped.
|
|
82
|
+
*
|
|
83
|
+
* Empty `sourceDirs` is treated as a legacy single-repo `src/` lookup
|
|
84
|
+
* to preserve behavior for callers that pre-date the source-dir
|
|
85
|
+
* pipeline (CLI / test harnesses that hand-wire `inferDomains`).
|
|
86
|
+
*
|
|
87
|
+
* Returns deduplicated subdir names sorted alphabetically.
|
|
88
|
+
*/
|
|
89
|
+
function topLevelSrcSubdirs(root: string, sourceDirs: readonly string[]): string[] {
|
|
90
|
+
const effective = sourceDirs.length > 0 ? sourceDirs : ['src'];
|
|
91
|
+
const seen = new Set<string>();
|
|
92
|
+
for (const rel of effective) {
|
|
93
|
+
const abs = join(root, rel);
|
|
94
|
+
if (!existsSync(abs)) continue;
|
|
95
|
+
try {
|
|
96
|
+
for (const e of readdirSync(abs, { withFileTypes: true })) {
|
|
97
|
+
if (!e.isDirectory()) continue;
|
|
98
|
+
if (IGNORED_SUBDIRS.has(e.name)) continue;
|
|
99
|
+
seen.add(e.name);
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// skip directories that cannot be read; do not throw.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return Array.from(seen).sort();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Flatten a `SourceDirMap` into a unique, deduplicated list of relative
|
|
110
|
+
* source paths across all detected languages.
|
|
111
|
+
*
|
|
112
|
+
* Drops the root sentinels `.` and `''` — those are emitted by the
|
|
113
|
+
* source-dir-detector when source files live directly at the project
|
|
114
|
+
* root (e.g. Django's `manage.py` or Swift's `Package.swift`). Treating
|
|
115
|
+
* them as enumerable source dirs causes spurious top-level directory
|
|
116
|
+
* inclusion (Tests/, Sources/, etc.), which collides with the
|
|
117
|
+
* language-fallback path in `inferDomains`. Root-source repos rely on
|
|
118
|
+
* the language-fallback path to emit `{Python}` / `{Swift}` domains,
|
|
119
|
+
* NOT a fan-out of every root subdirectory.
|
|
120
|
+
*
|
|
121
|
+
* Order is not guaranteed — callers that need determinism must sort.
|
|
122
|
+
*/
|
|
123
|
+
function flattenSourceDirs(sourceDirs: SourceDirMap): string[] {
|
|
124
|
+
const flat = new Set<string>();
|
|
125
|
+
for (const entry of Object.values(sourceDirs)) {
|
|
126
|
+
if (!entry) continue;
|
|
127
|
+
for (const dir of entry.source_dirs) {
|
|
128
|
+
if (dir === '.' || dir === '') continue;
|
|
129
|
+
flat.add(dir);
|
|
130
|
+
}
|
|
80
131
|
}
|
|
132
|
+
return Array.from(flat);
|
|
81
133
|
}
|
|
82
134
|
|
|
83
135
|
/**
|
|
@@ -100,8 +152,11 @@ export function inferDomains(
|
|
|
100
152
|
domains.push(domainFromWorkspace(pkg));
|
|
101
153
|
}
|
|
102
154
|
} else {
|
|
103
|
-
// Single repo: suggest one domain per top-level
|
|
104
|
-
|
|
155
|
+
// Single repo: suggest one domain per top-level <sourceDir>/<subdir>/ for
|
|
156
|
+
// every detected source dir (formerly hardcoded to `src/` only — see
|
|
157
|
+
// P-B-002 in plan-1.7.0-cohesive-cleanup).
|
|
158
|
+
const flat = flattenSourceDirs(sourceDirs);
|
|
159
|
+
const subdirs = topLevelSrcSubdirs(projectRoot, flat);
|
|
105
160
|
for (const s of subdirs) {
|
|
106
161
|
domains.push({
|
|
107
162
|
name: titleCase(s),
|
package/src/license.ts
CHANGED
|
@@ -203,6 +203,26 @@ export function annotateToolDefinitions(defs: ToolDefinition[]): ToolDefinition[
|
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
// ============================================================
|
|
207
|
+
// plan-1.7.0-cohesive-cleanup P-A-003: Cloud feature availability gate
|
|
208
|
+
// ============================================================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Whether cloud-gated tool surfaces (team knowledge, etc.) are exposed.
|
|
212
|
+
*
|
|
213
|
+
* Returns true ONLY when `massu.config.yaml` opts the workspace into the
|
|
214
|
+
* cloud feature surface via `cloud.enabled: true`. Defaults to false for
|
|
215
|
+
* fresh installs (the schema's `enabled` default is `false`).
|
|
216
|
+
*
|
|
217
|
+
* This is distinct from {@link isLicenseTool} (which matches tool NAMES);
|
|
218
|
+
* `isCloudFeatureAvailable` is a runtime feature-availability check used
|
|
219
|
+
* by `tools.ts` to gate team-tool registration and routing at the
|
|
220
|
+
* tools-list and dispatch boundaries.
|
|
221
|
+
*/
|
|
222
|
+
export function isCloudFeatureAvailable(): boolean {
|
|
223
|
+
return getConfig().cloud?.enabled === true;
|
|
224
|
+
}
|
|
225
|
+
|
|
206
226
|
// ============================================================
|
|
207
227
|
// P3-005/P3-006/P3-007/P3-013: License validation & caching
|
|
208
228
|
// ============================================================
|
|
@@ -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-12T04:00:39.397Z.
|
|
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
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
+
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MCP server dispatch logic — pure, factory-based, no module-level mutable state.
|
|
6
|
+
*
|
|
7
|
+
* Production (`server.ts`) calls `createDispatcher()` once at startup and wires
|
|
8
|
+
* its `processLine` into stdin. Tests call `createDispatcher()` per test for
|
|
9
|
+
* fresh DB cache state (no test bleed).
|
|
10
|
+
*
|
|
11
|
+
* Three error envelopes live here (CR-12 / plan-1.6.2-server-lazy-db-deps):
|
|
12
|
+
* -32001 Tool needs CodeGraph but `.codegraph/codegraph.db` is missing
|
|
13
|
+
* (CodegraphDbNotInitializedError → structured remedy data)
|
|
14
|
+
* -32602 Tool not registered in `TOOL_DB_NEEDS` manifest
|
|
15
|
+
* (UnknownToolError → points at tool-db-needs.ts)
|
|
16
|
+
* -32603 Other internal errors raised by handleToolCall — request id
|
|
17
|
+
* is preserved (NOT id:null, which is reserved for -32700 parse
|
|
18
|
+
* failures per JSON-RPC §5.1).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type Database from 'better-sqlite3';
|
|
22
|
+
import { getCodeGraphDb, getDataDb, CodegraphDbNotInitializedError } from './db.ts';
|
|
23
|
+
import { getConfig } from './config.ts';
|
|
24
|
+
import { getToolDefinitions, handleToolCall } from './tools.ts';
|
|
25
|
+
import { getToolDbNeeds, UnknownToolError, type DbNeed } from './tool-db-needs.ts';
|
|
26
|
+
|
|
27
|
+
export interface JsonRpcRequest {
|
|
28
|
+
jsonrpc: '2.0';
|
|
29
|
+
id?: number | string;
|
|
30
|
+
method: string;
|
|
31
|
+
params?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface JsonRpcResponse {
|
|
35
|
+
jsonrpc: '2.0';
|
|
36
|
+
id: number | string | null;
|
|
37
|
+
result?: unknown;
|
|
38
|
+
error?: { code: number; message: string; data?: unknown };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Per-line dispatch result. `emit=false` when the request was a notification (no id). */
|
|
42
|
+
export interface ProcessLineResult {
|
|
43
|
+
response: JsonRpcResponse;
|
|
44
|
+
emit: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DispatcherOptions {
|
|
48
|
+
/** Version string surfaced in `initialize.result.serverInfo.version`. */
|
|
49
|
+
serverInfoVersion: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Dispatcher {
|
|
53
|
+
handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse>;
|
|
54
|
+
processLine(line: string): Promise<ProcessLineResult | null>;
|
|
55
|
+
closeCachedDbs(): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createDispatcher(options: DispatcherOptions): Dispatcher {
|
|
59
|
+
let codegraphDbCache: Database.Database | null = null;
|
|
60
|
+
let dataDbCache: Database.Database | null = null;
|
|
61
|
+
|
|
62
|
+
function resolveDbsForTool(toolName: string): {
|
|
63
|
+
needs: readonly DbNeed[];
|
|
64
|
+
dataDb?: Database.Database;
|
|
65
|
+
codegraphDb?: Database.Database;
|
|
66
|
+
} {
|
|
67
|
+
const needs = getToolDbNeeds(toolName, getConfig().toolPrefix);
|
|
68
|
+
|
|
69
|
+
let dataDbResolved: Database.Database | undefined;
|
|
70
|
+
let codegraphDbResolved: Database.Database | undefined;
|
|
71
|
+
|
|
72
|
+
if (needs.includes('data')) {
|
|
73
|
+
if (!dataDbCache) dataDbCache = getDataDb();
|
|
74
|
+
dataDbResolved = dataDbCache;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (needs.includes('codegraph')) {
|
|
78
|
+
// Throws CodegraphDbNotInitializedError when .codegraph/codegraph.db is missing.
|
|
79
|
+
if (!codegraphDbCache) codegraphDbCache = getCodeGraphDb();
|
|
80
|
+
codegraphDbResolved = codegraphDbCache;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { needs, dataDb: dataDbResolved, codegraphDb: codegraphDbResolved };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
|
|
87
|
+
const { method, params, id } = request;
|
|
88
|
+
|
|
89
|
+
switch (method) {
|
|
90
|
+
case 'initialize': {
|
|
91
|
+
return {
|
|
92
|
+
jsonrpc: '2.0',
|
|
93
|
+
id: id ?? null,
|
|
94
|
+
result: {
|
|
95
|
+
protocolVersion: '2024-11-05',
|
|
96
|
+
capabilities: { tools: {} },
|
|
97
|
+
serverInfo: {
|
|
98
|
+
name: getConfig().toolPrefix || 'massu',
|
|
99
|
+
version: options.serverInfoVersion,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case 'notifications/initialized': {
|
|
106
|
+
return { jsonrpc: '2.0', id: id ?? null, result: {} };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
case 'tools/list': {
|
|
110
|
+
const tools = getToolDefinitions();
|
|
111
|
+
return { jsonrpc: '2.0', id: id ?? null, result: { tools } };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
case 'tools/call': {
|
|
115
|
+
const toolName = (params as { name?: string })?.name ?? '';
|
|
116
|
+
const toolArgs = (params as { arguments?: Record<string, unknown> })?.arguments ?? {};
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const { dataDb, codegraphDb } = resolveDbsForTool(toolName);
|
|
120
|
+
const result = await handleToolCall(toolName, toolArgs, dataDb, codegraphDb);
|
|
121
|
+
return { jsonrpc: '2.0', id: id ?? null, result };
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (err instanceof CodegraphDbNotInitializedError) {
|
|
124
|
+
return {
|
|
125
|
+
jsonrpc: '2.0',
|
|
126
|
+
id: id ?? null,
|
|
127
|
+
error: {
|
|
128
|
+
code: -32001,
|
|
129
|
+
message: 'Tool requires CodeGraph database which is not initialized for this repo',
|
|
130
|
+
data: {
|
|
131
|
+
remedy: 'npx @colbymchenry/codegraph@0.7.4 init . && npx @colbymchenry/codegraph@0.7.4 index .',
|
|
132
|
+
codegraphDbPath: err.dbPath,
|
|
133
|
+
tool: toolName,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (err instanceof UnknownToolError) {
|
|
139
|
+
return {
|
|
140
|
+
jsonrpc: '2.0',
|
|
141
|
+
id: id ?? null,
|
|
142
|
+
error: {
|
|
143
|
+
code: -32602,
|
|
144
|
+
message: `Unknown tool: ${err.toolName}`,
|
|
145
|
+
data: {
|
|
146
|
+
remedy: 'Tool not registered in TOOL_DB_NEEDS manifest. See packages/core/src/tool-db-needs.ts.',
|
|
147
|
+
tool: toolName,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case 'ping': {
|
|
157
|
+
return { jsonrpc: '2.0', id: id ?? null, result: {} };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
default: {
|
|
161
|
+
return {
|
|
162
|
+
jsonrpc: '2.0',
|
|
163
|
+
id: id ?? null,
|
|
164
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function processLine(line: string): Promise<ProcessLineResult | null> {
|
|
171
|
+
const trimmed = line.trim();
|
|
172
|
+
if (!trimmed) return null;
|
|
173
|
+
|
|
174
|
+
let request: JsonRpcRequest;
|
|
175
|
+
try {
|
|
176
|
+
request = JSON.parse(trimmed) as JsonRpcRequest;
|
|
177
|
+
} catch (parseError) {
|
|
178
|
+
// JSON-RPC §5.1: parse failure → -32700 + id:null (no id is extractable).
|
|
179
|
+
return {
|
|
180
|
+
response: {
|
|
181
|
+
jsonrpc: '2.0',
|
|
182
|
+
id: null,
|
|
183
|
+
error: {
|
|
184
|
+
code: -32700,
|
|
185
|
+
message: `Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
emit: true,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const response = await handleRequest(request);
|
|
194
|
+
// Notifications (no id) MUST NOT receive a response per JSON-RPC §4.1.
|
|
195
|
+
return { response, emit: request.id !== undefined };
|
|
196
|
+
} catch (error) {
|
|
197
|
+
// Request-processing failure: -32603 with the request id preserved.
|
|
198
|
+
// Specific subclasses (-32001/-32602) are handled inside tools/call.
|
|
199
|
+
return {
|
|
200
|
+
response: {
|
|
201
|
+
jsonrpc: '2.0',
|
|
202
|
+
id: request.id ?? null,
|
|
203
|
+
error: {
|
|
204
|
+
code: -32603,
|
|
205
|
+
message: `Internal error: ${error instanceof Error ? error.message : String(error)}`,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
emit: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function closeCachedDbs(): void {
|
|
214
|
+
if (codegraphDbCache) {
|
|
215
|
+
codegraphDbCache.close();
|
|
216
|
+
codegraphDbCache = null;
|
|
217
|
+
}
|
|
218
|
+
if (dataDbCache) {
|
|
219
|
+
dataDbCache.close();
|
|
220
|
+
dataDbCache = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { handleRequest, processLine, closeCachedDbs };
|
|
225
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -14,13 +14,9 @@
|
|
|
14
14
|
import { readFileSync } from 'fs';
|
|
15
15
|
import { resolve, dirname } from 'path';
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
|
-
import type Database from 'better-sqlite3';
|
|
18
|
-
import { getCodeGraphDb, getDataDb, CodegraphDbNotInitializedError } from './db.ts';
|
|
19
|
-
import { getConfig, getResolvedPaths } from './config.ts';
|
|
20
|
-
import { getToolDefinitions, handleToolCall } from './tools.ts';
|
|
21
17
|
import { getMemoryDb, pruneOldConversationTurns, pruneOldObservations } from './memory-db.ts';
|
|
22
18
|
import { getCurrentTier } from './license.ts';
|
|
23
|
-
import {
|
|
19
|
+
import { createDispatcher } from './server-dispatch.ts';
|
|
24
20
|
|
|
25
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
22
|
const PKG_VERSION = (() => {
|
|
@@ -32,167 +28,7 @@ const PKG_VERSION = (() => {
|
|
|
32
28
|
}
|
|
33
29
|
})();
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
jsonrpc: '2.0';
|
|
37
|
-
id?: number | string;
|
|
38
|
-
method: string;
|
|
39
|
-
params?: Record<string, unknown>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface JsonRpcResponse {
|
|
43
|
-
jsonrpc: '2.0';
|
|
44
|
-
id: number | string | null;
|
|
45
|
-
result?: unknown;
|
|
46
|
-
error?: { code: number; message: string; data?: unknown };
|
|
47
|
-
}
|
|
48
|
-
|
|
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`.
|
|
61
|
-
|
|
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 };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
|
|
99
|
-
const { method, params, id } = request;
|
|
100
|
-
|
|
101
|
-
switch (method) {
|
|
102
|
-
case 'initialize': {
|
|
103
|
-
return {
|
|
104
|
-
jsonrpc: '2.0',
|
|
105
|
-
id: id ?? null,
|
|
106
|
-
result: {
|
|
107
|
-
protocolVersion: '2024-11-05',
|
|
108
|
-
capabilities: {
|
|
109
|
-
tools: {},
|
|
110
|
-
},
|
|
111
|
-
serverInfo: {
|
|
112
|
-
name: getConfig().toolPrefix || 'massu',
|
|
113
|
-
version: PKG_VERSION,
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
case 'notifications/initialized': {
|
|
120
|
-
// Client acknowledges initialization - no response needed for notifications
|
|
121
|
-
return { jsonrpc: '2.0', id: id ?? null, result: {} };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
case 'tools/list': {
|
|
125
|
-
const tools = getToolDefinitions();
|
|
126
|
-
return {
|
|
127
|
-
jsonrpc: '2.0',
|
|
128
|
-
id: id ?? null,
|
|
129
|
-
result: { tools },
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
case 'tools/call': {
|
|
134
|
-
const toolName = (params as { name: string })?.name;
|
|
135
|
-
const toolArgs = (params as { arguments?: Record<string, unknown> })?.arguments ?? {};
|
|
136
|
-
|
|
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
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
case 'ping': {
|
|
184
|
-
return { jsonrpc: '2.0', id: id ?? null, result: {} };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
default: {
|
|
188
|
-
return {
|
|
189
|
-
jsonrpc: '2.0',
|
|
190
|
-
id: id ?? null,
|
|
191
|
-
error: { code: -32601, message: `Method not found: ${method}` },
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
31
|
+
const dispatcher = createDispatcher({ serverInfoVersion: PKG_VERSION });
|
|
196
32
|
|
|
197
33
|
// === Startup: prune stale memory data (non-blocking) ===
|
|
198
34
|
|
|
@@ -244,62 +80,20 @@ process.stdin.on('data', async (chunk: string) => {
|
|
|
244
80
|
// Process complete messages (newline-delimited JSON-RPC)
|
|
245
81
|
let newlineIndex: number;
|
|
246
82
|
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
247
|
-
const line = buffer.slice(0, newlineIndex)
|
|
83
|
+
const line = buffer.slice(0, newlineIndex);
|
|
248
84
|
buffer = buffer.slice(newlineIndex + 1);
|
|
249
85
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
// -32700) from request-processing failures (-32603 Internal error,
|
|
254
|
-
// preserving the request id when parseable).
|
|
255
|
-
let request: JsonRpcRequest | null = null;
|
|
256
|
-
try {
|
|
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
|
-
}
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
const response = await handleRequest(request);
|
|
275
|
-
// Don't send responses for notifications (no id)
|
|
276
|
-
if (request.id !== undefined) {
|
|
277
|
-
const responseStr = JSON.stringify(response);
|
|
278
|
-
process.stdout.write(responseStr + '\n');
|
|
279
|
-
}
|
|
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.
|
|
285
|
-
const errorResponse: JsonRpcResponse = {
|
|
286
|
-
jsonrpc: '2.0',
|
|
287
|
-
id: request.id ?? null,
|
|
288
|
-
error: {
|
|
289
|
-
code: -32603,
|
|
290
|
-
message: `Internal error: ${error instanceof Error ? error.message : String(error)}`,
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
|
86
|
+
const result = await dispatcher.processLine(line);
|
|
87
|
+
if (result && result.emit) {
|
|
88
|
+
process.stdout.write(JSON.stringify(result.response) + '\n');
|
|
294
89
|
}
|
|
295
90
|
}
|
|
296
91
|
});
|
|
297
92
|
|
|
298
93
|
process.stdin.on('end', () => {
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
if (dataDbCache) dataDbCache.close();
|
|
94
|
+
// Close cached CodeGraph + Data connections. Memory + Knowledge are
|
|
95
|
+
// per-call (closed inside their routing branches in tools.ts).
|
|
96
|
+
dispatcher.closeCachedDbs();
|
|
303
97
|
process.exit(0);
|
|
304
98
|
});
|
|
305
99
|
|
package/src/tools.ts
CHANGED
|
@@ -38,7 +38,7 @@ import { getKnowledgeToolDefinitions, isKnowledgeTool, handleKnowledgeToolCall }
|
|
|
38
38
|
import { getKnowledgeDb } from './knowledge-db.ts';
|
|
39
39
|
import { getPythonToolDefinitions, isPythonTool, handlePythonToolCall } from './python-tools.ts';
|
|
40
40
|
import { getConfig, getProjectRoot, getResolvedPaths } from './config.ts';
|
|
41
|
-
import { getCurrentTier, getToolTier, isToolAllowed, annotateToolDefinitions, getLicenseToolDefinitions, isLicenseTool, handleLicenseToolCall } from './license.ts';
|
|
41
|
+
import { getCurrentTier, getToolTier, isToolAllowed, annotateToolDefinitions, getLicenseToolDefinitions, isLicenseTool, handleLicenseToolCall, isCloudFeatureAvailable } from './license.ts';
|
|
42
42
|
|
|
43
43
|
export interface ToolDefinition {
|
|
44
44
|
name: string;
|
|
@@ -172,7 +172,8 @@ export function getToolDefinitions(): ToolDefinition[] {
|
|
|
172
172
|
...getSecurityToolDefinitions(),
|
|
173
173
|
...getDependencyToolDefinitions(),
|
|
174
174
|
// Enterprise layer (team knowledge, regression detection)
|
|
175
|
-
|
|
175
|
+
// P-A-003: team tools are cloud-gated — only listed when cloud.enabled is true.
|
|
176
|
+
...(isCloudFeatureAvailable() ? getTeamToolDefinitions() : []),
|
|
176
177
|
...getRegressionToolDefinitions(),
|
|
177
178
|
// Knowledge layer (indexed .claude/ knowledge — rules, patterns, incidents)
|
|
178
179
|
...getKnowledgeToolDefinitions(),
|
|
@@ -410,7 +411,9 @@ export async function handleToolCall(
|
|
|
410
411
|
}
|
|
411
412
|
|
|
412
413
|
// Route enterprise layer tools
|
|
413
|
-
if
|
|
414
|
+
// P-A-003: team-tool dispatch gated on cloud availability; if a stale client
|
|
415
|
+
// still has the tool name cached, fall through to the unknown-tool branch.
|
|
416
|
+
if (isTeamTool(name) && isCloudFeatureAvailable()) {
|
|
414
417
|
const memDb = getMemoryDb();
|
|
415
418
|
try { return handleTeamToolCall(name, args, memDb); }
|
|
416
419
|
finally { memDb.close(); }
|