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.
- package/README.md +5 -5
- package/bin/cli.js +5 -33
- package/config/constitution.yaml +9 -2
- package/dashboard/dist/assets/{icons-B_Xg4B-s.js → icons-BkT3XrKf.js} +105 -100
- package/dashboard/dist/assets/index-BsB7DzW4.css +1 -0
- package/dashboard/dist/assets/index-DdmQMrJJ.js +155 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/AiScanService.js +13 -11
- package/lib/cli/KnowledgeSyncService.js +343 -0
- package/lib/cli/SetupService.js +9 -27
- package/lib/core/ast/ProjectGraph.js +160 -0
- package/lib/core/gateway/GatewayActionRegistry.js +48 -58
- package/lib/domain/index.js +16 -11
- package/lib/domain/knowledge/KnowledgeEntry.js +351 -0
- package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
- package/lib/domain/knowledge/Lifecycle.js +109 -0
- package/lib/domain/knowledge/index.js +27 -0
- package/lib/domain/knowledge/values/Constraints.js +125 -0
- package/lib/domain/knowledge/values/Content.js +86 -0
- package/lib/domain/knowledge/values/Quality.js +93 -0
- package/lib/domain/knowledge/values/Reasoning.js +69 -0
- package/lib/domain/knowledge/values/Relations.js +168 -0
- package/lib/domain/knowledge/values/Stats.js +87 -0
- package/lib/domain/knowledge/values/index.js +9 -0
- package/lib/external/ai/AiProvider.js +48 -0
- package/lib/external/ai/providers/GoogleGeminiProvider.js +12 -3
- package/lib/external/mcp/McpServer.js +7 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +3 -2
- package/lib/external/mcp/handlers/bootstrap.js +121 -12
- package/lib/external/mcp/handlers/browse.js +77 -73
- package/lib/external/mcp/handlers/candidate.js +29 -276
- package/lib/external/mcp/handlers/guard.js +2 -0
- package/lib/external/mcp/handlers/knowledge.js +205 -0
- package/lib/external/mcp/handlers/skill.js +4 -2
- package/lib/external/mcp/handlers/structure.js +25 -23
- package/lib/external/mcp/handlers/system.js +10 -12
- package/lib/external/mcp/tools.js +125 -138
- package/lib/http/HttpServer.js +4 -8
- package/lib/http/middleware/requestLogger.js +3 -3
- package/lib/http/routes/ai.js +17 -1
- package/lib/http/routes/extract.js +48 -4
- package/lib/http/routes/knowledge.js +246 -0
- package/lib/http/routes/search.js +12 -17
- package/lib/http/routes/skills.js +44 -1
- package/lib/infrastructure/cache/GraphCache.js +143 -0
- package/lib/infrastructure/database/migrations/015_create_token_usage.js +27 -0
- package/lib/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
- package/lib/infrastructure/external/XcodeAutomation.js +187 -103
- package/lib/infrastructure/realtime/RealtimeService.js +14 -2
- package/lib/injection/ServiceContainer.js +164 -63
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +373 -0
- package/lib/repository/token/TokenUsageStore.js +162 -0
- package/lib/service/automation/DirectiveDetector.js +2 -3
- package/lib/service/automation/FileWatcher.js +67 -28
- package/lib/service/automation/XcodeIntegration.js +931 -156
- package/lib/service/automation/handlers/AlinkHandler.js +6 -4
- package/lib/service/automation/handlers/CreateHandler.js +53 -18
- package/lib/service/automation/handlers/GuardHandler.js +183 -20
- package/lib/service/automation/handlers/SearchHandler.js +35 -17
- package/lib/service/chat/AnalystAgent.js +25 -14
- package/lib/service/chat/CandidateGuardrail.js +1 -1
- package/lib/service/chat/ChatAgent.js +280 -48
- package/lib/service/chat/ContextWindow.js +92 -8
- package/lib/service/chat/HandoffProtocol.js +26 -1
- package/lib/service/chat/ProducerAgent.js +11 -9
- package/lib/service/chat/tools.js +298 -194
- package/lib/service/guard/GuardCheckEngine.js +114 -10
- package/lib/service/guard/GuardService.js +59 -48
- package/lib/service/knowledge/ConfidenceRouter.js +159 -0
- package/lib/service/knowledge/KnowledgeFileWriter.js +602 -0
- package/lib/service/knowledge/KnowledgeService.js +725 -0
- package/lib/service/search/SearchEngine.js +92 -19
- package/lib/service/skills/SignalCollector.js +15 -9
- package/lib/service/skills/SkillAdvisor.js +13 -11
- package/lib/service/snippet/SnippetFactory.js +5 -5
- package/lib/service/spm/SpmService.js +119 -18
- package/package.json +1 -1
- package/scripts/install-cursor-skill.js +0 -6
- package/scripts/migrate-md-to-knowledge.mjs +364 -0
- package/skills/autosnippet-analysis/SKILL.md +15 -7
- package/skills/autosnippet-candidates/SKILL.md +6 -6
- package/skills/autosnippet-coldstart/SKILL.md +7 -3
- package/skills/autosnippet-concepts/SKILL.md +7 -6
- package/skills/autosnippet-create/SKILL.md +13 -13
- package/skills/autosnippet-intent/SKILL.md +3 -2
- package/skills/autosnippet-lifecycle/SKILL.md +5 -5
- package/skills/autosnippet-recipes/SKILL.md +16 -4
- package/templates/constitution.yaml +1 -1
- package/templates/copilot-instructions.md +6 -6
- package/templates/recipes-setup/README.md +3 -3
- package/dashboard/dist/assets/index-CkIih2CC.css +0 -1
- package/dashboard/dist/assets/index-Duc8Qk-c.js +0 -197
- package/lib/cli/CandidateSyncService.js +0 -261
- package/lib/cli/SyncService.js +0 -356
- package/lib/domain/candidate/Candidate.js +0 -196
- package/lib/domain/candidate/CandidateRepository.js +0 -107
- package/lib/domain/candidate/Reasoning.js +0 -52
- package/lib/domain/recipe/Recipe.js +0 -421
- package/lib/domain/recipe/RecipeRepository.js +0 -54
- package/lib/domain/types/CandidateStatus.js +0 -52
- package/lib/http/routes/candidates.js +0 -559
- package/lib/http/routes/recipes.js +0 -397
- package/lib/repository/candidate/CandidateRepository.impl.js +0 -230
- package/lib/repository/recipe/RecipeRepository.impl.js +0 -498
- package/lib/service/candidate/CandidateAggregator.js +0 -52
- package/lib/service/candidate/CandidateFileWriter.js +0 -383
- package/lib/service/candidate/CandidateService.js +0 -973
- package/lib/service/recipe/RecipeFileWriter.js +0 -514
- package/lib/service/recipe/RecipeService.js +0 -786
- 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
|
-
// ==========
|
|
20
|
+
// ========== Knowledge Actions (V3: replaces Candidate + Recipe) ==========
|
|
21
21
|
|
|
22
22
|
gateway.register('candidate:create', async (ctx) => {
|
|
23
|
-
const service = container.get('
|
|
24
|
-
return service.
|
|
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('
|
|
33
|
-
return service.
|
|
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('
|
|
40
|
-
return service.
|
|
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('
|
|
47
|
-
return service.
|
|
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('
|
|
54
|
-
return service.
|
|
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('
|
|
59
|
-
return service.
|
|
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('
|
|
64
|
-
return service.
|
|
59
|
+
const service = container.get('knowledgeService');
|
|
60
|
+
return service.getStats();
|
|
65
61
|
});
|
|
66
62
|
|
|
67
63
|
gateway.register('candidate:get', async (ctx) => {
|
|
68
|
-
const
|
|
69
|
-
return
|
|
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('
|
|
74
|
-
return service.
|
|
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('
|
|
81
|
-
return service.
|
|
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('
|
|
90
|
-
return service.
|
|
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('
|
|
97
|
-
return service.
|
|
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('
|
|
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('
|
|
111
|
-
return service.
|
|
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('
|
|
116
|
-
return service.
|
|
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('
|
|
121
|
-
return service.
|
|
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('
|
|
126
|
-
return service.
|
|
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('
|
|
131
|
-
return service.
|
|
122
|
+
const service = container.get('knowledgeService');
|
|
123
|
+
return service.getStats();
|
|
132
124
|
});
|
|
133
125
|
|
|
134
126
|
gateway.register('recipe:get', async (ctx) => {
|
|
135
|
-
const
|
|
136
|
-
return
|
|
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('
|
|
141
|
-
return service.
|
|
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('
|
|
146
|
-
return service.
|
|
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('
|
|
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
|
-
// ==========
|
|
201
|
+
// ========== Knowledge Update (enrich/refine) ==========
|
|
210
202
|
|
|
211
203
|
gateway.register('candidate:update', async (ctx) => {
|
|
212
|
-
const service = container.get('
|
|
213
|
-
return service.
|
|
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 ==========
|
package/lib/domain/index.js
CHANGED
|
@@ -3,18 +3,23 @@
|
|
|
3
3
|
* 导出所有实体、值对象和仓储接口
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
//
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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;
|