autosnippet 2.9.0 → 2.11.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 +12 -12
- package/bin/cli.js +53 -40
- package/config/constitution.yaml +9 -2
- package/dashboard/dist/assets/{icons-CH-H9x0E.js → icons-D4IWpDIk.js} +105 -100
- package/dashboard/dist/assets/index-CWBNcF9z.css +1 -0
- package/dashboard/dist/assets/index-DHtzhbuG.js +120 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/AiScanService.js +35 -36
- package/lib/cli/KnowledgeSyncService.js +345 -0
- package/lib/cli/SetupService.js +8 -26
- package/lib/cli/UpgradeService.js +28 -0
- package/lib/core/gateway/GatewayActionRegistry.js +48 -58
- package/lib/domain/index.js +16 -11
- package/lib/domain/knowledge/KnowledgeEntry.js +289 -0
- package/lib/domain/knowledge/KnowledgeRepository.js +123 -0
- package/lib/domain/knowledge/Lifecycle.js +99 -0
- package/lib/domain/knowledge/index.js +27 -0
- package/lib/domain/knowledge/values/Constraints.js +128 -0
- package/lib/domain/knowledge/values/Content.js +69 -0
- package/lib/domain/knowledge/values/Quality.js +81 -0
- package/lib/domain/knowledge/values/Reasoning.js +70 -0
- package/lib/domain/knowledge/values/Relations.js +142 -0
- package/lib/domain/knowledge/values/Stats.js +72 -0
- package/lib/domain/knowledge/values/index.js +9 -0
- package/lib/external/ai/AiProvider.js +85 -11
- package/lib/external/mcp/McpServer.js +7 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +18 -2
- package/lib/external/mcp/handlers/bootstrap.js +116 -11
- package/lib/external/mcp/handlers/browse.js +76 -73
- package/lib/external/mcp/handlers/candidate.js +26 -275
- package/lib/external/mcp/handlers/guard.js +2 -0
- package/lib/external/mcp/handlers/knowledge.js +267 -0
- package/lib/external/mcp/handlers/structure.js +25 -23
- package/lib/external/mcp/handlers/system.js +10 -12
- package/lib/external/mcp/tools.js +134 -140
- package/lib/http/HttpServer.js +14 -8
- package/lib/http/routes/ai.js +4 -3
- 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/infrastructure/database/migrations/016_unified_knowledge_entries.js +395 -0
- package/lib/infrastructure/database/migrations/017_camelcase_knowledge_entries.js +107 -0
- package/lib/infrastructure/external/XcodeAutomation.js +187 -103
- package/lib/injection/ServiceContainer.js +69 -60
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +338 -0
- package/lib/service/automation/DirectiveDetector.js +2 -3
- package/lib/service/automation/FileWatcher.js +59 -28
- package/lib/service/automation/XcodeIntegration.js +931 -156
- package/lib/service/automation/handlers/AlinkHandler.js +5 -4
- package/lib/service/automation/handlers/CreateHandler.js +53 -19
- package/lib/service/automation/handlers/DraftHandler.js +1 -1
- package/lib/service/automation/handlers/GuardHandler.js +183 -20
- package/lib/service/automation/handlers/SearchHandler.js +25 -22
- package/lib/service/candidate/SimilarityService.js +2 -2
- package/lib/service/chat/AnalystAgent.js +9 -0
- package/lib/service/chat/CandidateGuardrail.js +22 -11
- package/lib/service/chat/ChatAgent.js +132 -54
- package/lib/service/chat/ContextWindow.js +5 -5
- package/lib/service/chat/HandoffProtocol.js +1 -0
- package/lib/service/chat/ProducerAgent.js +40 -13
- package/lib/service/chat/ReasoningLayer.js +854 -0
- package/lib/service/chat/ReasoningTrace.js +329 -0
- package/lib/service/chat/tools.js +308 -205
- package/lib/service/cursor/CursorDeliveryPipeline.js +279 -0
- package/lib/service/cursor/KnowledgeCompressor.js +87 -0
- package/lib/service/cursor/RulesGenerator.js +168 -0
- package/lib/service/cursor/SkillsSyncer.js +268 -0
- package/lib/service/cursor/TokenBudget.js +58 -0
- package/lib/service/cursor/TopicClassifier.js +141 -0
- package/lib/service/guard/GuardCheckEngine.js +99 -10
- package/lib/service/guard/GuardService.js +57 -46
- package/lib/service/knowledge/ConfidenceRouter.js +159 -0
- package/lib/service/knowledge/KnowledgeFileWriter.js +595 -0
- package/lib/service/knowledge/KnowledgeService.js +802 -0
- package/lib/service/recipe/RecipeParser.js +3 -12
- package/lib/service/search/SearchEngine.js +67 -22
- package/lib/service/skills/SignalCollector.js +14 -9
- package/lib/service/skills/SkillAdvisor.js +13 -11
- package/lib/service/snippet/SnippetFactory.js +5 -5
- package/lib/service/spm/SpmService.js +15 -48
- package/lib/shared/RecipeReadinessChecker.js +6 -11
- 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 +8 -8
- package/skills/autosnippet-coldstart/SKILL.md +8 -4
- 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 +18 -6
- 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-CqJRvYRL.js +0 -197
- package/dashboard/dist/assets/index-DICm9PNa.css +0 -1
- 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 -1001
- package/lib/service/recipe/RecipeFileWriter.js +0 -514
- package/lib/service/recipe/RecipeService.js +0 -786
- package/lib/service/recipe/RecipeStatsTracker.js +0 -148
|
@@ -52,6 +52,7 @@ router.post('/path', asyncHandler(async (req, res) => {
|
|
|
52
52
|
const aiResult = await chatAgent.executeTool('extract_recipes', {
|
|
53
53
|
targetName: fileName,
|
|
54
54
|
files: [{ name: fileName, content: file.code || '' }],
|
|
55
|
+
comprehensive: true,
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
if (aiResult && !aiResult.error && Array.isArray(aiResult.recipes) && aiResult.recipes.length > 0) {
|
|
@@ -82,6 +83,7 @@ router.post('/path', asyncHandler(async (req, res) => {
|
|
|
82
83
|
/**
|
|
83
84
|
* POST /api/v1/extract/text
|
|
84
85
|
* 从文本内容提取代码片段(剪贴板等)
|
|
86
|
+
* 管线: RecipeParser(MD解析) → AI 提取(ChatAgent) → 基础兜底
|
|
85
87
|
*/
|
|
86
88
|
router.post('/text', asyncHandler(async (req, res) => {
|
|
87
89
|
const { text, language, relativePath, projectRoot: bodyRoot } = req.body;
|
|
@@ -94,21 +96,63 @@ router.post('/text', asyncHandler(async (req, res) => {
|
|
|
94
96
|
const recipeParser = container.get('recipeParser');
|
|
95
97
|
const projectRoot = bodyRoot || container.singletons?._projectRoot || process.cwd();
|
|
96
98
|
|
|
97
|
-
// 先尝试解析为 Recipe Markdown 格式
|
|
99
|
+
// 1. 先尝试解析为 Recipe Markdown 格式
|
|
98
100
|
let result;
|
|
99
101
|
try {
|
|
100
102
|
result = await recipeParser.parseFromText(text, {
|
|
101
103
|
language,
|
|
102
104
|
relativePath,
|
|
103
105
|
});
|
|
106
|
+
// 解析成功,直接返回
|
|
107
|
+
return res.json({
|
|
108
|
+
success: true,
|
|
109
|
+
data: {
|
|
110
|
+
result: Array.isArray(result) ? result : [result],
|
|
111
|
+
source: 'text',
|
|
112
|
+
},
|
|
113
|
+
});
|
|
104
114
|
} catch (error) {
|
|
105
|
-
logger.debug('Recipe MD parse failed,
|
|
115
|
+
logger.debug('Recipe MD parse failed, trying AI extraction', {
|
|
106
116
|
error: error.message,
|
|
107
117
|
});
|
|
108
|
-
// 回退到 AI 提取
|
|
109
|
-
result = await recipeParser.extractFromText(text, { language });
|
|
110
118
|
}
|
|
111
119
|
|
|
120
|
+
// 2. Recipe MD 解析失败 → 尝试 ChatAgent AI 提取
|
|
121
|
+
try {
|
|
122
|
+
const chatAgent = container.get('chatAgent');
|
|
123
|
+
if (chatAgent) {
|
|
124
|
+
const lang = language || (relativePath && /\.swift$/i.test(relativePath) ? 'swift' : 'objc');
|
|
125
|
+
const fileName = relativePath ? basename(relativePath) : `clipboard.${lang === 'swift' ? 'swift' : 'm'}`;
|
|
126
|
+
const aiResult = await chatAgent.executeTool('extract_recipes', {
|
|
127
|
+
targetName: fileName,
|
|
128
|
+
files: [{ name: fileName, content: text }],
|
|
129
|
+
comprehensive: true,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (aiResult && !aiResult.error && Array.isArray(aiResult.recipes) && aiResult.recipes.length > 0) {
|
|
133
|
+
logger.info('extract/text: AI extraction succeeded', { count: aiResult.recipes.length });
|
|
134
|
+
|
|
135
|
+
// 多条 Recipe 时在第一条上标记总数(供前端提示)
|
|
136
|
+
if (aiResult.recipes.length > 1) {
|
|
137
|
+
aiResult.recipes[0]._multipleCount = aiResult.recipes.length;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return res.json({
|
|
141
|
+
success: true,
|
|
142
|
+
data: {
|
|
143
|
+
result: aiResult.recipes,
|
|
144
|
+
source: 'text',
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
logger.debug('extract/text: AI extraction failed, using basic fallback', { error: err.message });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 3. AI 也失败 → 基础代码块提取兜底
|
|
154
|
+
result = await recipeParser.extractFromText(text, { language });
|
|
155
|
+
|
|
112
156
|
res.json({
|
|
113
157
|
success: true,
|
|
114
158
|
data: {
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge API 路由 (V3)
|
|
3
|
+
* 统一知识条目的 CRUD + 生命周期操作
|
|
4
|
+
* 替代 recipes.js + candidates.js (旧路由继续保留用于向后兼容)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import express from 'express';
|
|
8
|
+
import { asyncHandler } from '../middleware/errorHandler.js';
|
|
9
|
+
import { getServiceContainer } from '../../injection/ServiceContainer.js';
|
|
10
|
+
import { ValidationError } from '../../shared/errors/index.js';
|
|
11
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
12
|
+
import { getContext, safeInt } from '../utils/routeHelpers.js';
|
|
13
|
+
|
|
14
|
+
const logger = Logger.getInstance();
|
|
15
|
+
const router = express.Router();
|
|
16
|
+
|
|
17
|
+
const MAX_BATCH_SIZE = 100;
|
|
18
|
+
|
|
19
|
+
/* ═══ 查询 ═══════════════════════════════════════════════ */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* GET /api/v1/knowledge
|
|
23
|
+
* 获取知识条目列表(支持筛选和分页)
|
|
24
|
+
*/
|
|
25
|
+
router.get('/', asyncHandler(async (req, res) => {
|
|
26
|
+
const { lifecycle, kind, category, language, knowledgeType, scope, keyword, tag, source } = req.query;
|
|
27
|
+
const page = safeInt(req.query.page, 1);
|
|
28
|
+
const pageSize = safeInt(req.query.limit, 20, 1, 1000);
|
|
29
|
+
|
|
30
|
+
const container = getServiceContainer();
|
|
31
|
+
const knowledgeService = container.get('knowledgeService');
|
|
32
|
+
|
|
33
|
+
if (keyword) {
|
|
34
|
+
const result = await knowledgeService.search(keyword, { page, pageSize });
|
|
35
|
+
return res.json({ success: true, data: result });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const filters = {};
|
|
39
|
+
if (lifecycle) filters.lifecycle = lifecycle;
|
|
40
|
+
if (kind) filters.kind = kind;
|
|
41
|
+
if (category) filters.category = category;
|
|
42
|
+
if (language) filters.language = language;
|
|
43
|
+
if (knowledgeType) filters.knowledgeType = knowledgeType;
|
|
44
|
+
if (scope) filters.scope = scope;
|
|
45
|
+
if (tag) filters.tag = tag;
|
|
46
|
+
if (source) filters.source = source;
|
|
47
|
+
|
|
48
|
+
const result = await knowledgeService.list(filters, { page, pageSize });
|
|
49
|
+
res.json({ success: true, data: result });
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* GET /api/v1/knowledge/stats
|
|
54
|
+
* 获取统计信息
|
|
55
|
+
*/
|
|
56
|
+
router.get('/stats', asyncHandler(async (req, res) => {
|
|
57
|
+
const container = getServiceContainer();
|
|
58
|
+
const knowledgeService = container.get('knowledgeService');
|
|
59
|
+
const stats = await knowledgeService.getStats();
|
|
60
|
+
res.json({ success: true, data: stats });
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* GET /api/v1/knowledge/:id
|
|
65
|
+
* 获取知识条目详情
|
|
66
|
+
*/
|
|
67
|
+
router.get('/:id', asyncHandler(async (req, res) => {
|
|
68
|
+
const { id } = req.params;
|
|
69
|
+
const container = getServiceContainer();
|
|
70
|
+
const knowledgeService = container.get('knowledgeService');
|
|
71
|
+
const entry = await knowledgeService.get(id);
|
|
72
|
+
res.json({ success: true, data: entry.toJSON() });
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
/* ═══ CRUD ═══════════════════════════════════════════════ */
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* POST /api/v1/knowledge
|
|
79
|
+
* 创建知识条目(wire format 直通)
|
|
80
|
+
*/
|
|
81
|
+
router.post('/', asyncHandler(async (req, res) => {
|
|
82
|
+
const data = req.body;
|
|
83
|
+
|
|
84
|
+
if (!data.title || !data.content) {
|
|
85
|
+
throw new ValidationError('title and content are required');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const container = getServiceContainer();
|
|
89
|
+
const knowledgeService = container.get('knowledgeService');
|
|
90
|
+
const context = getContext(req);
|
|
91
|
+
|
|
92
|
+
const entry = await knowledgeService.create(data, context);
|
|
93
|
+
res.status(201).json({
|
|
94
|
+
success: true,
|
|
95
|
+
data: entry.toJSON(),
|
|
96
|
+
});
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* PATCH /api/v1/knowledge/:id
|
|
101
|
+
* 更新知识条目(白名单字段)
|
|
102
|
+
*/
|
|
103
|
+
router.patch('/:id', asyncHandler(async (req, res) => {
|
|
104
|
+
const { id } = req.params;
|
|
105
|
+
const container = getServiceContainer();
|
|
106
|
+
const knowledgeService = container.get('knowledgeService');
|
|
107
|
+
const context = getContext(req);
|
|
108
|
+
|
|
109
|
+
const entry = await knowledgeService.update(id, req.body, context);
|
|
110
|
+
res.json({ success: true, data: entry.toJSON() });
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* DELETE /api/v1/knowledge/:id
|
|
115
|
+
* 删除知识条目
|
|
116
|
+
*/
|
|
117
|
+
router.delete('/:id', asyncHandler(async (req, res) => {
|
|
118
|
+
const { id } = req.params;
|
|
119
|
+
const container = getServiceContainer();
|
|
120
|
+
const knowledgeService = container.get('knowledgeService');
|
|
121
|
+
const context = getContext(req);
|
|
122
|
+
|
|
123
|
+
const result = await knowledgeService.delete(id, context);
|
|
124
|
+
res.json({ success: true, data: result });
|
|
125
|
+
}));
|
|
126
|
+
|
|
127
|
+
/* ═══ 生命周期操作(3 状态: pending / active / deprecated)═══ */
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* PATCH /api/v1/knowledge/:id/publish
|
|
131
|
+
* 发布 (pending → active) — 仅开发者
|
|
132
|
+
*/
|
|
133
|
+
router.patch('/:id/publish', asyncHandler(async (req, res) => {
|
|
134
|
+
const { id } = req.params;
|
|
135
|
+
const container = getServiceContainer();
|
|
136
|
+
const knowledgeService = container.get('knowledgeService');
|
|
137
|
+
const context = getContext(req);
|
|
138
|
+
|
|
139
|
+
const entry = await knowledgeService.publish(id, context);
|
|
140
|
+
res.json({ success: true, data: entry.toJSON() });
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* PATCH /api/v1/knowledge/:id/deprecate
|
|
145
|
+
* 废弃 (pending|active → deprecated)
|
|
146
|
+
*/
|
|
147
|
+
router.patch('/:id/deprecate', asyncHandler(async (req, res) => {
|
|
148
|
+
const { id } = req.params;
|
|
149
|
+
const { reason } = req.body;
|
|
150
|
+
|
|
151
|
+
if (!reason) {
|
|
152
|
+
throw new ValidationError('reason is required for deprecation');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const container = getServiceContainer();
|
|
156
|
+
const knowledgeService = container.get('knowledgeService');
|
|
157
|
+
const context = getContext(req);
|
|
158
|
+
|
|
159
|
+
const entry = await knowledgeService.deprecate(id, reason, context);
|
|
160
|
+
res.json({ success: true, data: entry.toJSON() });
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* PATCH /api/v1/knowledge/:id/reactivate
|
|
165
|
+
* 重新激活 (deprecated → pending)
|
|
166
|
+
*/
|
|
167
|
+
router.patch('/:id/reactivate', asyncHandler(async (req, res) => {
|
|
168
|
+
const { id } = req.params;
|
|
169
|
+
const container = getServiceContainer();
|
|
170
|
+
const knowledgeService = container.get('knowledgeService');
|
|
171
|
+
const context = getContext(req);
|
|
172
|
+
|
|
173
|
+
const entry = await knowledgeService.reactivate(id, context);
|
|
174
|
+
res.json({ success: true, data: entry.toJSON() });
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
/* ═══ 批量操作 ═══════════════════════════════════════════ */
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* POST /api/v1/knowledge/batch-publish
|
|
181
|
+
* 批量发布 (pending → active)
|
|
182
|
+
* 支持 autoApprovableOnly=true 参数,只发布 autoApprovable 的条目
|
|
183
|
+
*/
|
|
184
|
+
router.post('/batch-publish', asyncHandler(async (req, res) => {
|
|
185
|
+
const { ids } = req.body;
|
|
186
|
+
|
|
187
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
188
|
+
throw new ValidationError('ids array is required and must not be empty');
|
|
189
|
+
}
|
|
190
|
+
if (ids.length > MAX_BATCH_SIZE) {
|
|
191
|
+
throw new ValidationError(`Batch size exceeds limit of ${MAX_BATCH_SIZE}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const container = getServiceContainer();
|
|
195
|
+
const knowledgeService = container.get('knowledgeService');
|
|
196
|
+
const context = getContext(req);
|
|
197
|
+
|
|
198
|
+
const results = await Promise.allSettled(
|
|
199
|
+
ids.map(id => knowledgeService.publish(id, context)),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const published = results.filter(r => r.status === 'fulfilled').map(r => r.value.toJSON());
|
|
203
|
+
const failed = results
|
|
204
|
+
.map((r, i) => r.status === 'rejected' ? { id: ids[i], error: r.reason?.message } : null)
|
|
205
|
+
.filter(Boolean);
|
|
206
|
+
|
|
207
|
+
res.json({
|
|
208
|
+
success: true,
|
|
209
|
+
data: { published, failed, total: ids.length, successCount: published.length, failureCount: failed.length },
|
|
210
|
+
});
|
|
211
|
+
}));
|
|
212
|
+
|
|
213
|
+
/* ═══ 使用 / 质量 ═══════════════════════════════════════ */
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* POST /api/v1/knowledge/:id/usage
|
|
217
|
+
* 记录使用(adoption / application / guard_hit / view / success)
|
|
218
|
+
*/
|
|
219
|
+
router.post('/:id/usage', asyncHandler(async (req, res) => {
|
|
220
|
+
const { id } = req.params;
|
|
221
|
+
const { type = 'adoption', feedback } = req.body;
|
|
222
|
+
const context = getContext(req);
|
|
223
|
+
|
|
224
|
+
const container = getServiceContainer();
|
|
225
|
+
const knowledgeService = container.get('knowledgeService');
|
|
226
|
+
|
|
227
|
+
await knowledgeService.incrementUsage(id, type, { actor: context.userId, feedback });
|
|
228
|
+
res.json({ success: true, message: `${type} recorded` });
|
|
229
|
+
}));
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* PATCH /api/v1/knowledge/:id/quality
|
|
233
|
+
* 重新计算质量评分
|
|
234
|
+
*/
|
|
235
|
+
router.patch('/:id/quality', asyncHandler(async (req, res) => {
|
|
236
|
+
const { id } = req.params;
|
|
237
|
+
const context = getContext(req);
|
|
238
|
+
|
|
239
|
+
const container = getServiceContainer();
|
|
240
|
+
const knowledgeService = container.get('knowledgeService');
|
|
241
|
+
|
|
242
|
+
const result = await knowledgeService.updateQuality(id, context);
|
|
243
|
+
res.json({ success: true, data: result });
|
|
244
|
+
}));
|
|
245
|
+
|
|
246
|
+
export default router;
|
|
@@ -44,13 +44,13 @@ router.get('/', asyncHandler(async (req, res) => {
|
|
|
44
44
|
const results = {};
|
|
45
45
|
const pagination = { page, pageSize: limit };
|
|
46
46
|
|
|
47
|
-
//
|
|
47
|
+
// 搜索知识条目(V3 统一模型)
|
|
48
48
|
if (type === 'all' || type === 'recipe' || type === 'solution') {
|
|
49
49
|
try {
|
|
50
|
-
const
|
|
51
|
-
results.recipes = await
|
|
50
|
+
const knowledgeService = container.get('knowledgeService');
|
|
51
|
+
results.recipes = await knowledgeService.search(q, pagination);
|
|
52
52
|
} catch (err) {
|
|
53
|
-
logger.warn('
|
|
53
|
+
logger.warn('Knowledge 搜索失败', { query: q, error: err.message });
|
|
54
54
|
results.recipes = { items: [], total: 0 };
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -66,11 +66,11 @@ router.get('/', asyncHandler(async (req, res) => {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// 搜索候选知识条目 (V3: lifecycle=draft/pending)
|
|
70
70
|
if (type === 'all' || type === 'candidate') {
|
|
71
71
|
try {
|
|
72
|
-
const
|
|
73
|
-
results.candidates = await
|
|
72
|
+
const knowledgeService = container.get('knowledgeService');
|
|
73
|
+
results.candidates = await knowledgeService.search(q, pagination);
|
|
74
74
|
} catch (err) {
|
|
75
75
|
logger.warn('Candidate 搜索失败', { query: q, error: err.message });
|
|
76
76
|
results.candidates = { items: [], total: 0 };
|
|
@@ -172,18 +172,14 @@ router.get('/graph/all', asyncHandler(async (req, res) => {
|
|
|
172
172
|
const nodeTypes = {}; // id → 主要类型(供前端区分渲染)
|
|
173
173
|
const nodeCategories = {}; // id → category/target 名(供前端分组布局)
|
|
174
174
|
if (nodeMap.size > 0) {
|
|
175
|
-
const
|
|
176
|
-
// 使用 repository.findById 避免 RecipeService.getRecipe 在找不到时记录 error 日志
|
|
177
|
-
const recipeRepo = recipeService?.recipeRepository ?? null;
|
|
175
|
+
const knowledgeRepo = container.get('knowledgeRepository');
|
|
178
176
|
for (const [id, types] of nodeMap) {
|
|
179
|
-
// 记录节点主要类型
|
|
180
177
|
const primaryType = types.has('recipe') ? 'recipe' : [...types][0];
|
|
181
178
|
nodeTypes[id] = primaryType;
|
|
182
179
|
|
|
183
|
-
if (primaryType === 'recipe' &&
|
|
184
|
-
// 仅 recipe 类型才查 DB;直接用 repo 避免 NotFoundError 日志噪音
|
|
180
|
+
if ((primaryType === 'recipe' || primaryType === 'knowledge') && knowledgeRepo) {
|
|
185
181
|
try {
|
|
186
|
-
const r = await
|
|
182
|
+
const r = await knowledgeRepo.findById(id);
|
|
187
183
|
if (r) {
|
|
188
184
|
nodeLabels[id] = r.title || r.name || id;
|
|
189
185
|
nodeCategories[id] = r.category || '';
|
|
@@ -191,7 +187,6 @@ router.get('/graph/all', asyncHandler(async (req, res) => {
|
|
|
191
187
|
}
|
|
192
188
|
} catch { /* not found – fall through */ }
|
|
193
189
|
}
|
|
194
|
-
// module / candidate 或查不到的 recipe → 直接用 ID
|
|
195
190
|
nodeLabels[id] = id;
|
|
196
191
|
}
|
|
197
192
|
}
|
|
@@ -226,9 +221,9 @@ router.post('/context-aware', asyncHandler(async (req, res) => {
|
|
|
226
221
|
throw new ValidationError('keyword is required');
|
|
227
222
|
}
|
|
228
223
|
const container = getServiceContainer();
|
|
229
|
-
const
|
|
224
|
+
const knowledgeService = container.get('knowledgeService');
|
|
230
225
|
const pageSize = Math.min(limit || 10, 100);
|
|
231
|
-
const list = await
|
|
226
|
+
const list = await knowledgeService.search(keyword, { page: 1, pageSize });
|
|
232
227
|
const items = list.data || list.items || [];
|
|
233
228
|
const results = items.map(r => ({
|
|
234
229
|
name: (r.title || r.id) + '.md',
|