autosnippet 2.19.7 → 3.0.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.
Files changed (32) hide show
  1. package/README.md +26 -18
  2. package/dashboard/dist/assets/{icons-C7FN32VL.js → icons-CEfgGaZi.js} +1 -1
  3. package/dashboard/dist/assets/{index-B5dbY-cS.js → index-_Sk_Dmg3.js} +42 -42
  4. package/dashboard/dist/assets/{vendor-Ba1BZjav.js → vendor-CEnWn7aV.js} +384 -380
  5. package/dashboard/dist/index.html +3 -5
  6. package/lib/external/mcp/McpServer.js +54 -65
  7. package/lib/external/mcp/handlers/bootstrap.js +7 -7
  8. package/lib/external/mcp/handlers/consolidated.js +290 -0
  9. package/lib/external/mcp/handlers/guard.js +5 -5
  10. package/lib/external/mcp/handlers/knowledge.js +23 -8
  11. package/lib/external/mcp/handlers/skill.js +4 -4
  12. package/lib/external/mcp/handlers/structure.js +16 -16
  13. package/lib/external/mcp/handlers/system.js +37 -40
  14. package/lib/external/mcp/tools.js +250 -646
  15. package/lib/service/cursor/RulesGenerator.js +2 -2
  16. package/lib/service/skills/SkillAdvisor.js +1 -1
  17. package/package.json +1 -1
  18. package/scripts/install-cursor-skill.js +10 -10
  19. package/skills/autosnippet-analysis/SKILL.md +23 -18
  20. package/skills/autosnippet-candidates/SKILL.md +38 -39
  21. package/skills/autosnippet-coldstart/SKILL.md +11 -14
  22. package/skills/autosnippet-concepts/SKILL.md +26 -31
  23. package/skills/autosnippet-create/SKILL.md +4 -6
  24. package/skills/autosnippet-guard/SKILL.md +14 -17
  25. package/skills/autosnippet-intent/SKILL.md +10 -11
  26. package/skills/autosnippet-lifecycle/SKILL.md +13 -18
  27. package/skills/autosnippet-recipes/SKILL.md +29 -62
  28. package/skills/autosnippet-structure/SKILL.md +19 -19
  29. package/templates/copilot-instructions.md +42 -41
  30. package/templates/recipes-setup/README.md +4 -7
  31. package/dashboard/dist/assets/react-markdown-Dc1U8Kko.js +0 -1
  32. package/dashboard/dist/assets/syntax-highlighter-BkDyUteW.js +0 -5
@@ -5,13 +5,11 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>AutoSnippet Dashboard</title>
8
- <script type="module" crossorigin src="/assets/index-B5dbY-cS.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-_Sk_Dmg3.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/yaml-qRaU8Ldn.js">
10
- <link rel="modulepreload" crossorigin href="/assets/syntax-highlighter-BkDyUteW.js">
11
- <link rel="modulepreload" crossorigin href="/assets/vendor-Ba1BZjav.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/vendor-CEnWn7aV.js">
12
11
  <link rel="modulepreload" crossorigin href="/assets/axios-C0Zqfgkc.js">
13
- <link rel="modulepreload" crossorigin href="/assets/icons-C7FN32VL.js">
14
- <link rel="modulepreload" crossorigin href="/assets/react-markdown-Dc1U8Kko.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/icons-CEfgGaZi.js">
15
13
  <link rel="stylesheet" crossorigin href="/assets/index-Bun3ld_J.css">
16
14
  </head>
17
15
  <body>
@@ -1,14 +1,18 @@
1
1
  /**
2
- * AutoSnippet V2 MCP Server
2
+ * AutoSnippet V3 MCP Server — 整合版
3
3
  *
4
4
  * Model Context Protocol (stdio transport)
5
5
  * 提供给 IDE AI Agent (Cursor/VSCode Copilot) 的工具集
6
- * 38 工具,全部基于 V2 服务层,不依赖 V1
7
- * Gateway 权限 gating: 写操作经过 Gateway 权限/宪法/审计检查
6
+ *
7
+ * V3 整合:39 16 工具(12 agent + 4 admin)
8
+ * 通过 ASD_MCP_TIER 环境变量控制可见工具集(agent/admin)
9
+ *
10
+ * Gateway 权限 gating: 写操作经过 Gateway 权限/宪法/审计检查(支持动态 resolver)
8
11
  *
9
12
  * 本文件仅包含服务编排层(初始化、路由、Gateway gating、生命周期)。
10
13
  * 工具定义 → tools.js
11
14
  * Handler 实现 → handlers/*.js
15
+ * 整合路由 → handlers/consolidated.js
12
16
  */
13
17
 
14
18
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -19,22 +23,15 @@ import {
19
23
  } from '@modelcontextprotocol/sdk/types.js';
20
24
  import Logger from '../../infrastructure/logging/Logger.js';
