UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

327 lines (326 loc) 11.7 kB
import { v4 as uuidv4 } from 'uuid'; import { inferKind, isCandidate as isLifecycleCandidate, isValidTransition, Lifecycle, normalizeLifecycle, } from './Lifecycle.js'; import { Constraints, Content, Quality, Reasoning, Relations, Stats } from './values/index.js'; export class KnowledgeEntry { // Identification id; title; description; // Lifecycle lifecycle; lifecycleHistory; autoApprovable; stagingDeadline; // Language & Classification language; category; knowledgeType; kind; complexity; scope; difficulty; tags; // Cursor delivery fields trigger; topicHint; whenClause; doClause; dontClause; coreCode; usageGuide; // Value objects content; relations; constraints; reasoning; quality; stats; // Code headers (ObjC/Swift) headers; headerPaths; moduleName; includeHeaders; // AI agentNotes; aiInsight; // Review reviewedBy; reviewedAt; rejectionReason; // Source source; sourceFile; sourceCandidateId; // Timestamps createdBy; createdAt; updatedAt; publishedAt; publishedBy; constructor(props = {}) { // ── 标识 ── this.id = props.id || uuidv4(); this.title = props.title || ''; this.description = props.description || ''; // ── 生命周期 ── this.lifecycle = normalizeLifecycle(props.lifecycle || Lifecycle.PENDING); this.lifecycleHistory = props.lifecycleHistory || []; this.autoApprovable = props.autoApprovable ?? false; this.stagingDeadline = props.stagingDeadline ?? null; // ── 语言与分类 ── this.language = props.language || ''; this.category = props.category || ''; this.knowledgeType = props.knowledgeType || 'code-pattern'; this.kind = props.kind || inferKind(this.knowledgeType); this.complexity = props.complexity || 'intermediate'; this.scope = props.scope || 'universal'; this.difficulty = props.difficulty || null; this.tags = props.tags || []; // ── Cursor 交付字段(AI 直接产出)── this.trigger = props.trigger || ''; this.topicHint = props.topicHint || ''; this.whenClause = props.whenClause || ''; this.doClause = props.doClause || ''; this.dontClause = props.dontClause || ''; this.coreCode = props.coreCode || ''; this.usageGuide = props.usageGuide || ''; // ── 值对象 ── this.content = Content.from(props.content); this.relations = Relations.from(props.relations); this.constraints = Constraints.from(props.constraints); this.reasoning = Reasoning.from(props.reasoning); this.quality = Quality.from(props.quality); this.stats = Stats.from(props.stats); // ── 代码头文件 (ObjC/Swift) ── this.headers = props.headers || []; this.headerPaths = props.headerPaths || []; this.moduleName = props.moduleName || ''; this.includeHeaders = props.includeHeaders ?? false; // ── AI 润色 ── this.agentNotes = props.agentNotes || null; this.aiInsight = props.aiInsight || null; // ── 审核 ── this.reviewedBy = props.reviewedBy || null; this.reviewedAt = props.reviewedAt || null; this.rejectionReason = props.rejectionReason || null; // ── 来源 ── this.source = props.source || 'manual'; this.sourceFile = props.sourceFile || null; this.sourceCandidateId = props.sourceCandidateId || null; // ── 时间 ── this.createdBy = props.createdBy || 'system'; this.createdAt = props.createdAt || Math.floor(Date.now() / 1000); this.updatedAt = props.updatedAt || Math.floor(Date.now() / 1000); this.publishedAt = props.publishedAt || null; this.publishedBy = props.publishedBy || null; } /* ═══ 生命周期操作 ═══════════════════════════════════ */ /** * 发布 (pending|staging|evolving → active) */ publish(publisher) { if (!this.isValid()) { return { success: false, error: '内容不完整,无法发布' }; } const result = this._transition(Lifecycle.ACTIVE); if (result.success) { this.publishedAt = this._now(); this.publishedBy = publisher; } return result; } /** * 进入暂存期 (pending → staging) */ stage() { return this._transition(Lifecycle.STAGING); } /** * 进入进化态 (active → evolving) */ evolve() { return this._transition(Lifecycle.EVOLVING); } /** * 进入衰退观察 (active|evolving → decaying) */ decay() { return this._transition(Lifecycle.DECAYING); } /** * 恢复为已发布 (decaying|evolving → active),不更新 publishedAt */ restore() { return this._transition(Lifecycle.ACTIVE); } /** * 弃用 (pending|active|decaying → deprecated) */ deprecate(reason) { const result = this._transition(Lifecycle.DEPRECATED); if (result.success) { this.rejectionReason = reason; } return result; } /** * 重新激活 (deprecated|staging → pending) */ reactivate() { const result = this._transition(Lifecycle.PENDING); if (result.success) { this.rejectionReason = null; } return result; } /** * 将最后一条 lifecycleHistory 条目标记操作人。 * 由 KnowledgeService._lifecycleTransition() 在 entity method 执行后调用。 */ stampLastTransition(by) { const last = this.lifecycleHistory[this.lifecycleHistory.length - 1]; if (last) { last.by = by; } } /* ═══ 谓词 ═══════════════════════════════════════════ */ /** 是否处于候选阶段 */ isCandidate() { return isLifecycleCandidate(this.lifecycle); } /** 是否可被 Guard/Search/Export 消费 */ isActive() { return this.lifecycle === Lifecycle.ACTIVE; } /** 是否为 Guard 规则类型 */ isRule() { return this.kind === 'rule'; } /** 内容是否有效 */ isValid() { return !!(this.title?.trim() && this.content.hasContent()); } /* ═══ Guard 消费 ═══════════════════════════════════ */ /** 返回此 Entry 中可被 GuardCheckEngine 消费的规则列表 */ getGuardRules() { if (!this.isActive() || !this.isRule()) { return []; } const regexRules = this.constraints.getRegexGuards().map((g) => ({ id: g.id || this.id, type: 'regex', name: g.message || this.title, message: g.message || this.description || this.title, pattern: g.pattern, languages: this.language ? [this.language] : [], severity: g.severity || 'warning', source: 'knowledge_entry', fixSuggestion: g.fix_suggestion || null, })); const astRules = this.constraints.getAstGuards().map((g) => ({ id: g.id || `${this.id}:ast`, type: 'ast', name: g.message || this.title, message: g.message, astQuery: g.ast_query, languages: g.ast_query?.language ? [g.ast_query.language] : [], severity: g.severity || 'warning', source: 'knowledge_entry', fixSuggestion: g.fix_suggestion || null, })); return [...regexRules, ...astRules]; } /* ═══ 系统标签 ═══════════════════════════════════ */ /** 系统内部标签前缀 — 内部元数据,不应暴露给最终用户 */ static SYSTEM_TAG_PREFIXES = ['dimension:', 'bootstrap:', 'internal:', 'system:']; /** 判断是否为系统内部标签 */ static isSystemTag(tag) { return KnowledgeEntry.SYSTEM_TAG_PREFIXES.some((p) => tag.startsWith(p)); } /* ═══ 序列化 ═══════════════════════════════════════ */ /** * Domain → JSON (camelCase 直出,全链路统一) * 注意: tags 保留原始值(含系统标签),对外 API 使用 sanitizeForAPI() 过滤 */ toJSON() { return { id: this.id, title: this.title, description: this.description, lifecycle: this.lifecycle, lifecycleHistory: this.lifecycleHistory, autoApprovable: this.autoApprovable, language: this.language, category: this.category, kind: this.kind, knowledgeType: this.knowledgeType, complexity: this.complexity, scope: this.scope, difficulty: this.difficulty, tags: this.tags, trigger: this.trigger, topicHint: this.topicHint, whenClause: this.whenClause, doClause: this.doClause, dontClause: this.dontClause, coreCode: this.coreCode, usageGuide: this.usageGuide, content: this.content.toJSON(), relations: this.relations.toJSON(), constraints: this.constraints.toJSON(), reasoning: this.reasoning.toJSON(), quality: this.quality.toJSON(), stats: this.stats.toJSON(), headers: this.headers, headerPaths: this.headerPaths, moduleName: this.moduleName, includeHeaders: this.includeHeaders, agentNotes: this.agentNotes, aiInsight: this.aiInsight, reviewedBy: this.reviewedBy, reviewedAt: this.reviewedAt, rejectionReason: this.rejectionReason, source: this.source, sourceFile: this.sourceFile, sourceCandidateId: this.sourceCandidateId, createdBy: this.createdBy, createdAt: this.createdAt, updatedAt: this.updatedAt, publishedAt: this.publishedAt, publishedBy: this.publishedBy, }; } /** JSON → Domain (camelCase 直入) */ static fromJSON(data) { if (!data) { return new KnowledgeEntry(); } return new KnowledgeEntry(data); } /* ═══ 私有 ═══════════════════════════════════════════ */ /** @returns } */ _transition(to, by) { if (!isValidTransition(this.lifecycle, to)) { return { success: false, error: `Invalid lifecycle transition: ${this.lifecycle} → ${to}`, }; } const entry = { from: this.lifecycle, to, at: this._now(), }; if (by) { entry.by = by; } this.lifecycleHistory.push(entry); this.lifecycle = to; this.updatedAt = this._now(); return { success: true }; } _now() { return Math.floor(Date.now() / 1000); } } export default KnowledgeEntry;