autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
327 lines (326 loc) • 11.7 kB
JavaScript
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;