21
25
  import { envelope } from './envelope.js';
22
- import { TOOLS, TOOL_GATEWAY_MAP } from './tools.js';
26
+ import { TOOLS, TOOL_GATEWAY_MAP, TIER_ORDER } from './tools.js';
23
27
  import { wrapHandler } from './errorHandler.js';
24
28
 
25
29
  // ─── Handler 模块 ─────────────────────────────────────────────
26
30
 
27
31
  import * as systemHandlers from './handlers/system.js';
28
- import * as searchHandlers from './handlers/search.js';
29
- import * as browseHandlers from './handlers/browse.js';
30
- import * as structureHandlers from './handlers/structure.js';
31
32
  import * as candidateHandlers from './handlers/candidate.js';
32
- import * as guardHandlers from './handlers/guard.js';
33
- import * as bootstrapHandlers from './handlers/bootstrap.js';
34
- import * as skillHandlers from './handlers/skill.js';
35
33
  import * as knowledgeHandlers from './handlers/knowledge.js';
36
- // wiki handlers 暂不注册 MCP — Wiki 仅由 Bootstrap 自动触发 + Dashboard API 访问
37
- // import * as wikiHandlers from './handlers/wiki.js';
34
+ import * as consolidated from './handlers/consolidated.js';
38
35
 
39
36
  // ─── McpServer 类 ─────────────────────────────────────────────
40
37
 
@@ -89,7 +86,7 @@ export class McpServer {
89
86
  }
90
87
 
91
88
  this.server = new Server(
92
- { name: 'autosnippet-v2', version: '2.0.0' },
89
+ { name: 'autosnippet-v3', version: '3.0.0' },
93
90
  { capabilities: { tools: {} } },
94
91
  );
95
92
 
@@ -97,9 +94,20 @@ export class McpServer {
97
94
  return this;
98
95
  }
99
96
 
97
+ /**
98
+ * 注册 ListTools / CallTool 请求处理器
99
+ * ListTools 基于 ASD_MCP_TIER 过滤可见工具
100
+ */
100
101
  _registerHandlers() {
101
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
102
+ // ── ListTools: tier 过滤 ──
103
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
104
+ const tierName = process.env.ASD_MCP_TIER || 'agent';
105
+ const maxTier = TIER_ORDER[tierName] ?? TIER_ORDER.agent;
106
+ const visible = TOOLS.filter(t => (TIER_ORDER[t.tier || 'agent'] ?? 0) <= maxTier);
107
+ return { tools: visible };
108
+ });
102
109
 
110
+ // ── CallTool: 路由到 handler ──
103
111
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
104
112
  const { name, arguments: args } = request.params;
105
113
  const t0 = Date.now();
@@ -129,59 +137,29 @@ export class McpServer {
129
137
  }
130
138
 
131
139
  /**
132
- * 解析工具名到 handler 函数
140
+ * 解析工具名到 handler 函数(V3 整合版)
133
141
  * @private
134
142
  */
