autosnippet 2.8.3 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +5 -5
  2. package/bin/cli.js +5 -33
  3. package/config/constitution.yaml +9 -2
  4. package/dashboard/dist/assets/{icons-B_Xg4B-s.js → icons-BkT3XrKf.js} +105 -100
  5. package/dashboard/dist/assets/index-BsB7DzW4.css +1 -0
  6. package/dashboard/dist/assets/index-DdmQMrJJ.js +155 -0
  7. package/dashboard/dist/index.html +3 -3
  8. package/lib/cli/AiScanService.js +13 -11
  9. package/lib/cli/KnowledgeSyncService.js +343 -0
  10. package/lib/cli/SetupService.js +9 -27
  11. package/lib/core/ast/ProjectGraph.js +160 -0
  12. package/lib/core/gateway/GatewayActionRegistry.js +48 -58
  13. package/lib/domain/index.js +16 -11
  14. package/lib/domain/knowledge/KnowledgeEntry.js +351 -0
  15. package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
  16. package/lib/domain/knowledge/Lifecycle.js +109 -0
  17. package/lib/domain/knowledge/index.js +27 -0
  18. package/lib/domain/knowledge/values/Constraints.js +125 -0
  19. package/lib/domain/knowledge/values/Content.js +86 -0
  20. package/lib/domain/knowledge/values/Quality.js +93 -0
  21. package/lib/domain/knowledge/values/Reasoning.js +69 -0
  22. package/lib/domain/knowledge/values/Relations.js +168 -0
  23. package/lib/domain/knowledge/values/Stats.js +87 -0
  24. package/lib/domain/knowledge/values/index.js +9 -0
  25. package/lib/external/ai/AiProvider.js +48 -0
  26. package/lib/external/ai/providers/GoogleGeminiProvider.js +12 -3
  27. package/lib/external/mcp/McpServer.js +7 -5
  28. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +3 -2
  29. package/lib/external/mcp/handlers/bootstrap.js +121 -12
  30. package/lib/external/mcp/handlers/browse.js +77 -73
  31. package/lib/external/mcp/handlers/candidate.js +29 -276
  32. package/lib/external/mcp/handlers/guard.js +2 -0
  33. package/lib/external/mcp/handlers/knowledge.js +205 -0
  34. package/lib/external/mcp/handlers/skill.js +4 -2
  35. package/lib/external/mcp/handlers/structure.js +25 -23
  36. package/lib/external/mcp/handlers/system.js +10 -12
  37. package/lib/external/mcp/tools.js +125 -138
  38. package/lib/http/HttpServer.js +4 -8
  39. package/lib/http/middleware/requestLogger.js +3 -3
  40. package/lib/http/routes/ai.js +17 -1
  41. package/lib/http/routes/extract.js +48 -4
  42. package/lib/http/routes/knowledge.js +246 -0
  43. package/lib/http/routes/search.js +12 -17
  44. package/lib/http/routes/skills.js +44 -1
  45. package/lib/infrastructure/cache/GraphCache.js +143 -0
  46. package/lib/infrastructure/database/migrations/015_create_token_usage.js +27 -0
  47. package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
  48. package/lib/infrastructure/external/XcodeAutomation.js +187 -103
  49. package/lib/infrastructure/realtime/RealtimeService.js +14 -2
  50. package/lib/injection/ServiceContainer.js +164 -63
  51. package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
  52. package/lib/repository/token/TokenUsageStore.js +162 -0
  53. package/lib/service/automation/DirectiveDetector.js +2 -3
  54. package/lib/service/automation/FileWatcher.js +67 -28
  55. package/lib/service/automation/XcodeIntegration.js +931 -156
  56. package/lib/service/automation/handlers/AlinkHandler.js +6 -4
  57. package/lib/service/automation/handlers/CreateHandler.js +53 -18
  58. package/lib/service/automation/handlers/GuardHandler.js +183 -20
  59. package/lib/service/automation/handlers/SearchHandler.js +35 -17
  60. package/lib/service/chat/AnalystAgent.js +25 -14
  61. package/lib/service/chat/CandidateGuardrail.js +1 -1
  62. package/lib/service/chat/ChatAgent.js +280 -48
  63. package/lib/service/chat/ContextWindow.js +92 -8
  64. package/lib/service/chat/HandoffProtocol.js +26 -1
  65. package/lib/service/chat/ProducerAgent.js +11 -9
  66. package/lib/service/chat/tools.js +298 -194
  67. package/lib/service/guard/GuardCheckEngine.js +114 -10
  68. package/lib/service/guard/GuardService.js +59 -48
  69. package/lib/service/knowledge/ConfidenceRouter.js +159 -0
  70. package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
  71. package/lib/service/knowledge/KnowledgeService.js +725 -0
  72. package/lib/service/search/SearchEngine.js +92 -19
  73. package/lib/service/skills/SignalCollector.js +15 -9
  74. package/lib/service/skills/SkillAdvisor.js +13 -11
  75. package/lib/service/snippet/SnippetFactory.js +5 -5
  76. package/lib/service/spm/SpmService.js +119 -18
  77. package/package.json +1 -1
  78. package/scripts/install-cursor-skill.js +0 -6
  79. package/scripts/migrate-md-to-knowledge.mjs +364 -0
  80. package/skills/autosnippet-analysis/SKILL.md +15 -7
  81. package/skills/autosnippet-candidates/SKILL.md +6 -6
  82. package/skills/autosnippet-coldstart/SKILL.md +7 -3
  83. package/skills/autosnippet-concepts/SKILL.md +7 -6
  84. package/skills/autosnippet-create/SKILL.md +13 -13
  85. package/skills/autosnippet-intent/SKILL.md +3 -2
  86. package/skills/autosnippet-lifecycle/SKILL.md +5 -5
  87. package/skills/autosnippet-recipes/SKILL.md +16 -4
  88. package/templates/constitution.yaml +1 -1
  89. package/templates/copilot-instructions.md +6 -6
  90. package/templates/recipes-setup/README.md +3 -3
  91. package/dashboard/dist/assets/index-CkIih2CC.css +0 -1
  92. package/dashboard/dist/assets/index-Duc8Qk-c.js +0 -197
  93. package/lib/cli/CandidateSyncService.js +0 -261
  94. package/lib/cli/SyncService.js +0 -356
  95. package/lib/domain/candidate/Candidate.js +0 -196
  96. package/lib/domain/candidate/CandidateRepository.js +0 -107
  97. package/lib/domain/candidate/Reasoning.js +0 -52
  98. package/lib/domain/recipe/Recipe.js +0 -421
  99. package/lib/domain/recipe/RecipeRepository.js +0 -54
  100. package/lib/domain/types/CandidateStatus.js +0 -52
  101. package/lib/http/routes/candidates.js +0 -559
  102. package/lib/http/routes/recipes.js +0 -397
  103. package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
  104. package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
  105. package/lib/service/candidate/CandidateAggregator.js +0 -52
  106. package/lib/service/candidate/CandidateFileWriter.js +0 -383
  107. package/lib/service/candidate/CandidateService.js +0 -973
  108. package/lib/service/recipe/RecipeFileWriter.js +0 -514
  109. package/lib/service/recipe/RecipeService.js +0 -786
  110. package/lib/service/recipe/RecipeStatsTracker.js +0 -148
