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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Handlers — V3 知识条目提交 & 生命周期
|
|
3
|
+
* submitKnowledge, submitKnowledgeBatch, knowledgeLifecycle
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { envelope } from '../envelope.js';
|
|
7
|
+
import { checkRecipeReadiness } from '../../../shared/RecipeReadinessChecker.js';
|
|
8
|
+
|
|
9
|
+
// ─── 限流 ──────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
async function _checkRateLimit(toolName, clientId) {
|
|
12
|
+
const { checkRecipeSave } = await import('../../../http/middleware/RateLimiter.js');
|
|
13
|
+
const projectRoot = process.cwd();
|
|
14
|
+
const limitCheck = checkRecipeSave(projectRoot, clientId || process.env.USER || 'mcp-client');
|
|
15
|
+
if (!limitCheck.allowed) {
|
|
16
|
+
return envelope({
|
|
17
|
+
success: false,
|
|
18
|
+
message: `提交过于频繁,请 ${limitCheck.retryAfter}s 后再试。`,
|
|
19
|
+
errorCode: 'RATE_LIMIT',
|
|
20
|
+
meta: { tool: toolName },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── V3 wire format → KnowledgeService.create() 直通 ────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 单条知识提交 (autosnippet_submit_knowledge)
|
|
30
|
+
*
|
|
31
|
+
* args 就是 wire format — 直接交给 knowledgeService.create()。
|
|
32
|
+
* 无需字段映射,fromJSON() 处理一切。
|
|
33
|
+
*/
|
|
34
|
+
export async function submitKnowledge(ctx, args) {
|
|
35
|
+
// 限流
|
|
36
|
+
const blocked = await _checkRateLimit('autosnippet_submit_knowledge', args.client_id);
|
|
37
|
+
if (blocked) return blocked;
|
|
38
|
+
|
|
39
|
+
const service = ctx.container.get('knowledgeService');
|
|
40
|
+
|
|
41
|
+
// MCP 参数直接是 wire format
|
|
42
|
+
const entry = await service.create(args, { userId: 'mcp' });
|
|
43
|
+
|
|
44
|
+
// Recipe-Ready 诊断(兼容旧格式)
|
|
45
|
+
const readinessInput = _toReadinessInput(args);
|
|
46
|
+
const readiness = checkRecipeReadiness(readinessInput);
|
|
47
|
+
|
|
48
|
+
const data = {
|
|
49
|
+
id: entry.id,
|
|
50
|
+
lifecycle: entry.lifecycle,
|
|
51
|
+
title: entry.title,
|
|
52
|
+
kind: entry.kind,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (!readiness.ready) {
|
|
56
|
+
data.recipeReadyHints = {
|
|
57
|
+
ready: false,
|
|
58
|
+
missingFields: readiness.missing,
|
|
59
|
+
suggestions: readiness.suggestions,
|
|
60
|
+
hint: '请补全以上字段后重新提交,或调用 autosnippet_enrich_candidates 进行完整性诊断',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return envelope({
|
|
65
|
+
success: true,
|
|
66
|
+
data,
|
|
67
|
+
meta: { tool: 'autosnippet_submit_knowledge' },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 批量知识提交 (autosnippet_submit_knowledge_batch)
|
|
73
|
+
*/
|
|
74
|
+
export async function submitKnowledgeBatch(ctx, args) {
|
|
75
|
+
if (!args.target_name || !Array.isArray(args.items) || args.items.length === 0) {
|
|
76
|
+
throw new Error('需要 target_name 与 items(非空数组)');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 限流
|
|
80
|
+
const blocked = await _checkRateLimit('autosnippet_submit_knowledge_batch', args.client_id);
|
|
81
|
+
if (blocked) return blocked;
|
|
82
|
+
|
|
83
|
+
// 去重(可选)
|
|
84
|
+
let items = args.items;
|
|
85
|
+
if (args.deduplicate !== false) {
|
|
86
|
+
try {
|
|
87
|
+
const { aggregateCandidates } = await import('../../../service/candidate/CandidateAggregator.js');
|
|
88
|
+
// 对 title 字段做去重
|
|
89
|
+
const readinessItems = items.map(it => ({
|
|
90
|
+
...it,
|
|
91
|
+
code: it.content?.pattern || it.code || '',
|
|
92
|
+
}));
|
|
93
|
+
const result = aggregateCandidates(readinessItems);
|
|
94
|
+
// 保留原始 items 顺序中去重后的
|
|
95
|
+
if (result.items && result.items.length < items.length) {
|
|
96
|
+
const titles = new Set(result.items.map(it => it.title));
|
|
97
|
+
items = items.filter(it => titles.has(it.title));
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// CandidateAggregator 加载失败时降级:不去重
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const service = ctx.container.get('knowledgeService');
|
|
105
|
+
const source = args.source || 'cursor-scan';
|
|
106
|
+
let count = 0;
|
|
107
|
+
const itemErrors = [];
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < items.length; i++) {
|
|
110
|
+
try {
|
|
111
|
+
const itemData = { ...items[i], source };
|
|
112
|
+
await service.create(itemData, { userId: 'mcp' });
|
|
113
|
+
count++;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
itemErrors.push({ index: i, title: items[i].title || '(untitled)', error: err.message });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const data = { count, total: items.length, targetName: args.target_name };
|
|
120
|
+
if (itemErrors.length > 0) data.errors = itemErrors;
|
|
121
|
+
|
|
122
|
+
// Recipe-Ready 统计
|
|
123
|
+
const notReady = items.filter(it => !checkRecipeReadiness(_toReadinessInput(it)).ready);
|
|
124
|
+
if (notReady.length > 0) {
|
|
125
|
+
const allMissing = [...new Set(notReady.flatMap(it => checkRecipeReadiness(_toReadinessInput(it)).missing))];
|
|
126
|
+
data.recipeReadyHints = {
|
|
127
|
+
notReadyCount: notReady.length,
|
|
128
|
+
totalCount: items.length,
|
|
129
|
+
commonMissingFields: allMissing,
|
|
130
|
+
hint: `${notReady.length}/${items.length} 条知识条目缺少必要字段(${allMissing.join(', ')}),请补全后重新提交`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return envelope({
|
|
135
|
+
success: true,
|
|
136
|
+
data,
|
|
137
|
+
message: `已提交 ${count}/${items.length} 条知识条目。`,
|
|
138
|
+
meta: { tool: 'autosnippet_submit_knowledge_batch' },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 知识条目生命周期操作 (autosnippet_knowledge_lifecycle)
|
|
144
|
+
*
|
|
145
|
+
* 简化为 3 状态: pending / active / deprecated
|
|
146
|
+
* 外部 Agent 允许 reactivate(废弃 → 待审核);发布/废弃由开发者在 Dashboard 操作
|
|
147
|
+
* 外部 Agent 也可以通过 submitKnowledge / submitKnowledgeBatch 提交新条目(→ pending)
|
|
148
|
+
*/
|
|
149
|
+
const MCP_ALLOWED_LIFECYCLE_ACTIONS = new Set(['reactivate']);
|
|
150
|
+
|
|
151
|
+
export async function knowledgeLifecycle(ctx, args) {
|
|
152
|
+
const { id, action } = args;
|
|
153
|
+
if (!id || !action) {
|
|
154
|
+
throw new Error('需要 id 和 action');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!MCP_ALLOWED_LIFECYCLE_ACTIONS.has(action)) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`[PERMISSION_DENIED] 外部 Agent 不允许执行 "${action}" 操作,仅支持: reactivate。发布、废弃等操作请在 Dashboard 中完成。提交新知识请使用 autosnippet_submit_knowledge 工具。`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const service = ctx.container.get('knowledgeService');
|
|
164
|
+
const context = { userId: 'mcp' };
|
|
165
|
+
|
|
166
|
+
const entry = await service.reactivate(id, context);
|
|
167
|
+
|
|
168
|
+
return envelope({
|
|
169
|
+
success: true,
|
|
170
|
+
data: {
|
|
171
|
+
id: entry.id,
|
|
172
|
+
lifecycle: entry.lifecycle,
|
|
173
|
+
title: entry.title,
|
|
174
|
+
action,
|
|
175
|
+
},
|
|
176
|
+
meta: { tool: 'autosnippet_knowledge_lifecycle' },
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── 内部辅助 ──────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* V3 wire format → RecipeReadinessChecker 兼容格式
|
|
184
|
+
* RecipeReadinessChecker 期望旧字段名 (code, summary_cn, ...)
|
|
185
|
+
*/
|
|
186
|
+
function _toReadinessInput(args) {
|
|
187
|
+
return {
|
|
188
|
+
title: args.title,
|
|
189
|
+
code: args.content?.pattern || args.code || '',
|
|
190
|
+
language: args.language,
|
|
191
|
+
category: args.category,
|
|
192
|
+
trigger: args.trigger,
|
|
193
|
+
summary_cn: args.summary_cn,
|
|
194
|
+
summary_en: args.summary_en,
|
|
195
|
+
headers: args.headers,
|
|
196
|
+
reasoning: args.reasoning ? {
|
|
197
|
+
whyStandard: args.reasoning.why_standard || args.reasoning.whyStandard,
|
|
198
|
+
sources: args.reasoning.sources,
|
|
199
|
+
confidence: args.reasoning.confidence,
|
|
200
|
+
} : undefined,
|
|
201
|
+
knowledgeType: args.knowledge_type || args.knowledgeType,
|
|
202
|
+
complexity: args.complexity,
|
|
203
|
+
usageGuide: args.usage_guide_cn || args.usageGuide,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -613,8 +613,10 @@ export function updateSkill(_ctx, args) {
|
|
|
613
613
|
export async function suggestSkills(ctx) {
|
|
614
614
|
try {
|
|
615
615
|
const { SkillAdvisor } = await import('../../../service/skills/SkillAdvisor.js');
|
|
616
|
-
const
|
|
617
|
-
const
|
|
616
|
+
const dbConn = ctx?.container?.get?.('database') || null;
|
|
617
|
+
const database = dbConn?.getDb?.() || dbConn || null;
|
|
618
|
+
const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
|
|
619
|
+
const advisor = new SkillAdvisor(projectRoot, { database });
|
|
618
620
|
const result = advisor.suggest();
|
|
619
621
|
|
|
620
622
|
return JSON.stringify({
|
|
@@ -262,40 +262,41 @@ export async function graphImpact(ctx, args) {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
/**
|
|
265
|
-
* 降级:从
|
|
265
|
+
* 降级:从 knowledge_entries.relations 提取关系(不依赖 knowledge_edges 表)
|
|
266
266
|
*/
|
|
267
267
|
async function _fallbackRelationsFromRecipe(ctx, nodeId, relation, direction) {
|
|
268
|
-
const recipeService = ctx.container.get('recipeService');
|
|
269
268
|
try {
|
|
270
|
-
const
|
|
271
|
-
|
|
269
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
270
|
+
const entry = await knowledgeService.get(nodeId);
|
|
271
|
+
if (!entry) return { outgoing: [], incoming: [] };
|
|
272
272
|
|
|
273
|
+
const relJson = typeof entry.relations?.toJSON === 'function' ? entry.relations.toJSON() : (entry.relations || {});
|
|
273
274
|
const outgoing = [];
|
|
274
275
|
if (direction === 'both' || direction === 'out') {
|
|
275
|
-
for (const [relType, targets] of Object.entries(
|
|
276
|
+
for (const [relType, targets] of Object.entries(relJson)) {
|
|
276
277
|
if (relation && relType !== relation) continue;
|
|
277
278
|
for (const t of (Array.isArray(targets) ? targets : [])) {
|
|
278
|
-
outgoing.push({ fromId: nodeId, fromType: '
|
|
279
|
+
outgoing.push({ fromId: nodeId, fromType: 'knowledge', toId: t.target || t.id || t, toType: 'knowledge', relation: relType });
|
|
279
280
|
}
|
|
280
281
|
}
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
//
|
|
284
|
+
// 反向查找:其他条目中 relations 包含当前 nodeId
|
|
284
285
|
const incoming = [];
|
|
285
286
|
if (direction === 'both' || direction === 'in') {
|
|
286
|
-
const
|
|
287
|
-
const reverseRows =
|
|
288
|
-
`SELECT id,
|
|
287
|
+
const knowledgeRepo = ctx.container.get('knowledgeRepository');
|
|
288
|
+
const reverseRows = knowledgeRepo.db.prepare(
|
|
289
|
+
`SELECT id, relations FROM knowledge_entries WHERE relations LIKE ? AND id != ?`
|
|
289
290
|
).all(`%${nodeId}%`, nodeId);
|
|
290
291
|
for (const row of reverseRows) {
|
|
291
292
|
try {
|
|
292
|
-
const rels = JSON.parse(row.
|
|
293
|
+
const rels = JSON.parse(row.relations || '{}');
|
|
293
294
|
for (const [relType, targets] of Object.entries(rels)) {
|
|
294
295
|
if (relation && relType !== relation) continue;
|
|
295
296
|
for (const t of (Array.isArray(targets) ? targets : [])) {
|
|
296
297
|
const targetId = t.target || t.id || t;
|
|
297
298
|
if (targetId === nodeId) {
|
|
298
|
-
incoming.push({ fromId: row.id, fromType: '
|
|
299
|
+
incoming.push({ fromId: row.id, fromType: 'knowledge', toId: nodeId, toType: 'knowledge', relation: relType });
|
|
299
300
|
}
|
|
300
301
|
}
|
|
301
302
|
}
|
|
@@ -308,23 +309,23 @@ async function _fallbackRelationsFromRecipe(ctx, nodeId, relation, direction) {
|
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
/**
|
|
311
|
-
* 降级:从
|
|
312
|
+
* 降级:从 knowledge_entries.relations 反查受影响的条目
|
|
312
313
|
*/
|
|
313
314
|
async function _fallbackImpactFromRecipe(ctx, nodeId) {
|
|
314
315
|
try {
|
|
315
|
-
const
|
|
316
|
-
const rows =
|
|
317
|
-
`SELECT id, title,
|
|
316
|
+
const knowledgeRepo = ctx.container.get('knowledgeRepository');
|
|
317
|
+
const rows = knowledgeRepo.db.prepare(
|
|
318
|
+
`SELECT id, title, relations FROM knowledge_entries WHERE relations LIKE ? AND id != ?`
|
|
318
319
|
).all(`%${nodeId}%`, nodeId);
|
|
319
320
|
|
|
320
321
|
const impacted = [];
|
|
321
322
|
for (const row of rows) {
|
|
322
323
|
try {
|
|
323
|
-
const rels = JSON.parse(row.
|
|
324
|
+
const rels = JSON.parse(row.relations || '{}');
|
|
324
325
|
for (const [relType, targets] of Object.entries(rels)) {
|
|
325
326
|
for (const t of (Array.isArray(targets) ? targets : [])) {
|
|
326
327
|
if ((t.target || t.id || t) === nodeId) {
|
|
327
|
-
impacted.push({ id: row.id, title: row.title, type: '
|
|
328
|
+
impacted.push({ id: row.id, title: row.title, type: 'knowledge', relation: relType, depth: 1 });
|
|
328
329
|
}
|
|
329
330
|
}
|
|
330
331
|
}
|
|
@@ -364,17 +365,18 @@ export async function graphPath(ctx, args) {
|
|
|
364
365
|
*/
|
|
365
366
|
async function _fallbackPathFromRecipe(ctx, fromId, toId) {
|
|
366
367
|
try {
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
if (!
|
|
368
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
369
|
+
const entry = await knowledgeService.get(fromId);
|
|
370
|
+
if (!entry) return { found: false, path: [], depth: -1 };
|
|
370
371
|
|
|
371
|
-
|
|
372
|
+
const relJson = typeof entry.relations?.toJSON === 'function' ? entry.relations.toJSON() : (entry.relations || {});
|
|
373
|
+
for (const [relType, targets] of Object.entries(relJson)) {
|
|
372
374
|
for (const t of (Array.isArray(targets) ? targets : [])) {
|
|
373
375
|
const targetId = t.target || t.id || t;
|
|
374
376
|
if (targetId === toId) {
|
|
375
377
|
return {
|
|
376
378
|
found: true,
|
|
377
|
-
path: [{ from: { id: fromId, type: '
|
|
379
|
+
path: [{ from: { id: fromId, type: 'knowledge' }, to: { id: toId, type: 'knowledge' }, relation: relType }],
|
|
378
380
|
depth: 1,
|
|
379
381
|
};
|
|
380
382
|
}
|
|
@@ -30,22 +30,23 @@ export async function health(ctx) {
|
|
|
30
30
|
checks.database = true;
|
|
31
31
|
// 知识库统计(轻量聚合查询)
|
|
32
32
|
try {
|
|
33
|
+
// V3: knowledge_entries 统一表(lifecycle 替代 status)
|
|
33
34
|
const rStats = db.prepare(`
|
|
34
35
|
SELECT COUNT(*) as total,
|
|
35
|
-
SUM(CASE WHEN
|
|
36
|
+
SUM(CASE WHEN lifecycle='active' THEN 1 ELSE 0 END) as active,
|
|
36
37
|
SUM(CASE WHEN kind='rule' THEN 1 ELSE 0 END) as rules,
|
|
37
38
|
SUM(CASE WHEN kind='pattern' THEN 1 ELSE 0 END) as patterns,
|
|
38
39
|
SUM(CASE WHEN kind='fact' THEN 1 ELSE 0 END) as facts
|
|
39
|
-
FROM
|
|
40
|
+
FROM knowledge_entries
|
|
40
41
|
`).get();
|
|
41
|
-
const
|
|
42
|
+
const cPending = db.prepare(`
|
|
42
43
|
SELECT COUNT(*) as total,
|
|
43
|
-
SUM(CASE WHEN
|
|
44
|
-
FROM
|
|
44
|
+
SUM(CASE WHEN lifecycle='pending' THEN 1 ELSE 0 END) as pending
|
|
45
|
+
FROM knowledge_entries
|
|
45
46
|
`).get();
|
|
46
47
|
knowledgeBase = {
|
|
47
48
|
recipes: { total: rStats.total, active: rStats.active, rules: rStats.rules, patterns: rStats.patterns, facts: rStats.facts },
|
|
48
|
-
candidates: { total:
|
|
49
|
+
candidates: { total: cPending.total, pending: cPending.pending },
|
|
49
50
|
};
|
|
50
51
|
} catch { /* 统计查询失败不影响 health */ }
|
|
51
52
|
}
|
|
@@ -132,9 +133,6 @@ export function capabilities() {
|
|
|
132
133
|
autosnippet_get_target_metadata: 'structure',
|
|
133
134
|
autosnippet_validate_candidate: 'validate',
|
|
134
135
|
autosnippet_check_duplicate: 'validate',
|
|
135
|
-
autosnippet_submit_candidate: 'submit',
|
|
136
|
-
autosnippet_submit_candidates: 'submit',
|
|
137
|
-
autosnippet_submit_draft_recipes: 'submit',
|
|
138
136
|
autosnippet_guard_check: 'guard',
|
|
139
137
|
autosnippet_guard_audit_files: 'guard',
|
|
140
138
|
autosnippet_scan_project: 'scan',
|
|
@@ -191,9 +189,9 @@ export function capabilities() {
|
|
|
191
189
|
tools,
|
|
192
190
|
workflows: [
|
|
193
191
|
{ name: '知识查询', steps: ['search(推荐首选,auto mode 融合)', 'get_recipe', 'confirm_usage'], tips: '精确匹配用 keyword_search,需意图+会话上下文用 context_search' },
|
|
194
|
-
{ name: '
|
|
195
|
-
{ name: '批量 Target 扫描', steps: ['get_targets', 'get_target_files', '(Agent 分析)', '
|
|
196
|
-
{ name: '全项目深度扫描', steps: ['scan_project', '(Agent 语义分析)', '
|
|
192
|
+
{ name: '单条知识提交', steps: ['check_duplicate', 'validate_candidate', 'submit_knowledge'] },
|
|
193
|
+
{ name: '批量 Target 扫描', steps: ['get_targets', 'get_target_files', '(Agent 分析)', 'submit_knowledge_batch'] },
|
|
194
|
+
{ name: '全项目深度扫描', steps: ['scan_project', '(Agent 语义分析)', 'submit_knowledge_batch', 'enrich_candidates'] },
|
|
197
195
|
{ name: '候选就绪检查', steps: ['enrich_candidates', 'validate_candidate', 'check_duplicate'] },
|
|
198
196
|
],
|
|
199
197
|
},
|