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
|
@@ -36,7 +36,7 @@ const PROJECT_ROOT = path.resolve(__dirname, '../../..');
|
|
|
36
36
|
const SKILLS_DIR = path.resolve(PROJECT_ROOT, 'skills');
|
|
37
37
|
const SOUL_PATH = path.resolve(PROJECT_ROOT, 'SOUL.md');
|
|
38
38
|
const MAX_ITERATIONS = 6;
|
|
39
|
-
/** 系统调用 (如 bootstrap) 允许更多迭代,因为每维度需要多次
|
|
39
|
+
/** 系统调用 (如 bootstrap) 允许更多迭代,因为每维度需要多次 submit_knowledge */
|
|
40
40
|
const MAX_ITERATIONS_SYSTEM = 30;
|
|
41
41
|
/** 原生函数调用模式下,已提交 ≥ MIN_SUBMITS_FOR_EARLY_EXIT 个候选后,连续 N 轮无新提交则提前退出 */
|
|
42
42
|
const MIN_SUBMITS_FOR_EARLY_EXIT = 1;
|
|
@@ -69,23 +69,23 @@ const SYSTEM_CONTINUATION_PROMPT = `你的分析计划很好。但你需要 **
|
|
|
69
69
|
请现在开始执行:
|
|
70
70
|
1. 用 \`search_project_code\` 搜索项目代码获取真实示例
|
|
71
71
|
2. 用 \`read_project_file\` 查看完整文件内容
|
|
72
|
-
3. 对每个值得保留的信号,用 \`
|
|
72
|
+
3. 对每个值得保留的信号,用 \`submit_knowledge\` 提交候选
|
|
73
73
|
|
|
74
74
|
⚡ 推荐使用 batch_actions 一次提交多条候选:
|
|
75
75
|
\`\`\`batch_actions
|
|
76
76
|
[
|
|
77
|
-
{"tool": "
|
|
78
|
-
{"tool": "
|
|
77
|
+
{"tool": "submit_knowledge", "params": {"title": "[Bootstrap] xxx/子主题", "code": "# 标题 — 项目特写\\n\\n> 摘要...\\n\\n描述和代码交织...", "language": "objectivec", "category": "Service", "summary": "...", "tags": ["bootstrap"], "source": "bootstrap", "reasoning": {"whyStandard": "...", "sources": ["file1"], "confidence": 0.7}}},
|
|
78
|
+
{"tool": "submit_knowledge", "params": {"title": "...", "code": "...", ...}}
|
|
79
79
|
]
|
|
80
80
|
\`\`\`
|
|
81
81
|
|
|
82
82
|
请立即开始执行,不要再输出分析文字。`;
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* 系统调用提交提示 — 当 AI 做了工具调用(search/read)、写了分析文本,但没调
|
|
86
|
-
* 引导 AI 将已有分析转化为实际的
|
|
85
|
+
* 系统调用提交提示 — 当 AI 做了工具调用(search/read)、写了分析文本,但没调 submit_knowledge 时注入
|
|
86
|
+
* 引导 AI 将已有分析转化为实际的 submit_knowledge 调用
|
|
87
87
|
*/
|
|
88
|
-
const SYSTEM_SUBMIT_PROMPT = `你的分析很好,已经获取了足够的项目信息。但你还没有调用 \`
|
|
88
|
+
const SYSTEM_SUBMIT_PROMPT = `你的分析很好,已经获取了足够的项目信息。但你还没有调用 \`submit_knowledge\` 提交任何候选。
|
|
89
89
|
|
|
90
90
|
**你的分析不能只停留在文字描述层面** — 必须通过工具调用将分析结果持久化。
|
|
91
91
|
|
|
@@ -93,7 +93,7 @@ const SYSTEM_SUBMIT_PROMPT = `你的分析很好,已经获取了足够的项
|
|
|
93
93
|
|
|
94
94
|
\`\`\`batch_actions
|
|
95
95
|
[
|
|
96
|
-
{"tool": "
|
|
96
|
+
{"tool": "submit_knowledge", "params": {
|
|
97
97
|
"title": "[Bootstrap] 维度/子主题",
|
|
98
98
|
"code": "# 标题 — 项目特写\\n\\n> 本项目使用 XX 模式, N 个文件采用此写法\\n\\n描述...\\n\\n\`\`\`objc\\n// 真实代码示例\\n\`\`\`\\n\\n要点说明...",
|
|
99
99
|
"language": "objectivec",
|
|
@@ -103,11 +103,11 @@ const SYSTEM_SUBMIT_PROMPT = `你的分析很好,已经获取了足够的项
|
|
|
103
103
|
"source": "bootstrap",
|
|
104
104
|
"reasoning": {"whyStandard": "为什么值得保留", "sources": ["真实文件名"], "confidence": 0.7}
|
|
105
105
|
}},
|
|
106
|
-
{"tool": "
|
|
106
|
+
{"tool": "submit_knowledge", "params": {...}}
|
|
107
107
|
]
|
|
108
108
|
\`\`\`
|
|
109
109
|
|
|
110
|
-
将你上面分析出的每个有价值的发现都转化为一条
|
|
110
|
+
将你上面分析出的每个有价值的发现都转化为一条 submit_knowledge 调用。code 字段写「项目特写」风格: 描述和代码交织,用项目真实类名和代码。`;
|
|
111
111
|
|
|
112
112
|
export class ChatAgent {
|
|
113
113
|
#toolRegistry;
|
|
@@ -253,6 +253,31 @@ export class ChatAgent {
|
|
|
253
253
|
// 附加 token 用量统计
|
|
254
254
|
result.tokenUsage = { ...this.#currentTokenUsage };
|
|
255
255
|
|
|
256
|
+
// 持久化 token 消耗到数据库(fire-and-forget)
|
|
257
|
+
try {
|
|
258
|
+
const tokenStore = this.#container?.get?.('tokenUsageStore');
|
|
259
|
+
if (tokenStore) {
|
|
260
|
+
const aiProvider = this.#aiProvider;
|
|
261
|
+
tokenStore.record({
|
|
262
|
+
source: source || 'unknown',
|
|
263
|
+
dimension: dimensionId || dimensionMeta?.id || null,
|
|
264
|
+
provider: aiProvider?.name || null,
|
|
265
|
+
model: aiProvider?.model || null,
|
|
266
|
+
inputTokens: this.#currentTokenUsage.input,
|
|
267
|
+
outputTokens: this.#currentTokenUsage.output,
|
|
268
|
+
durationMs: Date.now() - execStartTime,
|
|
269
|
+
toolCalls: result.toolCalls?.length || 0,
|
|
270
|
+
sessionId: conversationId || null,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// 通知前端 token 用量变化
|
|
274
|
+
try {
|
|
275
|
+
const realtime = this.#container?.get?.('realtimeService');
|
|
276
|
+
realtime?.broadcastTokenUsageUpdated?.();
|
|
277
|
+
} catch { /* optional */ }
|
|
278
|
+
}
|
|
279
|
+
} catch { /* token logging should never break execution */ }
|
|
280
|
+
|
|
256
281
|
return { ...result, conversationId };
|
|
257
282
|
}
|
|
258
283
|
|
|
@@ -306,12 +331,18 @@ export class ChatAgent {
|
|
|
306
331
|
const phaseRouter = (isSystem && !disablePhaseRouter) ? new PhaseRouter(budget, isSkillOnly) : null;
|
|
307
332
|
|
|
308
333
|
// ── 系统提示词 (支持外部覆盖) ──
|
|
309
|
-
|
|
334
|
+
let baseSystemPrompt = systemPromptOverride || this.#buildNativeToolSystemPrompt(budget);
|
|
335
|
+
// 统一注入轮次预算,确保 AI 始终知道具体数字和节奏
|
|
336
|
+
if (isSystem && !baseSystemPrompt.includes('轮次预算')) {
|
|
337
|
+
const exploreEnd = Math.floor(budget.maxIterations * 0.6);
|
|
338
|
+
const verifyEnd = Math.floor(budget.maxIterations * 0.8);
|
|
339
|
+
baseSystemPrompt += `\n\n## 轮次预算\n- 总轮次: **${budget.maxIterations} 轮**\n- 探索阶段: 第 1-${exploreEnd} 轮(搜索和结构化查询)\n- 验证阶段: 第 ${exploreEnd + 1}-${verifyEnd} 轮(读取关键文件确认细节)\n- 总结阶段: 第 ${verifyEnd + 1}-${budget.maxIterations} 轮(**停止工具调用,输出分析文本**)\n\n到达第 ${verifyEnd} 轮时你必须开始输出总结,不要继续搜索。`;
|
|
340
|
+
}
|
|
310
341
|
|
|
311
342
|
// Bootstrap 场景限制可用工具集 (支持外部覆盖)
|
|
312
343
|
const effectiveAllowedTools = allowedTools || (isSystem ? [
|
|
313
344
|
'search_project_code', 'read_project_file',
|
|
314
|
-
'
|
|
345
|
+
'submit_knowledge', 'submit_with_check',
|
|
315
346
|
'list_project_structure', 'get_file_summary', 'semantic_search_code',
|
|
316
347
|
// AST 结构化分析工具
|
|
317
348
|
'get_project_overview', 'get_class_hierarchy', 'get_class_info',
|
|
@@ -328,6 +359,20 @@ export class ChatAgent {
|
|
|
328
359
|
const submittedTitles = new Set(this.#globalSubmittedTitles);
|
|
329
360
|
const sharedState = {}; // P2.2: 跨工具调用共享状态(搜索计数器等)
|
|
330
361
|
|
|
362
|
+
// ── 进度感知收敛 (Analyst 专用: disablePhaseRouter=true) ──
|
|
363
|
+
// 追踪搜索/读取新信息的效率,当探索饱和时提前引导 AI 收敛
|
|
364
|
+
const explorationMetrics = {
|
|
365
|
+
uniqueFiles: new Set(), // 已读取的唯一文件
|
|
366
|
+
uniquePatterns: new Set(), // 已搜索的唯一 pattern
|
|
367
|
+
uniqueQueries: new Set(), // 已查询的唯一类名/目录(AST + list_project_structure)
|
|
368
|
+
totalToolCalls: 0, // 总工具调用数
|
|
369
|
+
newInfoRounds: 0, // 最近 N 轮中获取到新信息的轮次
|
|
370
|
+
staleRounds: 0, // 连续未获取新信息的轮次
|
|
371
|
+
convergenceNudged: false, // 是否已注入收敛 nudge
|
|
372
|
+
};
|
|
373
|
+
const MIN_EXPLORE_ITERS = 16; // 最少探索轮次(冷启动质量保障)
|
|
374
|
+
const STALE_THRESHOLD = 3; // 连续无新信息轮次触发收敛
|
|
375
|
+
|
|
331
376
|
// ── 主循环 ──
|
|
332
377
|
while (true) {
|
|
333
378
|
// PhaseRouter tick + 退出检查
|
|
@@ -337,9 +382,53 @@ export class ChatAgent {
|
|
|
337
382
|
this.#logger.info(`[ChatAgent] PhaseRouter exit: phase=${phaseRouter.phase}, iter=${phaseRouter.totalIterations}, submits=${phaseRouter.totalSubmits}`);
|
|
338
383
|
break;
|
|
339
384
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
385
|
+
// PhaseRouter 因 maxIterations 强制转入 SUMMARIZE → 注入收尾 nudge
|
|
386
|
+
if (phaseRouter.consumeForcedSummarize()) {
|
|
387
|
+
const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
|
|
388
|
+
ctx.appendUserNudge(
|
|
389
|
+
`⚠️ 轮次即将耗尽 (${phaseRouter.totalIterations}/${budget.maxIterations}),**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
|
|
390
|
+
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
|
|
391
|
+
);
|
|
392
|
+
this.#logger.info('[ChatAgent] 📝 injected forced SUMMARIZE nudge (maxIterations reached)');
|
|
393
|
+
}
|
|
394
|
+
} else if (isSystem && !phaseRouter && !ctx.__gracefulExitInjected && !explorationMetrics.convergenceNudged
|
|
395
|
+
&& iterationCount >= MIN_EXPLORE_ITERS && explorationMetrics.staleRounds >= STALE_THRESHOLD) {
|
|
396
|
+
// ── 进度感知收敛:探索已饱和,提前引导 AI 总结 ──
|
|
397
|
+
this.#logger.info(
|
|
398
|
+
`[ChatAgent] 📊 Exploration saturated at iter ${iterationCount}/${maxIter} — ` +
|
|
399
|
+
`files=${explorationMetrics.uniqueFiles.size}, patterns=${explorationMetrics.uniquePatterns.size}, ` +
|
|
400
|
+
`queries=${explorationMetrics.uniqueQueries.size}, staleRounds=${explorationMetrics.staleRounds} — nudging convergence`
|
|
401
|
+
);
|
|
402
|
+
ctx.appendUserNudge(
|
|
403
|
+
`你已经充分探索了项目代码(${explorationMetrics.uniqueFiles.size} 个文件,${explorationMetrics.uniquePatterns.size} 次不同搜索,${explorationMetrics.uniqueQueries.size} 次结构化查询)。` +
|
|
404
|
+
`最近 ${explorationMetrics.staleRounds} 轮没有发现新信息,建议开始撰写分析总结。\n` +
|
|
405
|
+
`如果你确信还有重要方面未覆盖,可以继续探索(剩余 ${maxIter - iterationCount} 轮);否则请直接输出你的分析发现。`
|
|
406
|
+
);
|
|
407
|
+
explorationMetrics.convergenceNudged = true;
|
|
408
|
+
// 不 break,不设 toolChoice=none — 这是软 nudge,AI 仍可继续探索
|
|
409
|
+
} else if (isSystem && !phaseRouter && !ctx.__budgetWarningInjected
|
|
410
|
+
&& iterationCount >= Math.floor(maxIter * 0.75)) {
|
|
411
|
+
// ── 预算感知提醒:75% 预算消耗时轻量提示 ──
|
|
412
|
+
ctx.appendUserNudge(
|
|
413
|
+
`📌 进度提醒:你已使用 ${iterationCount}/${maxIter} 轮次(${Math.round(iterationCount / maxIter * 100)}%)。` +
|
|
414
|
+
`请确保核心方面已覆盖,开始准备总结。剩余 ${maxIter - iterationCount} 轮,优先填补最重要的分析空白。`
|
|
415
|
+
);
|
|
416
|
+
ctx.__budgetWarningInjected = true;
|
|
417
|
+
this.#logger.info(`[ChatAgent] 📌 Budget warning at ${iterationCount}/${maxIter} (${Math.round(iterationCount / maxIter * 100)}%)`);
|
|
418
|
+
} else if (isSystem && iterationCount >= maxIter && !ctx.__gracefulExitInjected) {
|
|
419
|
+
// 达到上限 → 注入收尾消息让 AI 快速总结(而非硬中断)
|
|
420
|
+
this.#logger.info(`[ChatAgent] Iteration cap reached (${iterationCount}/${maxIter}) — injecting graceful exit nudge`);
|
|
421
|
+
const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
|
|
422
|
+
ctx.appendUserNudge(
|
|
423
|
+
`⚠️ 你已使用 ${iterationCount}/${maxIter} 轮次,**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
|
|
424
|
+
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
|
|
425
|
+
);
|
|
426
|
+
ctx.__gracefulExitInjected = true;
|
|
427
|
+
// 不 break,给 AI 2 轮 grace 来产出总结
|
|
428
|
+
// 继续 while 循环
|
|
429
|
+
} else if (isSystem && iterationCount >= maxIter + 2) {
|
|
430
|
+
// 硬上限兜底:grace 轮次也耗尽
|
|
431
|
+
this.#logger.info(`[ChatAgent] Hard cap reached: ${iterationCount}/${maxIter + 2}`);
|
|
343
432
|
break;
|
|
344
433
|
} else if (!isSystem && ctx.length > maxIter * 2 + 2) {
|
|
345
434
|
// 用户对话模式: 简单的消息数限制
|
|
@@ -352,7 +441,10 @@ export class ChatAgent {
|
|
|
352
441
|
|
|
353
442
|
// ── 动态 toolChoice (由 PhaseRouter 决定) ──
|
|
354
443
|
let currentChoice;
|
|
355
|
-
if (
|
|
444
|
+
if (ctx.__gracefulExitInjected) {
|
|
445
|
+
// graceful exit: 禁止工具调用,强制 AI 输出文本总结
|
|
446
|
+
currentChoice = 'none';
|
|
447
|
+
} else if (phaseRouter) {
|
|
356
448
|
currentChoice = phaseRouter.getToolChoice();
|
|
357
449
|
} else {
|
|
358
450
|
currentChoice = 'auto';
|
|
@@ -371,6 +463,19 @@ export class ChatAgent {
|
|
|
371
463
|
if (hint) {
|
|
372
464
|
systemPrompt += `\n\n## 当前状态\n${hint}`;
|
|
373
465
|
}
|
|
466
|
+
} else if (isSystem) {
|
|
467
|
+
// 非 PhaseRouter 路径:注入实时轮次计数器,让 AI 始终感知进度
|
|
468
|
+
const verifyStart = Math.floor(maxIter * 0.8);
|
|
469
|
+
const remaining = maxIter - iterationCount;
|
|
470
|
+
let phaseLabel;
|
|
471
|
+
if (iterationCount <= Math.floor(maxIter * 0.6)) {
|
|
472
|
+
phaseLabel = '探索阶段';
|
|
473
|
+
} else if (iterationCount <= verifyStart) {
|
|
474
|
+
phaseLabel = '验证阶段';
|
|
475
|
+
} else {
|
|
476
|
+
phaseLabel = '⚠ 总结阶段 — 请停止工具调用,直接输出分析文本';
|
|
477
|
+
}
|
|
478
|
+
systemPrompt += `\n\n## 当前进度\n第 ${iterationCount}/${maxIter} 轮 | ${phaseLabel} | 剩余 ${remaining} 轮`;
|
|
374
479
|
}
|
|
375
480
|
|
|
376
481
|
// ── AI 调用 ──
|
|
@@ -438,6 +543,20 @@ export class ChatAgent {
|
|
|
438
543
|
|
|
439
544
|
// ── 处理 functionCalls ──
|
|
440
545
|
if (aiResult.functionCalls && aiResult.functionCalls.length > 0) {
|
|
546
|
+
// ── Graceful exit 保护: Gemini 有时会无视 toolChoice='none' 继续返回工具调用
|
|
547
|
+
// 强制忽略工具调用,将附带文本视为最终回复
|
|
548
|
+
if (ctx.__gracefulExitInjected) {
|
|
549
|
+
this.#logger.warn(`[ChatAgent] ⚠ AI returned ${aiResult.functionCalls.length} tool calls despite toolChoice=none (graceful exit) — ignoring tools, treating as text`);
|
|
550
|
+
if (aiResult.text) {
|
|
551
|
+
const reply = this.#cleanFinalAnswer(aiResult.text);
|
|
552
|
+
const totalDuration = Date.now() - execStartTime;
|
|
553
|
+
this.#logger.info(`[ChatAgent] ✅ final answer (graceful exit, forced) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`);
|
|
554
|
+
return { reply, toolCalls, hasContext: toolCalls.length > 0 };
|
|
555
|
+
}
|
|
556
|
+
// 无文本时继续循环,下一轮硬上限兜底
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
|
|
441
560
|
// 限制单次工具调用数量(防上下文溢出)
|
|
442
561
|
const MAX_TOOL_CALLS_PER_ITER = 8;
|
|
443
562
|
let activeCalls = aiResult.functionCalls;
|
|
@@ -450,6 +569,7 @@ export class ChatAgent {
|
|
|
450
569
|
ctx.appendAssistantWithToolCalls(aiResult.text || null, activeCalls);
|
|
451
570
|
|
|
452
571
|
let roundSubmitCount = 0;
|
|
572
|
+
let roundHasNewInfo = false; // 本轮是否获取到新信息
|
|
453
573
|
|
|
454
574
|
for (const fc of activeCalls) {
|
|
455
575
|
const toolStartTime = Date.now();
|
|
@@ -474,12 +594,105 @@ export class ChatAgent {
|
|
|
474
594
|
const summarized = this.#summarizeResult(toolResult);
|
|
475
595
|
toolCalls.push({ tool: fc.name, params: fc.args, result: summarized });
|
|
476
596
|
|
|
597
|
+
// ── 探索进度追踪 (非 PhaseRouter 路径) ──
|
|
598
|
+
if (!phaseRouter && isSystem) {
|
|
599
|
+
explorationMetrics.totalToolCalls++;
|
|
600
|
+
let foundNewInfo = false;
|
|
601
|
+
|
|
602
|
+
if (fc.name === 'search_project_code') {
|
|
603
|
+
const pattern = fc.args?.pattern || '';
|
|
604
|
+
const patterns = fc.args?.patterns || [];
|
|
605
|
+
// 单模式
|
|
606
|
+
if (pattern && !explorationMetrics.uniquePatterns.has(pattern)) {
|
|
607
|
+
explorationMetrics.uniquePatterns.add(pattern);
|
|
608
|
+
foundNewInfo = true;
|
|
609
|
+
}
|
|
610
|
+
// 批量模式
|
|
611
|
+
for (const p of patterns) {
|
|
612
|
+
if (!explorationMetrics.uniquePatterns.has(p)) {
|
|
613
|
+
explorationMetrics.uniquePatterns.add(p);
|
|
614
|
+
foundNewInfo = true;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
// 检查搜索结果是否有新文件
|
|
618
|
+
if (toolResult && typeof toolResult === 'object') {
|
|
619
|
+
const matches = toolResult.matches || [];
|
|
620
|
+
const batchResults = toolResult.batchResults || {};
|
|
621
|
+
for (const m of matches) {
|
|
622
|
+
if (m.file && !explorationMetrics.uniqueFiles.has(m.file)) {
|
|
623
|
+
explorationMetrics.uniqueFiles.add(m.file);
|
|
624
|
+
foundNewInfo = true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
for (const sub of Object.values(batchResults)) {
|
|
628
|
+
for (const m of (sub.matches || [])) {
|
|
629
|
+
if (m.file && !explorationMetrics.uniqueFiles.has(m.file)) {
|
|
630
|
+
explorationMetrics.uniqueFiles.add(m.file);
|
|
631
|
+
foundNewInfo = true;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
} else if (fc.name === 'read_project_file') {
|
|
637
|
+
const fp = fc.args?.filePath || '';
|
|
638
|
+
const fps = fc.args?.filePaths || [];
|
|
639
|
+
if (fp && !explorationMetrics.uniqueFiles.has(fp)) {
|
|
640
|
+
explorationMetrics.uniqueFiles.add(fp);
|
|
641
|
+
foundNewInfo = true;
|
|
642
|
+
}
|
|
643
|
+
for (const f of fps) {
|
|
644
|
+
if (!explorationMetrics.uniqueFiles.has(f)) {
|
|
645
|
+
explorationMetrics.uniqueFiles.add(f);
|
|
646
|
+
foundNewInfo = true;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
} else if (fc.name === 'list_project_structure') {
|
|
650
|
+
// 目录结构:同一目录只算一次新信息
|
|
651
|
+
const dir = fc.args?.directory || '/';
|
|
652
|
+
const qKey = `list:${dir}`;
|
|
653
|
+
if (!explorationMetrics.uniqueQueries.has(qKey)) {
|
|
654
|
+
explorationMetrics.uniqueQueries.add(qKey);
|
|
655
|
+
foundNewInfo = true;
|
|
656
|
+
}
|
|
657
|
+
} else if (fc.name === 'get_class_info' || fc.name === 'get_class_hierarchy'
|
|
658
|
+
|| fc.name === 'get_protocol_info' || fc.name === 'get_method_overrides'
|
|
659
|
+
|| fc.name === 'get_category_map') {
|
|
660
|
+
// AST 结构化查询:同一类名/协议名只算一次新信息
|
|
661
|
+
const queryTarget = fc.args?.className || fc.args?.protocolName || fc.args?.name || '';
|
|
662
|
+
const qKey = `${fc.name}:${queryTarget}`;
|
|
663
|
+
if (!explorationMetrics.uniqueQueries.has(qKey)) {
|
|
664
|
+
explorationMetrics.uniqueQueries.add(qKey);
|
|
665
|
+
foundNewInfo = true;
|
|
666
|
+
}
|
|
667
|
+
} else if (fc.name === 'get_project_overview') {
|
|
668
|
+
// 项目概览只需调一次
|
|
669
|
+
const qKey = 'overview';
|
|
670
|
+
if (!explorationMetrics.uniqueQueries.has(qKey)) {
|
|
671
|
+
explorationMetrics.uniqueQueries.add(qKey);
|
|
672
|
+
foundNewInfo = true;
|
|
673
|
+
}
|
|
674
|
+
} else if (fc.name !== 'submit_knowledge' && fc.name !== 'submit_with_check') {
|
|
675
|
+
// 其他未分类工具 — 首次算新信息,之后同工具名+同参数去重
|
|
676
|
+
const qKey = `${fc.name}:${JSON.stringify(fc.args || {}).substring(0, 80)}`;
|
|
677
|
+
if (!explorationMetrics.uniqueQueries.has(qKey)) {
|
|
678
|
+
explorationMetrics.uniqueQueries.add(qKey);
|
|
679
|
+
foundNewInfo = true;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (foundNewInfo) {
|
|
684
|
+
explorationMetrics.staleRounds = 0;
|
|
685
|
+
explorationMetrics.newInfoRounds++;
|
|
686
|
+
roundHasNewInfo = true;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
477
690
|
// ── Layer 3: ToolResultLimiter — 动态配额压缩 ──
|
|
478
691
|
const quota = ctx.getToolResultQuota();
|
|
479
692
|
let resultStr = limitToolResult(fc.name, toolResult, quota);
|
|
480
693
|
|
|
481
694
|
// ── 重复提交 / 维度范围校验 ──
|
|
482
|
-
if (fc.name === '
|
|
695
|
+
if (fc.name === 'submit_knowledge' || fc.name === 'submit_with_check') {
|
|
483
696
|
const title = fc.args?.title || fc.args?.category || '';
|
|
484
697
|
const isRejected = typeof toolResult === 'object' && toolResult?.status === 'rejected';
|
|
485
698
|
const isError = typeof toolResult === 'object' && (toolResult?.error || toolResult?.status === 'error');
|
|
@@ -503,6 +716,11 @@ export class ChatAgent {
|
|
|
503
716
|
ctx.appendToolResult(fc.id, fc.name, resultStr);
|
|
504
717
|
}
|
|
505
718
|
|
|
719
|
+
// ── 探索饱和度更新 (非 PhaseRouter 路径) ──
|
|
720
|
+
if (!phaseRouter && isSystem && !roundHasNewInfo) {
|
|
721
|
+
explorationMetrics.staleRounds++;
|
|
722
|
+
}
|
|
723
|
+
|
|
506
724
|
// ── PhaseRouter 更新 ──
|
|
507
725
|
if (phaseRouter) {
|
|
508
726
|
const transition = phaseRouter.update({
|
|
@@ -516,7 +734,7 @@ export class ChatAgent {
|
|
|
516
734
|
// 需要一条 user 消息明确告知 AI 切换到提交模式
|
|
517
735
|
if (transition.transitioned && transition.newPhase === 'PRODUCE') {
|
|
518
736
|
ctx.appendUserNudge(
|
|
519
|
-
'你已充分探索了项目代码,现在请开始调用
|
|
737
|
+
'你已充分探索了项目代码,现在请开始调用 submit_knowledge 工具来提交你发现的知识候选。不要再搜索,直接提交。'
|
|
520
738
|
);
|
|
521
739
|
this.#logger.info('[ChatAgent] 📝 injected PRODUCE transition nudge');
|
|
522
740
|
}
|
|
@@ -525,7 +743,7 @@ export class ChatAgent {
|
|
|
525
743
|
// skill-only 维度从 EXPLORE 直接进入 SUMMARIZE (跳过 PRODUCE),
|
|
526
744
|
// 需要明确告知 AI 输出 dimensionDigest JSON
|
|
527
745
|
if (transition.transitioned && transition.newPhase === 'SUMMARIZE') {
|
|
528
|
-
const submitCount = toolCalls.filter(tc => tc.tool === '
|
|
746
|
+
const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
|
|
529
747
|
ctx.appendUserNudge(
|
|
530
748
|
`你已完成分析探索。请在回复中直接输出 dimensionDigest JSON(用 \`\`\`json 包裹),包含以下字段:\n\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因(如:提交上限已达)","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]\`。如果有未来得及处理的信号,请在此标记,系统会在下次运行时续传。`
|
|
531
749
|
);
|
|
@@ -563,7 +781,7 @@ export class ChatAgent {
|
|
|
563
781
|
// 注入 nudge 让 AI 再输出一次 digest JSON
|
|
564
782
|
if (transition.transitioned) {
|
|
565
783
|
ctx.appendAssistantText(aiResult.text || '');
|
|
566
|
-
const submitCount = toolCalls.filter(tc => tc.tool === '
|
|
784
|
+
const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
|
|
567
785
|
ctx.appendUserNudge(
|
|
568
786
|
`请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹):\n\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理的信号","reason":"原因","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 记录未来得及处理的信号。已全部覆盖则留空 \`[]\`。`
|
|
569
787
|
);
|
|
@@ -584,7 +802,7 @@ export class ChatAgent {
|
|
|
584
802
|
ctx.appendAssistantText(aiResult.text || '');
|
|
585
803
|
if (phaseRouter.phase === 'PRODUCE') {
|
|
586
804
|
ctx.appendUserNudge(
|
|
587
|
-
'你的分析很好。请继续调用
|
|
805
|
+
'你的分析很好。请继续调用 submit_knowledge 提交你发现的知识候选,每个值得记录的模式/实践都应该提交。'
|
|
588
806
|
);
|
|
589
807
|
this.#logger.info('[ChatAgent] 📝 injected submit nudge (text in PRODUCE, not transitioning)');
|
|
590
808
|
}
|
|
@@ -602,7 +820,15 @@ export class ChatAgent {
|
|
|
602
820
|
continue;
|
|
603
821
|
}
|
|
604
822
|
|
|
605
|
-
//
|
|
823
|
+
// 用户对话 / graceful exit: 文字回答即最终回答
|
|
824
|
+
if (ctx.__gracefulExitInjected || !isSystem) {
|
|
825
|
+
const reply = this.#cleanFinalAnswer(aiResult.text || '');
|
|
826
|
+
const totalDuration = Date.now() - execStartTime;
|
|
827
|
+
this.#logger.info(`[ChatAgent] ✅ final answer (${ctx.__gracefulExitInjected ? 'graceful exit' : 'user'}) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`);
|
|
828
|
+
return { reply, toolCalls, hasContext: toolCalls.length > 0 };
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// system 模式非 graceful exit 的文字回答(理论上不应到这里)
|
|
606
832
|
const reply = this.#cleanFinalAnswer(aiResult.text || '');
|
|
607
833
|
const totalDuration = Date.now() - execStartTime;
|
|
608
834
|
this.#logger.info(`[ChatAgent] ✅ final answer — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`);
|
|
@@ -624,7 +850,7 @@ export class ChatAgent {
|
|
|
624
850
|
this.#logger.info(`[ChatAgent] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls)`);
|
|
625
851
|
|
|
626
852
|
const candidateCount = toolCalls.filter(tc =>
|
|
627
|
-
tc.tool === '
|
|
853
|
+
tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
|
|
628
854
|
).length;
|
|
629
855
|
|
|
630
856
|
let finalReply;
|
|
@@ -639,7 +865,7 @@ export class ChatAgent {
|
|
|
639
865
|
if (isCircuitOpen) throw new Error('circuit open — skip to synthetic digest');
|
|
640
866
|
|
|
641
867
|
const submitSummary = toolCalls
|
|
642
|
-
.filter(tc => tc.tool === '
|
|
868
|
+
.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
643
869
|
.map((tc, i) => `${i + 1}. ${tc.params?.title || tc.params?.category || 'untitled'}`)
|
|
644
870
|
.join('\n');
|
|
645
871
|
|
|
@@ -686,7 +912,7 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
686
912
|
this.#logger.warn(`[ChatAgent] forced summary AI call failed: ${err.message}`);
|
|
687
913
|
// 合成 digest 兜底
|
|
688
914
|
const titles = toolCalls
|
|
689
|
-
.filter(tc => tc.tool === '
|
|
915
|
+
.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
|
|
690
916
|
.map(tc => tc.params?.title || 'untitled');
|
|
691
917
|
finalReply = `\`\`\`json
|
|
692
918
|
{
|
|
@@ -771,7 +997,7 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
|
|
|
771
997
|
|
|
772
998
|
if (!actions) {
|
|
773
999
|
// ── 系统调用自动续跑 ──
|
|
774
|
-
const hasSubmits = toolCalls.some(tc => tc.tool === '
|
|
1000
|
+
const hasSubmits = toolCalls.some(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check');
|
|
775
1001
|
if (source === 'system' && iterations < maxIter && !hasSubmits) {
|
|
776
1002
|
if (this.#looksLikeIncompleteStep(response)) {
|
|
777
1003
|
this.#logger.info(`[ChatAgent] 🔄 detected planning-only response at iteration ${iterations}, injecting continuation prompt`);
|
|
@@ -1046,13 +1272,13 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1046
1272
|
*/
|
|
1047
1273
|
async #taskDiscoverAllRelations({ batchSize = 20 } = {}) {
|
|
1048
1274
|
const ctx = this.#getToolContext();
|
|
1049
|
-
const
|
|
1050
|
-
if (!
|
|
1275
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1276
|
+
if (!knowledgeService) throw new Error('KnowledgeService 不可用');
|
|
1051
1277
|
|
|
1052
1278
|
if (!ctx.aiProvider) throw new Error('AI Provider 未配置,请先设置 API Key');
|
|
1053
1279
|
|
|
1054
|
-
//
|
|
1055
|
-
const { items = [], data = [] } = await
|
|
1280
|
+
// 获取所有活跃知识条目
|
|
1281
|
+
const { items = [], data = [] } = await knowledgeService.list({ lifecycle: 'active' }, { page: 1, pageSize: 500 });
|
|
1056
1282
|
const recipes = items.length > 0 ? items : data;
|
|
1057
1283
|
if (recipes.length < 2) return { discovered: 0, totalPairs: 0, message: `只有 ${recipes.length} 条 Recipe,至少需要 2 条` };
|
|
1058
1284
|
|
|
@@ -1108,10 +1334,10 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1108
1334
|
*/
|
|
1109
1335
|
async #taskFullEnrich({ status = 'pending', maxCount = 50 } = {}) {
|
|
1110
1336
|
const ctx = this.#getToolContext();
|
|
1111
|
-
const
|
|
1337
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1112
1338
|
|
|
1113
|
-
const { items = [], data = [] } = await
|
|
1114
|
-
{ status }, { page: 1, pageSize: maxCount }
|
|
1339
|
+
const { items = [], data = [] } = await knowledgeService.list(
|
|
1340
|
+
{ lifecycle: status }, { page: 1, pageSize: maxCount }
|
|
1115
1341
|
);
|
|
1116
1342
|
const candidates = items.length > 0 ? items : data;
|
|
1117
1343
|
if (candidates.length === 0) return { enriched: 0, message: 'No candidates to enrich' };
|
|
@@ -1137,10 +1363,10 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
|
|
|
1137
1363
|
*/
|
|
1138
1364
|
async #taskQualityAudit({ threshold = 0.6, maxCount = 100 } = {}) {
|
|
1139
1365
|
const ctx = this.#getToolContext();
|
|
1140
|
-
const
|
|
1366
|
+
const knowledgeService = ctx.container.get('knowledgeService');
|
|
1141
1367
|
|
|
1142
|
-
const { items = [], data = [] } = await
|
|
1143
|
-
{
|
|
1368
|
+
const { items = [], data = [] } = await knowledgeService.list(
|
|
1369
|
+
{ lifecycle: 'active' }, { page: 1, pageSize: maxCount }
|
|
1144
1370
|
);
|
|
1145
1371
|
const recipes = items.length > 0 ? items : data;
|
|
1146
1372
|
if (recipes.length === 0) return { total: 0, lowQuality: [], message: 'No active recipes' };
|
|
@@ -1267,7 +1493,7 @@ ${code.substring(0, 3000)}
|
|
|
1267
1493
|
// 核心工具 — 使用最频繁,直接展示完整 schema
|
|
1268
1494
|
const coreTools = new Set([
|
|
1269
1495
|
'search_project_code', 'read_project_file',
|
|
1270
|
-
'search_knowledge', '
|
|
1496
|
+
'search_knowledge', 'submit_knowledge', 'submit_with_check', 'analyze_code',
|
|
1271
1497
|
'bootstrap_knowledge', 'load_skill', 'suggest_skills',
|
|
1272
1498
|
'create_skill', 'knowledge_overview', 'get_tool_details',
|
|
1273
1499
|
'plan_task', 'review_my_output',
|
|
@@ -1323,8 +1549,8 @@ ${skillSection}
|
|
|
1323
1549
|
|
|
1324
1550
|
\`\`\`batch_actions
|
|
1325
1551
|
[
|
|
1326
|
-
{"tool": "
|
|
1327
|
-
{"tool": "
|
|
1552
|
+
{"tool": "submit_knowledge", "params": {"title": "...", "code": "..."}},
|
|
1553
|
+
{"tool": "submit_knowledge", "params": {"title": "...", "code": "..."}}
|
|
1328
1554
|
]
|
|
1329
1555
|
\`\`\`
|
|
1330
1556
|
|
|
@@ -1390,10 +1616,15 @@ ${this.#projectBriefingCache}
|
|
|
1390
1616
|
2. **定向探索** → get_file_summary 快速了解文件角色
|
|
1391
1617
|
3. **深入研读** → search_project_code / read_project_file 获取真实代码
|
|
1392
1618
|
4. **语义发现** → semantic_search_code 在知识库查找相关知识
|
|
1393
|
-
5. **知识产出** →
|
|
1619
|
+
5. **知识产出** → submit_knowledge 提交有价值的发现
|
|
1620
|
+
|
|
1621
|
+
## 高效使用工具(节省轮次)
|
|
1622
|
+
- **批量搜索**: search_project_code({ patterns: ["keywordA", "keywordB", "keywordC"] })
|
|
1623
|
+
- **批量读文件**: read_project_file({ filePaths: ["path/a.m", "path/b.m"] })
|
|
1624
|
+
- 合并同类请求为一次调用,避免逐个搜索/读取浪费轮次。
|
|
1394
1625
|
|
|
1395
1626
|
## 「项目特写」= 基本用法 + 项目特征融合
|
|
1396
|
-
|
|
1627
|
+
submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基本用法与本项目的特征融合为一体:
|
|
1397
1628
|
1. **项目选择了什么**: 采用了哪种写法/模式/约定
|
|
1398
1629
|
2. **为什么这样选**: 统计数据(N 个文件、占比 M%)
|
|
1399
1630
|
3. **项目禁止什么**: 被放弃的写法、反模式、显式禁用标记
|
|
@@ -1771,15 +2002,16 @@ submit_candidate 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
1771
2002
|
// rule: code-standard, code-style, best-practice, boundary-constraint
|
|
1772
2003
|
// pattern: code-pattern, architecture, solution
|
|
1773
2004
|
// fact: code-relation, inheritance, call-chain, data-flow, module-dependency
|
|
2005
|
+
// V3: knowledge_entries 统一表(candidates 已合并,lifecycle 替代 status)
|
|
1774
2006
|
const stats = db.prepare(`
|
|
1775
2007
|
SELECT
|
|
1776
|
-
(SELECT COUNT(*) FROM
|
|
1777
|
-
(SELECT COUNT(*) FROM
|
|
1778
|
-
(SELECT COUNT(*) FROM
|
|
1779
|
-
(SELECT COUNT(*) FROM
|
|
1780
|
-
(SELECT COUNT(*) FROM
|
|
1781
|
-
(SELECT COUNT(*) FROM
|
|
1782
|
-
(SELECT COUNT(*) FROM
|
|
2008
|
+
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active') as recipeCount,
|
|
2009
|
+
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledge_type IN ('code-standard','code-style','best-practice','boundary-constraint')) as ruleCount,
|
|
2010
|
+
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledge_type IN ('code-pattern','architecture','solution')) as patternCount,
|
|
2011
|
+
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledge_type IN ('code-relation','inheritance','call-chain','data-flow','module-dependency')) as factCount,
|
|
2012
|
+
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledge_type = 'boundary-constraint') as guardRuleCount,
|
|
2013
|
+
(SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'pending') as pendingCandidates,
|
|
2014
|
+
(SELECT COUNT(*) FROM knowledge_entries) as totalCandidates
|
|
1783
2015
|
`).get();
|
|
1784
2016
|
if (!stats || stats.recipeCount === 0) {
|
|
1785
2017
|
return '\n## 项目状态\n⚠️ 知识库为空。建议先执行冷启动(bootstrap_knowledge)。\n';
|