mcp-probe-kit 3.0.13 → 3.0.14
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
CHANGED
|
@@ -347,6 +347,12 @@ Recommended on Windows:
|
|
|
347
347
|
2. Prefer stable local/global CLI usage for GitNexus when your MCP client supports `env`.
|
|
348
348
|
3. Increase GitNexus connect/call timeouts on slower or first-run environments.
|
|
349
349
|
|
|
350
|
+
Quick install command (Windows):
|
|
351
|
+
|
|
352
|
+
```powershell
|
|
353
|
+
winget install Microsoft.VisualStudio.2022.BuildTools
|
|
354
|
+
```
|
|
355
|
+
|
|
350
356
|
Example config using a preinstalled `gitnexus` CLI:
|
|
351
357
|
|
|
352
358
|
```json
|
|
@@ -333,6 +333,60 @@ function buildAmbiguities(executions) {
|
|
|
333
333
|
};
|
|
334
334
|
});
|
|
335
335
|
}
|
|
336
|
+
function getLastExecutionByTool(executions, tool) {
|
|
337
|
+
for (let index = executions.length - 1; index >= 0; index -= 1) {
|
|
338
|
+
if (executions[index]?.tool === tool) {
|
|
339
|
+
return executions[index];
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
function inferSymbolKind(value) {
|
|
345
|
+
const record = toRecord(value);
|
|
346
|
+
if (!record) {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
for (const key of ["kind", "type", "symbolType"]) {
|
|
350
|
+
const candidate = record[key];
|
|
351
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
352
|
+
return candidate.trim();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const id = record.id;
|
|
356
|
+
if (typeof id === "string" && id.includes(":")) {
|
|
357
|
+
return id.split(":")[0];
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
function isNonCallableSymbolKind(kind) {
|
|
362
|
+
if (!kind) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
const normalized = kind.toLowerCase();
|
|
366
|
+
return normalized.includes("folder")
|
|
367
|
+
|| normalized.includes("file")
|
|
368
|
+
|| normalized.includes("module")
|
|
369
|
+
|| normalized.includes("community")
|
|
370
|
+
|| normalized.includes("process")
|
|
371
|
+
|| normalized.includes("package");
|
|
372
|
+
}
|
|
373
|
+
function normalizeImpactTargetByContext(contextExecution, originalTarget) {
|
|
374
|
+
const target = toRecord(contextExecution.structuredContent)?.target;
|
|
375
|
+
const kind = inferSymbolKind(target);
|
|
376
|
+
if (!isNonCallableSymbolKind(kind)) {
|
|
377
|
+
return contextExecution;
|
|
378
|
+
}
|
|
379
|
+
const message = `impact 目标 "${originalTarget}" 被解析为 ${kind},请改用函数/方法 uid 或补充 file_path 再重试。`;
|
|
380
|
+
contextExecution.status = "ambiguous";
|
|
381
|
+
contextExecution.text = message;
|
|
382
|
+
contextExecution.structuredContent = {
|
|
383
|
+
status: "ambiguous",
|
|
384
|
+
message,
|
|
385
|
+
suggestion: "For impact analysis, prefer Method/Function over Folder/File.",
|
|
386
|
+
candidates: target ? [target] : [],
|
|
387
|
+
};
|
|
388
|
+
return contextExecution;
|
|
389
|
+
}
|
|
336
390
|
const QUERY_STOP_WORDS = new Set([
|
|
337
391
|
"the", "and", "for", "with", "from", "into", "that", "this", "user", "flow",
|
|
338
392
|
"sign", "code", "project", "module",
|
|
@@ -383,23 +437,38 @@ function scoreQueryCandidate(candidate, terms) {
|
|
|
383
437
|
if (fields.length === 0 || terms.length === 0) {
|
|
384
438
|
return 0;
|
|
385
439
|
}
|
|
386
|
-
let score =
|
|
440
|
+
let score = -30;
|
|
387
441
|
let matchedTerms = 0;
|
|
388
442
|
for (const term of terms) {
|
|
389
443
|
let matched = false;
|
|
390
444
|
for (const field of fields) {
|
|
391
|
-
|
|
445
|
+
const value = field.value;
|
|
446
|
+
const key = field.key;
|
|
447
|
+
const exact = value === term;
|
|
448
|
+
const wholeWord = new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i").test(value);
|
|
449
|
+
const partial = value.includes(term);
|
|
450
|
+
if (!partial) {
|
|
392
451
|
continue;
|
|
393
452
|
}
|
|
394
453
|
matched = true;
|
|
395
|
-
|
|
396
|
-
|
|
454
|
+
const inName = key.includes("name") || key.includes("title") || key.includes("label") || key.includes("symbol");
|
|
455
|
+
const inPath = key.includes("path") || key.includes("file") || key.includes("module");
|
|
456
|
+
const inDocs = key.includes("comment") || key.includes("doc") || key.includes("description") || key.includes("summary");
|
|
457
|
+
const inCode = key.includes("content") || key.includes("source") || key.includes("body") || key.includes("code");
|
|
458
|
+
if (inName) {
|
|
459
|
+
score += exact ? 120 : wholeWord ? 90 : 55;
|
|
460
|
+
}
|
|
461
|
+
else if (inPath) {
|
|
462
|
+
score += wholeWord ? 70 : 45;
|
|
397
463
|
}
|
|
398
|
-
else if (
|
|
399
|
-
score +=
|
|
464
|
+
else if (inDocs) {
|
|
465
|
+
score += wholeWord ? 26 : 16;
|
|
466
|
+
}
|
|
467
|
+
else if (inCode) {
|
|
468
|
+
score += wholeWord ? 18 : 10;
|
|
400
469
|
}
|
|
401
470
|
else {
|
|
402
|
-
score +=
|
|
471
|
+
score += wholeWord ? 24 : 12;
|
|
403
472
|
}
|
|
404
473
|
}
|
|
405
474
|
if (matched) {
|
|
@@ -407,15 +476,18 @@ function scoreQueryCandidate(candidate, terms) {
|
|
|
407
476
|
}
|
|
408
477
|
}
|
|
409
478
|
if (matchedTerms > 0) {
|
|
410
|
-
score += matchedTerms *
|
|
479
|
+
score += matchedTerms * 14;
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
score -= 60;
|
|
411
483
|
}
|
|
412
484
|
if (matchedTerms === terms.length) {
|
|
413
|
-
score +=
|
|
485
|
+
score += 35;
|
|
414
486
|
}
|
|
415
487
|
const candidateRecord = toRecord(candidate);
|
|
416
488
|
const priority = candidateRecord?.priority;
|
|
417
489
|
if (typeof priority === "number" && Number.isFinite(priority)) {
|
|
418
|
-
score += priority;
|
|
490
|
+
score += matchedTerms > 0 ? (priority * 5) : (priority * 0.5);
|
|
419
491
|
}
|
|
420
492
|
return score;
|
|
421
493
|
}
|
|
@@ -957,6 +1029,28 @@ export async function runCodeInsightBridge(request) {
|
|
|
957
1029
|
warnings.push("缺少 target/uid 参数,已跳过 impact");
|
|
958
1030
|
return;
|
|
959
1031
|
}
|
|
1032
|
+
if (!request.uid && request.target) {
|
|
1033
|
+
let contextExecution = getLastExecutionByTool(executions, "context");
|
|
1034
|
+
if (!contextExecution) {
|
|
1035
|
+
contextExecution = await callBridgeTool(client, "context", {
|
|
1036
|
+
name: request.target,
|
|
1037
|
+
...(request.filePath ? { file_path: request.filePath } : {}),
|
|
1038
|
+
...(effectiveRepo ? { repo: effectiveRepo } : {}),
|
|
1039
|
+
}, request.signal, workspace);
|
|
1040
|
+
executions.push(contextExecution);
|
|
1041
|
+
}
|
|
1042
|
+
if (contextExecution.ok && contextExecution.status === "ambiguous") {
|
|
1043
|
+
warnings.push("impact_target_ambiguous");
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (contextExecution.ok) {
|
|
1047
|
+
normalizeImpactTargetByContext(contextExecution, request.target);
|
|
1048
|
+
if (contextExecution.status === "ambiguous") {
|
|
1049
|
+
warnings.push("impact_target_non_callable");
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
960
1054
|
executions.push(await callBridgeTool(client, "impact", {
|
|
961
1055
|
target: request.uid || request.target,
|
|
962
1056
|
direction: request.direction || "upstream",
|
|
@@ -101,6 +101,7 @@ describe("code_insight 单元测试", () => {
|
|
|
101
101
|
});
|
|
102
102
|
expect(status).toBe("ambiguous");
|
|
103
103
|
expect(plan?.kind).toBe("ambiguity");
|
|
104
|
+
expect(plan?.steps).toHaveLength(2);
|
|
104
105
|
expect(plan?.steps[1].action).toMatch(/uid 或 file_path/);
|
|
105
106
|
});
|
|
106
107
|
test("未显式要求保存时不生成 docs delegated plan", async () => {
|
|
@@ -113,7 +114,8 @@ describe("code_insight 单元测试", () => {
|
|
|
113
114
|
expect(result.isError).toBe(false);
|
|
114
115
|
const text = String(result.content?.[0]?.text || "");
|
|
115
116
|
const structured = result.structuredContent;
|
|
116
|
-
expect(text).not.toMatch(
|
|
117
|
+
expect(text).not.toMatch(/## delegated plan/);
|
|
118
|
+
expect(text).toMatch(/使用场景指南/);
|
|
117
119
|
expect(structured.plan).toBeUndefined();
|
|
118
120
|
expect(structured.projectDocs).toBeUndefined();
|
|
119
121
|
}
|
|
@@ -141,15 +143,16 @@ describe("code_insight 单元测试", () => {
|
|
|
141
143
|
expect(text).toMatch(/delegated plan/);
|
|
142
144
|
expect(text).toMatch(/不要只口头总结而不写文件/);
|
|
143
145
|
expect(text).toMatch(/docs\/graph-insights\/latest\.md/);
|
|
144
|
-
expect(text).toMatch(
|
|
146
|
+
expect(text).toMatch(/使用场景指南/);
|
|
145
147
|
expect(structured.projectDocs.latestMarkdownFilePath).toContain("/docs/graph-insights/latest.md");
|
|
146
148
|
expect(structured.projectDocs.archiveMarkdownFilePath).toContain("/docs/graph-insights/");
|
|
147
149
|
expect(structured.projectDocs.projectContextFilePath).toContain("/docs/project-context.md");
|
|
148
150
|
expect(structured.projectDocs.navigationSnippet).toMatch(/代码图谱洞察/);
|
|
149
151
|
expect(structured.plan.mode).toBe("delegated");
|
|
150
|
-
expect(structured.plan.steps
|
|
151
|
-
|
|
152
|
-
expect(
|
|
152
|
+
expect(structured.plan.steps).toHaveLength(2);
|
|
153
|
+
expect(structured.plan.steps[0].id).toBe("consume-result");
|
|
154
|
+
expect(structured.plan.steps[1].id).toBe("optional-save");
|
|
155
|
+
expect(structured.plan.steps[1].outputs[0]).toContain("/docs/graph-insights/latest.md");
|
|
153
156
|
expect(fs.existsSync(path.join(projectRoot, "docs", "graph-insights", "latest.md"))).toBe(false);
|
|
154
157
|
}
|
|
155
158
|
finally {
|
|
@@ -151,17 +151,12 @@ export function buildCodeInsightDelegatedPlan(input) {
|
|
|
151
151
|
kind: "ambiguity",
|
|
152
152
|
steps: [
|
|
153
153
|
{
|
|
154
|
-
id: "
|
|
155
|
-
action: "
|
|
156
|
-
note: "若存在同名符号,优先使用 uid;需要限定文件时使用 file_path",
|
|
154
|
+
id: "consume-candidates",
|
|
155
|
+
action: "消费本次 candidates 列表,确认唯一目标符号(优先 uid,其次 file_path)",
|
|
157
156
|
},
|
|
158
157
|
{
|
|
159
158
|
id: "rerun-with-disambiguate",
|
|
160
|
-
action: "重新调用 code_insight
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: "resume-analysis",
|
|
164
|
-
action: "消歧后再继续 context/impact 分析,必要时再决定是否保存到 docs/graph-insights",
|
|
159
|
+
action: "重新调用 code_insight,并传入 uid 或 file_path 后继续 context/impact 分析",
|
|
165
160
|
},
|
|
166
161
|
],
|
|
167
162
|
};
|
|
@@ -175,45 +170,32 @@ export function buildCodeInsightDelegatedPlan(input) {
|
|
|
175
170
|
kind: "docs",
|
|
176
171
|
steps: [
|
|
177
172
|
{
|
|
178
|
-
id: "
|
|
179
|
-
action:
|
|
180
|
-
? `确认 ${input.projectDocs.projectContextFilePath} 已存在并可更新`
|
|
181
|
-
: `检查 ${input.projectDocs.projectContextFilePath} 是否存在;若不存在,先调用 init_project_context 生成项目上下文索引`,
|
|
182
|
-
outputs: [input.projectDocs.projectContextFilePath],
|
|
183
|
-
note: projectContextExists
|
|
184
|
-
? "已有项目上下文,可直接补充图谱入口"
|
|
185
|
-
: "只有 project-context.md 存在,后续图谱文档入口才可持续复用",
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
id: "save-latest-md",
|
|
189
|
-
action: `将本次 code_insight 的文本分析结果写入 ${input.projectDocs.latestMarkdownFilePath}`,
|
|
190
|
-
outputs: [input.projectDocs.latestMarkdownFilePath],
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
id: "save-archive-md",
|
|
194
|
-
action: `将本次 code_insight 的文本分析结果归档到 ${input.projectDocs.archiveMarkdownFilePath}`,
|
|
195
|
-
outputs: [input.projectDocs.archiveMarkdownFilePath],
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
id: "save-latest-json",
|
|
199
|
-
action: `将本次 code_insight 的 structuredContent 写入 ${input.projectDocs.latestJsonFilePath}`,
|
|
200
|
-
outputs: [input.projectDocs.latestJsonFilePath],
|
|
201
|
-
note: "建议保留完整结构化结果,便于后续 AI 继续读取",
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
id: "save-archive-json",
|
|
205
|
-
action: `将本次 code_insight 的 structuredContent 归档到 ${input.projectDocs.archiveJsonFilePath}`,
|
|
206
|
-
outputs: [input.projectDocs.archiveJsonFilePath],
|
|
173
|
+
id: "consume-result",
|
|
174
|
+
action: "先消费本次分析结果(processes/symbols/impact),确认是否满足当前问题",
|
|
207
175
|
},
|
|
208
176
|
{
|
|
209
|
-
id: "
|
|
210
|
-
action:
|
|
211
|
-
outputs: [input.projectDocs.
|
|
212
|
-
note:
|
|
177
|
+
id: "optional-save",
|
|
178
|
+
action: `如需保存,再写入 ${input.projectDocs.latestMarkdownFilePath}(文本)和 ${input.projectDocs.latestJsonFilePath}(结构化)`,
|
|
179
|
+
outputs: [input.projectDocs.latestMarkdownFilePath, input.projectDocs.latestJsonFilePath],
|
|
180
|
+
note: projectContextExists
|
|
181
|
+
? `可选同步更新 ${input.projectDocs.projectContextFilePath} 的图谱入口`
|
|
182
|
+
: "若后续要持续沉淀,建议先补 init_project_context",
|
|
213
183
|
},
|
|
214
184
|
],
|
|
215
185
|
};
|
|
216
186
|
}
|
|
187
|
+
function renderUsageGuide() {
|
|
188
|
+
return `## 使用场景指南
|
|
189
|
+
- 探索调用链: \`{ mode: "query", query: "login", goal: "理解登录认证流程" }\`
|
|
190
|
+
- 深入函数上下文: \`{ mode: "context", target: "login", file_path: "src/auth/login.ts" }\`
|
|
191
|
+
- 评估影响范围: \`{ mode: "impact", target: "login", direction: "upstream", file_path: "..." }\`
|
|
192
|
+
- 查看代码内容: \`{ mode: "context", target: "login", include_content: true }\`
|
|
193
|
+
|
|
194
|
+
## 下一步建议
|
|
195
|
+
- 查询不精确: 增加 \`goal\`(例如“理解登录认证流程”)
|
|
196
|
+
- 出现歧义: 传入 \`uid\` 或 \`file_path\` 重新执行
|
|
197
|
+
- 需要落盘: 传 \`save_to_docs: true\`,再按 delegated plan 写入 docs/graph-insights`;
|
|
198
|
+
}
|
|
217
199
|
export function resolveCodeInsightQuery(input) {
|
|
218
200
|
const finalTarget = input.target || ((input.mode === "context" || input.mode === "impact") ? input.input : "");
|
|
219
201
|
const finalQuery = input.query
|
|
@@ -325,6 +307,7 @@ ${executionSummary}
|
|
|
325
307
|
|
|
326
308
|
${ambiguityText ? `歧义候选:\n${ambiguityText}\n\n` : ""}\
|
|
327
309
|
${result.warnings.length > 0 ? `警告: ${result.warnings.join(", ")}` : ""}`.trim();
|
|
310
|
+
const usageGuide = renderUsageGuide();
|
|
328
311
|
const structured = {
|
|
329
312
|
status,
|
|
330
313
|
provider: result.provider,
|
|
@@ -397,13 +380,15 @@ ${result.warnings.length > 0 ? `警告: ${result.warnings.join(", ")}` : ""}`.tr
|
|
|
397
380
|
${renderPlanSteps(delegatedPlan.steps)}
|
|
398
381
|
|
|
399
382
|
${delegatedPlan.kind === "docs" && projectDocs ? `后续操作:
|
|
400
|
-
-
|
|
401
|
-
-
|
|
402
|
-
- 如需归档,请额外保存到 ${projectDocs.archiveMarkdownFilePath}
|
|
403
|
-
- 如需结构化副本,请保存 JSON 到 ${projectDocs.latestJsonFilePath} 或 ${projectDocs.archiveJsonFilePath}` : `后续操作:
|
|
383
|
+
- 如需落盘,写入 ${projectDocs.latestMarkdownFilePath} 与 ${projectDocs.latestJsonFilePath}
|
|
384
|
+
- 如需长期沉淀,可再补充 ${projectDocs.projectContextFilePath} 的图谱入口` : `后续操作:
|
|
404
385
|
- 请先从 candidates 中选定唯一符号
|
|
405
|
-
- 重新传入 uid 或 file_path 后再继续 context / impact 分析`}
|
|
406
|
-
|
|
386
|
+
- 重新传入 uid 或 file_path 后再继续 context / impact 分析`}
|
|
387
|
+
|
|
388
|
+
${usageGuide}`
|
|
389
|
+
: `${message}
|
|
390
|
+
|
|
391
|
+
${usageGuide}`, structured);
|
|
407
392
|
}
|
|
408
393
|
catch (error) {
|
|
409
394
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-probe-kit",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.14",
|
|
4
4
|
"description": "AI-Powered Development Toolkit - MCP Server with 22 tools covering code quality, development efficiency, project management, and UI/UX design. Features: Structured Output, Workflow Orchestration, UI/UX Pro Max, and Requirements Interview.",
|
|
5
5
|
"mcpName": "io.github.mybolide/mcp-probe-kit",
|
|
6
6
|
"type": "module",
|