@@ -17,133 +17,125 @@ const logger = Logger.getInstance();
17
17
  * @param {import('../../injection/ServiceContainer.js').ServiceContainer} container
18
18
  */
19
19
  export function registerGatewayActions(gateway, container) {
20
- // ========== Candidate Actions ==========
20
+ // ========== Knowledge Actions (V3: replaces Candidate + Recipe) ==========
21
21
 
22
22
  gateway.register('candidate:create', async (ctx) => {
23
- const service = container.get('candidateService');
24
- return service.createCandidate(ctx.data, {
23
+ const service = container.get('knowledgeService');
24
+ return service.create(ctx.data, {
25
25
  userId: ctx.actor,
26
- ip: ctx.data._ip,
27
- userAgent: ctx.data._userAgent,
28
26
  });
29
27
  });
30
28
 
31
29
  gateway.register('candidate:approve', async (ctx) => {
32
- const service = container.get('candidateService');
33
- return service.approveCandidate(ctx.data.candidateId, {
30
+ const service = container.get('knowledgeService');
31
+ return service.approve(ctx.data.candidateId, {
34
32
  userId: ctx.actor,
35
33
  });
36
34
  });
37
35
 
38
36
  gateway.register('candidate:reject', async (ctx) => {
39
- const service = container.get('candidateService');
40
- return service.rejectCandidate(ctx.data.candidateId, ctx.data.reason, {
37
+ const service = container.get('knowledgeService');
38
+ return service.reject(ctx.data.candidateId, ctx.data.reason, {
41
39
  userId: ctx.actor,
42
40
  });
43
41
  });
44
42
 
45
43
  gateway.register('candidate:apply_to_recipe', async (ctx) => {
46
- const service = container.get('candidateService');
47
- return service.applyToRecipe(ctx.data.candidateId, ctx.data.recipeId, {
48
- userId: ctx.actor,
49
- });
44
+ const service = container.get('knowledgeService');
45
+ return service.publish(ctx.data.candidateId, { userId: ctx.actor });
50
46
  });
51
47
 
52
48
  gateway.register('candidate:list', async (ctx) => {
53
- const service = container.get('candidateService');
54
- return service.listCandidates(ctx.data.filters, ctx.data.pagination);
49
+ const service = container.get('knowledgeService');
50
+ return service.list(ctx.data.filters, ctx.data.pagination);
55
51
  });
56
52
 
57
53
  gateway.register('candidate:search', async (ctx) => {
58
- const service = container.get('candidateService');
59
- return service.searchCandidates(ctx.data.keyword, ctx.data.pagination);
54
+ const service = container.get('knowledgeService');
55
+ return service.search(ctx.data.keyword, ctx.data.pagination);
60
56
  });
61
57
 
62
58
  gateway.register('candidate:get_stats', async (ctx) => {
63
- const service = container.get('candidateService');
64
- return service.getCandidateStats();
59
+ const service = container.get('knowledgeService');
60
+ return service.getStats();
65
61
  });
66
62
 
67
63
  gateway.register('candidate:get', async (ctx) => {
68
- const repo = container.get('candidateRepository');
69
- return repo.findById(ctx.data.id);
64
+ const service = container.get('knowledgeService');
65
+ return service.get(ctx.data.id);
70
66
  });
71
67
 
72
68
  gateway.register('candidate:delete', async (ctx) => {
73
- const service = container.get('candidateService');
74
- return service.deleteCandidate(ctx.data.candidateId, { userId: ctx.actor });
69
+ const service = container.get('knowledgeService');
70
+ return service.delete(ctx.data.candidateId, { userId: ctx.actor });
75
71
  });
76
72
 
77
- // ========== Recipe Actions ==========
73
+ // ========== Recipe Actions (V3: routed to knowledgeService) ==========
78
74
 
79
75
  gateway.register('recipe:create', async (ctx) => {
80
- const service = container.get('recipeService');
81
- return service.createRecipe(ctx.data, {
76
+ const service = container.get('knowledgeService');
77
+ return service.create(ctx.data, {
82
78
  userId: ctx.actor,
83
- ip: ctx.data._ip,
84
- userAgent: ctx.data._userAgent,
85
79
  });
86
80
  });
87
81
 
88
82
  gateway.register('recipe:publish', async (ctx) => {
89
- const service = container.get('recipeService');
90
- return service.publishRecipe(ctx.data.recipeId, {
83
+ const service = container.get('knowledgeService');
84
+ return service.publish(ctx.data.recipeId, {
91
85
  userId: ctx.actor,
92
86
  });
93
87
  });
94
88
 
95
89
  gateway.register('recipe:deprecate', async (ctx) => {
96
- const service = container.get('recipeService');
97
- return service.deprecateRecipe(ctx.data.recipeId, ctx.data.reason, {
90
+ const service = container.get('knowledgeService');
91
+ return service.deprecate(ctx.data.recipeId, ctx.data.reason, {
98
92
  userId: ctx.actor,
99
93
  });
100
94
  });
101
95
 
102
96
  gateway.register('recipe:update_quality', async (ctx) => {
103
- const service = container.get('recipeService');
104
- return service.updateQuality(ctx.data.recipeId, ctx.data.metrics, {
105
- userId: ctx.actor,
106
- });
97
+ const service = container.get('knowledgeService');
98
+ return service.updateQuality(ctx.data.recipeId, ctx.data.metrics);
107
99
  });
108
100
 
109
101
  gateway.register('recipe:adopt', async (ctx) => {
110
- const service = container.get('recipeService');
111
- return service.incrementAdoption(ctx.data.recipeId);
102
+ const service = container.get('knowledgeService');
103
+ return service.incrementUsage(ctx.data.recipeId, 'adoption');
112
104
  });
113
105
 
114
106
  gateway.register('recipe:apply', async (ctx) => {
115
- const service = container.get('recipeService');
116
- return service.incrementApplication(ctx.data.recipeId);
107
+ const service = container.get('knowledgeService');
108
+ return service.incrementUsage(ctx.data.recipeId, 'application');
117
109
  });
118
110
 
119
111
  gateway.register('recipe:list', async (ctx) => {
120
- const service = container.get('recipeService');
121
- return service.listRecipes(ctx.data.filters, ctx.data.pagination);
112
+ const service = container.get('knowledgeService');
113
+ return service.list(ctx.data.filters, ctx.data.pagination);
122
114
  });
123
115
 
124
116
  gateway.register('recipe:search', async (ctx) => {
125
- const service = container.get('recipeService');
126
- return service.searchRecipes(ctx.data.keyword, ctx.data.pagination);
117
+ const service = container.get('knowledgeService');
118
+ return service.search(ctx.data.keyword, ctx.data.pagination);
127
119
  });
128
120
 
129
121
  gateway.register('recipe:get_stats', async (ctx) => {
130
- const service = container.get('recipeService');
131
- return service.getRecipeStats();
122
+ const service = container.get('knowledgeService');
123
+ return service.getStats();
132
124
  });
133
125
 
134
126
  gateway.register('recipe:get', async (ctx) => {
135
- const repo = container.get('recipeRepository');
136
- return repo.findById(ctx.data.id);
127
+ const service = container.get('knowledgeService');
128
+ return service.get(ctx.data.id);
137
129
  });
138
130
 
139
131
  gateway.register('recipe:get_recommendations', async (ctx) => {
140
- const service = container.get('recipeService');
141
- return service.getRecommendations(ctx.data.limit);
132
+ const service = container.get('knowledgeService');
133
+ return service.list({ lifecycle: 'active' }, { page: 1, pageSize: ctx.data.limit || 10 });
142
134
  });
143
135
 
144
136
  gateway.register('recipe:delete', async (ctx) => {
145
- const service = container.get('recipeService');
146
- return service.deleteRecipe(ctx.data.recipeId, {
137
+ const service = container.get('knowledgeService');
138
+ return service.delete(ctx.data.recipeId, {
147
139
  userId: ctx.actor,
148
140
  });
149
141
  });
@@ -200,19 +192,17 @@ export function registerGatewayActions(gateway, container) {
200
192
  });
201
193
 
202
194
  gateway.register('guard_rule:get', async (ctx) => {
203
- const repo = container.get('recipeRepository');
195
+ const repo = container.get('knowledgeRepository');
204
196
  return repo.findById(ctx.data.id);
205
197
  });
206
198
 
207
199
  // ========== Search Actions ==========
208
200
 
209
- // ========== Candidate Update (enrich/refine) ==========
201
+ // ========== Knowledge Update (enrich/refine) ==========
210
202
 
211
203
  gateway.register('candidate:update', async (ctx) => {
212
- const service = container.get('candidateService');
213
- return service.updateCandidate
214
- ? service.updateCandidate(ctx.data.id, ctx.data, { userId: ctx.actor })
215
- : service.createCandidate(ctx.data, { userId: ctx.actor });
204
+ const service = container.get('knowledgeService');
205
+ return service.update(ctx.data.id, ctx.data, { userId: ctx.actor });
216
206
  });
217
207
 
218
208
  // ========== Search ==========
@@ -3,18 +3,23 @@
3
3
  * 导出所有实体、值对象和仓储接口
4
4
  */
5
5
 
6
- // Candidate 相关
7
- export { Candidate } from './candidate/Candidate.js';
8
- export { default as Reasoning } from './candidate/Reasoning.js';
9
- export { CandidateStatus, isValidCandidateStatus, isValidStateTransition } from './types/CandidateStatus.js';
10
- export { default as CandidateRepository } from './candidate/CandidateRepository.js';
11
-
12
- // Recipe 相关(统一知识实体)
6
+ // Knowledge 统一知识实体 (V3)
13
7
  export {
14
- Recipe, RecipeStatus, KnowledgeType, Complexity,
15
- RelationType, Kind, inferKind,
16
- } from './recipe/Recipe.js';
17
- export { default as RecipeRepository } from './recipe/RecipeRepository.js';
8
+ KnowledgeEntry,
9
+ Lifecycle,
10
+ isValidTransition,
11
+ isValidLifecycle,
12
+ isCandidate as isLifecycleCandidate,
13
+ CANDIDATE_STATES,
14
+ inferKind as inferKindV3,
15
+ } from './knowledge/index.js';
16
+ export { Content } from './knowledge/values/Content.js';
17
+ export { Relations, RELATION_BUCKETS, RELATION_BUCKETS as RelationType } from './knowledge/values/Relations.js';
18
+ export { Constraints } from './knowledge/values/Constraints.js';
19
+ export { Reasoning as ReasoningV3 } from './knowledge/values/Reasoning.js';
20
+ export { Quality } from './knowledge/values/Quality.js';
21
+ export { Stats } from './knowledge/values/Stats.js';
22
+ export { KnowledgeRepository } from './knowledge/KnowledgeRepository.js';
18
23
 
19
24
  // Snippet 相关
20
25
  export { Snippet } from './snippet/Snippet.js';
@@ -0,0 +1,351 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { Lifecycle, isValidTransition, isCandidate as isLifecycleCandidate, inferKind, normalizeLifecycle } from './Lifecycle.js';
3
+ import { Content, Relations, Constraints, Reasoning, Quality, Stats } from './values/index.js';
4
+
5
+ /* ═══════════════════════════════════════════════════════════
6
+ * KnowledgeEntry — 统一知识实体
7
+ *
8
+ * 合并原 Candidate + Recipe。
9
+ * lifecycle 状态决定其行为(3 状态简化版):
10
+ * pending → 待审核(新建条目初始状态)
11
+ * active → 已发布(被 Guard/Search/Export 消费)
12
+ * deprecated → 已废弃
13
+ * ═══════════════════════════════════════════════════════════ */
14
+
15
+ export class KnowledgeEntry {
16
+ /**
17
+ * @param {Object} props
18
+ */
19
+ constructor(props = {}) {
20
+ // ── 标识 ──
21
+ this.id = props.id || uuidv4();
22
+ this.title = props.title || '';
23
+ this.trigger = props.trigger || '';
24
+ this.description = props.description || '';
25
+
26
+ // ── 生命周期 ──
27
+ this.lifecycle = normalizeLifecycle(props.lifecycle || Lifecycle.PENDING);
28
+ this.lifecycleHistory = props.lifecycleHistory || [];
29
+ this.autoApprovable = props.autoApprovable ?? props.probation ?? false;
30
+
31
+ // ── 语言与分类 ──
32
+ this.language = props.language || '';
33
+ this.category = props.category || '';
34
+ this.knowledgeType = props.knowledgeType || 'code-pattern';
35
+ this.kind = props.kind || inferKind(this.knowledgeType);
36
+ this.complexity = props.complexity || 'intermediate';
37
+ this.scope = props.scope || 'universal';
38
+ this.difficulty = props.difficulty || null;
39
+ this.tags = props.tags || [];
40
+
41
+ // ── 国际化文本 ──
42
+ this.summaryCn = props.summaryCn ?? '';
43
+ this.summaryEn = props.summaryEn ?? '';
44
+ this.usageGuideCn = props.usageGuideCn ?? '';
45
+ this.usageGuideEn = props.usageGuideEn ?? '';
46
+
47
+ // ── 值对象 ──
48
+ this.content = Content.from(props.content);
49
+ this.relations = Relations.from(props.relations);
50
+ this.constraints = Constraints.from(props.constraints);
51
+ this.reasoning = Reasoning.from(props.reasoning);
52
+ this.quality = Quality.from(props.quality);
53
+ this.stats = Stats.from(props.stats);
54
+
55
+ // ── 代码头文件 (ObjC/Swift) ──
56
+ this.headers = props.headers || [];
57
+ this.headerPaths = props.headerPaths || [];
58
+ this.moduleName = props.moduleName || '';
59
+ this.includeHeaders = props.includeHeaders ?? false;
60
+
61
+ // ── AI 润色 ──
62
+ this.agentNotes = props.agentNotes || null;
63
+ this.aiInsight = props.aiInsight || null;
64
+
65
+ // ── 审核 ──
66
+ this.reviewedBy = props.reviewedBy || null;
67
+ this.reviewedAt = props.reviewedAt || null;
68
+ this.rejectionReason = props.rejectionReason || null;
69
+
70
+ // ── 来源 ──
71
+ this.source = props.source || 'manual';
72
+ this.sourceFile = props.sourceFile || null;
73
+ this.sourceCandidateId = props.sourceCandidateId || null;
74
+
75
+ // ── 时间 ──
76
+ this.createdBy = props.createdBy || 'system';
77
+ this.createdAt = props.createdAt || Math.floor(Date.now() / 1000);
78
+ this.updatedAt = props.updatedAt || Math.floor(Date.now() / 1000);
79
+ this.publishedAt = props.publishedAt || null;
80
+ this.publishedBy = props.publishedBy || null;
81
+ }
82
+
83
+ /* ═══ 生命周期操作 ═══════════════════════════════════ */
84
+
85
+ /**
86
+ * 发布 (pending → active)
87
+ * @param {string} publisher
88
+ * @returns {{ success: boolean, error?: string }}
89
+ */
90
+ publish(publisher) {
91
+ if (!this.isValid()) {
92
+ return { success: false, error: '内容不完整,无法发布' };
93
+ }
94
+ const result = this._transition(Lifecycle.ACTIVE);
95
+ if (result.success) {
96
+ this.publishedAt = this._now();
97
+ this.publishedBy = publisher;
98
+ }
99
+ return result;
100
+ }
101
+
102
+ /**
103
+ * 弃用 (pending|active → deprecated)
104
+ * @param {string} reason
105
+ * @returns {{ success: boolean, error?: string }}
106
+ */
107
+ deprecate(reason) {
108
+ const result = this._transition(Lifecycle.DEPRECATED);
109
+ if (result.success) {
110
+ this.rejectionReason = reason;
111
+ }
112
+ return result;
113
+ }
114
+
115
+ /**
116
+ * 重新激活 (deprecated → pending)
117
+ * @returns {{ success: boolean, error?: string }}
118
+ */
119
+ reactivate() {
120
+ const result = this._transition(Lifecycle.PENDING);
121
+ if (result.success) {
122
+ this.rejectionReason = null;
123
+ }
124
+ return result;
125
+ }
126
+
127
+ // ── 向后兼容的别名方法(旧代码可能引用) ──
128
+
129
+ /** @deprecated 简化后统一为 pending,无需 submit */
130
+ submit() { return { success: true }; }
131
+
132
+ /** @deprecated 简化后无需 approve,直接 publish */
133
+ approve(reviewer) { return this.publish(reviewer); }
134
+
135
+ /** @deprecated 简化后 reject = deprecate */
136
+ reject(reviewer, reason) { return this.deprecate(reason); }
137
+
138
+ /** @deprecated 简化后无需 toDraft */
139
+ toDraft() { return this.reactivate(); }
140
+
141
+ /** @deprecated 简化后 fastTrack = publish */
142
+ fastTrack(publisher) { return this.publish(publisher); }
143
+
144
+ /** @deprecated 简化后无需 autoApprove */
145
+ autoApprove() { return { success: true }; }
146
+
147
+ /* ═══ 谓词 ═══════════════════════════════════════════ */
148
+
149
+ /**
150
+ * 是否处于候选阶段
151
+ * @returns {boolean}
152
+ */
153
+ isCandidate() {
154
+ return isLifecycleCandidate(this.lifecycle);
155
+ }
156
+
157
+ /**
158
+ * 是否可被 Guard/Search/Export 消费
159
+ * @returns {boolean}
160
+ */
161
+ isActive() {
162
+ return this.lifecycle === Lifecycle.ACTIVE;
163
+ }
164
+
165
+ /**
166
+ * 是否为 Guard 规则类型
167
+ * @returns {boolean}
168
+ */
169
+ isRule() {
170
+ return this.kind === 'rule';
171
+ }
172
+
173
+ /**
174
+ * 内容是否有效
175
+ * @returns {boolean}
176
+ */
177
+ isValid() {
178
+ return !!(this.title?.trim() && this.content.hasContent());
179
+ }
180
+
181
+ /* ═══ Guard 消费 ═══════════════════════════════════ */
182
+
183
+ /**
184
+ * 返回此 Entry 中可被 GuardCheckEngine 消费的规则列表
185
+ * @returns {Array<Object>}
186
+ */
187
+ getGuardRules() {
188
+ if (!this.isActive() || !this.isRule()) return [];
189
+
190
+ const regexRules = this.constraints.getRegexGuards().map(g => ({
191
+ id: g.id || this.id,
192
+ type: 'regex',
193
+ name: g.message || this.title,
194
+ message: g.message || this.description || this.title,
195
+ pattern: g.pattern,
196
+ languages: this.language ? [this.language] : [],
197
+ severity: g.severity || 'warning',
198
+ source: 'knowledge_entry',
199
+ fixSuggestion: g.fix_suggestion || null,
200
+ }));
201
+
202
+ const astRules = this.constraints.getAstGuards().map(g => ({
203
+ id: g.id || `${this.id}:ast`,
204
+ type: 'ast',
205
+ name: g.message || this.title,
206
+ message: g.message,
207
+ astQuery: g.ast_query,
208
+ languages: g.ast_query?.language ? [g.ast_query.language] : [],
209
+ severity: g.severity || 'warning',
210
+ source: 'knowledge_entry',
211
+ fixSuggestion: g.fix_suggestion || null,
212
+ }));
213
+
214
+ return [...regexRules, ...astRules];
215
+ }
216
+
217
+ /* ═══ 序列化 ═══════════════════════════════════════ */
218
+
219
+ /**
220
+ * Domain → wire format JSON (统一格式,DB/MCP/API 共用)
221
+ */
222
+ toJSON() {
223
+ return {
224
+ id: this.id,
225
+ title: this.title,
226
+ trigger: this.trigger,
227
+ description: this.description,
228
+ lifecycle: this.lifecycle,
229
+ lifecycle_history: this.lifecycleHistory,
230
+ auto_approvable: this.autoApprovable,
231
+ language: this.language,
232
+ category: this.category,
233
+ kind: this.kind,
234
+ knowledge_type: this.knowledgeType,
235
+ complexity: this.complexity,
236
+ scope: this.scope,
237
+ difficulty: this.difficulty,
238
+ tags: this.tags,
239
+ summary_cn: this.summaryCn,
240
+ summary_en: this.summaryEn,
241
+ usage_guide_cn: this.usageGuideCn,
242
+ usage_guide_en: this.usageGuideEn,
243
+ content: this.content.toJSON(),
244
+ relations: this.relations.toJSON(),
245
+ constraints: this.constraints.toJSON(),
246
+ reasoning: this.reasoning.toJSON(),
247
+ quality: this.quality.toJSON(),
248
+ stats: this.stats.toJSON(),
249
+ headers: this.headers,
250
+ header_paths: this.headerPaths,
251
+ module_name: this.moduleName,
252
+ include_headers: this.includeHeaders,
253
+ agent_notes: this.agentNotes,
254
+ ai_insight: this.aiInsight,
255
+ reviewed_by: this.reviewedBy,
256
+ reviewed_at: this.reviewedAt,
257
+ rejection_reason: this.rejectionReason,
258
+ source: this.source,
259
+ source_file: this.sourceFile,
260
+ source_candidate_id: this.sourceCandidateId,
261
+ created_by: this.createdBy,
262
+ created_at: this.createdAt,
263
+ updated_at: this.updatedAt,
264
+ published_at: this.publishedAt,
265
+ published_by: this.publishedBy,
266
+ };
267
+ }
268
+
269
+ /**
270
+ * wire format → Domain
271
+ * @param {Object} data snake_case 格式数据
272
+ * @returns {KnowledgeEntry}
273
+ */
274
+ static fromJSON(data) {
275
+ if (!data) return new KnowledgeEntry();
276
+ return new KnowledgeEntry({
277
+ id: data.id,
278
+ title: data.title,
279
+ trigger: data.trigger,
280
+ description: data.description,
281
+ lifecycle: data.lifecycle,
282
+ lifecycleHistory: data.lifecycle_history,
283
+ autoApprovable: data.auto_approvable ?? data.probation,
284
+ language: data.language,
285
+ category: data.category,
286
+ kind: data.kind,
287
+ knowledgeType: data.knowledge_type,
288
+ complexity: data.complexity,
289
+ scope: data.scope,
290
+ difficulty: data.difficulty,
291
+ tags: data.tags,
292
+ summaryCn: data.summary_cn,
293
+ summaryEn: data.summary_en,
294
+ usageGuideCn: data.usage_guide_cn,
295
+ usageGuideEn: data.usage_guide_en,
296
+ content: data.content,
297
+ relations: data.relations,
298
+ constraints: data.constraints,
299
+ reasoning: data.reasoning,
300
+ quality: data.quality,
301
+ stats: data.stats,
302
+ headers: data.headers,
303
+ headerPaths: data.header_paths,
304
+ moduleName: data.module_name,
305
+ includeHeaders: data.include_headers,
306
+ agentNotes: data.agent_notes,
307
+ aiInsight: data.ai_insight,
308
+ reviewedBy: data.reviewed_by,
309
+ reviewedAt: data.reviewed_at,
310
+ rejectionReason: data.rejection_reason,
311
+ source: data.source,
312
+ sourceFile: data.source_file,
313
+ sourceCandidateId: data.source_candidate_id,
314
+ createdBy: data.created_by,
315
+ createdAt: data.created_at,
316
+ updatedAt: data.updated_at,
317
+ publishedAt: data.published_at,
318
+ publishedBy: data.published_by,
319
+ });
320
+ }
321
+
322
+ /* ═══ 私有 ═══════════════════════════════════════════ */
323
+
324
+ /**
325
+ * @param {string} to
326
+ * @returns {{ success: boolean, error?: string }}
327
+ */
328
+ _transition(to) {
329
+ if (!isValidTransition(this.lifecycle, to)) {
330
+ return {
331
+ success: false,
332
+ error: `Invalid lifecycle transition: ${this.lifecycle} → ${to}`,
333
+ };
334
+ }
335
+ this.lifecycleHistory.push({
336
+ from: this.lifecycle,
337
+ to,
338
+ at: this._now(),
339
+ });
340
+ this.lifecycle = to;
341
+ this.updatedAt = this._now();
342
+ return { success: true };
343
+ }
344
+
345
+ /** @returns {number} */
346
+ _now() {
347
+ return Math.floor(Date.now() / 1000);
348
+ }
349
+ }
350
+
351
+ export default KnowledgeEntry;