135
143
  _resolveHandler(name) {
136
144
  const HANDLER_MAP = {
137
- // 系统
138
- autosnippet_health: (ctx) => systemHandlers.health(ctx),
139
- autosnippet_capabilities: () => systemHandlers.capabilities(),
140
- // 搜索
141
- autosnippet_search: (ctx, args) => searchHandlers.search(ctx, args),
142
- autosnippet_context_search: (ctx, args) => searchHandlers.contextSearch(ctx, args),
143
- autosnippet_keyword_search: (ctx, args) => searchHandlers.keywordSearch(ctx, args),
144
- autosnippet_semantic_search: (ctx, args) => searchHandlers.semanticSearch(ctx, args),
145
- // 知识浏览
146
- autosnippet_list_rules: (ctx, args) => browseHandlers.listByKind(ctx, 'rule', args),
147
- autosnippet_list_patterns: (ctx, args) => browseHandlers.listByKind(ctx, 'pattern', args),
148
- autosnippet_list_facts: (ctx, args) => browseHandlers.listByKind(ctx, 'fact', args),
149
- autosnippet_list_recipes: (ctx, args) => browseHandlers.listRecipes(ctx, args),
150
- autosnippet_get_recipe: (ctx, args) => browseHandlers.getRecipe(ctx, args),
151
- autosnippet_recipe_insights: (ctx, args) => browseHandlers.recipeInsights(ctx, args),
152
- autosnippet_confirm_usage: (ctx, args) => browseHandlers.confirmUsage(ctx, args),
153
- // 项目结构 & 图谱
154
- autosnippet_get_targets: (ctx) => structureHandlers.getTargets(ctx),
155
- autosnippet_get_target_files: (ctx, args) => structureHandlers.getTargetFiles(ctx, args),
156
- autosnippet_get_target_metadata: (ctx, args) => structureHandlers.getTargetMetadata(ctx, args),
157
- autosnippet_graph_query: (ctx, args) => structureHandlers.graphQuery(ctx, args),
158
- autosnippet_graph_impact: (ctx, args) => structureHandlers.graphImpact(ctx, args),
159
- autosnippet_graph_path: (ctx, args) => structureHandlers.graphPath(ctx, args),
160
- autosnippet_graph_stats: (ctx) => structureHandlers.graphStats(ctx),
161
- // 候选校验 & AI 补全
162
- autosnippet_validate_candidate: (ctx, args) => candidateHandlers.validateCandidate(ctx, args),
163
- autosnippet_check_duplicate: (ctx, args) => candidateHandlers.checkDuplicate(ctx, args),
164
- autosnippet_enrich_candidates: (ctx, args) => candidateHandlers.enrichCandidates(ctx, args),
165
- // Guard & 扫描
166
- autosnippet_guard_check: (ctx, args) => guardHandlers.guardCheck(ctx, args),
167
- autosnippet_guard_audit_files: (ctx, args) => guardHandlers.guardAuditFiles(ctx, args),
168
- autosnippet_scan_project: (ctx, args) => guardHandlers.scanProject(ctx, args),
169
- // Bootstrap 冷启动
170
- autosnippet_bootstrap_knowledge: (ctx, args) => bootstrapHandlers.bootstrapKnowledge(ctx, args),
171
- autosnippet_bootstrap_refine: (ctx, args) => bootstrapHandlers.bootstrapRefine(ctx, args),
172
- // Skills
173
- autosnippet_list_skills: () => skillHandlers.listSkills(),
174
- autosnippet_load_skill: (ctx, args) => skillHandlers.loadSkill(ctx, args),
175
- autosnippet_create_skill: (ctx, args) => skillHandlers.createSkill(ctx, args),
176
- autosnippet_delete_skill: (ctx, args) => skillHandlers.deleteSkill(ctx, args),
177
- autosnippet_update_skill: (ctx, args) => skillHandlers.updateSkill(ctx, args),
178
- autosnippet_suggest_skills: (ctx) => skillHandlers.suggestSkills(ctx),
179
- // V3 知识条目
180
- autosnippet_submit_knowledge: (ctx, args) => knowledgeHandlers.submitKnowledge(ctx, args),
145
+ // ── Agent 层 (12) ──
146
+ autosnippet_health: (ctx) => systemHandlers.health(ctx),
147
+ autosnippet_capabilities: () => systemHandlers.capabilities(),
148
+ autosnippet_search: (ctx, args) => consolidated.consolidatedSearch(ctx, args),
149
+ autosnippet_knowledge: (ctx, args) => consolidated.consolidatedKnowledge(ctx, args),
150
+ autosnippet_structure: (ctx, args) => consolidated.consolidatedStructure(ctx, args),
151
+ autosnippet_graph: (ctx, args) => consolidated.consolidatedGraph(ctx, args),
152
+ autosnippet_guard: (ctx, args) => consolidated.consolidatedGuard(ctx, args),
153
+ autosnippet_submit_knowledge: (ctx, args) => consolidated.enhancedSubmitKnowledge(ctx, args),
181
154
  autosnippet_submit_knowledge_batch: (ctx, args) => knowledgeHandlers.submitKnowledgeBatch(ctx, args),
182
- autosnippet_knowledge_lifecycle: (ctx, args) => knowledgeHandlers.knowledgeLifecycle(ctx, args),
183
155
  autosnippet_save_document: (ctx, args) => knowledgeHandlers.saveDocument(ctx, args),
184
- // Wiki: MCP 入口暂未开放 仅通过 Bootstrap 触发 + Dashboard HTTP API 访问
156
+ autosnippet_skill: (ctx, args) => consolidated.consolidatedSkill(ctx, args),
157
+ autosnippet_bootstrap: (ctx, args) => consolidated.consolidatedBootstrap(ctx, args),
158
+ // ── Admin 层 (+4) ──
159
+ autosnippet_enrich_candidates: (ctx, args) => candidateHandlers.enrichCandidates(ctx, args),
160
+ autosnippet_knowledge_lifecycle: (ctx, args) => knowledgeHandlers.knowledgeLifecycle(ctx, args),
161
+ autosnippet_validate_candidate: (ctx, args) => candidateHandlers.validateCandidate(ctx, args),
162
+ autosnippet_check_duplicate: (ctx, args) => candidateHandlers.checkDuplicate(ctx, args),
185
163
  };
186
164
  return HANDLER_MAP[name] || null;
187
165
  }
