autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
164 lines (163 loc) • 6.28 kB
JavaScript
/**
* 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;
}
}
}