UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

288 lines (287 loc) 11.9 kB
import { v4 as uuidv4 } from 'uuid'; import Logger from '../../infrastructure/logging/Logger.js'; import { ConflictError, NotFoundError, ValidationError } from '../../shared/errors/index.js'; import { unixNow } from '../../shared/utils/common.js'; /** * GuardService * 管理 Guard 约束规则的生命周期 (V3: 使用 KnowledgeEntry / knowledgeRepository) * Guard 规则 = kind='rule' + knowledgeType='boundary-constraint' 的 KnowledgeEntry, * 具体 pattern 存在 constraints.guards[] 里 */ export class GuardService { _engine; auditLogger; gateway; knowledgeRepository; logger; /** * @param [deps] 可选依赖注入 * @param [deps.guardCheckEngine] 核心引擎实例 */ constructor(knowledgeRepository, auditLogger, gateway, deps = {}) { this.knowledgeRepository = knowledgeRepository; this.auditLogger = auditLogger; this.gateway = gateway; this.logger = Logger.getInstance(); this._engine = deps.guardCheckEngine || null; } /** 创建新规则 → 创建一个 kind=rule, knowledgeType=boundary-constraint 的 KnowledgeEntry */ async createRule(data, context) { try { this._validateCreateInput(data); const { KnowledgeEntry } = await import('../../domain/knowledge/KnowledgeEntry.js'); const entry = KnowledgeEntry.fromJSON({ id: uuidv4(), title: data.name, description: data.description, language: (data.languages || [])[0] || '', category: data.category || 'guard', kind: 'rule', knowledgeType: 'boundary-constraint', content: { pattern: data.pattern || '', rationale: data.note || data.sourceReason || '', }, constraints: { boundaries: [], preconditions: [], sideEffects: [], guards: [ { ...(data.pattern ? { pattern: data.pattern } : {}), severity: data.severity || 'warning', message: data.description || '', type: data.type || 'regex', ...(data.astQuery ? { astQuery: data.astQuery } : {}), ...(data.fixSuggestion ? { fixSuggestion: data.fixSuggestion } : {}), }, ], }, tags: data.languages || [], lifecycle: 'active', createdBy: context.userId, }); const created = await this.knowledgeRepository.create(entry); await this.auditLogger.log({ action: 'create_guard_rule', resourceType: 'knowledge_entry', resourceId: created.id, actor: context.userId, details: `Created guard rule: ${data.name}`, timestamp: unixNow(), }); return created; } catch (error) { this.logger.error('Error creating guard rule', { error: error.message, data }); throw error; } } /** 启用规则(将 lifecycle 设为 active) */ async enableRule(ruleId, context) { try { const entry = await this.knowledgeRepository.findById(ruleId); if (!entry) { throw new NotFoundError('Guard rule not found', 'knowledge_entry', ruleId); } if (entry.lifecycle === 'active') { throw new ConflictError('Rule is already enabled', { reason: 'Cannot enable an already enabled rule', }); } await this.knowledgeRepository.update(ruleId, { lifecycle: 'active' }); await this.auditLogger.log({ action: 'enable_guard_rule', resourceType: 'knowledge_entry', resourceId: ruleId, actor: context.userId, details: `Enabled guard rule: ${entry.title}`, timestamp: unixNow(), }); return this.knowledgeRepository.findById(ruleId); } catch (error) { this.logger.error('Error enabling guard rule', { ruleId, error: error.message }); throw error; } } /** 禁用规则(将 lifecycle 设为 deprecated) */ async disableRule(ruleId, reason, context) { try { const entry = await this.knowledgeRepository.findById(ruleId); if (!entry) { throw new NotFoundError('Guard rule not found', 'knowledge_entry', ruleId); } if (entry.lifecycle === 'deprecated') { throw new ConflictError('Rule is already disabled', { reason: 'Cannot disable an already disabled rule', }); } if (!reason || reason.trim().length === 0) { throw new ValidationError('Disable reason is required'); } await this.knowledgeRepository.update(ruleId, { lifecycle: 'deprecated', rejectionReason: reason, }); await this.auditLogger.log({ action: 'disable_guard_rule', resourceType: 'knowledge_entry', resourceId: ruleId, actor: context.userId, details: `Disabled guard rule: ${reason}`, timestamp: unixNow(), }); return this.knowledgeRepository.findById(ruleId); } catch (error) { this.logger.error('Error disabling guard rule', { ruleId, error: error.message }); throw error; } } /** * 检查代码是否匹配 Guard 规则 * 优先代理到 GuardCheckEngine(完整管线: 内置 + DB + EP + Code-Level + AST), * 若引擎不可用则降级为仅 DB 规则的简化检查 */ async checkCode(code, options = {}) { try { if (!code || code.trim().length === 0) { throw new ValidationError('Code is required'); } const { language = null } = options; // ── 优先路径: 代理到 GuardCheckEngine(完整管线)── if (this._engine) { try { const violations = this._engine.checkCode(code, language || 'unknown', { scope: 'file', }); return violations.map((v) => ({ ruleId: v.ruleId, ruleName: v.ruleId, severity: v.severity || 'warning', message: v.message || '', line: v.line, snippet: v.snippet, matchCount: 1, ...(v.fixSuggestion ? { fixSuggestion: v.fixSuggestion } : {}), ...(v.reasoning ? { reasoning: v.reasoning } : {}), })); } catch (engineErr) { this.logger.debug('GuardCheckEngine.checkCode failed, falling back to DB-only check', { error: engineErr.message, }); } } // ── 降级路径: 仅 DB 规则简化检查 ── return this._checkCodeDbOnly(code, { language }); } catch (error) { this.logger.error('Error checking code against rules', { error: error.message }); throw error; } } /** * 仅 DB 规则的简化检查(降级路径) */ async _checkCodeDbOnly(code, options = {}) { const { language = null } = options; // V3: 使用 findActiveRules() 查询 kind='rule' + lifecycle='active' let guardEntries = await this.knowledgeRepository.findActiveRules(); // 按语言过滤 if (language) { guardEntries = guardEntries.filter((e) => !e.language || e.language === language); } const matches = []; for (const entry of guardEntries) { const guards = entry.constraints?.guards || []; for (const guard of guards) { try { const regex = new RegExp(guard.pattern, 'gm'); const codeMatches = [...code.matchAll(regex)]; if (codeMatches.length > 0) { matches.push({ ruleId: entry.id, ruleName: entry.title, severity: guard.severity || 'warning', message: guard.message || '', matches: codeMatches.map((m) => ({ match: m[0], index: m.index, line: code.substring(0, m.index).split('\\n').length, })), matchCount: codeMatches.length, }); } } catch (e) { this.logger.warn('Error matching guard pattern', { entryId: entry.id, error: e.message, }); } } } return matches; } /** 查询规则列表 (kind='rule' + knowledgeType='boundary-constraint') */ async listRules(filters = {}, pagination = {}) { try { const { page = 1, pageSize = 20 } = pagination; return this.knowledgeRepository.findWithPagination({ kind: 'rule', knowledgeType: 'boundary-constraint' }, { page, pageSize }); } catch (error) { this.logger.error('Error listing rules', { error: error.message, filters }); throw error; } } /** 搜索规则 */ async searchRules(keyword, pagination = {}) { try { const { page = 1, pageSize = 20 } = pagination; const result = await this.knowledgeRepository.search(keyword, { page, pageSize }); result.data = (result.data || []).filter((r) => r.kind === 'rule' && r.knowledgeType === 'boundary-constraint'); result.total = result.data.length; return result; } catch (error) { this.logger.error('Error searching rules', { keyword, error: error.message }); throw error; } } /** 获取规则统计 */ async getRuleStats() { try { return this.knowledgeRepository.getStats(); } catch (error) { this.logger.error('Error getting rule stats', { error: error.message }); throw error; } } /** * 验证创建输入 * type='regex' 时 pattern 必须提供;type='ast' 时 astQuery 必须提供 */ _validateCreateInput(data) { if (!data.name || data.name.trim().length === 0) { throw new ValidationError('Rule name is required'); } if (!data.description || data.description.trim().length === 0) { throw new ValidationError('Rule description is required'); } const ruleType = data.type || 'regex'; if (ruleType === 'ast') { if (!data.astQuery || !data.astQuery.queryType) { throw new ValidationError('AST query with queryType is required for type=ast rules'); } } else { if (!data.pattern || data.pattern.trim().length === 0) { throw new ValidationError('Pattern is required for regex rules'); } } } } export default GuardService;