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 = 0;
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
- if (!field.value.includes(term)) {
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
- if (field.key.includes("name") || field.key.includes("title") || field.key.includes("label")) {
396
- score += field.value === term ? 24 : 16;
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 (field.key.includes("path") || field.key.includes("file") || field.key.includes("module")) {
399
- score += 10;
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 += 4;
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 * 5;
479
+ score += matchedTerms * 14;
480
+ }
481
+ else {
482
+ score -= 60;
411
483
  }
412
484
  if (matchedTerms === terms.length) {
413
- score += 12;
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(/delegated plan/);
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(/docs\/project-context\.md/);
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[0].outputs[0]).toContain("/docs/project-context.md");
151
- const updateIndexStep = structured.plan.steps.find((step) => step.id === "update-project-context-index");
152
- expect(updateIndexStep.note).toMatch(/代码图谱洞察/);
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: "inspect-candidates",
155
- action: "阅读本次返回的 candidates,确认目标符号对应的 uid file_path",
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,并显式传入 uid 或 file_path 完成消歧",
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: "ensure-project-context",
179
- action: projectContextExists
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: "update-project-context-index",
210
- action: `更新 ${input.projectDocs.projectContextFilePath},在“## 📚 文档导航”加入图谱文档入口,并在“## 💡 开发时查看对应文档”加入代码图谱洞察链接`,
211
- outputs: [input.projectDocs.projectContextFilePath],
212
- note: `建议插入内容:\n${input.projectDocs.navigationSnippet}\n${input.projectDocs.devGuideSnippet}`,
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
- - 请先确保 ${projectDocs.projectContextFilePath} 可用,并把图谱入口挂到该索引中
401
- - 请将本次分析保存到 ${projectDocs.latestMarkdownFilePath}
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
- : message, structured);
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.13",
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",