@@ -189,11 +167,18 @@ export class McpServer {
189
167
  /**
190
168
  * Gateway 权限 gating — 写操作验证权限/宪法/审计
191
169
  * 只读工具直接跳过(不在 TOOL_GATEWAY_MAP 中)
170
+ * 支持动态 resolver(operation-based 工具按参数解析 action/resource)
192
171
  */
193
172
  async _gatewayGate(toolName, args) {
194
- const mapping = TOOL_GATEWAY_MAP[toolName];
173
+ let mapping = TOOL_GATEWAY_MAP[toolName];
195
174
  if (!mapping) return; // 只读工具,跳过
196
175
 
176
+ // 动态 resolver:根据 args 计算实际 action/resource
177
+ if (typeof mapping.resolver === 'function') {
178
+ mapping = mapping.resolver(args);
179
+ if (!mapping) return; // resolver 返回 null 表示只读操作
180
+ }
181
+
197
182
  try {
198
183
  const gateway = this.container.get('gateway');
199
184
  if (!gateway) return; // Gateway 未初始化,降级放行
@@ -229,9 +214,13 @@ export class McpServer {
229
214
  await this.initialize();
230
215
  const transport = new StdioServerTransport();
231
216
  await this.server.connect(transport);
232
- this.logger.info('MCP Server started (stdio) — 38 tools');
233
- // stderr 写一行简洁的就绪通知(不使用 winston,仅用于 Cursor 日志面板 & 调试)
234
- process.stderr.write('AutoSnippet MCP ready 38 tools\n');
217
+
218
+ const tierName = process.env.ASD_MCP_TIER || 'agent';
219
+ const maxTier = TIER_ORDER[tierName] ?? TIER_ORDER.agent;
220
+ const visibleCount = TOOLS.filter(t => (TIER_ORDER[t.tier || 'agent'] ?? 0) <= maxTier).length;
221
+
222
+ this.logger.info(`MCP Server started (stdio) — ${visibleCount} tools [tier=${tierName}]`);
223
+ process.stderr.write(`AutoSnippet MCP ready — ${visibleCount} tools [tier=${tierName}]\n`);
235
224
  }
236
225
 
237
226
  async shutdown() {
@@ -190,7 +190,7 @@ export async function bootstrapKnowledge(ctx, args) {
190
190
  return envelope({
191
191
  success: true,
192
192
  data: { report, message: 'No source files found, nothing to bootstrap' },
193
- meta: { tool: 'autosnippet_bootstrap_knowledge', responseTimeMs: Date.now() - t0 },
193
+ meta: { tool: 'autosnippet_bootstrap', responseTimeMs: Date.now() - t0 },
194
194
  });
195
195
  }
196
196
 
@@ -514,13 +514,13 @@ export async function bootstrapKnowledge(ctx, args) {
514
514
  '',
515
515
  '== 完成后可执行的后续操作 ==',
516
516
  '1. 调用 autosnippet_enrich_candidates(candidateIds) 补全候选缺失字段',
517
- '2. 调用 autosnippet_bootstrap_refine() 对候选进行 AI 精炼',
517
+ '2. 调用 autosnippet_bootstrap({ operation: "refine" }) 对候选进行 AI 精炼',
518
518
  '3. 使用 autosnippet_submit_knowledge_batch 手动提交更多知识条目',
519
- '4. 使用 autosnippet_load_skill 加载自动生成的 Project Skills',
519
+ '4. 使用 autosnippet_skill({ operation: "load", name }) 加载自动生成的 Project Skills',
520
520
  '',
521
521
  '== 宏观维度 → Project Skills ==',
522
522
  '宏观维度(architecture/code-standard/project-profile/agent-guidelines/objc-deep-scan/category-scan)',
523
- '自动生成 Project Skill 到 AutoSnippet/skills/,可通过 autosnippet_load_skill 加载。',
523
+ '自动生成 Project Skill 到 AutoSnippet/skills/,可通过 autosnippet_skill({ operation: "load" }) 加载。',
524
524
  ],
525
525
  };
526
526
 
@@ -607,7 +607,7 @@ export async function bootstrapKnowledge(ctx, args) {
607
607
  return envelope({
608
608
  success: true,
609
609
  data: responseData,
610
- meta: { tool: 'autosnippet_bootstrap_knowledge', responseTimeMs: Date.now() - t0 },
610
+ meta: { tool: 'autosnippet_bootstrap', responseTimeMs: Date.now() - t0 },
611
611
  });
612
612
  }
613
613
 
@@ -653,7 +653,7 @@ export async function bootstrapRefine(ctx, args) {
653
653
  }
654
654
 
655
655
  if (entries.length === 0) {
656
- return envelope({ success: true, data: { refined: 0, total: 0, errors: [], results: [] }, meta: { tool: 'autosnippet_bootstrap_refine', responseTimeMs: Date.now() - t0 } });
656
+ return envelope({ success: true, data: { refined: 0, total: 0, errors: [], results: [] }, meta: { tool: 'autosnippet_bootstrap', responseTimeMs: Date.now() - t0 } });
657
657
  }
658
658
 
659
659
  onProgress?.('refine:started', { total: entries.length, candidateIds: entries.map(e => e.id) });
@@ -872,6 +872,6 @@ ${refineInstruction}
872
872
  results,
873
873
  message: `Phase 6 AI 润色完成: ${refined}/${entries.length} 条知识条目已更新${args.dryRun ? '(预览模式)' : ''}`,
874
874
  },
875
- meta: { tool: 'autosnippet_bootstrap_refine', responseTimeMs: Date.now() - t0 },
875
+ meta: { tool: 'autosnippet_bootstrap', responseTimeMs: Date.now() - t0 },
876
876
  });
