autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
224 lines (223 loc) • 7.62 kB
JavaScript
/**
* Guard 文件检查 API 路由
*
* 提供 HTTP 端点供 VS Code Extension 调用,触发 Guard 实时检查。
* 返回格式面向 IDE DiagnosticCollection 优化。
*
* 端点:
* POST /api/v1/guard/file — 单文件检查(Extension onDidSave 调用)
* POST /api/v1/guard/batch — 批量文件检查(Extension 工作区扫描)
*/
import { readFileSync } from 'node:fs';
import express from 'express';
import { GuardBatchBody, GuardFileBody } from '#shared/schemas/http-requests.js';
import { getServiceContainer } from '../../injection/ServiceContainer.js';
import { validate } from '../middleware/validate.js';
const router = express.Router();
/**
* POST /api/v1/guard/file
*
* 请求体:
* { filePath: string, content?: string, language?: string }
*
* - filePath: 必须。文件路径(用于语言检测 + 违规追踪)
* - content: 可选。文件内容,若省略则从 filePath 磁盘读取
* - language: 可选。语言标识,若省略则从 filePath 扩展名推断
*
* 响应:
* {
* success: true,
* data: {
* filePath, language, violations[], summary,
* fixedViolations[] // 与上次检查对比已修复的违规
* }
* }
*/
router.post('/file', validate(GuardFileBody), async (req, res) => {
const { filePath, content, language } = req.body;
// 获取文件内容
let code = content;
if (!code) {
try {
code = readFileSync(filePath, 'utf8');
}
catch (err) {
return void res.status(400).json({
success: false,
message: `Cannot read file: ${err.message}`,
});
}
}
const container = getServiceContainer();
const { GuardCheckEngine, detectLanguage } = await import('../../service/guard/GuardCheckEngine.js');
// 获取 Engine(含 EP 注入)
const engine = await _getEngine(container, GuardCheckEngine);
// 检测语言
const lang = language || detectLanguage(filePath);
// 执行检查
const violations = engine.checkCode(code, lang, { filePath });
// 格式化违规消息面向 Agent
const formattedViolations = violations.map((v) => ({
...v,
// 面向 Agent 的诊断消息格式
diagnosticMessage: _buildDiagnosticMessage(v),
}));
const summary = {
total: violations.length,
errors: violations.filter((v) => v.severity === 'error').length,
warnings: violations.filter((v) => v.severity === 'warning').length,
infos: violations.filter((v) => v.severity === 'info').length,
};
// GuardFeedbackLoop: 检测修复 + confirmUsage
let fixedViolations = [];
try {
const feedbackLoop = container.get('guardFeedbackLoop');
fixedViolations = feedbackLoop.processFixDetection({ violations }, filePath);
}
catch {
/* feedbackLoop not available */
}
// 写入 ViolationsStore(供后续对比)
try {
const violationsStore = container.get('violationsStore');
violationsStore.appendRun({
filePath,
violations,
summary: `Guard file check: ${summary.errors}E ${summary.warnings}W ${summary.infos}I`,
});
}
catch {
/* violationsStore not available */
}
res.json({
success: true,
data: {
filePath,
language: lang,
violations: formattedViolations,
summary,
fixedViolations,
},
});
});
/**
* POST /api/v1/guard/batch
*
* 请求体:
* { files: Array<{ filePath: string, content?: string, language?: string }> }
*
* 批量检查多个文件(工作区级 Guard 扫描)
*/
router.post('/batch', validate(GuardBatchBody), async (req, res) => {
const { files } = req.body;
const container = getServiceContainer();
const { GuardCheckEngine, detectLanguage } = await import('../../service/guard/GuardCheckEngine.js');
const engine = await _getEngine(container, GuardCheckEngine);
const results = [];
let totalErrors = 0;
let totalWarnings = 0;
for (const file of files) {
if (!file.filePath) {
continue;
}
let code = file.content;
if (!code) {
try {
code = readFileSync(file.filePath, 'utf8');
}
catch {
results.push({
filePath: file.filePath,
language: 'unknown',
violations: [],
summary: { total: 0, errors: 0, warnings: 0, infos: 0 },
error: 'Cannot read file',
});
continue;
}
}
const lang = file.language || detectLanguage(file.filePath);
const violations = engine.checkCode(code, lang, { filePath: file.filePath });
const summary = {
total: violations.length,
errors: violations.filter((v) => v.severity === 'error').length,
warnings: violations.filter((v) => v.severity === 'warning')
.length,
infos: violations.filter((v) => v.severity === 'info').length,
};
totalErrors += summary.errors;
totalWarnings += summary.warnings;
results.push({
filePath: file.filePath,
language: lang,
violations: violations.map((v) => ({
...v,
diagnosticMessage: _buildDiagnosticMessage(v),
})),
summary,
});
}
res.json({
success: true,
data: {
files: results,
summary: {
totalFiles: results.length,
totalErrors,
totalWarnings,
},
},
});
});
// ═══ 内部工具 ═══════════════════════════════════════
/**
* 获取或创建 GuardCheckEngine,并注入 Enhancement Pack 规则
* @param container ServiceContainer
* @param GuardCheckEngine GuardCheckEngine class
* @returns engine
*/
async function _getEngine(container, GuardCheckEngineCtor) {
let engine;
try {
engine = container.get('guardCheckEngine');
}
catch {
const database = container.get('database');
engine = new GuardCheckEngineCtor(database);
}
// 注入 Enhancement Pack Guard 规则
if (!engine.isEpInjected()) {
try {
const { getEnhancementRegistry } = await import('../../core/enhancement/index.js');
const registry = getEnhancementRegistry();
if (registry) {
const packs = registry.all();
const guardRules = packs.flatMap((pack) => pack.getGuardRules());
engine.injectExternalRules(guardRules);
engine.markEpInjected();
}
}
catch {
/* EP not available */
}
}
return engine;
}
/**
* 构建面向 Agent 优化的诊断消息
*
* 双重受众设计:
* - 人类看到: 波浪线 + 违规描述
* - Agent 看到: ruleId + 明确的 MCP 搜索指令
*/
function _buildDiagnosticMessage(violation) {
const { ruleId, message, fixSuggestion } = violation;
let msg = `[AutoSnippet Guard] ${ruleId}: ${message}`;
if (fixSuggestion) {
msg += `\n修复建议: ${fixSuggestion}`;
}
// Agent 指引:嵌入 MCP 搜索建议
msg += `\n搜 autosnippet_search('${ruleId}') 查找正确写法。`;
return msg;
}
export default router;