UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

176 lines (175 loc) 6.93 kB
/** * RecipeCandidateValidator — Recipe 候选校验器 (V3) * * 验证候选是否满足 V3 结构化字段要求。 * 核心变更:用 content 对象替代旧版 code 字符串。 */ import { getRequiredFieldNames } from '#domain/knowledge/FieldSpec.js'; import { LanguageService } from '../../shared/LanguageService.js'; /* ── V3 必填字段(从 FieldSpec 获取顶层字段,排除嵌套和容器字段) ── */ const REQUIRED_FIELDS = getRequiredFieldNames().filter((f) => !['content', 'headers', 'reasoning', 'knowledgeType', 'usageGuide'].includes(f)); /* ── 需要 content 子对象有内容 ── */ // NOTE: reserved for future content sub-field validation // const REQUIRED_CONTENT_FIELDS = ['pattern', 'markdown', 'rationale']; const VALID_CATEGORIES = new Set([ 'view', 'service', 'tool', 'model', 'network', 'storage', 'ui', 'utility', ]); const VALID_KINDS = new Set(['rule', 'pattern', 'fact']); export class RecipeCandidateValidator { /** * 验证单个候选(V3 结构) * @returns } */ validate(candidate) { const errors = []; const warnings = []; if (!candidate || typeof candidate !== 'object') { return { valid: false, errors: ['候选为空或类型错误'], warnings: [] }; } // ── V3 必填字段 ── for (const field of REQUIRED_FIELDS) { const val = candidate[field]; if (!val || (typeof val === 'string' && !val.trim())) { errors.push(`缺少必填字段: ${field}`); } } // ── content 对象必须包含有效内容 ── const content = candidate.content; if (!content || typeof content !== 'object') { errors.push('缺少必填字段: content(需为 { pattern, markdown, rationale } 对象)'); } else { const hasPattern = !!(content.pattern && String(content.pattern).trim()); const hasMarkdown = !!(content.markdown && String(content.markdown).trim()); if (!hasPattern && !hasMarkdown) { errors.push('content.pattern 或 content.markdown 至少需要一个非空'); } if (!content.rationale || !String(content.rationale).trim()) { errors.push('缺少必填字段: content.rationale(设计原理)'); } } // ── trigger 格式 ── if (candidate.trigger && typeof candidate.trigger === 'string') { if (candidate.trigger.length < 2) { errors.push('trigger 过短'); } if (candidate.trigger.length > 64) { errors.push('trigger 过长 (>64)'); } if (!candidate.trigger.startsWith('@')) { warnings.push('trigger 应以 @ 开头'); } if (!/^@?[a-zA-Z0-9_\-:.]+$/.test(candidate.trigger)) { warnings.push('trigger 含特殊字符,建议仅使用字母/数字/下划线/连字符'); } } // ── kind 合法性 ── if (candidate.kind && !VALID_KINDS.has(candidate.kind)) { errors.push(`kind "${candidate.kind}" 无效 — 必须为 rule/pattern/fact`); } // ── category 合法性 ── if (candidate.category && !VALID_CATEGORIES.has(candidate.category.toLowerCase())) { warnings.push(`category "${candidate.category}" 不在标准列表(View/Service/Tool/Model/Network/Storage/UI/Utility)`); } // ── language 合法性 ── if (candidate.language) { const lang = candidate.language.toLowerCase(); if (!LanguageService.isKnownLang(lang) && lang !== 'objc' && lang !== 'markdown') { warnings.push(`language "${candidate.language}" 不在已知语言列表`); } } // ── headers 必填 ── if (!Array.isArray(candidate.headers)) { errors.push('缺少必填字段: headers(需为 import 语句数组,无 import 时传 [])'); } // ── knowledgeType 必填 ── if (!candidate.knowledgeType || !String(candidate.knowledgeType).trim()) { errors.push('缺少必填字段: knowledgeType'); } // ── usageGuide 必填 ── if (!candidate.usageGuide || !String(candidate.usageGuide).trim()) { errors.push('缺少必填字段: usageGuide(使用指南,### 章节格式)'); } // ── 推理依据 (reasoning) 必填 ── if (!candidate.reasoning) { errors.push('缺少必填字段: reasoning(需包含 whyStandard + sources + confidence)'); } else { if (!candidate.reasoning.whyStandard?.trim()) { errors.push('reasoning.whyStandard 不能为空'); } if (!Array.isArray(candidate.reasoning.sources) || candidate.reasoning.sources.length === 0) { errors.push('reasoning.sources 至少包含一项来源'); } if (typeof candidate.reasoning.confidence !== 'number' || candidate.reasoning.confidence < 0 || candidate.reasoning.confidence > 1) { warnings.push('reasoning.confidence 应为 0-1 的数字'); } } // ── 标签 ── if (candidate.tags && !Array.isArray(candidate.tags)) { warnings.push('tags 应为数组'); } return { valid: errors.length === 0, errors, warnings, }; } /** * 批量验证 * @returns }} */ validateBatch(candidates) { const valid = []; const invalid = []; for (const candidate of candidates) { const result = this.validate(candidate); if (result.valid) { valid.push({ candidate, ...result }); } else { invalid.push({ candidate, ...result }); } } return { valid, invalid, summary: { total: candidates.length, validCount: valid.length, invalidCount: invalid.length, }, }; } /** 获取有效类别列表 */ getValidCategories() { return [...VALID_CATEGORIES]; } /** 获取有效 kind 列表 */ getValidKinds() { return [...VALID_KINDS]; } /** 获取所有必填字段名列表 */ getRequiredFields() { return [ ...REQUIRED_FIELDS, 'content', 'content.rationale', 'headers', 'knowledgeType', 'usageGuide', 'reasoning', 'reasoning.whyStandard', 'reasoning.sources', ]; } }