UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

360 lines (359 loc) 21.5 kB
/** * mcp-tools.ts — MCP 工具输入 Zod Schema * * 每个 MCP 工具的输入参数定义为 Zod Schema,既做运行时校验, * 又可通过 zodToJsonSchema() 自动生成 inputSchema 声明(消除双重维护)。 * * 命名约定:`{ToolSuffix}Input`,如 `SearchInput` 对应 `autosnippet_search`。 * * @module shared/schemas/mcp-tools */ import { z } from 'zod'; import { ComplexityEnum, ContentSchema, IdField, KindEnum, LanguageField, ReasoningSchema, ScopeEnum, StrictKindEnum, TitleField, } from './common.js'; // ══════════════════════════════════════════════════════ // 1. autosnippet_health — 无参数 // ══════════════════════════════════════════════════════ export const HealthInput = z.object({}); // ══════════════════════════════════════════════════════ // 2. autosnippet_search // ══════════════════════════════════════════════════════ export const SearchInput = z.object({ query: z.string().min(1, 'query is required').describe('搜索关键词或自然语言描述'), mode: z .enum(['auto', 'keyword', 'bm25', 'semantic', 'context']) .default('auto') .describe('auto=自动选策略 | keyword=精确匹配 | bm25=全文检索 | semantic=向量语义 | context=综合+上下文'), kind: KindEnum.default('all').describe('过滤知识类型: all/rule/pattern/fact'), limit: z.number().int().min(1).max(100).default(10), language: z.string().optional().describe('按编程语言过滤,如 swift/typescript'), sessionId: z.string().optional(), sessionHistory: z.array(z.record(z.string(), z.unknown())).optional(), }); // ══════════════════════════════════════════════════════ // 3. autosnippet_knowledge // ══════════════════════════════════════════════════════ export const KnowledgeInput = z .object({ operation: z .enum(['list', 'get', 'insights', 'confirm_usage']) .default('list') .describe('list=列表 | get=单条详情(id) | insights=质量分析(id) | confirm_usage=记录采纳(id)'), id: z.string().optional().describe('get/insights/confirm_usage 时必填'), kind: KindEnum.optional(), language: z.string().optional(), category: z.string().optional(), knowledgeType: z.string().optional(), status: z.string().optional(), complexity: z.string().optional(), limit: z.number().int().min(1).max(200).default(20), usageType: z.enum(['adoption', 'application']).optional(), feedback: z.string().optional(), }) .refine((d) => { if (['get', 'insights', 'confirm_usage'].includes(d.operation) && !d.id) { return false; } return true; }, { message: 'id is required for get/insights/confirm_usage operations' }); // ══════════════════════════════════════════════════════ // 4. autosnippet_structure // ══════════════════════════════════════════════════════ export const StructureInput = z.object({ operation: z .enum(['targets', 'files', 'metadata']) .default('targets') .describe('targets=构建目标列表 | files=Target文件列表 | metadata=项目元数据'), targetName: z.string().optional().describe('files 操作时指定目标名'), includeSummary: z.boolean().default(true), includeContent: z.boolean().default(false), contentMaxLines: z.number().int().min(1).default(100), maxFiles: z.number().int().min(1).max(5000).default(500), }); // ══════════════════════════════════════════════════════ // 5. autosnippet_graph // ══════════════════════════════════════════════════════ export const GraphInput = z.object({ operation: z .enum(['query', 'impact', 'path', 'stats']) .describe('query=节点关系 | impact=影响分析 | path=路径查找 | stats=全局统计'), nodeId: z.string().optional().describe('query/impact 时指定节点 ID'), nodeType: z.string().default('recipe'), fromId: z.string().optional(), toId: z.string().optional(), direction: z.enum(['out', 'in', 'both']).default('both'), maxDepth: z.number().int().min(1).max(10).default(3), relation: z.string().optional(), }); // ══════════════════════════════════════════════════════ // 6. autosnippet_call_context // ══════════════════════════════════════════════════════ export const CallContextInput = z.object({ methodName: z.string().min(1, 'methodName is required').describe('函数/方法名称,支持部分匹配'), direction: z .enum(['callers', 'callees', 'both', 'impact']) .default('both') .describe('callers=上游调用者 | callees=下游依赖 | both=双向 | impact=影响半径'), maxDepth: z.number().int().min(1).max(5).default(2), }); // ══════════════════════════════════════════════════════ // 7. autosnippet_guard // ══════════════════════════════════════════════════════ export const GuardInput = z.object({ operation: z .enum(['check', 'review', 'reverse_audit', 'coverage_matrix', 'compliance_report']) .optional() .describe('Guard 操作类型。reverse_audit: Recipe→Code 反向验证;coverage_matrix: 模块覆盖率矩阵;compliance_report: 3D 合规报告(含 uncertain)。省略则按 code/files 自动路由。'), files: z.array(z.string()).optional(), code: z.string().optional(), language: z.string().optional(), filePath: z.string().optional(), maxFiles: z.number().optional().describe('reverse_audit/coverage_matrix 时扫描的最大文件数'), }); // ══════════════════════════════════════════════════════ // 7b. autosnippet_submit_knowledge (unified pipeline) // ══════════════════════════════════════════════════════ /** * 单条知识条目字段定义(items 数组内部元素的严格 Schema) * 用于文档/类型推导,实际 items 使用 z.record() 宽容接收后在 handler 层校验。 */ export const SubmitKnowledgeItemSchema = z.object({ // ── 必填字段 ── title: TitleField.describe('知识标题,简洁明确'), language: LanguageField.describe('编程语言,如 typescript/swift/python'), content: ContentSchema.describe('内容对象: { pattern?: "代码片段", markdown?: "正文", rationale: "设计原理" }。pattern/markdown 至少提供一个,rationale 必填'), kind: StrictKindEnum.describe('rule=规范约束 | pattern=代码模式 | fact=项目事实'), doClause: z .string() .min(1, 'doClause is required') .describe('✅ 应该怎么做(Channel A+B 硬依赖)'), dontClause: z.string().min(1, 'dontClause is required').describe('❌ 不应该怎么做'), whenClause: z.string().min(1, 'whenClause is required').describe('何时适用(Channel B 硬依赖)'), coreCode: z.string().min(1, 'coreCode is required').describe('核心代码片段(Channel B 模板块)'), category: z .string() .min(1, 'category is required') .describe('View/Service/Tool/Model/Network/Storage/UI/Utility'), trigger: z.string().min(1, 'trigger is required').describe('触发关键词,如 @NetworkMonitor'), description: z.string().min(1, 'description is required').describe('一句话描述用途'), headers: z.array(z.string()).describe('完整 import 语句列表'), usageGuide: z .string() .min(1, 'usageGuide is required') .describe('使用指南(Markdown,用 ### 分节:何时用/关键点/何时不用)'), knowledgeType: z .string() .min(1, 'knowledgeType is required') .describe('code-pattern/architecture/best-practice/code-standard 等'), reasoning: ReasoningSchema.describe('推理对象: { whyStandard: "原因", sources: ["来源"], confidence: 0.0-1.0 }'), // ── 可选字段 ── topicHint: z.string().optional(), complexity: ComplexityEnum.optional(), scope: ScopeEnum.optional(), difficulty: z.string().optional(), tags: z.array(z.string()).optional(), constraints: z.record(z.string(), z.unknown()).optional(), relations: z.record(z.string(), z.unknown()).optional(), headerPaths: z.array(z.string()).optional(), moduleName: z.string().optional(), includeHeaders: z.boolean().optional(), source: z.string().optional(), }); export const SubmitKnowledgeInput = z.object({ items: z .array(z.record(z.string(), z.unknown())) .min(1) .describe('知识条目数组(1~N 条)。单条与批量统一处理,所有条目严格校验 + 融合分析。' + '每条字段: title, language, content(对象), kind, doClause, dontClause, whenClause, coreCode, category, trigger, description, headers, usageGuide, knowledgeType, reasoning(对象)。'), target_name: z.string().optional().describe('来源标识,如 network-module-scan'), source: z.string().optional().describe('来源标记,默认 mcp'), skipConsolidation: z .boolean() .default(false) .describe('跳过融合分析(当确认需要独立新建时设为 true)'), skipDuplicateCheck: z.boolean().default(false), client_id: z.string().optional(), dimensionId: z.string().optional().describe('冷启动关联维度 ID'), supersedes: z .string() .optional() .describe('声明新 Recipe 替代旧 Recipe 的 ID。提交后系统将创建 supersede 提案,观察窗口内对比新旧表现后自动执行。'), }); // ══════════════════════════════════════════════════════ // 10. autosnippet_skill // ══════════════════════════════════════════════════════ export const SkillInput = z.object({ operation: z .enum(['list', 'load', 'create', 'update', 'delete', 'suggest']) .describe('list=列表 | load=加载内容(name) | create=创建 | update=更新 | delete=删除 | suggest=推荐'), name: z.string().optional().describe('Skill 名称(kebab-case,如 autosnippet-create)'), skillName: z.string().optional().describe('name 的别名,与 name 等价'), section: z.string().optional().describe('load 时过滤指定章节'), description: z.string().optional().describe('create/update 时的简短描述'), content: z.string().optional().describe('create/update 时的 Markdown 内容'), overwrite: z.boolean().default(false), createdBy: z.enum(['manual', 'user-ai', 'system-ai', 'external-ai']).default('external-ai'), }); // ══════════════════════════════════════════════════════ // 11. autosnippet_bootstrap — 无参数 // ══════════════════════════════════════════════════════ export const BootstrapInput = z.object({}); // ══════════════════════════════════════════════════════ // 11a. autosnippet_rescan — 增量知识更新 // ══════════════════════════════════════════════════════ export const RescanInput = z.object({ dimensions: z.array(z.string()).optional().describe('指定维度列表,空 = 全部活跃维度'), reason: z.string().optional().describe('触发原因(记录到报告)'), }); // ══════════════════════════════════════════════════════ // 11b. autosnippet_dimension_complete // ══════════════════════════════════════════════════════ export const DimensionCompleteInput = z.object({ sessionId: z.string().optional(), dimensionId: z.string().min(1, 'dimensionId is required'), submittedRecipeIds: z.array(z.string()).optional(), analysisText: z .string() .min(1, 'analysisText is required') .describe('维度分析报告(Markdown)。写得越详细,生成的 Skill 质量越高;若过短,系统会自动从候选知识中合成。'), referencedFiles: z.array(z.string()).optional(), keyFindings: z.array(z.string()).optional(), candidateCount: z.number().int().min(0).optional(), crossDimensionHints: z.record(z.string(), z.string()).optional(), }); // ══════════════════════════════════════════════════════ // 11c. autosnippet_wiki (merged: plan + finalize) // ══════════════════════════════════════════════════════ export const WikiInput = z.object({ operation: z .enum(['plan', 'finalize']) .describe('plan — 规划主题 + 数据包; finalize — 写入 meta.json + 验证'), // plan 参数 language: z.enum(['zh', 'en']).optional().describe('Wiki 语言,默认 zh'), sessionId: z.string().optional(), // finalize 参数 articlesWritten: z.array(z.string()).optional(), }); // ══════════════════════════════════════════════════════ // 12. autosnippet_capabilities — 无参数 // ══════════════════════════════════════════════════════ export const CapabilitiesInput = z.object({}); // ══════════════════════════════════════════════════════ // 13. autosnippet_task (5 operations) // ══════════════════════════════════════════════════════ export const TaskInput = z.object({ operation: z .enum(['prime', 'create', 'close', 'fail', 'record_decision']) .describe('prime=加载知识上下文 | create=创建任务锚点 | close=完成+Guard | fail=放弃 | record_decision=记录用户偏好'), title: z.string().optional().describe('Task or decision title (create / record_decision)'), description: z.string().optional().describe('Decision description (record_decision)'), id: z .string() .optional() .describe('Task ID (close / fail). Optional if a task was created in the current session.'), taskId: z.string().optional().describe('Alias for id (accepted for convenience)'), reason: z.string().optional().describe('Close reason or fail reason'), rationale: z.string().optional().describe('Decision rationale (record_decision)'), tags: z.array(z.string()).optional().describe('Decision tags (record_decision)'), userQuery: z .string() .optional() .describe('User current input / prompt text for knowledge-aware search'), activeFile: z.string().optional().describe('Currently active file path in IDE'), language: z.string().optional().describe('Current programming language'), }); // ══════════════════════════════════════════════════════ // Admin Tools // ══════════════════════════════════════════════════════ // 14. autosnippet_enrich_candidates export const EnrichCandidatesInput = z.object({ candidateIds: z .array(z.string()) .min(1, 'at least one candidate ID required') .max(20, 'max 20 candidates per call'), }); // 15. autosnippet_knowledge_lifecycle export const KnowledgeLifecycleInput = z.object({ id: IdField, action: z .enum([ 'submit', 'approve', 'reject', 'publish', 'deprecate', 'reactivate', 'to_draft', 'fast_track', ]) .describe('approve/fast_track=发布 | reject=拒绝 | deprecate=废弃 | reactivate=恢复 | to_draft=回草稿'), reason: z.string().optional().describe('reject/deprecate 时的理由'), }); // 18. autosnippet_panorama export const PanoramaInput = z.object({ operation: z .enum([ 'overview', 'module', 'gaps', 'health', 'governance_cycle', 'decay_report', 'staging_check', 'enhancement_suggestions', ]) .default('overview') .describe('overview=项目骨架+层级+模块角色 | module=单模块详情+邻居关系 | gaps=知识空白区 | health=全景健康度 | governance_cycle=新陈代谢完整周期 | decay_report=衰退报告 | staging_check=staging检查+自动发布 | enhancement_suggestions=增强建议'), module: z.string().optional().describe('模块名称(operation=module 时必填)'), }); // 19. autosnippet_evolve const EvolveDecisionSchema = z.object({ recipeId: z.string().describe('目标 Recipe ID'), action: z .enum(['propose_evolution', 'confirm_deprecation', 'skip']) .describe('propose_evolution=提案进化 | confirm_deprecation=确认废弃 | skip=跳过'), evidence: z .object({ codeSnippet: z.string().describe('读到的真实代码片段'), filePath: z.string().describe('代码所在文件路径'), type: z .enum(['enhance', 'correction']) .describe('enhance=模式迁移/功能扩展 | correction=描述错误/接口变更'), suggestedChanges: z.string().describe('建议的内容变更'), }) .optional() .describe('propose_evolution 时必填'), reason: z.string().optional().describe('confirm_deprecation 时必填,废弃原因'), skipReason: z .enum(['still_valid', 'insufficient_info']) .optional() .describe('skip 时必填: still_valid=仍然有效(刷新验证时间) | insufficient_info=信息不足'), }); export const EvolveInput = z.object({ decisions: z .array(EvolveDecisionSchema) .min(1) .describe('进化决策数组,每个元素对应一个 Recipe 的决策'), }); // ══════════════════════════════════════════════════════ // 工具名 → Schema 映射表(用于 wrapHandler 自动注入校验) // ══════════════════════════════════════════════════════ export const TOOL_SCHEMAS = { autosnippet_health: HealthInput, autosnippet_search: SearchInput, autosnippet_knowledge: KnowledgeInput, autosnippet_structure: StructureInput, autosnippet_graph: GraphInput, autosnippet_call_context: CallContextInput, autosnippet_guard: GuardInput, autosnippet_submit_knowledge: SubmitKnowledgeInput, autosnippet_skill: SkillInput, autosnippet_bootstrap: BootstrapInput, autosnippet_rescan: RescanInput, autosnippet_dimension_complete: DimensionCompleteInput, autosnippet_wiki: WikiInput, autosnippet_task: TaskInput, autosnippet_enrich_candidates: EnrichCandidatesInput, autosnippet_knowledge_lifecycle: KnowledgeLifecycleInput, autosnippet_panorama: PanoramaInput, autosnippet_evolve: EvolveInput, };