@massu/core 0.6.2 → 0.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/README.md +40 -0
- package/dist/cli.js +471 -83
- package/dist/hooks/auto-learning-pipeline.js +469 -0
- package/dist/hooks/cost-tracker.js +28 -1
- package/dist/hooks/fix-detector.js +462 -0
- package/dist/hooks/incident-pipeline.js +426 -0
- package/dist/hooks/post-edit-context.js +28 -1
- package/dist/hooks/post-tool-use.js +28 -1
- package/dist/hooks/pre-compact.js +28 -1
- package/dist/hooks/pre-delete-check.js +28 -1
- package/dist/hooks/quality-event.js +28 -1
- package/dist/hooks/rule-enforcement-pipeline.js +440 -0
- package/dist/hooks/session-end.js +28 -1
- package/dist/hooks/session-start.js +28 -1
- package/dist/hooks/user-prompt.js +28 -1
- package/package.json +2 -2
- package/src/config.ts +31 -0
- package/src/hooks/auto-learning-pipeline.ts +195 -0
- package/src/hooks/fix-detector.ts +186 -0
- package/src/hooks/incident-pipeline.ts +148 -0
- package/src/hooks/rule-enforcement-pipeline.ts +158 -0
- package/src/mcp-bridge-tools.ts +459 -0
- package/src/tools.ts +8 -0
package/dist/cli.js
CHANGED
|
@@ -125,7 +125,7 @@ function getResolvedPaths() {
|
|
|
125
125
|
settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
-
var DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, RawConfigSchema, _config, _projectRoot;
|
|
128
|
+
var DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, AutoLearningConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, RawConfigSchema, _config, _projectRoot;
|
|
129
129
|
var init_config = __esm({
|
|
130
130
|
"src/config.ts"() {
|
|
131
131
|
"use strict";
|
|
@@ -248,6 +248,32 @@ var init_config = __esm({
|
|
|
248
248
|
warning: z.number().default(50)
|
|
249
249
|
}).optional()
|
|
250
250
|
}).optional();
|
|
251
|
+
AutoLearningConfigSchema = z.object({
|
|
252
|
+
enabled: z.boolean().default(true),
|
|
253
|
+
incidentDir: z.string().default("docs/incidents"),
|
|
254
|
+
memoryDir: z.string().default("memory"),
|
|
255
|
+
memoryIndexFile: z.string().default("MEMORY.md"),
|
|
256
|
+
enforcementHooksDir: z.string().default("scripts/hooks"),
|
|
257
|
+
fixDetection: z.object({
|
|
258
|
+
enabled: z.boolean().default(true),
|
|
259
|
+
lookbackDays: z.number().default(7),
|
|
260
|
+
signals: z.array(z.string()).default([
|
|
261
|
+
"removed_broken_code",
|
|
262
|
+
"added_error_handling",
|
|
263
|
+
"method_name_correction",
|
|
264
|
+
"auth_fix",
|
|
265
|
+
"nil_handling_fix",
|
|
266
|
+
"concurrency_fix",
|
|
267
|
+
"async_pattern_fix",
|
|
268
|
+
"added_missing_import"
|
|
269
|
+
])
|
|
270
|
+
}).default({}),
|
|
271
|
+
pipeline: z.object({
|
|
272
|
+
requireIncidentReport: z.boolean().default(true),
|
|
273
|
+
requirePreventionRule: z.boolean().default(true),
|
|
274
|
+
requireEnforcement: z.boolean().default(true)
|
|
275
|
+
}).default({})
|
|
276
|
+
}).optional();
|
|
251
277
|
CloudConfigSchema = z.object({
|
|
252
278
|
enabled: z.boolean().default(false),
|
|
253
279
|
apiKey: z.string().optional(),
|
|
@@ -337,7 +363,8 @@ var init_config = __esm({
|
|
|
337
363
|
regression: RegressionConfigSchema,
|
|
338
364
|
cloud: CloudConfigSchema,
|
|
339
365
|
conventions: ConventionsConfigSchema,
|
|
340
|
-
python: PythonConfigSchema
|
|
366
|
+
python: PythonConfigSchema,
|
|
367
|
+
autoLearning: AutoLearningConfigSchema
|
|
341
368
|
}).passthrough();
|
|
342
369
|
_config = null;
|
|
343
370
|
_projectRoot = null;
|
|
@@ -989,12 +1016,12 @@ function addSummary(db, sessionId, summary) {
|
|
|
989
1016
|
Math.floor(now.getTime() / 1e3)
|
|
990
1017
|
);
|
|
991
1018
|
}
|
|
992
|
-
function addUserPrompt(db, sessionId,
|
|
1019
|
+
function addUserPrompt(db, sessionId, text19, promptNumber) {
|
|
993
1020
|
const now = /* @__PURE__ */ new Date();
|
|
994
1021
|
db.prepare(`
|
|
995
1022
|
INSERT INTO user_prompts (session_id, prompt_text, prompt_number, created_at, created_at_epoch)
|
|
996
1023
|
VALUES (?, ?, ?, ?, ?)
|
|
997
|
-
`).run(sessionId,
|
|
1024
|
+
`).run(sessionId, text19, promptNumber, now.toISOString(), Math.floor(now.getTime() / 1e3));
|
|
998
1025
|
}
|
|
999
1026
|
function searchObservations(db, query, opts) {
|
|
1000
1027
|
const limit = opts?.limit ?? 20;
|
|
@@ -3980,7 +4007,7 @@ function parseFromImportLine(line, lineNum) {
|
|
|
3980
4007
|
function parsePlainImportLine(line, lineNum) {
|
|
3981
4008
|
const results = [];
|
|
3982
4009
|
const rest = line.replace(/^import\s+/, "");
|
|
3983
|
-
const parts = rest.split(",").map((
|
|
4010
|
+
const parts = rest.split(",").map((p19) => p19.trim()).filter((p19) => p19.length > 0);
|
|
3984
4011
|
for (const part of parts) {
|
|
3985
4012
|
const asMatch = part.match(/^(\S+)\s+as\s+(\S+)$/);
|
|
3986
4013
|
const moduleName = asMatch ? asMatch[1] : part;
|
|
@@ -4141,8 +4168,8 @@ function joinLogicalLines(source) {
|
|
|
4141
4168
|
}
|
|
4142
4169
|
return logical;
|
|
4143
4170
|
}
|
|
4144
|
-
function extractQuotedString(
|
|
4145
|
-
const m =
|
|
4171
|
+
function extractQuotedString(text19) {
|
|
4172
|
+
const m = text19.match(/(['"])(.*?)\1/);
|
|
4146
4173
|
return m ? m[2] : null;
|
|
4147
4174
|
}
|
|
4148
4175
|
function extractResponseModel(argStr) {
|
|
@@ -4209,8 +4236,8 @@ function parsePythonRoutes(source) {
|
|
|
4209
4236
|
const logicalLines = joinLogicalLines(source);
|
|
4210
4237
|
const pendingDecorators = [];
|
|
4211
4238
|
for (let i = 0; i < logicalLines.length; i++) {
|
|
4212
|
-
const { text:
|
|
4213
|
-
const trimmed =
|
|
4239
|
+
const { text: text19, startLine } = logicalLines[i];
|
|
4240
|
+
const trimmed = text19.trim();
|
|
4214
4241
|
const decoratorMatch = trimmed.match(
|
|
4215
4242
|
/^@\s*(\w+)\s*\.\s*(\w+)\s*\((.*)\)\s*$/s
|
|
4216
4243
|
);
|
|
@@ -4445,11 +4472,11 @@ function parseRelationship(argsStr) {
|
|
|
4445
4472
|
back_populates: bpMatch ? bpMatch[1] : null
|
|
4446
4473
|
};
|
|
4447
4474
|
}
|
|
4448
|
-
function findClosingParen(
|
|
4475
|
+
function findClosingParen(text19, openIndex, openChar, closeChar) {
|
|
4449
4476
|
let depth = 1;
|
|
4450
|
-
for (let i = openIndex + 1; i <
|
|
4451
|
-
if (
|
|
4452
|
-
else if (
|
|
4477
|
+
for (let i = openIndex + 1; i < text19.length; i++) {
|
|
4478
|
+
if (text19[i] === openChar) depth++;
|
|
4479
|
+
else if (text19[i] === closeChar) {
|
|
4453
4480
|
depth--;
|
|
4454
4481
|
if (depth === 0) return i;
|
|
4455
4482
|
}
|
|
@@ -4725,16 +4752,16 @@ function extractTableAndColumn(argsStr) {
|
|
|
4725
4752
|
}
|
|
4726
4753
|
return { table, column };
|
|
4727
4754
|
}
|
|
4728
|
-
function splitTopLevelCommas(
|
|
4755
|
+
function splitTopLevelCommas(text19) {
|
|
4729
4756
|
const parts = [];
|
|
4730
4757
|
let current = "";
|
|
4731
4758
|
let depth = 0;
|
|
4732
4759
|
let inString = null;
|
|
4733
|
-
for (let i = 0; i <
|
|
4734
|
-
const ch =
|
|
4760
|
+
for (let i = 0; i < text19.length; i++) {
|
|
4761
|
+
const ch = text19[i];
|
|
4735
4762
|
if (inString) {
|
|
4736
4763
|
current += ch;
|
|
4737
|
-
if (ch === inString &&
|
|
4764
|
+
if (ch === inString && text19[i - 1] !== "\\") {
|
|
4738
4765
|
inString = null;
|
|
4739
4766
|
}
|
|
4740
4767
|
continue;
|
|
@@ -5579,7 +5606,7 @@ function handleDocsAudit(args2) {
|
|
|
5579
5606
|
const fileName = basename3(file);
|
|
5580
5607
|
if (mapping.routers.includes(fileName)) {
|
|
5581
5608
|
const procedures = extractProcedureNames(file);
|
|
5582
|
-
const undocumented = procedures.filter((
|
|
5609
|
+
const undocumented = procedures.filter((p19) => !contentMentions(content, p19));
|
|
5583
5610
|
if (undocumented.length > 0) {
|
|
5584
5611
|
staleReasons.push(
|
|
5585
5612
|
`Router ${fileName}: procedures not documented: ${undocumented.slice(0, 5).join(", ")}${undocumented.length > 5 ? ` (+${undocumented.length - 5} more)` : ""}`
|
|
@@ -5905,26 +5932,26 @@ function handleToolPatterns(args2, db) {
|
|
|
5905
5932
|
case "tool":
|
|
5906
5933
|
lines.push("| Tool | Calls | Successes | Failures | Success Rate | Avg Output Size | Avg Input Size |");
|
|
5907
5934
|
lines.push("|------|-------|-----------|----------|--------------|-----------------|----------------|");
|
|
5908
|
-
for (const
|
|
5909
|
-
const total =
|
|
5910
|
-
const successes =
|
|
5911
|
-
const failures =
|
|
5935
|
+
for (const p19 of patterns) {
|
|
5936
|
+
const total = p19.call_count;
|
|
5937
|
+
const successes = p19.successes;
|
|
5938
|
+
const failures = p19.failures;
|
|
5912
5939
|
const rate = total > 0 ? Math.round(successes / total * 100) : 0;
|
|
5913
|
-
lines.push(`| ${
|
|
5940
|
+
lines.push(`| ${p19.tool_name} | ${total} | ${successes} | ${failures} | ${rate}% | ${Math.round(p19.avg_output_size ?? 0)} | ${Math.round(p19.avg_input_size ?? 0)} |`);
|
|
5914
5941
|
}
|
|
5915
5942
|
break;
|
|
5916
5943
|
case "session":
|
|
5917
5944
|
lines.push("| Session | Calls | Unique Tools | Successes | Failures | Avg Output Size |");
|
|
5918
5945
|
lines.push("|---------|-------|--------------|-----------|----------|-----------------|");
|
|
5919
|
-
for (const
|
|
5920
|
-
lines.push(`| ${
|
|
5946
|
+
for (const p19 of patterns) {
|
|
5947
|
+
lines.push(`| ${p19.session_id.slice(0, 8)}... | ${p19.call_count} | ${p19.unique_tools} | ${p19.successes} | ${p19.failures} | ${Math.round(p19.avg_output_size ?? 0)} |`);
|
|
5921
5948
|
}
|
|
5922
5949
|
break;
|
|
5923
5950
|
case "day":
|
|
5924
5951
|
lines.push("| Day | Calls | Unique Tools | Successes |");
|
|
5925
5952
|
lines.push("|-----|-------|--------------|-----------|");
|
|
5926
|
-
for (const
|
|
5927
|
-
lines.push(`| ${
|
|
5953
|
+
for (const p19 of patterns) {
|
|
5954
|
+
lines.push(`| ${p19.day} | ${p19.call_count} | ${p19.unique_tools} | ${p19.successes} |`);
|
|
5928
5955
|
}
|
|
5929
5956
|
break;
|
|
5930
5957
|
}
|
|
@@ -6572,15 +6599,15 @@ function handleDetail2(args2, db) {
|
|
|
6572
6599
|
}
|
|
6573
6600
|
if (detail.procedures.length > 0) {
|
|
6574
6601
|
lines.push(`### Procedures (${detail.procedures.length})`);
|
|
6575
|
-
for (const
|
|
6576
|
-
lines.push(`- ${
|
|
6602
|
+
for (const p19 of detail.procedures) {
|
|
6603
|
+
lines.push(`- ${p19.router_name}.${p19.procedure_name}${p19.procedure_type ? " (" + p19.procedure_type + ")" : ""}`);
|
|
6577
6604
|
}
|
|
6578
6605
|
lines.push("");
|
|
6579
6606
|
}
|
|
6580
6607
|
if (detail.pages.length > 0) {
|
|
6581
6608
|
lines.push(`### Pages (${detail.pages.length})`);
|
|
6582
|
-
for (const
|
|
6583
|
-
lines.push(`- ${
|
|
6609
|
+
for (const p19 of detail.pages) {
|
|
6610
|
+
lines.push(`- ${p19.page_route}${p19.portal ? " (" + p19.portal + ")" : ""}`);
|
|
6584
6611
|
}
|
|
6585
6612
|
lines.push("");
|
|
6586
6613
|
}
|
|
@@ -6663,7 +6690,7 @@ function handleValidate(args2, db) {
|
|
|
6663
6690
|
lines.push(` Missing components: ${item.missing_components.join(", ")}`);
|
|
6664
6691
|
}
|
|
6665
6692
|
if (item.missing_procedures.length > 0) {
|
|
6666
|
-
lines.push(` Missing procedures: ${item.missing_procedures.map((
|
|
6693
|
+
lines.push(` Missing procedures: ${item.missing_procedures.map((p19) => `${p19.router}.${p19.procedure}`).join(", ")}`);
|
|
6667
6694
|
}
|
|
6668
6695
|
if (item.missing_pages.length > 0) {
|
|
6669
6696
|
lines.push(` Missing pages: ${item.missing_pages.join(", ")}`);
|
|
@@ -6710,14 +6737,14 @@ function handleRegister(args2, db) {
|
|
|
6710
6737
|
}
|
|
6711
6738
|
const procedures = args2.procedures;
|
|
6712
6739
|
if (procedures) {
|
|
6713
|
-
for (const
|
|
6714
|
-
linkProcedure(db, featureId,
|
|
6740
|
+
for (const p19 of procedures) {
|
|
6741
|
+
linkProcedure(db, featureId, p19.router, p19.procedure, p19.type);
|
|
6715
6742
|
}
|
|
6716
6743
|
}
|
|
6717
6744
|
const pages = args2.pages;
|
|
6718
6745
|
if (pages) {
|
|
6719
|
-
for (const
|
|
6720
|
-
linkPage(db, featureId,
|
|
6746
|
+
for (const p19 of pages) {
|
|
6747
|
+
linkPage(db, featureId, p19.route, p19.portal);
|
|
6721
6748
|
}
|
|
6722
6749
|
}
|
|
6723
6750
|
logChange(db, featureId, "created", `Registered via ${p4("sentinel_register")}`);
|
|
@@ -9652,22 +9679,22 @@ function buildCrossReferences(db) {
|
|
|
9652
9679
|
}
|
|
9653
9680
|
const chunks = db.prepare("SELECT id, content, metadata FROM knowledge_chunks").all();
|
|
9654
9681
|
for (const chunk of chunks) {
|
|
9655
|
-
const
|
|
9656
|
-
const crRefs =
|
|
9682
|
+
const text19 = chunk.content;
|
|
9683
|
+
const crRefs = text19.match(/CR-\d+/g);
|
|
9657
9684
|
if (crRefs) {
|
|
9658
9685
|
for (const cr of [...new Set(crRefs)]) {
|
|
9659
9686
|
insertEdge.run("chunk", String(chunk.id), "cr", cr, "references");
|
|
9660
9687
|
edgeCount++;
|
|
9661
9688
|
}
|
|
9662
9689
|
}
|
|
9663
|
-
const vrRefs =
|
|
9690
|
+
const vrRefs = text19.match(/VR-[\w-]+/g);
|
|
9664
9691
|
if (vrRefs) {
|
|
9665
9692
|
for (const vr of [...new Set(vrRefs)]) {
|
|
9666
9693
|
insertEdge.run("chunk", String(chunk.id), "vr", vr, "references");
|
|
9667
9694
|
edgeCount++;
|
|
9668
9695
|
}
|
|
9669
9696
|
}
|
|
9670
|
-
const incRefs =
|
|
9697
|
+
const incRefs = text19.match(/Incident #(\d+)/gi);
|
|
9671
9698
|
if (incRefs) {
|
|
9672
9699
|
for (const ref of incRefs) {
|
|
9673
9700
|
const numMatch = ref.match(/\d+/);
|
|
@@ -9713,7 +9740,7 @@ function indexAllKnowledge(db) {
|
|
|
9713
9740
|
}
|
|
9714
9741
|
if (existsSync19(paths.docsDir)) {
|
|
9715
9742
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
9716
|
-
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((
|
|
9743
|
+
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p19) => f.includes(p19)));
|
|
9717
9744
|
files.push(...docsFiles);
|
|
9718
9745
|
}
|
|
9719
9746
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -9880,7 +9907,7 @@ function isKnowledgeStale(db) {
|
|
|
9880
9907
|
}
|
|
9881
9908
|
if (existsSync19(paths.docsDir)) {
|
|
9882
9909
|
const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
|
|
9883
|
-
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((
|
|
9910
|
+
const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f) => !f.includes("/plans/") && !excludePatterns.some((p19) => f.includes(p19)));
|
|
9884
9911
|
files.push(...docsFiles);
|
|
9885
9912
|
}
|
|
9886
9913
|
for (const filePath of files) {
|
|
@@ -11734,13 +11761,368 @@ var init_python_tools = __esm({
|
|
|
11734
11761
|
}
|
|
11735
11762
|
});
|
|
11736
11763
|
|
|
11737
|
-
// src/tools.ts
|
|
11764
|
+
// src/mcp-bridge-tools.ts
|
|
11765
|
+
import { spawn } from "child_process";
|
|
11738
11766
|
import { readFileSync as readFileSync22, existsSync as existsSync22 } from "fs";
|
|
11739
|
-
import { resolve as resolve18
|
|
11767
|
+
import { resolve as resolve18 } from "path";
|
|
11768
|
+
function p17(baseName) {
|
|
11769
|
+
return `${getConfig().toolPrefix}_${baseName}`;
|
|
11770
|
+
}
|
|
11771
|
+
function buildSubprocessEnv() {
|
|
11772
|
+
const env = {};
|
|
11773
|
+
const projectName = getConfig().project?.name?.toUpperCase() || "";
|
|
11774
|
+
const safePrefixes = projectName ? [`${projectName}_CONFIG_`, `${projectName}_LOG_`] : [];
|
|
11775
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
11776
|
+
if (!value) continue;
|
|
11777
|
+
if (ENV_DENY_PATTERNS.some((pat) => key.includes(pat))) continue;
|
|
11778
|
+
if (ENV_ALLOW_LIST.has(key)) {
|
|
11779
|
+
env[key] = value;
|
|
11780
|
+
} else if (safePrefixes.length > 0 && safePrefixes.some((pfx) => key.startsWith(pfx))) {
|
|
11781
|
+
env[key] = value;
|
|
11782
|
+
}
|
|
11783
|
+
}
|
|
11784
|
+
return env;
|
|
11785
|
+
}
|
|
11786
|
+
function loadMcpConfig() {
|
|
11787
|
+
const root = getProjectRoot();
|
|
11788
|
+
const mcpPath = resolve18(root, ".mcp.json");
|
|
11789
|
+
if (!existsSync22(mcpPath)) return {};
|
|
11790
|
+
try {
|
|
11791
|
+
const raw = JSON.parse(readFileSync22(mcpPath, "utf-8"));
|
|
11792
|
+
const servers = {};
|
|
11793
|
+
const mcpServers = raw.mcpServers || {};
|
|
11794
|
+
const pfx = getConfig().toolPrefix;
|
|
11795
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
11796
|
+
if (name === pfx) continue;
|
|
11797
|
+
servers[name] = config;
|
|
11798
|
+
}
|
|
11799
|
+
return servers;
|
|
11800
|
+
} catch (e) {
|
|
11801
|
+
console.error("[mcp-bridge] Failed to parse .mcp.json:", e);
|
|
11802
|
+
return {};
|
|
11803
|
+
}
|
|
11804
|
+
}
|
|
11805
|
+
async function mcpRequest(conn, method, params = {}) {
|
|
11806
|
+
if (!conn.process || !conn.process.stdin || !conn.process.stdout) {
|
|
11807
|
+
throw new Error("MCP process not connected");
|
|
11808
|
+
}
|
|
11809
|
+
conn.requestId++;
|
|
11810
|
+
const request = {
|
|
11811
|
+
jsonrpc: "2.0",
|
|
11812
|
+
id: conn.requestId,
|
|
11813
|
+
method,
|
|
11814
|
+
params
|
|
11815
|
+
};
|
|
11816
|
+
return new Promise((resolve22, reject) => {
|
|
11817
|
+
const timeout = setTimeout(() => {
|
|
11818
|
+
conn.process?.stdout?.removeListener("data", onData);
|
|
11819
|
+
reject(new Error(`MCP request timed out: ${method}`));
|
|
11820
|
+
}, 3e4);
|
|
11821
|
+
let buffer2 = "";
|
|
11822
|
+
const onData = (data) => {
|
|
11823
|
+
buffer2 += data.toString("utf-8");
|
|
11824
|
+
const lines = buffer2.split("\n");
|
|
11825
|
+
buffer2 = lines.pop() || "";
|
|
11826
|
+
for (const line of lines) {
|
|
11827
|
+
if (!line.trim()) continue;
|
|
11828
|
+
try {
|
|
11829
|
+
const response = JSON.parse(line);
|
|
11830
|
+
if (response.id === conn.requestId) {
|
|
11831
|
+
clearTimeout(timeout);
|
|
11832
|
+
conn.process?.stdout?.removeListener("data", onData);
|
|
11833
|
+
if (response.error) {
|
|
11834
|
+
reject(new Error(`MCP error ${response.error.code}: ${response.error.message}`));
|
|
11835
|
+
} else {
|
|
11836
|
+
resolve22(response.result || {});
|
|
11837
|
+
}
|
|
11838
|
+
return;
|
|
11839
|
+
}
|
|
11840
|
+
} catch (e) {
|
|
11841
|
+
clearTimeout(timeout);
|
|
11842
|
+
conn.process?.stdout?.removeListener("data", onData);
|
|
11843
|
+
reject(new Error(`Failed to parse MCP response: ${e}`));
|
|
11844
|
+
return;
|
|
11845
|
+
}
|
|
11846
|
+
}
|
|
11847
|
+
};
|
|
11848
|
+
conn.process.stdout.on("data", onData);
|
|
11849
|
+
conn.process.stdin.write(JSON.stringify(request) + "\n");
|
|
11850
|
+
});
|
|
11851
|
+
}
|
|
11852
|
+
async function connectToServer(name, config) {
|
|
11853
|
+
const existing = connections.get(name);
|
|
11854
|
+
if (existing?.connected && existing.process && !existing.process.killed) {
|
|
11855
|
+
return existing;
|
|
11856
|
+
}
|
|
11857
|
+
const root = getProjectRoot();
|
|
11858
|
+
const cwd = config.cwd ? resolve18(root, config.cwd) : root;
|
|
11859
|
+
const args2 = config.args || [];
|
|
11860
|
+
const proc = spawn(config.command, args2, {
|
|
11861
|
+
cwd,
|
|
11862
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11863
|
+
env: buildSubprocessEnv()
|
|
11864
|
+
});
|
|
11865
|
+
const conn = {
|
|
11866
|
+
config,
|
|
11867
|
+
process: proc,
|
|
11868
|
+
connected: false,
|
|
11869
|
+
tools: [],
|
|
11870
|
+
requestId: 0
|
|
11871
|
+
};
|
|
11872
|
+
try {
|
|
11873
|
+
await mcpRequest(conn, "initialize", {
|
|
11874
|
+
protocolVersion: "2024-11-05",
|
|
11875
|
+
capabilities: {},
|
|
11876
|
+
clientInfo: { name: "massu-mcp-bridge", version: "1.0.0" }
|
|
11877
|
+
});
|
|
11878
|
+
if (proc.stdin) {
|
|
11879
|
+
proc.stdin.write(JSON.stringify({
|
|
11880
|
+
jsonrpc: "2.0",
|
|
11881
|
+
method: "notifications/initialized",
|
|
11882
|
+
params: {}
|
|
11883
|
+
}) + "\n");
|
|
11884
|
+
}
|
|
11885
|
+
conn.connected = true;
|
|
11886
|
+
const toolsResult = await mcpRequest(conn, "tools/list", {});
|
|
11887
|
+
conn.tools = toolsResult.tools || [];
|
|
11888
|
+
connections.set(name, conn);
|
|
11889
|
+
return conn;
|
|
11890
|
+
} catch (e) {
|
|
11891
|
+
if (!proc.killed) proc.kill("SIGTERM");
|
|
11892
|
+
throw e;
|
|
11893
|
+
}
|
|
11894
|
+
}
|
|
11895
|
+
function disconnectServer(name) {
|
|
11896
|
+
const conn = connections.get(name);
|
|
11897
|
+
if (conn) {
|
|
11898
|
+
conn.connected = false;
|
|
11899
|
+
if (conn.process && !conn.process.killed) {
|
|
11900
|
+
conn.process.kill("SIGTERM");
|
|
11901
|
+
const proc = conn.process;
|
|
11902
|
+
setTimeout(() => {
|
|
11903
|
+
if (!proc.killed) {
|
|
11904
|
+
try {
|
|
11905
|
+
proc.kill("SIGKILL");
|
|
11906
|
+
} catch {
|
|
11907
|
+
}
|
|
11908
|
+
}
|
|
11909
|
+
}, 3e3);
|
|
11910
|
+
}
|
|
11911
|
+
connections.delete(name);
|
|
11912
|
+
}
|
|
11913
|
+
}
|
|
11914
|
+
function text17(s) {
|
|
11915
|
+
return { content: [{ type: "text", text: s }] };
|
|
11916
|
+
}
|
|
11917
|
+
function getMcpBridgeToolDefinitions() {
|
|
11918
|
+
return [
|
|
11919
|
+
{
|
|
11920
|
+
name: p17("mcp_servers"),
|
|
11921
|
+
description: "List all configured MCP servers from .mcp.json and their connection status.",
|
|
11922
|
+
inputSchema: {
|
|
11923
|
+
type: "object",
|
|
11924
|
+
properties: {},
|
|
11925
|
+
required: []
|
|
11926
|
+
}
|
|
11927
|
+
},
|
|
11928
|
+
{
|
|
11929
|
+
name: p17("mcp_tools"),
|
|
11930
|
+
description: "List tools available from a specific MCP server. Connects to the server if not already connected.",
|
|
11931
|
+
inputSchema: {
|
|
11932
|
+
type: "object",
|
|
11933
|
+
properties: {
|
|
11934
|
+
server: { type: "string", description: "MCP server name from .mcp.json" }
|
|
11935
|
+
},
|
|
11936
|
+
required: ["server"]
|
|
11937
|
+
}
|
|
11938
|
+
},
|
|
11939
|
+
{
|
|
11940
|
+
name: p17("mcp_call"),
|
|
11941
|
+
description: "Call a tool on a connected MCP server. Connects automatically if needed.",
|
|
11942
|
+
inputSchema: {
|
|
11943
|
+
type: "object",
|
|
11944
|
+
properties: {
|
|
11945
|
+
server: { type: "string", description: "MCP server name from .mcp.json" },
|
|
11946
|
+
tool: { type: "string", description: "Tool name to call on the remote server" },
|
|
11947
|
+
arguments: {
|
|
11948
|
+
type: "object",
|
|
11949
|
+
description: "Arguments to pass to the remote tool",
|
|
11950
|
+
additionalProperties: true
|
|
11951
|
+
}
|
|
11952
|
+
},
|
|
11953
|
+
required: ["server", "tool"]
|
|
11954
|
+
}
|
|
11955
|
+
},
|
|
11956
|
+
{
|
|
11957
|
+
name: p17("mcp_status"),
|
|
11958
|
+
description: "Health check all MCP server connections. Shows which are connected, disconnected, or errored.",
|
|
11959
|
+
inputSchema: {
|
|
11960
|
+
type: "object",
|
|
11961
|
+
properties: {},
|
|
11962
|
+
required: []
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11965
|
+
];
|
|
11966
|
+
}
|
|
11967
|
+
function isMcpBridgeTool(name) {
|
|
11968
|
+
const pfx = getConfig().toolPrefix;
|
|
11969
|
+
return name.startsWith(`${pfx}_mcp_`);
|
|
11970
|
+
}
|
|
11971
|
+
async function handleMcpBridgeToolCall(name, args2) {
|
|
11972
|
+
const pfx = getConfig().toolPrefix;
|
|
11973
|
+
const baseName = name.startsWith(`${pfx}_`) ? name.slice(pfx.length + 1) : name;
|
|
11974
|
+
switch (baseName) {
|
|
11975
|
+
case "mcp_servers":
|
|
11976
|
+
return handleMcpServers();
|
|
11977
|
+
case "mcp_tools":
|
|
11978
|
+
return await handleMcpTools(args2.server);
|
|
11979
|
+
case "mcp_call":
|
|
11980
|
+
return await handleMcpCall(
|
|
11981
|
+
args2.server,
|
|
11982
|
+
args2.tool,
|
|
11983
|
+
args2.arguments || {}
|
|
11984
|
+
);
|
|
11985
|
+
case "mcp_status":
|
|
11986
|
+
return handleMcpStatus();
|
|
11987
|
+
default:
|
|
11988
|
+
return text17(`Unknown MCP bridge tool: ${name}`);
|
|
11989
|
+
}
|
|
11990
|
+
}
|
|
11991
|
+
function handleMcpServers() {
|
|
11992
|
+
const configs = loadMcpConfig();
|
|
11993
|
+
const servers = Object.entries(configs).map(([name, config]) => {
|
|
11994
|
+
const conn = connections.get(name);
|
|
11995
|
+
return {
|
|
11996
|
+
name,
|
|
11997
|
+
command: config.command,
|
|
11998
|
+
args: config.args || [],
|
|
11999
|
+
cwd: config.cwd || ".",
|
|
12000
|
+
status: conn?.connected ? "connected" : "disconnected",
|
|
12001
|
+
toolCount: conn?.tools.length || 0
|
|
12002
|
+
};
|
|
12003
|
+
});
|
|
12004
|
+
if (servers.length === 0) {
|
|
12005
|
+
return text17("No MCP servers configured in .mcp.json (excluding self).");
|
|
12006
|
+
}
|
|
12007
|
+
const lines = ["# MCP Servers\n"];
|
|
12008
|
+
for (const srv of servers) {
|
|
12009
|
+
const status = srv.status === "connected" ? "CONNECTED" : "DISCONNECTED";
|
|
12010
|
+
lines.push(`- **${srv.name}** [${status}] \u2014 \`${srv.command} ${srv.args.join(" ")}\` (${srv.toolCount} tools)`);
|
|
12011
|
+
}
|
|
12012
|
+
return text17(lines.join("\n"));
|
|
12013
|
+
}
|
|
12014
|
+
async function handleMcpTools(server) {
|
|
12015
|
+
const configs = loadMcpConfig();
|
|
12016
|
+
const config = configs[server];
|
|
12017
|
+
if (!config) {
|
|
12018
|
+
return text17(`MCP server "${server}" not found in .mcp.json. Available: ${Object.keys(configs).join(", ") || "none"}`);
|
|
12019
|
+
}
|
|
12020
|
+
try {
|
|
12021
|
+
const conn = await connectToServer(server, config);
|
|
12022
|
+
if (conn.tools.length === 0) {
|
|
12023
|
+
return text17(`MCP server "${server}" is connected but has no tools.`);
|
|
12024
|
+
}
|
|
12025
|
+
const lines = [`# Tools from ${server} (${conn.tools.length})
|
|
12026
|
+
`];
|
|
12027
|
+
for (const tool of conn.tools) {
|
|
12028
|
+
lines.push(`- **${tool.name}**: ${tool.description}`);
|
|
12029
|
+
}
|
|
12030
|
+
return text17(lines.join("\n"));
|
|
12031
|
+
} catch (e) {
|
|
12032
|
+
return text17(`Failed to connect to MCP server "${server}": ${e}`);
|
|
12033
|
+
}
|
|
12034
|
+
}
|
|
12035
|
+
async function handleMcpCall(server, tool, args2) {
|
|
12036
|
+
const configs = loadMcpConfig();
|
|
12037
|
+
const config = configs[server];
|
|
12038
|
+
if (!config) {
|
|
12039
|
+
return text17(`MCP server "${server}" not found in .mcp.json.`);
|
|
12040
|
+
}
|
|
12041
|
+
try {
|
|
12042
|
+
const conn = await connectToServer(server, config);
|
|
12043
|
+
const result = await mcpRequest(conn, "tools/call", { name: tool, arguments: args2 });
|
|
12044
|
+
const content = result.content;
|
|
12045
|
+
if (Array.isArray(content)) {
|
|
12046
|
+
const texts = content.filter((c) => c.type === "text").map((c) => c.text);
|
|
12047
|
+
if (result.isError) {
|
|
12048
|
+
return text17(`MCP tool error: ${texts.join("\n")}`);
|
|
12049
|
+
}
|
|
12050
|
+
return text17(texts.join("\n"));
|
|
12051
|
+
}
|
|
12052
|
+
return text17(JSON.stringify(result, null, 2));
|
|
12053
|
+
} catch (e) {
|
|
12054
|
+
const conn = connections.get(server);
|
|
12055
|
+
if (conn) conn.connected = false;
|
|
12056
|
+
return text17(`MCP call failed (${server}/${tool}): ${e}`);
|
|
12057
|
+
}
|
|
12058
|
+
}
|
|
12059
|
+
function handleMcpStatus() {
|
|
12060
|
+
const configs = loadMcpConfig();
|
|
12061
|
+
const lines = ["# MCP Connection Status\n"];
|
|
12062
|
+
for (const [name] of Object.entries(configs)) {
|
|
12063
|
+
const conn = connections.get(name);
|
|
12064
|
+
if (!conn) {
|
|
12065
|
+
lines.push(`- **${name}**: NOT CONNECTED`);
|
|
12066
|
+
} else if (conn.connected && conn.process && !conn.process.killed) {
|
|
12067
|
+
lines.push(`- **${name}**: HEALTHY (pid=${conn.process.pid}, ${conn.tools.length} tools)`);
|
|
12068
|
+
} else {
|
|
12069
|
+
lines.push(`- **${name}**: DISCONNECTED`);
|
|
12070
|
+
disconnectServer(name);
|
|
12071
|
+
}
|
|
12072
|
+
}
|
|
12073
|
+
if (Object.keys(configs).length === 0) {
|
|
12074
|
+
lines.push("No MCP servers configured.");
|
|
12075
|
+
}
|
|
12076
|
+
return text17(lines.join("\n"));
|
|
12077
|
+
}
|
|
12078
|
+
var connections, ENV_ALLOW_LIST, ENV_DENY_PATTERNS;
|
|
12079
|
+
var init_mcp_bridge_tools = __esm({
|
|
12080
|
+
"src/mcp-bridge-tools.ts"() {
|
|
12081
|
+
"use strict";
|
|
12082
|
+
init_config();
|
|
12083
|
+
connections = /* @__PURE__ */ new Map();
|
|
12084
|
+
process.on("exit", () => {
|
|
12085
|
+
for (const [, conn] of connections) {
|
|
12086
|
+
if (conn.process && !conn.process.killed) {
|
|
12087
|
+
try {
|
|
12088
|
+
conn.process.kill("SIGTERM");
|
|
12089
|
+
} catch {
|
|
12090
|
+
}
|
|
12091
|
+
}
|
|
12092
|
+
}
|
|
12093
|
+
});
|
|
12094
|
+
process.on("SIGTERM", () => {
|
|
12095
|
+
for (const [name] of connections) disconnectServer(name);
|
|
12096
|
+
process.exit(0);
|
|
12097
|
+
});
|
|
12098
|
+
ENV_ALLOW_LIST = /* @__PURE__ */ new Set([
|
|
12099
|
+
"PATH",
|
|
12100
|
+
"HOME",
|
|
12101
|
+
"LANG",
|
|
12102
|
+
"LC_ALL",
|
|
12103
|
+
"TERM",
|
|
12104
|
+
"PYTHONPATH",
|
|
12105
|
+
"NODE_PATH"
|
|
12106
|
+
]);
|
|
12107
|
+
ENV_DENY_PATTERNS = [
|
|
12108
|
+
"PRIVATE_KEY",
|
|
12109
|
+
"SECRET_KEY",
|
|
12110
|
+
"API_SECRET",
|
|
12111
|
+
"AUTH_DISABLED",
|
|
12112
|
+
"COLD_STORAGE",
|
|
12113
|
+
"PASSWORD",
|
|
12114
|
+
"TOKEN"
|
|
12115
|
+
];
|
|
12116
|
+
}
|
|
12117
|
+
});
|
|
12118
|
+
|
|
12119
|
+
// src/tools.ts
|
|
12120
|
+
import { readFileSync as readFileSync23, existsSync as existsSync23 } from "fs";
|
|
12121
|
+
import { resolve as resolve19, basename as basename7 } from "path";
|
|
11740
12122
|
function prefix() {
|
|
11741
12123
|
return getConfig().toolPrefix;
|
|
11742
12124
|
}
|
|
11743
|
-
function
|
|
12125
|
+
function p18(name) {
|
|
11744
12126
|
return `${prefix()}_${name}`;
|
|
11745
12127
|
}
|
|
11746
12128
|
function stripPrefix3(name) {
|
|
@@ -11771,7 +12153,7 @@ function ensureIndexes(dataDb2, codegraphDb2, force = false) {
|
|
|
11771
12153
|
if (config.python?.root) {
|
|
11772
12154
|
const pythonRoot = config.python.root;
|
|
11773
12155
|
const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
|
|
11774
|
-
if (force || isPythonDataStale(dataDb2,
|
|
12156
|
+
if (force || isPythonDataStale(dataDb2, resolve19(getProjectRoot(), pythonRoot))) {
|
|
11775
12157
|
const pyImports = buildPythonImportIndex(dataDb2, pythonRoot, excludeDirs);
|
|
11776
12158
|
results.push(`Python imports: ${pyImports}`);
|
|
11777
12159
|
const pyRoutes = buildPythonRouteIndex(dataDb2, pythonRoot, excludeDirs);
|
|
@@ -11820,11 +12202,13 @@ function getToolDefinitions() {
|
|
|
11820
12202
|
...getKnowledgeToolDefinitions(),
|
|
11821
12203
|
// Python code intelligence tools
|
|
11822
12204
|
...getPythonToolDefinitions(),
|
|
12205
|
+
// MCP bridge tools (cross-project tool mesh)
|
|
12206
|
+
...getMcpBridgeToolDefinitions(),
|
|
11823
12207
|
// License tools (always available)
|
|
11824
12208
|
...getLicenseToolDefinitions(),
|
|
11825
12209
|
// Core tools
|
|
11826
12210
|
{
|
|
11827
|
-
name:
|
|
12211
|
+
name: p18("sync"),
|
|
11828
12212
|
description: "Force rebuild all indexes (import edges, tRPC mappings, page deps, middleware tree). Run this after significant code changes.",
|
|
11829
12213
|
inputSchema: {
|
|
11830
12214
|
type: "object",
|
|
@@ -11833,7 +12217,7 @@ function getToolDefinitions() {
|
|
|
11833
12217
|
}
|
|
11834
12218
|
},
|
|
11835
12219
|
{
|
|
11836
|
-
name:
|
|
12220
|
+
name: p18("context"),
|
|
11837
12221
|
description: "Get context for a file: applicable rules, pattern warnings, schema mismatch alerts, and whether the file is in the middleware import tree.",
|
|
11838
12222
|
inputSchema: {
|
|
11839
12223
|
type: "object",
|
|
@@ -11845,7 +12229,7 @@ function getToolDefinitions() {
|
|
|
11845
12229
|
},
|
|
11846
12230
|
...config.framework.router === "trpc" ? [
|
|
11847
12231
|
{
|
|
11848
|
-
name:
|
|
12232
|
+
name: p18("trpc_map"),
|
|
11849
12233
|
description: "Map tRPC procedures to their UI call sites. Find which components call a router, which procedures have no UI callers, or list all procedures for a router.",
|
|
11850
12234
|
inputSchema: {
|
|
11851
12235
|
type: "object",
|
|
@@ -11858,7 +12242,7 @@ function getToolDefinitions() {
|
|
|
11858
12242
|
}
|
|
11859
12243
|
},
|
|
11860
12244
|
{
|
|
11861
|
-
name:
|
|
12245
|
+
name: p18("coupling_check"),
|
|
11862
12246
|
description: "Automated coupling check. Finds all procedures with zero UI callers and components not rendered in any page.",
|
|
11863
12247
|
inputSchema: {
|
|
11864
12248
|
type: "object",
|
|
@@ -11874,7 +12258,7 @@ function getToolDefinitions() {
|
|
|
11874
12258
|
}
|
|
11875
12259
|
] : [],
|
|
11876
12260
|
{
|
|
11877
|
-
name:
|
|
12261
|
+
name: p18("impact"),
|
|
11878
12262
|
description: "Full impact analysis for a file: which pages are affected, which database tables are in the chain, middleware tree membership, domain crossings.",
|
|
11879
12263
|
inputSchema: {
|
|
11880
12264
|
type: "object",
|
|
@@ -11885,7 +12269,7 @@ function getToolDefinitions() {
|
|
|
11885
12269
|
}
|
|
11886
12270
|
},
|
|
11887
12271
|
...config.domains.length > 0 ? [{
|
|
11888
|
-
name:
|
|
12272
|
+
name: p18("domains"),
|
|
11889
12273
|
description: "Domain boundary information. Classify a file into its domain, show cross-domain imports, or list all files in a domain.",
|
|
11890
12274
|
inputSchema: {
|
|
11891
12275
|
type: "object",
|
|
@@ -11898,7 +12282,7 @@ function getToolDefinitions() {
|
|
|
11898
12282
|
}
|
|
11899
12283
|
}] : [],
|
|
11900
12284
|
...config.framework.orm === "prisma" ? [{
|
|
11901
|
-
name:
|
|
12285
|
+
name: p18("schema"),
|
|
11902
12286
|
description: "Prisma schema cross-reference. Show columns for a table, detect mismatches between code and schema, or verify column references in a file.",
|
|
11903
12287
|
inputSchema: {
|
|
11904
12288
|
type: "object",
|
|
@@ -11916,7 +12300,7 @@ async function handleToolCall(name, args2, dataDb2, codegraphDb2) {
|
|
|
11916
12300
|
const userTier = await getCurrentTier();
|
|
11917
12301
|
const requiredTier = getToolTier(name);
|
|
11918
12302
|
if (!isToolAllowed(name, userTier)) {
|
|
11919
|
-
return
|
|
12303
|
+
return text18(`This tool requires ${requiredTier} tier. Current tier: ${userTier}. Upgrade at https://massu.ai/pricing`);
|
|
11920
12304
|
}
|
|
11921
12305
|
const syncMessage = ensureIndexes(dataDb2, codegraphDb2);
|
|
11922
12306
|
const pfx = prefix();
|
|
@@ -12034,6 +12418,9 @@ async function handleToolCall(name, args2, dataDb2, codegraphDb2) {
|
|
|
12034
12418
|
if (isPythonTool(name)) {
|
|
12035
12419
|
return handlePythonToolCall(name, args2, dataDb2);
|
|
12036
12420
|
}
|
|
12421
|
+
if (isMcpBridgeTool(name)) {
|
|
12422
|
+
return await handleMcpBridgeToolCall(name, args2);
|
|
12423
|
+
}
|
|
12037
12424
|
if (isLicenseTool(name)) {
|
|
12038
12425
|
const memDb = getMemoryDb();
|
|
12039
12426
|
try {
|
|
@@ -12059,26 +12446,26 @@ async function handleToolCall(name, args2, dataDb2, codegraphDb2) {
|
|
|
12059
12446
|
case "schema":
|
|
12060
12447
|
return handleSchema(args2);
|
|
12061
12448
|
default:
|
|
12062
|
-
return
|
|
12449
|
+
return text18(`Unknown tool: ${name}`);
|
|
12063
12450
|
}
|
|
12064
12451
|
} catch (error) {
|
|
12065
12452
|
const msg = error instanceof Error ? error.message : String(error);
|
|
12066
12453
|
const safeMsg = msg.split("\n")[0].replace(/\/(Users|home|var|tmp)\/[^\s:]+/g, "<path>");
|
|
12067
|
-
return
|
|
12454
|
+
return text18(`Error in ${name}: ${safeMsg}`);
|
|
12068
12455
|
}
|
|
12069
12456
|
}
|
|
12070
|
-
function
|
|
12457
|
+
function text18(content) {
|
|
12071
12458
|
return { content: [{ type: "text", text: content }] };
|
|
12072
12459
|
}
|
|
12073
12460
|
function handleSync(dataDb2, codegraphDb2) {
|
|
12074
12461
|
const result = ensureIndexes(dataDb2, codegraphDb2, true);
|
|
12075
12462
|
try {
|
|
12076
12463
|
const scanResult = runFeatureScan(dataDb2);
|
|
12077
|
-
return
|
|
12464
|
+
return text18(`${result}
|
|
12078
12465
|
|
|
12079
12466
|
Feature scan: ${scanResult.registered} features registered (${scanResult.fromProcedures} from procedures, ${scanResult.fromPages} from pages, ${scanResult.fromComponents} from components)`);
|
|
12080
12467
|
} catch (error) {
|
|
12081
|
-
return
|
|
12468
|
+
return text18(`${result}
|
|
12082
12469
|
|
|
12083
12470
|
Feature scan failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
12084
12471
|
}
|
|
@@ -12170,9 +12557,9 @@ function handleContext(file, dataDb2, codegraphDb2) {
|
|
|
12170
12557
|
try {
|
|
12171
12558
|
const resolvedPaths = getResolvedPaths();
|
|
12172
12559
|
const root = getProjectRoot();
|
|
12173
|
-
const absFilePath = ensureWithinRoot(
|
|
12174
|
-
if (
|
|
12175
|
-
const fileContent =
|
|
12560
|
+
const absFilePath = ensureWithinRoot(resolve19(resolvedPaths.srcDir, "..", file), root);
|
|
12561
|
+
if (existsSync23(absFilePath)) {
|
|
12562
|
+
const fileContent = readFileSync23(absFilePath, "utf-8").slice(0, 3e3);
|
|
12176
12563
|
const keywords = [];
|
|
12177
12564
|
if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
|
|
12178
12565
|
if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
|
|
@@ -12294,7 +12681,7 @@ function handleContext(file, dataDb2, codegraphDb2) {
|
|
|
12294
12681
|
} finally {
|
|
12295
12682
|
memDb?.close();
|
|
12296
12683
|
}
|
|
12297
|
-
return
|
|
12684
|
+
return text18(lines.join("\n") || "No context available for this file.");
|
|
12298
12685
|
}
|
|
12299
12686
|
function handleTrpcMap(args2, dataDb2) {
|
|
12300
12687
|
const lines = [];
|
|
@@ -12369,7 +12756,7 @@ function handleTrpcMap(args2, dataDb2) {
|
|
|
12369
12756
|
lines.push('Use { router: "name" } to see details for a specific router.');
|
|
12370
12757
|
lines.push("Use { uncoupled: true } to see all procedures without UI callers.");
|
|
12371
12758
|
}
|
|
12372
|
-
return
|
|
12759
|
+
return text18(lines.join("\n"));
|
|
12373
12760
|
}
|
|
12374
12761
|
function handleCouplingCheck(args2, dataDb2, codegraphDb2) {
|
|
12375
12762
|
const lines = [];
|
|
@@ -12427,7 +12814,7 @@ function handleCouplingCheck(args2, dataDb2, codegraphDb2) {
|
|
|
12427
12814
|
}
|
|
12428
12815
|
const totalIssues = uncoupledProcs.length + orphanComponents.length;
|
|
12429
12816
|
lines.push(`### RESULT: ${totalIssues === 0 ? "PASS" : `FAIL (${totalIssues} issues)`}`);
|
|
12430
|
-
return
|
|
12817
|
+
return text18(lines.join("\n"));
|
|
12431
12818
|
}
|
|
12432
12819
|
function handleImpact2(file, dataDb2, codegraphDb2) {
|
|
12433
12820
|
const lines = [];
|
|
@@ -12435,9 +12822,9 @@ function handleImpact2(file, dataDb2, codegraphDb2) {
|
|
|
12435
12822
|
lines.push("");
|
|
12436
12823
|
const affectedPages = findAffectedPages(dataDb2, file);
|
|
12437
12824
|
if (affectedPages.length > 0) {
|
|
12438
|
-
const portals = [...new Set(affectedPages.map((
|
|
12439
|
-
const allTables = [...new Set(affectedPages.flatMap((
|
|
12440
|
-
const allRouters = [...new Set(affectedPages.flatMap((
|
|
12825
|
+
const portals = [...new Set(affectedPages.map((p19) => p19.portal))];
|
|
12826
|
+
const allTables = [...new Set(affectedPages.flatMap((p19) => p19.tables))];
|
|
12827
|
+
const allRouters = [...new Set(affectedPages.flatMap((p19) => p19.routers))];
|
|
12441
12828
|
lines.push(`### Pages Affected: ${affectedPages.length}`);
|
|
12442
12829
|
for (const page of affectedPages) {
|
|
12443
12830
|
lines.push(`- ${page.route} (${page.portal})`);
|
|
@@ -12489,7 +12876,7 @@ function handleImpact2(file, dataDb2, codegraphDb2) {
|
|
|
12489
12876
|
lines.push(`- -> ${crossing}`);
|
|
12490
12877
|
}
|
|
12491
12878
|
}
|
|
12492
|
-
return
|
|
12879
|
+
return text18(lines.join("\n"));
|
|
12493
12880
|
}
|
|
12494
12881
|
function handleDomains(args2, dataDb2, codegraphDb2) {
|
|
12495
12882
|
const lines = [];
|
|
@@ -12534,7 +12921,7 @@ function handleDomains(args2, dataDb2, codegraphDb2) {
|
|
|
12534
12921
|
for (const r of files.routers) lines.push(`- ${r}`);
|
|
12535
12922
|
lines.push("");
|
|
12536
12923
|
lines.push(`### Pages (${files.pages.length})`);
|
|
12537
|
-
for (const
|
|
12924
|
+
for (const p19 of files.pages) lines.push(`- ${p19}`);
|
|
12538
12925
|
lines.push("");
|
|
12539
12926
|
lines.push(`### Components (${files.components.length})`);
|
|
12540
12927
|
for (const c of files.components.slice(0, 30)) lines.push(`- ${c}`);
|
|
@@ -12545,7 +12932,7 @@ function handleDomains(args2, dataDb2, codegraphDb2) {
|
|
|
12545
12932
|
lines.push(`- **${domain.name}**: ${domain.routers.length} router patterns, imports from: ${domain.allowedImportsFrom.join(", ") || "any"}`);
|
|
12546
12933
|
}
|
|
12547
12934
|
}
|
|
12548
|
-
return
|
|
12935
|
+
return text18(lines.join("\n"));
|
|
12549
12936
|
}
|
|
12550
12937
|
function handleSchema(args2) {
|
|
12551
12938
|
const lines = [];
|
|
@@ -12570,7 +12957,7 @@ function handleSchema(args2) {
|
|
|
12570
12957
|
const tableName = args2.table;
|
|
12571
12958
|
const model = models.find((m) => m.tableName === tableName || m.name === tableName);
|
|
12572
12959
|
if (!model) {
|
|
12573
|
-
return
|
|
12960
|
+
return text18(`Model/table "${tableName}" not found in Prisma schema.`);
|
|
12574
12961
|
}
|
|
12575
12962
|
lines.push(`## ${model.name} (table: ${model.tableName})`);
|
|
12576
12963
|
lines.push("");
|
|
@@ -12596,11 +12983,11 @@ function handleSchema(args2) {
|
|
|
12596
12983
|
lines.push("Checking all column references against Prisma schema...");
|
|
12597
12984
|
lines.push("");
|
|
12598
12985
|
const projectRoot = getProjectRoot();
|
|
12599
|
-
const absPath = ensureWithinRoot(
|
|
12600
|
-
if (!
|
|
12601
|
-
return
|
|
12986
|
+
const absPath = ensureWithinRoot(resolve19(projectRoot, file), projectRoot);
|
|
12987
|
+
if (!existsSync23(absPath)) {
|
|
12988
|
+
return text18(`File not found: ${file}`);
|
|
12602
12989
|
}
|
|
12603
|
-
const source =
|
|
12990
|
+
const source = readFileSync23(absPath, "utf-8");
|
|
12604
12991
|
const config = getConfig();
|
|
12605
12992
|
const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
|
|
12606
12993
|
const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
|
|
@@ -12633,7 +13020,7 @@ function handleSchema(args2) {
|
|
|
12633
13020
|
}
|
|
12634
13021
|
}
|
|
12635
13022
|
}
|
|
12636
|
-
return
|
|
13023
|
+
return text18(lines.join("\n"));
|
|
12637
13024
|
}
|
|
12638
13025
|
var init_tools = __esm({
|
|
12639
13026
|
"src/tools.ts"() {
|
|
@@ -12673,13 +13060,14 @@ var init_tools = __esm({
|
|
|
12673
13060
|
init_python_tools();
|
|
12674
13061
|
init_config();
|
|
12675
13062
|
init_license();
|
|
13063
|
+
init_mcp_bridge_tools();
|
|
12676
13064
|
}
|
|
12677
13065
|
});
|
|
12678
13066
|
|
|
12679
13067
|
// src/server.ts
|
|
12680
13068
|
var server_exports = {};
|
|
12681
|
-
import { readFileSync as
|
|
12682
|
-
import { resolve as
|
|
13069
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
13070
|
+
import { resolve as resolve20, dirname as dirname11 } from "path";
|
|
12683
13071
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12684
13072
|
function getDb() {
|
|
12685
13073
|
if (!codegraphDb) codegraphDb = getCodeGraphDb();
|
|
@@ -12774,7 +13162,7 @@ var init_server = __esm({
|
|
|
12774
13162
|
__dirname4 = dirname11(fileURLToPath4(import.meta.url));
|
|
12775
13163
|
PKG_VERSION = (() => {
|
|
12776
13164
|
try {
|
|
12777
|
-
const pkg = JSON.parse(
|
|
13165
|
+
const pkg = JSON.parse(readFileSync24(resolve20(__dirname4, "..", "package.json"), "utf-8"));
|
|
12778
13166
|
return pkg.version ?? "0.0.0";
|
|
12779
13167
|
} catch {
|
|
12780
13168
|
return "0.0.0";
|
|
@@ -12838,8 +13226,8 @@ var init_server = __esm({
|
|
|
12838
13226
|
});
|
|
12839
13227
|
|
|
12840
13228
|
// src/cli.ts
|
|
12841
|
-
import { readFileSync as
|
|
12842
|
-
import { resolve as
|
|
13229
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
13230
|
+
import { resolve as resolve21, dirname as dirname12 } from "path";
|
|
12843
13231
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
12844
13232
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
12845
13233
|
var __dirname5 = dirname12(__filename4);
|
|
@@ -12913,7 +13301,7 @@ Documentation: https://massu.ai/docs
|
|
|
12913
13301
|
}
|
|
12914
13302
|
function printVersion() {
|
|
12915
13303
|
try {
|
|
12916
|
-
const pkg = JSON.parse(
|
|
13304
|
+
const pkg = JSON.parse(readFileSync25(resolve21(__dirname5, "../package.json"), "utf-8"));
|
|
12917
13305
|
console.log(`massu v${pkg.version}`);
|
|
12918
13306
|
} catch {
|
|
12919
13307
|
console.log("massu v0.1.0");
|