autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
144 lines (143 loc) • 5.52 kB
JavaScript
/**
* GuardFeedbackLoop — Guard ↔ Recipe 闭环联动
*
* 功能:
* 1. 对比当前和历史 violations,检测已修复的违规
* 2. 已修复违规如有 fixSuggestion → 自动 confirmUsage(记录 Recipe 使用)
* 3. 集成到 guardAuditFiles MCP handler 和 GuardHandler (FileWatcher)
*/
import Logger from '../../infrastructure/logging/Logger.js';
export class GuardFeedbackLoop {
feedbackCollector;
guardCheckEngine;
logger;
violationsStore;
_signalBus;
/** @param [options.guardCheckEngine] 用于查找规则 */
constructor(violationsStore, feedbackCollector, options = {}) {
this.violationsStore = violationsStore;
this.feedbackCollector = feedbackCollector;
this.guardCheckEngine = options.guardCheckEngine || null;
this._signalBus = options.signalBus || null;
this.logger = Logger.getInstance();
}
/**
* 对比当前和历史 violations,检测已修复的违规
* @param currentResult 本次检查结果
* @param filePath 文件路径
* @returns >} 已修复且有 Recipe 关联的列表
*/
detectFixedViolations(currentResult, filePath) {
if (!this.violationsStore) {
return [];
}
try {
const previousRuns = this.violationsStore.getRunsByFile(filePath);
if (previousRuns.length === 0) {
return [];
}
// 取最近一次运行结果
const lastRun = previousRuns[previousRuns.length - 1];
const lastRuleIds = new Set((lastRun.violations || []).map((v) => v.ruleId));
const currentRuleIds = new Set((currentResult.violations || []).map((v) => v.ruleId));
const fixed = [];
for (const ruleId of lastRuleIds) {
if (!currentRuleIds.has(ruleId)) {
// 该规则的违规已消失 → 修复了
const fixRecipeId = this._findFixRecipe(ruleId, lastRun.violations);
if (fixRecipeId) {
fixed.push({ ruleId, filePath, fixRecipeId });
}
}
}
return fixed;
}
catch (err) {
this.logger.debug(`[GuardFeedbackLoop] detectFixedViolations error: ${err.message}`);
return [];
}
}
/**
* 对已修复的违规自动确认使用
* @param fixedList
*/
autoConfirmUsage(fixedList) {
if (!this.feedbackCollector || !fixedList?.length) {
return;
}
for (const { ruleId, fixRecipeId, filePath } of fixedList) {
try {
this.feedbackCollector.record('insert', fixRecipeId, {
source: 'guard_fix_detection',
automatic: true,
ruleId,
filePath,
});
this.logger.info(`[GuardFeedbackLoop] Auto-confirmed usage: recipe=${fixRecipeId} from fixing rule=${ruleId}`);
// ── Signal: usage confirmation ──
if (this._signalBus) {
this._signalBus.send('usage', 'GuardFeedbackLoop', 1, {
target: fixRecipeId,
metadata: { ruleId, filePath, source: 'guard_fix_detection' },
});
}
}
catch (err) {
this.logger.debug(`[GuardFeedbackLoop] autoConfirmUsage error: ${err.message}`);
}
}
}
/**
* 一站式处理:检测修复 + 自动确认
* 供 MCP handler、GuardHandler、HTTP guard/file 端点集成调用
* @param currentResult
*/
processFixDetection(currentResult, filePath) {
const fixed = this.detectFixedViolations(currentResult, filePath);
if (fixed.length > 0) {
this.autoConfirmUsage(fixed);
this.logger.info(`[GuardFeedbackLoop] Detected ${fixed.length} fixed violations in ${filePath}`);
}
return fixed;
}
/**
* 获取闭环统计数据
* @returns }
*/
getStats() {
return {
hasViolationsStore: !!this.violationsStore,
hasFeedbackCollector: !!this.feedbackCollector,
hasGuardCheckEngine: !!this.guardCheckEngine,
};
}
/**
* 从 violation 或 GuardCheckEngine 查找 fixRecipeId
* 增强:当无显式 fixSuggestion 时,以 ruleId 本身作为 fallback recipeId
* 这允许 Knowledge Base 中以 ruleId 命名的条目自动关联
*/
_findFixRecipe(ruleId, violations) {
// 先从 violation 本身的 fixSuggestion 查找
for (const v of violations || []) {
if (v.ruleId === ruleId && v.fixSuggestion) {
return v.fixSuggestion.replace(/^recipe:/, '');
}
}
// 再从 GuardCheckEngine 的规则定义中查找
if (this.guardCheckEngine) {
try {
const rules = this.guardCheckEngine.getRules();
const rule = rules.find((r) => r.id === ruleId);
if (rule?.fixSuggestion) {
return rule.fixSuggestion.replace(/^recipe:/, '');
}
}
catch {
/* ignore */
}
}
// fallback: 用 ruleId 本身作为 recipeId — 允许知识库按规则 ID 索引
return ruleId || null;
}
}
export default GuardFeedbackLoop;