UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

164 lines (163 loc) 6.28 kB
/** * DeliveryVerifier — Bootstrap/Rescan 完成后交付完整性检查 * * 验证以下交付物是否正确生成: * - Channel A: autosnippet-project-rules.mdc * - Channel B: autosnippet-patterns 系列文件 * - Channel C: .cursor/skills/ 目录 * - Channel F: AGENTS.md, CLAUDE.md, copilot-instructions.md * - Wiki: meta.json * - Skills: project 级 Skill 目录 * - 向量索引: asvec 文件 * * @module service/bootstrap/DeliveryVerifier */ import fs from 'node:fs'; import path from 'node:path'; import { getContextIndexPath, getProjectKnowledgePath, getProjectSkillsPath, } from '#infra/config/Paths.js'; // ── DeliveryVerifier ──────────────────────────────────────── export class DeliveryVerifier { #projectRoot; constructor(projectRoot) { this.#projectRoot = projectRoot; } /** * 验证所有交付物是否正确生成 */ verify() { const failures = []; // Channel A: .cursor/rules/autosnippet-project-rules.mdc const channelA = this.#verifyChannelA(); if (!channelA.generated) { failures.push('Channel A: autosnippet-project-rules.mdc missing or empty'); } // Channel B: .cursor/rules/autosnippet-patterns-*.mdc const channelB = this.#verifyChannelB(); if (!channelB.generated) { failures.push('Channel B: no autosnippet-patterns-*.mdc files found'); } // Channel C: .cursor/skills/ const channelC = this.#verifyChannelC(); if (!channelC.generated) { failures.push('Channel C: .cursor/skills/ directory missing'); } // Channel F: AGENTS.md, CLAUDE.md, copilot-instructions.md const channelF = this.#verifyChannelF(); if (!channelF.generated) { failures.push('Channel F: agent instruction files incomplete'); } // Wiki const wiki = this.#verifyWiki(); // Skills const skills = this.#verifySkills(); // Vector Index const vectorIndex = this.#verifyVectorIndex(); return { channelA, channelB, channelC, channelF, wiki, skills, vectorIndex, allPassed: failures.length === 0, failures, }; } // ─── 各通道验证 ─────────────────────────────────────── #verifyChannelA() { const filePath = path.join(this.#projectRoot, '.cursor', 'rules', 'autosnippet-project-rules.mdc'); if (fs.existsSync(filePath)) { const size = fs.statSync(filePath).size; return { generated: size > 0, file: 'autosnippet-project-rules.mdc', size }; } return { generated: false }; } #verifyChannelB() { const rulesDir = path.join(this.#projectRoot, '.cursor', 'rules'); if (!fs.existsSync(rulesDir)) { return { generated: false, files: [], count: 0 }; } const files = fs .readdirSync(rulesDir) .filter((f) => f.startsWith('autosnippet-patterns-') && f.endsWith('.mdc')); return { generated: files.length > 0, files, count: files.length, }; } #verifyChannelC() { const skillsDir = path.join(this.#projectRoot, '.cursor', 'skills'); if (!fs.existsSync(skillsDir)) { return { generated: false, skillCount: 0 }; } const count = fs.readdirSync(skillsDir).length; return { generated: true, skillCount: count }; } #verifyChannelF() { const agentsMd = this.#hasAutoSnippetSection(path.join(this.#projectRoot, 'AGENTS.md')); const claudeMd = this.#hasAutoSnippetSection(path.join(this.#projectRoot, 'CLAUDE.md')); const copilotInstructions = this.#hasAutoSnippetSection(path.join(this.#projectRoot, '.github', 'copilot-instructions.md')); return { generated: agentsMd || claudeMd || copilotInstructions, agentsMd, claudeMd, copilotInstructions, }; } #verifyWiki() { const kbPath = getProjectKnowledgePath(this.#projectRoot); const metaPath = path.join(kbPath, 'wiki', 'meta.json'); if (fs.existsSync(metaPath)) { try { const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); return { generated: true, pageCount: meta.pages?.length || 0, }; } catch { return { generated: false }; } } return { generated: false }; } #verifySkills() { const skillsDir = getProjectSkillsPath(this.#projectRoot); if (!fs.existsSync(skillsDir)) { return { generated: false, skillCount: 0 }; } const dirs = fs .readdirSync(skillsDir) .filter((d) => d.startsWith('project-') && fs.statSync(path.join(skillsDir, d)).isDirectory()); return { generated: dirs.length > 0, skillCount: dirs.length }; } #verifyVectorIndex() { const indexDir = getContextIndexPath(this.#projectRoot); if (!fs.existsSync(indexDir)) { return { generated: false, rebuilt: false, documentCount: 0 }; } const asvecFiles = fs .readdirSync(indexDir) .filter((f) => f.endsWith('.asvec') || f.endsWith('.json')); return { generated: asvecFiles.length > 0, rebuilt: true, documentCount: asvecFiles.length, }; } // ─── 辅助 ─────────────────────────────────────────── #hasAutoSnippetSection(filePath) { try { if (!fs.existsSync(filePath)) { return false; } const content = fs.readFileSync(filePath, 'utf8'); return content.includes('autosnippet:begin'); } catch { return false; } } }