mcp-probe-kit 3.1.0 → 3.2.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.
@@ -0,0 +1,81 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { parseArgs, getString } from '../utils/parseArgs.js';
4
+ import { okStructured } from '../lib/response.js';
5
+ import { handleToolError } from '../utils/error-handler.js';
6
+ import { resolveWorkspaceRoot } from '../lib/workspace-root.js';
7
+ import { validateSpecDocuments } from '../lib/spec-validator.js';
8
+ /**
9
+ * check_spec 工具(P1「填写后校验」闸门)
10
+ *
11
+ * 读取 docs/specs/{feature_name}/{requirements,design,tasks}.md,
12
+ * 机械校验完整性。未通过则列出逐条待修项并要求补全后重跑,通过前不应进入实现。
13
+ */
14
+ const DEFAULT_DOCS_DIR = 'docs';
15
+ function readIfExists(filePath) {
16
+ try {
17
+ return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : null;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ function formatIssue(issue) {
24
+ const tag = issue.severity === 'error' ? '必须修复' : '提醒';
25
+ return `- [${tag}] (${issue.file}) ${issue.message}`;
26
+ }
27
+ export async function checkSpec(args) {
28
+ try {
29
+ const parsed = parseArgs(args, {
30
+ defaultValues: {
31
+ feature_name: '',
32
+ docs_dir: DEFAULT_DOCS_DIR,
33
+ },
34
+ primaryField: 'feature_name',
35
+ fieldAliases: {
36
+ feature_name: ['name', 'feature', 'spec', '功能名', '功能名称', '规格'],
37
+ docs_dir: ['dir', 'output', '目录', '文档目录'],
38
+ project_root: ['projectRoot', 'project_path', 'projectPath', 'root', '项目路径', '项目根目录'],
39
+ },
40
+ });
41
+ const featureName = getString(parsed.feature_name);
42
+ const docsDir = getString(parsed.docs_dir) || DEFAULT_DOCS_DIR;
43
+ const projectRoot = resolveWorkspaceRoot(getString(parsed.project_root));
44
+ if (!featureName) {
45
+ throw new Error('请提供 feature_name(要校验的规格目录名,对应 docs/specs/<feature_name>/)');
46
+ }
47
+ const specDir = path.join(projectRoot, docsDir, 'specs', featureName);
48
+ const report = validateSpecDocuments({
49
+ requirements: readIfExists(path.join(specDir, 'requirements.md')),
50
+ design: readIfExists(path.join(specDir, 'design.md')),
51
+ tasks: readIfExists(path.join(specDir, 'tasks.md')),
52
+ });
53
+ const relDir = `${docsDir}/specs/${featureName}`;
54
+ const issueLines = report.issues.length > 0
55
+ ? report.issues.map(formatIssue).join('\n')
56
+ : '- 无';
57
+ const text = report.passed
58
+ ? `# ✅ 规格校验通过:${featureName}
59
+
60
+ ${report.summary}
61
+
62
+ **需求清单**: ${report.frIds.join(', ') || '(未发现 FR)'}
63
+ ${report.warningCount > 0 ? `\n仍有 ${report.warningCount} 个提醒(非阻塞,建议处理):\n${issueLines}\n` : ''}
64
+ **下一步**: 规格已具备可实现性,可进入实现/估算阶段。`
65
+ : `# ❌ 规格校验未通过:${featureName}
66
+
67
+ ${report.summary}
68
+
69
+ ## 待修复(${relDir})
70
+ ${issueLines}
71
+
72
+ ## 处理方式
73
+ 1. 按上述每一条补全 \`${relDir}/\` 下的 requirements.md / design.md / tasks.md
74
+ 2. **重新运行** \`check_spec\`(同样的 feature_name)直到通过
75
+ 3. **校验通过前不要开始写实现代码** —— 这是为了确保需求被完整实现,而不是写到一半才发现规格有缺口`;
76
+ return okStructured(text, report);
77
+ }
78
+ catch (error) {
79
+ return handleToolError(error, 'check_spec');
80
+ }
81
+ }
@@ -6,6 +6,7 @@ export { gentest } from "./gentest.js";
6
6
  export { refactor } from "./refactor.js";
7
7
  export { initProjectContext } from "./init_project_context.js";
8
8
  export { addFeature } from "./add_feature.js";
9
+ export { checkSpec } from "./check_spec.js";
9
10
  export { fixBug } from "./fix_bug.js";
10
11
  export { estimate } from "./estimate.js";
11
12
  export { startFeature } from "./start_feature.js";
@@ -6,6 +6,7 @@ export { gentest } from "./gentest.js";
6
6
  export { refactor } from "./refactor.js";
7
7
  export { initProjectContext } from "./init_project_context.js";
8
8
  export { addFeature } from "./add_feature.js";
9
+ export { checkSpec } from "./check_spec.js";
9
10
  export { fixBug } from "./fix_bug.js";
10
11
  export { estimate } from "./estimate.js";
11
12
  // 智能编排工具
@@ -425,6 +425,17 @@ ${graphContext.highlights.length > 0
425
425
  `;
426
426
  const memoryContext = await loadMemoryInjectionContext([errorMessage, stackTrace, codeContext].filter(Boolean).join("\n"), "bugfix");
427
427
  const memoryGuideSection = renderMemoryGuideSection(memoryContext);
428
+ // 记忆优先:先消化历史同类 Bug 的根因/修复(坑),再做 TBP 真因分析
429
+ const memoryRecallStep = memoryContext.enabled
430
+ ? [{
431
+ id: 'recall-memory',
432
+ tool: 'search_memory',
433
+ when: '开干前(下方「历史经验与坑」已自动注入相似历史修复;需要更多同类案例时再调)',
434
+ args: { query: errorMessage, limit: 5 },
435
+ outputs: [],
436
+ note: '先看历史同类 Bug 的根因与已验证修复,优先复用其修复路径并规避同类坑;据此收敛 TBP-4/5/6 的真因方向',
437
+ }]
438
+ : [];
428
439
  if (requirementsMode === "loop") {
429
440
  throwIfAborted(context?.signal, "start_bugfix(loop) 已取消");
430
441
  await reportToolProgress(context, 70, "start_bugfix: 生成 loop 计划");
@@ -450,6 +461,7 @@ ${graphContext.highlights.length > 0
450
461
  const plan = {
451
462
  mode: 'delegated',
452
463
  steps: [
464
+ ...memoryRecallStep,
453
465
  {
454
466
  id: 'context',
455
467
  tool: 'init_project_context',
@@ -512,19 +524,18 @@ ${graphContext.highlights.length > 0
512
524
  ],
513
525
  notes: [
514
526
  ...headerNotes,
515
- ...(memoryContext.enabled ? ['记忆系统: 已启用,历史修复经验全文已自动注入,结束后评估沉淀'] : []),
527
+ ...(memoryContext.enabled ? ['记忆优先: 已自动注入历史同类 Bug 的根因与修复(见顶部),先复用、规避同类坑;结束后评估沉淀'] : []),
516
528
  ],
517
529
  });
518
530
  const loopTemplate = profileDecision.resolved === 'strict'
519
531
  ? LOOP_PROMPT_TEMPLATE_STRICT
520
532
  : LOOP_PROMPT_TEMPLATE_GUIDED;
521
- const guide = (header + loopTemplate
533
+ const renderedLoopPrompt = loopTemplate
522
534
  .replace(/{error_message}/g, errorMessage)
523
535
  .replace(/{analysis_mode}/g, analysisMode)
524
536
  .replace(/{question_budget}/g, String(questionBudget))
525
- .replace(/{assumption_cap}/g, String(assumptionCap)))
526
- + graphGuideSection
527
- + memoryGuideSection;
537
+ .replace(/{assumption_cap}/g, String(assumptionCap));
538
+ const guide = header + memoryGuideSection + renderedLoopPrompt + graphGuideSection;
528
539
  const loopReport = {
529
540
  mode: 'loop',
530
541
  round: 1,
@@ -577,22 +588,22 @@ ${graphContext.highlights.length > 0
577
588
  ],
578
589
  notes: [
579
590
  ...headerNotes,
580
- ...(memoryContext.enabled ? ['记忆系统: 已启用,相似历史经验全文已自动注入'] : []),
591
+ ...(memoryContext.enabled ? ['记忆优先: 已自动注入历史同类 Bug 的根因与修复(见顶部),先复用、规避同类坑'] : []),
581
592
  ],
582
593
  });
583
594
  const promptTemplate = profileDecision.resolved === 'strict'
584
595
  ? PROMPT_TEMPLATE_STRICT
585
596
  : PROMPT_TEMPLATE_GUIDED;
586
- const guide = (header + promptTemplate
597
+ const renderedPrompt = promptTemplate
587
598
  .replace(/{error_message}/g, errorMessage)
588
599
  .replace(/{stack_trace}/g, stackTrace)
589
600
  .replace(/{analysis_mode}/g, analysisMode)
590
- .replace(/{stack_trace_section}/g, stackTraceSection))
591
- + graphGuideSection
592
- + memoryGuideSection;
601
+ .replace(/{stack_trace_section}/g, stackTraceSection);
602
+ const guide = header + memoryGuideSection + renderedPrompt + graphGuideSection;
593
603
  const plan = {
594
604
  mode: 'delegated',
595
605
  steps: [
606
+ ...memoryRecallStep,
596
607
  {
597
608
  id: 'context',
598
609
  tool: 'init_project_context',
@@ -91,7 +91,14 @@ const PROMPT_TEMPLATE = `# 🚀 新功能开发编排(委托式)
91
91
  - \`{docs_dir}/specs/{feature_name}/design.md\`
92
92
  - \`{docs_dir}/specs/{feature_name}/tasks.md\`
93
93
 
94
- ### 2) 工作量估算
94
+ ### 2) 校验规格完整性(闸门)
95
+ **调用**: \`check_spec\`
96
+ \`\`\`json
97
+ { "feature_name": "{feature_name}", "docs_dir": "{docs_dir}" }
98
+ \`\`\`
99
+ **未通过**:按报告逐条补全 requirements/design/tasks 后**重跑 check_spec**;**通过前不要写实现代码**。
100
+
101
+ ### 3) 工作量估算
95
102
  **调用**: \`estimate\`
96
103
  \`\`\`json
97
104
  {
@@ -305,6 +312,17 @@ ${graphContext.highlights.length > 0
305
312
  .join("\n");
306
313
  const memoryContext = await loadMemoryInjectionContext(`${featureName}\n${description}`, 'feature');
307
314
  const memoryGuideSection = renderMemoryGuideSection(memoryContext);
315
+ // 记忆优先:把"先检索消化历史经验/坑"作为编排的显式第一步
316
+ const memoryRecallStep = memoryContext.enabled
317
+ ? [{
318
+ id: 'recall-memory',
319
+ tool: 'search_memory',
320
+ when: '开干前(下方「历史经验与坑」已自动注入 top 命中;需要更多历史需求/坑时再调)',
321
+ args: { query: `${featureName} ${description}`, limit: 5 },
322
+ outputs: [],
323
+ note: '先消化历史经验与可复用模式,并逐条核对「历史坑」是否已在本次设计中规避;据此收敛需求范围,并把要点填入 requirements.md 的「历史经验与坑」节',
324
+ }]
325
+ : [];
308
326
  if (requirementsMode === "loop") {
309
327
  throwIfAborted(context?.signal, "start_feature(loop) 已取消");
310
328
  await reportToolProgress(context, 70, "start_feature: 生成 loop 计划");
@@ -330,6 +348,7 @@ ${graphContext.highlights.length > 0
330
348
  const plan = {
331
349
  mode: 'delegated',
332
350
  steps: [
351
+ ...memoryRecallStep,
333
352
  {
334
353
  id: 'context',
335
354
  tool: 'init_project_context',
@@ -369,6 +388,14 @@ ${graphContext.highlights.length > 0
369
388
  `${docsDir}/specs/${featureName}/tasks.md`,
370
389
  ],
371
390
  },
391
+ {
392
+ id: 'check-spec',
393
+ tool: 'check_spec',
394
+ when: 'stopConditions.ready=true 且 requirements/design/tasks 已落盘',
395
+ args: { feature_name: featureName, docs_dir: docsDir, ...(projectRoot ? { project_root: projectRoot } : {}) },
396
+ outputs: [],
397
+ note: '未通过则按报告补全规格后重跑 check_spec;通过前不要写实现代码',
398
+ },
372
399
  {
373
400
  id: 'estimate',
374
401
  tool: 'estimate',
@@ -392,17 +419,16 @@ ${graphContext.highlights.length > 0
392
419
  notes: [
393
420
  `模板档位: ${templateProfile}`,
394
421
  graphStatusNote,
395
- ...(memoryContext.enabled ? ['记忆系统: 已启用,历史经验全文已自动注入,结束后评估是否沉淀'] : []),
422
+ ...(memoryContext.enabled ? ['记忆优先: 已自动注入历史经验与坑(见顶部),开干前先消化、约束范围并规避同类坑;结束后评估是否沉淀'] : []),
396
423
  ],
397
424
  });
398
- const guide = (header + LOOP_PROMPT_TEMPLATE
425
+ const renderedLoopPrompt = LOOP_PROMPT_TEMPLATE
399
426
  .replace(/{feature_name}/g, featureName)
400
427
  .replace(/{description}/g, description)
401
428
  .replace(/{project_root}/g, toWorkspacePath(projectRoot))
402
429
  .replace(/{question_budget}/g, String(questionBudget))
403
- .replace(/{assumption_cap}/g, String(assumptionCap)))
404
- + graphGuideSection
405
- + memoryGuideSection;
430
+ .replace(/{assumption_cap}/g, String(assumptionCap));
431
+ const guide = header + memoryGuideSection + renderedLoopPrompt + graphGuideSection;
406
432
  const loopReport = {
407
433
  mode: 'loop',
408
434
  round: 1,
@@ -452,20 +478,20 @@ ${graphContext.highlights.length > 0
452
478
  notes: [
453
479
  `模板档位: ${templateProfile}`,
454
480
  graphStatusNote,
455
- ...(memoryContext.enabled ? ['记忆系统: 已启用,历史经验全文已自动注入'] : []),
481
+ ...(memoryContext.enabled ? ['记忆优先: 已自动注入历史经验与坑(见顶部),开干前先消化、约束范围并规避同类坑'] : []),
456
482
  ],
457
483
  });
458
- const guide = (header + PROMPT_TEMPLATE
484
+ const renderedPrompt = PROMPT_TEMPLATE
459
485
  .replace(/{feature_name}/g, featureName)
460
486
  .replace(/{description}/g, description)
461
487
  .replace(/{docs_dir}/g, docsDir)
462
488
  .replace(/{project_root}/g, (projectRoot || process.cwd()).replace(/\\/g, "/"))
463
- .replace(/{template_profile}/g, templateProfile))
464
- + graphGuideSection
465
- + memoryGuideSection;
489
+ .replace(/{template_profile}/g, templateProfile);
490
+ const guide = header + memoryGuideSection + renderedPrompt + graphGuideSection;
466
491
  const plan = {
467
492
  mode: 'delegated',
468
493
  steps: [
494
+ ...memoryRecallStep,
469
495
  {
470
496
  id: 'context',
471
497
  tool: 'init_project_context',
@@ -487,6 +513,14 @@ ${graphContext.highlights.length > 0
487
513
  `${docsDir}/specs/${featureName}/tasks.md`,
488
514
  ],
489
515
  },
516
+ {
517
+ id: 'check-spec',
518
+ tool: 'check_spec',
519
+ when: 'requirements/design/tasks 落盘后、进入估算/实现前',
520
+ args: { feature_name: featureName, docs_dir: docsDir, ...(projectRoot ? { project_root: projectRoot } : {}) },
521
+ outputs: [],
522
+ note: '未通过则按报告逐条补全规格后重跑 check_spec;通过前不要写实现代码',
523
+ },
490
524
  {
491
525
  id: 'estimate',
492
526
  tool: 'estimate',
@@ -526,6 +560,7 @@ ${graphContext.highlights.length > 0
526
560
  `如果缺少 ${graphDocs.latestMarkdownPath} / ${graphDocs.latestJsonPath},先调用 init_project_context 补齐图谱初始化`,
527
561
  `优先读取 ${graphDocs.latestMarkdownPath} 获取模块依赖与调用链摘要`,
528
562
  '调用 add_feature 工具生成功能规格文档',
563
+ '调用 check_spec 校验规格完整性,未通过先补全再重跑(通过前不要写实现代码)',
529
564
  '调用 estimate 工具进行工作量估算',
530
565
  '按照 tasks.md 开始开发',
531
566
  ],
@@ -565,6 +565,17 @@ export async function startUi(args, context) {
565
565
  headerNotes.push(buildSkillHeaderNote(skillBridge));
566
566
  const memoryContext = await loadMemoryInjectionContext(description || templateName, 'ui');
567
567
  const memoryGuideSection = renderMemoryGuideSection(memoryContext);
568
+ // 记忆优先:先复用历史 UI 资产/模式并规避历史 UI 坑,再进入设计与渲染
569
+ const memoryRecallStep = memoryContext.enabled
570
+ ? [{
571
+ id: 'recall-memory',
572
+ tool: 'search_memory',
573
+ when: '开干前(下方「历史经验与坑」已自动注入相似 UI 资产与坑;需要更多时再调)',
574
+ args: { query: description || templateName, limit: 5 },
575
+ outputs: [],
576
+ note: '先复用历史可复用 UI 组件/布局/交互模式,并规避历史 UI 坑(交互/兼容性/可访问性)',
577
+ }]
578
+ : [];
568
579
  // 验证 mode 参数
569
580
  const validModes = ["auto", "manual"];
570
581
  if (mode && !validModes.includes(mode)) {
@@ -629,6 +640,7 @@ start_ui <描述> --requirements_mode=loop
629
640
  const plan = {
630
641
  mode: 'delegated',
631
642
  steps: [
643
+ ...memoryRecallStep,
632
644
  skillBridgeStep,
633
645
  {
634
646
  id: 'loop-1',
@@ -712,17 +724,17 @@ start_ui <描述> --requirements_mode=loop
712
724
  ],
713
725
  notes: [
714
726
  ...headerNotes,
715
- ...(memoryContext.enabled ? ['记忆系统: 已启用,先复用历史 UI 资产,再决定是否沉淀本次结果'] : []),
727
+ ...(memoryContext.enabled ? ['记忆优先: 已自动注入相似历史 UI 资产与坑(见顶部),先复用并规避同类坑;再决定是否沉淀'] : []),
716
728
  ],
717
729
  });
718
730
  const loopTemplate = profileDecision.resolved === 'strict'
719
731
  ? LOOP_PROMPT_TEMPLATE_STRICT
720
732
  : LOOP_PROMPT_TEMPLATE_GUIDED;
721
- const guide = header + skillBridgeSection + loopTemplate
733
+ const renderedLoopPrompt = loopTemplate
722
734
  .replace(/{description}/g, description)
723
735
  .replace(/{question_budget}/g, String(questionBudget))
724
- .replace(/{assumption_cap}/g, String(assumptionCap))
725
- + memoryGuideSection;
736
+ .replace(/{assumption_cap}/g, String(assumptionCap));
737
+ const guide = header + memoryGuideSection + skillBridgeSection + renderedLoopPrompt;
726
738
  const loopReport = {
727
739
  mode: 'loop',
728
740
  round: 1,
@@ -862,6 +874,7 @@ ${recommendation.reasoning}
862
874
  const plan = {
863
875
  mode: 'delegated',
864
876
  steps: [
877
+ ...memoryRecallStep,
865
878
  skillBridgeStep,
866
879
  {
867
880
  id: 'context',
@@ -929,10 +942,10 @@ ${recommendation.reasoning}
929
942
  ],
930
943
  notes: [
931
944
  ...headerNotes,
932
- ...(memoryContext.enabled ? ['记忆系统: 已启用,相似历史 UI 经验全文已自动注入'] : []),
945
+ ...(memoryContext.enabled ? ['记忆优先: 已自动注入相似历史 UI 资产与坑(见顶部),先复用并规避同类坑'] : []),
933
946
  ],
934
947
  });
935
- const smartPlan = header + skillBridgeSection + (profileDecision.resolved === 'strict' ? smartPlanStrict : smartPlanGuided) + memoryGuideSection;
948
+ const smartPlan = header + memoryGuideSection + skillBridgeSection + (profileDecision.resolved === 'strict' ? smartPlanStrict : smartPlanGuided);
936
949
  // Create structured UI report for auto mode
937
950
  const uiReport = {
938
951
  summary: `智能 UI 开发:${description}`,
@@ -1060,21 +1073,22 @@ start_ui "设置页面" --framework=react
1060
1073
  ],
1061
1074
  notes: [
1062
1075
  ...headerNotes,
1063
- ...(memoryContext.enabled ? ['记忆系统: 已启用,相似历史 UI 经验全文已自动注入'] : []),
1076
+ ...(memoryContext.enabled ? ['记忆优先: 已自动注入相似历史 UI 资产与坑(见顶部),先复用并规避同类坑'] : []),
1064
1077
  ],
1065
1078
  });
1066
1079
  const baseTemplate = profileDecision.resolved === 'strict'
1067
1080
  ? PROMPT_TEMPLATE_STRICT
1068
1081
  : PROMPT_TEMPLATE_GUIDED;
1069
- let guide = header + skillBridgeSection + baseTemplate;
1070
- guide = safeReplace(guide, '{description}', escapeJson(description));
1071
- guide = safeReplace(guide, '{productType}', productType);
1072
- guide = safeReplace(guide, '{framework}', framework);
1073
- guide = safeReplace(guide, '{templateName}', templateName);
1074
- guide += memoryGuideSection;
1082
+ let body = skillBridgeSection + baseTemplate;
1083
+ body = safeReplace(body, '{description}', escapeJson(description));
1084
+ body = safeReplace(body, '{productType}', productType);
1085
+ body = safeReplace(body, '{framework}', framework);
1086
+ body = safeReplace(body, '{templateName}', templateName);
1087
+ const guide = header + memoryGuideSection + body;
1075
1088
  const plan = {
1076
1089
  mode: 'delegated',
1077
1090
  steps: [
1091
+ ...memoryRecallStep,
1078
1092
  skillBridgeStep,
1079
1093
  {
1080
1094
  id: 'context',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-probe-kit",
3
- "version": "3.1.0",
4
- "description": "AI-Powered Development Toolkit - MCP Server with 26 tools covering code quality, development efficiency, project management, and UI/UX design. Features: Structured Output, Workflow Orchestration, UI/UX Pro Max, and Requirements Interview.",
3
+ "version": "3.2.0",
4
+ "description": "AI-Powered Development Toolkit - MCP Server with 27 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",
7
7
  "main": "build/index.js",