877
877
  }
@@ -0,0 +1,290 @@
1
+ /**
2
+ * MCP 整合 Handler — 参数路由层
3
+ *
4
+ * 将整合后的工具(autosnippet_search / knowledge / structure / graph / guard / skill / bootstrap)
5
+ * 按 operation / mode 参数路由到已有 handler 实现。
6
+ *
7
+ * 不包含业务逻辑,仅做参数解构 → 路由 → 转发。
8
+ */
9
+
10
+ import * as searchHandlers from './search.js';
11
+ import * as browseHandlers from './browse.js';
12
+ import * as structureHandlers from './structure.js';
13
+ import * as guardHandlers from './guard.js';
14
+ import * as skillHandlers from './skill.js';
15
+ import * as bootstrapHandlers from './bootstrap.js';
16
+ import * as candidateHandlers from './candidate.js';
17
+
18
+ // ─── autosnippet_search (整合 4 → 1) ────────────────────────
19
+
20
+ /**
21
+ * 统合搜索:根据 mode 参数路由到对应搜索 handler
22
+ * auto (默认) → search()
23
+ * keyword → keywordSearch()
24
+ * semantic → semanticSearch()
25
+ * context → contextSearch()
26
+ */
27
+ export async function consolidatedSearch(ctx, args) {
28
+ const mode = args.mode || 'auto';
29
+ switch (mode) {
30
+ case 'keyword':
31
+ return searchHandlers.keywordSearch(ctx, args);
32
+ case 'semantic':
33
+ return searchHandlers.semanticSearch(ctx, args);
34
+ case 'context':
35
+ return searchHandlers.contextSearch(ctx, args);
36
+ case 'auto':
37
+ case 'bm25':
38
+ default:
39
+ return searchHandlers.search(ctx, { ...args, mode });
40
+ }
41
+ }
42
+
43
+ // ─── autosnippet_knowledge (整合 7 → 1) ─────────────────────
44
+
45
+ /**
46
+ * 知识浏览:根据 operation 参数路由
47
+ * list (默认) → listByKind() 或 listRecipes()
48
+ * get → getRecipe()
49
+ * insights → recipeInsights()
50
+ * confirm_usage → confirmUsage()
51
+ */
52
+ export async function consolidatedKnowledge(ctx, args) {
53
+ const op = args.operation || 'list';
54
+ switch (op) {
55
+ case 'list': {
56
+ const kind = args.kind;
57
+ if (kind && kind !== 'all') {
58
+ return browseHandlers.listByKind(ctx, kind, args);
59
+ }
60
+ return browseHandlers.listRecipes(ctx, args);
61
+ }
62
+ case 'get':
63
+ return browseHandlers.getRecipe(ctx, args);
64
+ case 'insights':
65
+ return browseHandlers.recipeInsights(ctx, args);
66
+ case 'confirm_usage':
67
+ // confirmUsage expects { recipeId, usageType, feedback }
68
+ // 适配:如果传了 id 但没传 recipeId,自动映射
69
+ if (args.id && !args.recipeId) {
70
+ args.recipeId = args.id;
71
+ }
72
+ return browseHandlers.confirmUsage(ctx, args);
73
+ default:
74
+ throw new Error(`Unknown knowledge operation: ${op}. Expected: list, get, insights, confirm_usage`);
75
+ }
76
+ }
77
+
78
+ // ─── autosnippet_structure (整合 3 → 1) ─────────────────────
79
+
80
+ /**
81
+ * 项目结构:根据 operation 参数路由
82
+ * targets (默认) → getTargets()
83
+ * files → getTargetFiles()
84
+ * metadata → getTargetMetadata()
85
+ */
86
+ export async function consolidatedStructure(ctx, args) {
87
+ const op = args.operation || 'targets';
88
+ switch (op) {
89
+ case 'targets':
90
+ return structureHandlers.getTargets(ctx, args);
91
+ case 'files':
92
+ return structureHandlers.getTargetFiles(ctx, args);
93
+ case 'metadata':
94
+ return structureHandlers.getTargetMetadata(ctx, args);
95
+ default:
96
+ throw new Error(`Unknown structure operation: ${op}. Expected: targets, files, metadata`);
97
+ }
98
+ }
99
+
100
+ // ─── autosnippet_graph (整合 4 → 1) ─────────────────────────
101
+
102
+ /**
103
+ * 知识图谱:根据 operation 参数路由
104
+ * query → graphQuery()
105
+ * impact → graphImpact()
106
+ * path → graphPath()
107
+ * stats → graphStats()
108
+ */
109
+ export async function consolidatedGraph(ctx, args) {
110
+ const op = args.operation;
111
+ if (!op) throw new Error('Missing required parameter: operation. Expected: query, impact, path, stats');
112
+ switch (op) {
113
+ case 'query':
114
+ return structureHandlers.graphQuery(ctx, args);
115
+ case 'impact':
116
+ return structureHandlers.graphImpact(ctx, args);
117
+ case 'path':
118
+ return structureHandlers.graphPath(ctx, args);
119
+ case 'stats':
120
+ return structureHandlers.graphStats(ctx);
121
+ default:
122
+ throw new Error(`Unknown graph operation: ${op}. Expected: query, impact, path, stats`);
123
+ }
124
+ }
125
+
126
+ // ─── autosnippet_guard (整合 2 → 1) ─────────────────────────
127
+
128
+ /**
129
+ * Guard 检查:按参数自动路由
130
+ * 有 code → guardCheck() (单文件)
131
+ * 有 files → guardAuditFiles() (多文件)
132
+ */
133
+ export async function consolidatedGuard(ctx, args) {
134
+ if (args.files && Array.isArray(args.files) && args.files.length > 0) {
135
+ return guardHandlers.guardAuditFiles(ctx, args);
136
+ }
137
+ if (args.code) {
138
+ return guardHandlers.guardCheck(ctx, args);
139
+ }
140
+ throw new Error('autosnippet_guard requires either "code" (single check) or "files" (batch audit) parameter');
141
+ }
142
+
143
+ // ─── autosnippet_skill (整合 6 → 1) ─────────────────────────
144
+
145
+ /**
146
+ * Skill 管理:根据 operation 参数路由
147
+ * list → listSkills()
148
+ * load → loadSkill()
149
+ * create → createSkill()
150
+ * update → updateSkill()
151
+ * delete → deleteSkill()
152
+ * suggest → suggestSkills()
153
+ */
154
+ export async function consolidatedSkill(ctx, args) {
155
+ const op = args.operation;
156
+ if (!op) throw new Error('Missing required parameter: operation. Expected: list, load, create, update, delete, suggest');
157
+
158
+ // loadSkill expects { skillName }, map from { name }
159
+ if (args.name && !args.skillName) {
160
+ args.skillName = args.name;
161
+ }
162
+
163
+ switch (op) {
164
+ case 'list':
165
+ return skillHandlers.listSkills();
166
+ case 'load':
167
+ return skillHandlers.loadSkill(ctx, args);
168
+ case 'create':
169
+ return skillHandlers.createSkill(ctx, args);
170
+ case 'update':
171
+ return skillHandlers.updateSkill(ctx, args);
172
+ case 'delete':
173
+ return skillHandlers.deleteSkill(ctx, args);
174
+ case 'suggest':
175
+ return skillHandlers.suggestSkills(ctx);
176
+ default:
177
+ throw new Error(`Unknown skill operation: ${op}. Expected: list, load, create, update, delete, suggest`);
178
+ }
179
+ }
180
+
181
+ // ─── autosnippet_bootstrap (整合 3 → 1) ─────────────────────
182
+
183
+ /**
184
+ * 冷启动 & 扫描:根据 operation 参数路由
185
+ * knowledge → bootstrapKnowledge() (完整冷启动)
186
+ * refine → bootstrapRefine() (AI 润色)
187
+ * scan → scanProject() (轻量探查)
188
+ */
189
+ export async function consolidatedBootstrap(ctx, args) {
190
+ const op = args.operation;
191
+ if (!op) throw new Error('Missing required parameter: operation. Expected: knowledge, refine, scan');
192
+ switch (op) {
193
+ case 'knowledge':
194
+ return bootstrapHandlers.bootstrapKnowledge(ctx, args);
195
+ case 'refine':
196
+ return bootstrapHandlers.bootstrapRefine(ctx, args);
197
+ case 'scan':
198
+ return guardHandlers.scanProject(ctx, args);
199
+ default:
200
+ throw new Error(`Unknown bootstrap operation: ${op}. Expected: knowledge, refine, scan`);
201
+ }
202
+ }
203
+
204
+ // ─── autosnippet_submit_knowledge (增强:严格前置校验 + dedup) ──
205
+
206
+ /**
207
+ * 增强版提交:严格前置校验,缺少必要字段直接拒绝(不入库)。
208
+ * 通过校验后执行提交 + 去重检测,结果附在响应中。
209
+ *
210
+ * 设计原则:
211
+ * - 不降级:缺字段不自动补全,要求 Agent 一次性生成完整数据
212
+ * - 不重复提交:拒绝时不创建任何记录,Agent 需补齐后重新调用
213
+ */
214
+ export async function enhancedSubmitKnowledge(ctx, args) {
215
+ const { submitKnowledge } = await import('./knowledge.js');
216
+ const { checkRecipeReadiness } = await import('../../../shared/RecipeReadinessChecker.js');
217
+ const { envelope } = await import('../envelope.js');
218
+
219
+ const skipDuplicateCheck = args.skipDuplicateCheck === true;
220
+
221
+ // ── 严格前置校验:RecipeReady 不通过则直接拒绝 ──
222
+ const readinessInput = {
223
+ title: args.title,
224
+ code: args.content?.pattern || args.code || '',
225
+ language: args.language,
226
+ category: args.category,
227
+ trigger: args.trigger,
228
+ description: args.description,
229
+ headers: args.headers,
230
+ reasoning: args.reasoning,
231
+ knowledgeType: args.knowledgeType,
232
+ complexity: args.complexity,
233
+ usageGuide: args.usageGuide,
234
+ rationale: args.content?.rationale || args.rationale,
235
+ kind: args.kind,
236
+ doClause: args.doClause,
237
+ dontClause: args.dontClause,
238
+ whenClause: args.whenClause,
239
+ topicHint: args.topicHint,
240
+ coreCode: args.coreCode,
241
+ };
242
+ const readiness = checkRecipeReadiness(readinessInput);
243
+ if (!readiness.ready) {
244
+ return envelope({
245
+ success: false,
246
+ message: `提交被拒绝:缺少必要字段 [${readiness.missing.join(', ')}]。请在单次调用中补齐所有字段后重新提交,不要分步提交或先提交再补全。`,
247
+ errorCode: 'INCOMPLETE_SUBMISSION',
248
+ data: {
249
+ missingFields: readiness.missing,
250
+ suggestions: readiness.suggestions,
251
+ requiredFields: ['title', 'language', 'content', 'kind', 'doClause', 'category', 'trigger', 'description', 'headers', 'usageGuide', 'knowledgeType', 'rationale (in content.rationale)'],
252
+ },
253
+ meta: { tool: 'autosnippet_submit_knowledge' },
254
+ });
255
+ }
256
+
257
+ // ── 校验通过,执行提交 ──
258
+ const result = await submitKnowledge(ctx, args);
259
+
260
+ // 如果提交本身失败,直接返回
261
+ if (result && !result.success) return result;
262
+
263
+ // ── 附加去重检测结果(非阻塞) ──
264
+ let duplicateCheck = null;
265
+ if (!skipDuplicateCheck) {
266
+ try {
267
+ const dedupCandidate = {
268
+ title: args.title,
269
+ summary: args.description || '',
270
+ code: args.content?.pattern || '',
271
+ };
272
+ const dedupResult = await candidateHandlers.checkDuplicate(ctx, { candidate: dedupCandidate, threshold: 0.7, topK: 3 });
273
+ if (dedupResult?.data) {
274
+ duplicateCheck = {
275
+ hasSimilar: dedupResult.data.hasDuplicate ?? (dedupResult.data.matches?.length > 0),
276
+ closest: dedupResult.data.matches?.[0] || null,
277
+ };
278
+ }
279
+ } catch {
280
+ duplicateCheck = { hasSimilar: false, note: 'dedup skipped due to error' };
281
+ }
282
+ }
283
+
284
+ // 将去重结果附到响应中
285
+ if (result && result.data) {
286
+ result.data.duplicateCheck = duplicateCheck;
287
+ }
288
+
289
+ return result;
290
+ }
@@ -15,7 +15,7 @@ export async function guardCheck(ctx, args) {
15
15
  return envelope({
16
16
  success: true,
17
17
  data: { language: args.language || 'unknown', violations: [], summary: { total: 0, errors: 0, warnings: 0 } },
18
- meta: { tool: 'autosnippet_guard_check', note: 'Empty code — skipped' },
18
+ meta: { tool: 'autosnippet_guard', note: 'Empty code — skipped' },
19
19
  });
20
20
  }
21
21
 
@@ -43,7 +43,7 @@ export async function guardCheck(ctx, args) {
43
43
  return envelope({
44
44
  success: true,
45
45
  data: { language, violations, summary: { total: violations.length, errors: violations.filter(v => v.severity === 'error').length, warnings: violations.filter(v => v.severity === 'warning').length }, ...(warnings.length ? { warnings } : {}) },
46
- meta: { tool: 'autosnippet_guard_check' },
46
+ meta: { tool: 'autosnippet_guard' },
47
47
  });
48
48
  }
49
49
 
@@ -97,7 +97,7 @@ export async function guardAuditFiles(ctx, args) {
97
97
  })),
98
98
  ...(result.crossFileViolations?.length ? { crossFileViolations: result.crossFileViolations } : {}),
99
99
  },
100
- meta: { tool: 'autosnippet_guard_audit_files' },
100
+ meta: { tool: 'autosnippet_guard' },
101
101
  });
102
102
  }
103
103
 
@@ -113,7 +113,7 @@ export async function scanProject(ctx, args) {
113
113
  const allTargets = await spm.listTargets();
114
114
 
115
115
  if (!allTargets || allTargets.length === 0) {
116
- return envelope({ success: true, data: { targets: [], files: [], guardAudit: null, message: 'No SPM targets found' }, meta: { tool: 'autosnippet_scan_project' } });
116
+ return envelope({ success: true, data: { targets: [], files: [], guardAudit: null, message: 'No SPM targets found' }, meta: { tool: 'autosnippet_bootstrap' } });
117
117
  }
118
118
 
119
119
  // 收集所有文件(去重)
@@ -197,6 +197,6 @@ export async function scanProject(ctx, args) {
197
197
  ...(guardAudit.crossFileViolations?.length ? { crossFileViolations: guardAudit.crossFileViolations } : {}),
198
198
  } : null,
199
199
  },
200
- meta: { tool: 'autosnippet_scan_project' },
200
+ meta: { tool: 'autosnippet_bootstrap' },
201
201
  });
202
202
  }
@@ -115,7 +115,6 @@ export async function submitKnowledge(ctx, args) {
115
115
  ready: false,
116
116
  missingFields: readiness.missing,
117
117
  suggestions: readiness.suggestions,
118
- hint: '请补全以上字段后重新提交,或调用 autosnippet_enrich_candidates 进行完整性诊断',
119
118
  };
120
119
  }
121
120
 
@@ -163,8 +162,22 @@ export async function submitKnowledgeBatch(ctx, args) {
163
162
  const source = args.source || 'cursor-scan';
164
163
  let count = 0;
165
164
  const itemErrors = [];
165
+ const rejectedItems = [];
166
166
 
167
167
  for (let i = 0; i < items.length; i++) {
168
+ // ── 严格前置校验:缺少必要字段的条目直接拒绝,不入库 ──
169
+ const readinessInput = _toReadinessInput(items[i]);
170
+ const readiness = checkRecipeReadiness(readinessInput);
171
+ if (!readiness.ready) {
172
+ rejectedItems.push({
173
+ index: i,
174
+ title: items[i].title || '(untitled)',
175
+ missingFields: readiness.missing,
176
+ suggestions: readiness.suggestions,
177
+ });
178
+ continue;
179
+ }
180
+
168
181
  try {
169
182
  const itemData = _enrichToV3({ ...items[i], source }, ctx.container);
170
183
  await service.create(itemData, { userId: 'mcp' });
@@ -177,15 +190,15 @@ export async function submitKnowledgeBatch(ctx, args) {
177
190
  const data = { count, total: items.length, targetName: args.target_name };
178
191
  if (itemErrors.length > 0) data.errors = itemErrors;
179
192
 
180
- // Recipe-Ready 统计
181
- const notReady = items.filter(it => !checkRecipeReadiness(_toReadinessInput(it)).ready);
182
- if (notReady.length > 0) {
183
- const allMissing = [...new Set(notReady.flatMap(it => checkRecipeReadiness(_toReadinessInput(it)).missing))];
184
- data.recipeReadyHints = {
185
- notReadyCount: notReady.length,
193
+ // 被拒绝的条目:告知 Agent 需补齐哪些字段
194
+ if (rejectedItems.length > 0) {
195
+ const allMissing = [...new Set(rejectedItems.flatMap(it => it.missingFields))];
196
+ data.rejectedItems = rejectedItems;
197
+ data.rejectedSummary = {
198
+ rejectedCount: rejectedItems.length,
186
199
  totalCount: items.length,
187
200
  commonMissingFields: allMissing,
188
- hint: `${notReady.length}/${items.length} 条知识条目缺少必要字段(${allMissing.join(', ')}),请补全后重新提交`,
201
+ message: `${rejectedItems.length}/${items.length} 条知识条目因缺少必要字段被拒绝(${allMissing.join(', ')})。请一次性补齐所有字段后重新提交被拒绝的条目。`,
189
202
  };
190
203
  }
191
204
 
@@ -330,6 +343,8 @@ function _toReadinessInput(args) {
330
343
  } : undefined,
331
344
  knowledgeType: args.knowledgeType,
332
345
  complexity: args.complexity,
346
+ usageGuide: args.usageGuide,
347
+ rationale: args.content?.rationale || args.rationale,
333
348
  // Cursor Delivery 字段
334
349
  kind: args.kind,
335
350
  doClause: args.doClause,