autosnippet 3.2.7 → 3.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/bin/cli.js +13 -5
  2. package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
  3. package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +26 -29
  6. package/lib/cli/SetupService.js +1 -1
  7. package/lib/core/AstAnalyzer.js +27 -5
  8. package/lib/core/analysis/CallEdgeResolver.js +402 -0
  9. package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
  10. package/lib/core/analysis/CallSiteExtractor.js +629 -0
  11. package/lib/core/analysis/DataFlowInferrer.js +57 -0
  12. package/lib/core/analysis/ImportPathResolver.js +189 -0
  13. package/lib/core/analysis/ImportRecord.js +105 -0
  14. package/lib/core/analysis/SymbolTableBuilder.js +211 -0
  15. package/lib/core/ast/ProjectGraph.js +8 -0
  16. package/lib/core/ast/lang-dart.js +352 -5
  17. package/lib/core/ast/lang-go.js +212 -10
  18. package/lib/core/ast/lang-java.js +205 -1
  19. package/lib/core/ast/lang-kotlin.js +330 -1
  20. package/lib/core/ast/lang-python.js +31 -2
  21. package/lib/core/ast/lang-rust.js +284 -3
  22. package/lib/core/ast/lang-swift.js +180 -1
  23. package/lib/core/ast/lang-typescript.js +290 -1
  24. package/lib/core/discovery/index.js +2 -2
  25. package/lib/external/ai/AiProvider.js +66 -172
  26. package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
  27. package/lib/external/mcp/McpServer.js +1 -0
  28. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
  29. package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
  30. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
  31. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
  32. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
  33. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
  34. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  35. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
  36. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
  37. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
  38. package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
  39. package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
  40. package/lib/external/mcp/handlers/consolidated.js +9 -0
  41. package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
  42. package/lib/external/mcp/handlers/guard.js +3 -3
  43. package/lib/external/mcp/handlers/structure.js +62 -0
  44. package/lib/external/mcp/handlers/wiki-external.js +66 -3
  45. package/lib/external/mcp/tools.js +36 -1
  46. package/lib/http/HttpServer.js +1 -1
  47. package/lib/http/middleware/requestLogger.js +1 -0
  48. package/lib/http/routes/ai.js +240 -35
  49. package/lib/http/routes/candidates.js +2 -3
  50. package/lib/http/routes/extract.js +13 -11
  51. package/lib/http/routes/modules.js +2 -2
  52. package/lib/http/routes/recipes.js +9 -5
  53. package/lib/http/routes/remote.js +149 -270
  54. package/lib/http/routes/violations.js +0 -54
  55. package/lib/http/utils/sse-sessions.js +1 -1
  56. package/lib/infrastructure/logging/Logger.js +5 -4
  57. package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
  58. package/lib/injection/ServiceContainer.js +70 -28
  59. package/lib/platform/ScreenCaptureService.js +177 -0
  60. package/lib/platform/ios/index.js +2 -2
  61. package/lib/platform/ios/routes/spm.js +2 -2
  62. package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
  63. package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
  64. package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
  65. package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
  66. package/lib/service/agent/AgentEventBus.js +207 -0
  67. package/lib/service/agent/AgentFactory.js +490 -0
  68. package/lib/service/agent/AgentMessage.js +240 -0
  69. package/lib/service/agent/AgentRouter.js +228 -0
  70. package/lib/service/agent/AgentRuntime.js +1016 -0
  71. package/lib/service/agent/AgentState.js +217 -0
  72. package/lib/service/agent/IntentClassifier.js +331 -0
  73. package/lib/service/agent/LarkTransport.js +389 -0
  74. package/lib/service/agent/capabilities.js +408 -0
  75. package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
  76. package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
  77. package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
  78. package/lib/service/agent/core/LoopContext.js +170 -0
  79. package/lib/service/agent/core/MessageAdapter.js +223 -0
  80. package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
  81. package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
  82. package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
  83. package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
  84. package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
  85. package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
  86. package/lib/service/agent/domain/insight-producer.js +267 -0
  87. package/lib/service/agent/domain/scan-prompts.js +105 -0
  88. package/lib/service/agent/forced-summary.js +266 -0
  89. package/lib/service/agent/index.js +91 -0
  90. package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
  91. package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
  92. package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
  93. package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
  94. package/lib/service/{chat → agent}/memory/index.js +1 -1
  95. package/lib/service/agent/policies.js +442 -0
  96. package/lib/service/agent/presets.js +303 -0
  97. package/lib/service/agent/strategies.js +717 -0
  98. package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
  99. package/lib/service/agent/tools/ai-analysis.js +75 -0
  100. package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
  101. package/lib/service/{chat → agent}/tools/composite.js +2 -1
  102. package/lib/service/{chat → agent}/tools/guard.js +1 -121
  103. package/lib/service/{chat → agent}/tools/index.js +33 -22
  104. package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
  105. package/lib/service/agent/tools/knowledge-graph.js +112 -0
  106. package/lib/service/agent/tools/scan-recipe.js +189 -0
  107. package/lib/service/agent/tools/system-interaction.js +476 -0
  108. package/lib/service/automation/DirectiveDetector.js +0 -1
  109. package/lib/service/automation/FileWatcher.js +0 -8
  110. package/lib/service/automation/handlers/CreateHandler.js +7 -3
  111. package/lib/service/automation/handlers/DraftHandler.js +7 -6
  112. package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
  113. package/lib/service/knowledge/CodeEntityGraph.js +327 -2
  114. package/lib/service/knowledge/KnowledgeService.js +5 -1
  115. package/lib/service/module/ModuleService.js +49 -73
  116. package/lib/service/skills/SignalCollector.js +26 -19
  117. package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
  118. package/lib/service/wiki/WikiGenerator.js +1 -1
  119. package/lib/shared/FieldSpec.js +1 -1
  120. package/lib/shared/PathGuard.js +1 -1
  121. package/lib/shared/StyleGuide.js +1 -1
  122. package/package.json +4 -1
  123. package/resources/native-ui/screenshot.swift +228 -0
  124. package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
  125. package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
  126. package/lib/core/discovery/SpmDiscoverer.js +0 -5
  127. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
  128. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
  129. package/lib/http/routes/spm.js +0 -5
  130. package/lib/infrastructure/external/XcodeAutomation.js +0 -15
  131. package/lib/service/chat/ChatAgent.js +0 -1602
  132. package/lib/service/chat/Memory.js +0 -161
  133. package/lib/service/chat/ProducerAgent.js +0 -431
  134. package/lib/service/chat/ReasoningTrace.js +0 -523
  135. package/lib/service/chat/TaskPipeline.js +0 -357
  136. package/lib/service/chat/WorkingMemory.js +0 -357
  137. package/lib/service/chat/memory/PersistentMemory.js +0 -450
  138. package/lib/service/chat/tools/ai-analysis.js +0 -267
  139. package/lib/service/chat/tools/knowledge-graph.js +0 -234
  140. package/lib/service/chat/tools.js +0 -18
  141. package/lib/service/snippet/PlaceholderConverter.js +0 -5
  142. package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
  143. /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
  144. /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
  145. /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
  146. /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
  147. /package/lib/service/{chat → agent}/tools/query.js +0 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * insight-producer.js — Insight Producer 领域函数
3
+ *
4
+ * 从旧 ProducerAgent.js 提取的纯领域逻辑:
5
+ * - Producer System Prompt
6
+ * - 工具白名单
7
+ * - 预算常量
8
+ * - Prompt 构建器 (v1 + v2)
9
+ * - 代码上下文注入 (evidenceMap → prompt section)
10
+ * - 拒绝率门控 (producerRejectionGateEvaluator)
11
+ *
12
+ * 被 PipelineStrategy 的 bootstrap preset 直接引用。
13
+ * 不再包含任何 Agent 类 — Agent 由 AgentRuntime + PipelineStrategy 驱动。
14
+ *
15
+ * @module insight-producer
16
+ */
17
+
18
+ import { buildProducerStyleGuide, SUBMIT_REQUIREMENTS } from '../../../shared/StyleGuide.js';
19
+
20
+ // ──────────────────────────────────────────────────────────────────
21
+ // System Prompt — Producer 专用 (~150 tokens)
22
+ // ──────────────────────────────────────────────────────────────────
23
+
24
+ export const PRODUCER_SYSTEM_PROMPT = `你是知识管理专家。你会收到一段代码分析文本,需要将其中的知识点转化为结构化的知识候选。
25
+
26
+ 核心原则: 分析文本已经包含了所有发现,你的唯一工作是将它们格式化为 submit_knowledge 调用。
27
+
28
+ 每个候选必须:
29
+ 1. 有清晰的标题 (描述知识点的核心,使用项目真实类名)
30
+ 2. 有项目特写风格的正文 (content.markdown 字段,结合代码展示)
31
+ 3. 标注相关文件路径
32
+ 4. 选择正确的 kind (rule/pattern/fact)
33
+ 5. 提供完整的 Cursor 交付字段 (trigger, doClause, whenClause 等)
34
+
35
+ 工作流程:
36
+ 1. 阅读分析文本,识别每个独立的知识点/发现
37
+ 2. 用 read_project_file 批量获取关键代码片段:
38
+ read_project_file({ filePaths: ["FileA.m", "FileB.m"], maxLines: 80 })
39
+ 3. 立刻调用 submit_knowledge 提交
40
+ 4. 重复直到分析中的所有知识点都已提交
41
+
42
+ 关键规则:
43
+ - 分析中的每个要点/段落都应转化为至少一个候选
44
+ - read_project_file 支持 filePaths 数组批量读取多个文件,一次调用完成
45
+ - read_project_file 时读取足够多的行数(startLine + maxLines 至少 30 行)
46
+ - reasoning.sources 必须是非空数组,填写相关文件路径如 ["FileName.m"]
47
+ - 如果分析提到了 3 个模式,就应该提交 3 个候选,不要合并
48
+ - 禁止: 不要搜索新文件、不要做额外分析,专注于格式化和提交
49
+
50
+ 容错规则:
51
+ - 如果 read_project_file 返回"文件不存在"或错误,不要重试同一文件的其他路径变体
52
+ - 文件读取失败时,直接使用分析文本中已有的代码和描述来提交候选
53
+ - 永远不要因为文件读取失败而跳过知识点 — 分析文本已经包含足够信息
54
+ - 先提交候选,再考虑是否需要读取更多代码(提交优先于验证)`;
55
+
56
+ // ──────────────────────────────────────────────────────────────────
57
+ // Producer 可用工具白名单 — 只做格式化和提交
58
+ // ──────────────────────────────────────────────────────────────────
59
+
60
+ export const PRODUCER_TOOLS = ['submit_knowledge', 'submit_with_check', 'read_project_file'];
61
+
62
+ // ──────────────────────────────────────────────────────────────────
63
+ // Producer 预算
64
+ // ──────────────────────────────────────────────────────────────────
65
+
66
+ export const PRODUCER_BUDGET = {
67
+ maxIterations: 24,
68
+ searchBudget: 4,
69
+ searchBudgetGrace: 3,
70
+ maxSubmits: 10,
71
+ softSubmitLimit: 10,
72
+ idleRoundsToExit: 3,
73
+ };
74
+
75
+ // ──────────────────────────────────────────────────────────────────
76
+ // 项目特写风格指南 (从共享 StyleGuide.js 获取)
77
+ // ──────────────────────────────────────────────────────────────────
78
+
79
+ const STYLE_GUIDE = buildProducerStyleGuide();
80
+
81
+ // ──────────────────────────────────────────────────────────────────
82
+ // Prompt 构建
83
+ // ──────────────────────────────────────────────────────────────────
84
+
85
+ /**
86
+ * 构建 Producer Prompt (v1 — 用于 AnalysisReport)
87
+ *
88
+ * @param {import('./bootstrap-gate.js').AnalysisReport} analysisReport
89
+ * @param {object} dimConfig — { id, label, allowedKnowledgeTypes, outputType }
90
+ * @param {object} projectInfo — { name }
91
+ * @returns {string}
92
+ */
93
+ export function buildProducerPrompt(analysisReport, dimConfig, projectInfo) {
94
+ const parts = [];
95
+
96
+ parts.push(`将以下对 ${projectInfo.name} 项目 "${dimConfig.label}" 维度的分析,转化为知识候选:`);
97
+ parts.push(`---\n${analysisReport.analysisText}\n---`);
98
+
99
+ if (analysisReport.referencedFiles.length > 0) {
100
+ parts.push(`分析中引用的关键文件: ${analysisReport.referencedFiles.join(', ')}`);
101
+ }
102
+
103
+ parts.push(`维度约束:
104
+ - dimensionId: ${dimConfig.id}
105
+ - 允许的 knowledgeType: ${(dimConfig.allowedKnowledgeTypes || []).join(', ') || '(all)'}
106
+ - category: ${dimConfig.id}`);
107
+
108
+ parts.push(STYLE_GUIDE);
109
+ parts.push(SUBMIT_REQUIREMENTS);
110
+
111
+ return parts.join('\n\n');
112
+ }
113
+
114
+ /**
115
+ * 构建 Producer Prompt v2 — 用于 AnalysisArtifact
116
+ *
117
+ * 相比 v1 增加:
118
+ * - §3 结构化发现 (findings)
119
+ * - §4 代码证据 (evidenceMap → code context)
120
+ * - §5 负空间信号
121
+ *
122
+ * @param {import('./bootstrap-gate.js').AnalysisArtifact} artifact
123
+ * @param {object} dimConfig
124
+ * @param {object} projectInfo
125
+ * @returns {string}
126
+ */
127
+ export function buildProducerPromptV2(artifact, dimConfig, projectInfo) {
128
+ const parts = [];
129
+
130
+ parts.push(`将以下对 ${projectInfo.name} 项目 "${dimConfig.label}" 维度的分析,转化为知识候选:`);
131
+ parts.push(`---\n${artifact.analysisText}\n---`);
132
+
133
+ // §3 结构化发现
134
+ if (artifact.findings?.length > 0) {
135
+ const findingLines = ['## 关键发现 (Analyst 已确认)'];
136
+ const sorted = [...artifact.findings].sort((a, b) => b.importance - a.importance);
137
+ for (const f of sorted) {
138
+ const badge = f.importance >= 8 ? '⚠️' : '📋';
139
+ findingLines.push(`${badge} **[${f.importance}/10]** ${f.finding}`);
140
+ if (f.evidence) {
141
+ findingLines.push(` 证据: ${f.evidence}`);
142
+ }
143
+ }
144
+ findingLines.push('');
145
+ findingLines.push('☝️ 上述每个发现都应至少转化为一个候选。');
146
+ parts.push(findingLines.join('\n'));
147
+ }
148
+
149
+ // §4 代码证据
150
+ const codeContext = buildCodeContextSection(artifact.evidenceMap);
151
+ if (codeContext) {
152
+ parts.push(codeContext);
153
+ }
154
+
155
+ // §5 负空间信号
156
+ if (artifact.negativeSignals?.length > 0) {
157
+ const nsLines = ['## ⛔ 不存在的模式 (不要猜测)'];
158
+ for (const ns of artifact.negativeSignals.slice(0, 5)) {
159
+ nsLines.push(`- "${ns.searchPattern}" — ${ns.implication}`);
160
+ }
161
+ parts.push(nsLines.join('\n'));
162
+ }
163
+
164
+ // §6 引用文件
165
+ if (artifact.referencedFiles.length > 0) {
166
+ parts.push(`分析中引用的关键文件: ${artifact.referencedFiles.slice(0, 15).join(', ')}`);
167
+ }
168
+
169
+ // §7 维度约束
170
+ parts.push(`维度约束:
171
+ - dimensionId: ${dimConfig.id}
172
+ - 允许的 knowledgeType: ${(dimConfig.allowedKnowledgeTypes || []).join(', ') || '(all)'}
173
+ - category: ${dimConfig.id}`);
174
+
175
+ // §8 写作指南 + 提交要求
176
+ parts.push(STYLE_GUIDE);
177
+ parts.push(SUBMIT_REQUIREMENTS);
178
+
179
+ return parts.join('\n\n');
180
+ }
181
+
182
+ // ──────────────────────────────────────────────────────────────────
183
+ // 代码上下文注入 (Producer v2 辅助)
184
+ // ──────────────────────────────────────────────────────────────────
185
+
186
+ /**
187
+ * 从 evidenceMap 构建代码上下文段
188
+ *
189
+ * 策略: 按代码片段数量排序
190
+ * 预算: ≤ 4000 chars (~1000 tokens)
191
+ *
192
+ * @param {Map<string, import('./EvidenceCollector.js').EvidenceEntry>} evidenceMap
193
+ * @returns {string|null}
194
+ */
195
+ export function buildCodeContextSection(evidenceMap) {
196
+ if (!evidenceMap || evidenceMap.size === 0) return null;
197
+
198
+ const parts = ['## 📄 Analyst 已读取的代码 (直接引用, 无需 read_file)'];
199
+ let totalChars = 0;
200
+ const BUDGET = 4000;
201
+
202
+ const sortedEntries = [...evidenceMap.values()]
203
+ .filter((e) => e.codeSnippets.length > 0)
204
+ .sort((a, b) => b.codeSnippets.length - a.codeSnippets.length);
205
+
206
+ for (const entry of sortedEntries) {
207
+ if (totalChars >= BUDGET) break;
208
+
209
+ const header = `### ${entry.filePath}${entry.role ? ` (${entry.role})` : ''}`;
210
+ parts.push(header);
211
+ totalChars += header.length;
212
+
213
+ if (entry.summary) {
214
+ parts.push(entry.summary);
215
+ totalChars += entry.summary.length;
216
+ }
217
+
218
+ for (const snippet of entry.codeSnippets.slice(0, 2)) {
219
+ if (totalChars >= BUDGET) break;
220
+ const codeBlock = `\`\`\`\n// L${snippet.startLine}-${snippet.endLine}\n${snippet.content}\n\`\`\``;
221
+ if (snippet.analystNote) {
222
+ parts.push(`> ${snippet.analystNote}`);
223
+ totalChars += snippet.analystNote.length + 4;
224
+ }
225
+ parts.push(codeBlock);
226
+ totalChars += codeBlock.length;
227
+ }
228
+ }
229
+
230
+ return parts.length > 1 ? parts.join('\n') : null;
231
+ }
232
+
233
+ // ──────────────────────────────────────────────────────────────────
234
+ // PipelineStrategy gate.evaluator — 拒绝率门控
235
+ // ──────────────────────────────────────────────────────────────────
236
+
237
+ /**
238
+ * Producer 拒绝率门控 — 面向 PipelineStrategy gate.evaluator
239
+ *
240
+ * 当 produce 阶段的提交拒绝率过高时触发 retry。
241
+ *
242
+ * @param {object} source — produce 阶段的 reactLoop 返回值
243
+ * @param {object} _phaseResults
244
+ * @param {object} _strategyContext
245
+ * @returns {{ action: 'pass'|'retry', reason: string }}
246
+ */
247
+ export function producerRejectionGateEvaluator(source, _phaseResults, _strategyContext = {}) {
248
+ if (!source?.toolCalls) {
249
+ return { action: 'pass', reason: '' };
250
+ }
251
+
252
+ const submitCalls = (source.toolCalls || []).filter(tc =>
253
+ ['submit_knowledge', 'submit_with_check'].includes(tc.tool || tc.name),
254
+ );
255
+ const rejected = submitCalls.filter(tc => {
256
+ const res = tc.result;
257
+ if (!res) return false;
258
+ if (typeof res === 'string') return res.includes('rejected') || res.includes('error');
259
+ return res.status === 'rejected' || res.status === 'error' || res.reason === 'validation_failed';
260
+ }).length;
261
+ const success = submitCalls.length - rejected;
262
+
263
+ if (rejected > success && rejected >= 2) {
264
+ return { action: 'retry', reason: `${rejected} rejections vs ${success} successes` };
265
+ }
266
+ return { action: 'pass', reason: '' };
267
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * scan-prompts.js — scanKnowledge 任务配置
3
+ *
4
+ * 三种 task 共享 Insight Pipeline (Analyze → Produce),
5
+ * Analyze 使用与冷启动一致的 ANALYST_SYSTEM_PROMPT + ExplorationTracker 四阶段管理,
6
+ * Produce 阶段的 systemPrompt 因 task 而异,但 Recipe 格式与冷启动 submit_knowledge 完全对齐。
7
+ *
8
+ * @module scan-prompts
9
+ */
10
+
11
+ /**
12
+ * @typedef {Object} ScanTaskConfig
13
+ * @property {string} producePrompt — Produce 阶段的 systemPrompt
14
+ * @property {(label: string) => Object} fallback — 解析失败时的默认值工厂
15
+ */
16
+
17
+ /**
18
+ * task → Produce 阶段配置
19
+ *
20
+ * extract 的 Recipe 格式与冷启动 submit_knowledge 完全对齐:
21
+ * 16 个必填字段 (title, language, content, kind, doClause, dontClause, whenClause,
22
+ * coreCode, category, trigger, description, headers, usageGuide, knowledgeType, reasoning, tags)
23
+ *
24
+ * @type {Record<string, ScanTaskConfig>}
25
+ */
26
+ export const SCAN_TASK_CONFIGS = {
27
+
28
+ // ─── extract: Recipe 提取(工具驱动,与冷启动 submit_knowledge 字段对齐) ─────
29
+
30
+ extract: {
31
+ producePrompt: `你是知识管理专家。你会收到一段代码分析文本,需要将其中的知识点转化为结构化的知识候选。
32
+
33
+ 核心原则: 分析文本已经包含了所有发现,你的唯一工作是将它们格式化为 collect_scan_recipe 调用。
34
+
35
+ 每个候选必须:
36
+ 1. 有清晰的标题 (描述知识点的核心,使用项目真实类名)
37
+ 2. 有项目特写风格的正文 (content.markdown 字段,结合代码展示)
38
+ 3. 标注相关文件路径 (reasoning.sources)
39
+ 4. 选择正确的 kind (rule/pattern/fact)
40
+ 5. 提供完整的 Cursor 交付字段 (trigger, doClause, whenClause 等)
41
+
42
+ ## 「项目特写」写作要求(content.markdown)
43
+ content.markdown 字段必须是「项目特写」:
44
+ 1. **项目选择了什么** — 采用了哪种写法/模式/约定
45
+ 2. **为什么这样选** — 统计分布、占比、历史决策
46
+ 3. **项目禁止什么** — 反模式、已废弃写法
47
+ 4. **新代码怎么写** — 可直接复制使用的代码模板 + 来源标注 (来源: FileName.ext:行号)
48
+
49
+ ## 工作流程
50
+ 1. 阅读分析文本,识别每个独立的知识点/发现
51
+ 2. 用 read_project_file 批量获取关键代码片段:
52
+ read_project_file({ filePaths: ["FileA.m", "FileB.m"], maxLines: 80 })
53
+ 3. 立刻调用 collect_scan_recipe 提交
54
+ 4. 重复直到分析中的所有知识点都已提交
55
+
56
+ ## 关键规则
57
+ - 分析中的每个要点/段落都应转化为至少一个候选
58
+ - read_project_file 支持 filePaths 数组批量读取多个文件,一次调用完成
59
+ - reasoning.sources 必须是非空数组,填写相关文件路径如 ["FileName.m"]
60
+ - 如果分析提到了 3 个模式,就应该提交 3 个候选,不要合并
61
+ - 禁止: 不要搜索新文件、不要做额外分析,专注于格式化和提交
62
+
63
+ 容错规则:
64
+ - 如果 read_project_file 返回"文件不存在"或错误,不要重试同一文件的其他路径变体
65
+ - 文件读取失败时,直接使用分析文本中已有的代码和描述来提交候选
66
+ - 永远不要因为文件读取失败而跳过知识点 — 分析文本已经包含足够信息
67
+ - 先提交候选,再考虑是否需要读取更多代码(提交优先于验证)`,
68
+ fallback: (label) => ({ targetName: label, extracted: 0, recipes: [] }),
69
+ },
70
+
71
+ // ─── summarize: 代码摘要 ──────────────────
72
+
73
+ summarize: {
74
+ producePrompt: `你是技术文档专家。将代码分析转化为结构化摘要。
75
+
76
+ ## 规则
77
+ - 输出纯 JSON,不含 markdown 包装
78
+ - title 简洁(30 字内),summary 完整(200 字内),usageGuide 实用(300 字内)
79
+ - 基于分析中的实际发现,不要臆造功能
80
+
81
+ ## 输出格式
82
+ { "title": "...", "summary": "...", "usageGuide": "..." }`,
83
+ fallback: () => ({ title: '', summary: '', usageGuide: '' }),
84
+ },
85
+
86
+ // ─── relations: 知识图谱关系发现 ──────────
87
+
88
+ relations: {
89
+ producePrompt: `你是软件架构师。根据代码分析识别知识点之间的语义关系。
90
+
91
+ ## 关系类型
92
+ requires / extends / enforces / depends_on / inherits / implements / calls / prerequisite
93
+
94
+ ## 规则
95
+ - 输出纯 JSON
96
+ - 每个关系需有明确的代码证据
97
+
98
+ ## 输出格式
99
+ { "analyzed": 数量, "relations": [{ "from": "title", "to": "title", "type": "关系", "evidence": "代码证据" }] }`,
100
+ fallback: () => ({ analyzed: 0, relations: [] }),
101
+ },
102
+
103
+ };
104
+
105
+ export default SCAN_TASK_CONFIGS;
@@ -0,0 +1,266 @@
1
+ /**
2
+ * forced-summary.js — 强制退出后的摘要生成
3
+ *
4
+ * 强制退出后的摘要生成独立模块,
5
+ * 供 AgentRuntime.reactLoop() 在循环退出后调用。
6
+ *
7
+ * 支持两种模式:
8
+ * - system: 输出 dimensionDigest JSON (供 Bootstrap 管线消费)
9
+ * - user: 输出人类可读的 Markdown 结构化总结 (前端 AI Chat 展示)
10
+ *
11
+ * @module forced-summary
12
+ */
13
+
14
+ import Logger from '../../infrastructure/logging/Logger.js';
15
+ import { cleanFinalAnswer } from './core/ChatAgentPrompts.js';
16
+
17
+ const logger = Logger.getInstance();
18
+
19
+ /**
20
+ * 生成强制摘要
21
+ *
22
+ * @param {Object} opts
23
+ * @param {import('../../external/ai/AiProvider.js').AiProvider} opts.aiProvider — LLM 提供商
24
+ * @param {string} [opts.source] — 'user' | 'system'
25
+ * @param {Array} opts.toolCalls — 工具调用记录
26
+ * @param {Object} [opts.tracker] — ExplorationTracker 实例
27
+ * @param {Object} [opts.contextWindow] — ContextWindow 实例 (用于避免超出 token)
28
+ * @param {string} opts.prompt — 原始用户 prompt
29
+ * @param {Object} [opts.tokenUsage] — token 用量 (会被修改)
30
+ * @returns {Promise<{ reply: string, tokenUsage: { input: number, output: number } }>}
31
+ */
32
+ export async function produceForcedSummary({
33
+ aiProvider,
34
+ source,
35
+ toolCalls = [],
36
+ tracker,
37
+ contextWindow,
38
+ prompt,
39
+ tokenUsage,
40
+ }) {
41
+ const isSystem = source === 'system';
42
+ const iterations = tracker?.iteration || 0;
43
+ const resultTokenUsage = { input: 0, output: 0 };
44
+
45
+ logger.info(
46
+ `[ForcedSummary] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls, source=${source})`
47
+ );
48
+
49
+ const candidateCount = toolCalls.filter(
50
+ tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
51
+ ).length;
52
+
53
+ let finalReply;
54
+
55
+ // 如果熔断器已打开,跳过 AI 调用直接合成摘要
56
+ const isCircuitOpen = aiProvider._circuitState === 'OPEN';
57
+ if (isCircuitOpen) {
58
+ logger.warn(
59
+ `[ForcedSummary] circuit breaker is OPEN — skipping AI summary, using synthetic ${isSystem ? 'digest' : 'summary'}`
60
+ );
61
+ }
62
+
63
+ // 收集工具调用摘要
64
+ const submitSummary = toolCalls
65
+ .filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
66
+ .map((tc, i) => `${i + 1}. ${tc.args?.title || tc.args?.category || tc.params?.title || tc.params?.category || 'untitled'}`)
67
+ .join('\n');
68
+
69
+ try {
70
+ if (isCircuitOpen) {
71
+ throw new Error('circuit open — skip to synthetic summary');
72
+ }
73
+
74
+ let summaryPrompt;
75
+ let systemPrompt;
76
+
77
+ if (isSystem) {
78
+ // system 源: dimensionDigest JSON
79
+ summaryPrompt = `你已完成 ${iterations} 轮工具调用(共 ${toolCalls.length} 次),提交了 ${candidateCount} 个候选。
80
+ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
81
+ **必须**输出 dimensionDigest JSON(用 \`\`\`json 包裹):
82
+ \`\`\`json
83
+ {
84
+ "dimensionDigest": {
85
+ "summary": "本维度分析总结",
86
+ "candidateCount": ${candidateCount},
87
+ "keyFindings": ["发现1", "发现2"],
88
+ "crossRefs": {},
89
+ "gaps": ["未覆盖方面"],
90
+ "remainingTasks": [
91
+ { "signal": "未处理信号名", "reason": "达到提交上限/时间限制", "priority": "high", "searchHints": ["搜索词"] }
92
+ ]
93
+ }
94
+ }
95
+ \`\`\`
96
+ > remainingTasks: 列出本次未来得及处理的信号/主题。已全部覆盖则留空 \`[]\`。`;
97
+ systemPrompt = '直接输出 dimensionDigest JSON 总结,不要调用工具。';
98
+ } else {
99
+ // user 源: Markdown 结构化总结
100
+ const userQuestion = prompt ? `用户的原始问题:「${prompt.slice(0, 500)}」\n\n` : '';
101
+ const toolContextSummary = buildToolContextForUserSummary(toolCalls);
102
+ summaryPrompt = `${userQuestion}你刚才通过 ${toolCalls.length} 次工具调用分析了项目代码。以下是你调用过的工具和获取到的关键信息:
103
+
104
+ ${toolContextSummary}
105
+
106
+ 请基于以上收集到的信息,用**清晰易读的 Markdown** 格式撰写分析总结,直接回答用户的问题。
107
+
108
+ 要求:
109
+ - 使用二级/三级标题组织内容
110
+ - 要有具体的代码文件路径、类名、模式名称等细节
111
+ - 关键发现用列表项罗列
112
+ - 如果发现了架构模式或最佳实践,用简短代码块举例
113
+ - 语言自然流畅,像一份技术分析报告`;
114
+ systemPrompt = '你是项目分析助手。请用纯 Markdown 格式输出结构清晰的分析总结,只输出人类可读的自然语言文档,不要输出 JSON 格式的数据。';
115
+ }
116
+
117
+ // 用空 messages 避免累积上下文导致 400
118
+ const summaryResult = await aiProvider.chatWithTools(summaryPrompt, {
119
+ messages: [],
120
+ toolChoice: 'none',
121
+ systemPrompt,
122
+ temperature: isSystem ? 0.3 : 0.5,
123
+ maxTokens: 8192,
124
+ });
125
+
126
+ if (summaryResult.usage) {
127
+ resultTokenUsage.input += summaryResult.usage.inputTokens || 0;
128
+ resultTokenUsage.output += summaryResult.usage.outputTokens || 0;
129
+ }
130
+ // system 源: dimensionDigest JSON 是预期输出,不能被 cleanFinalAnswer 剥掉
131
+ finalReply = isSystem
132
+ ? (summaryResult.text || '').trim()
133
+ : cleanFinalAnswer(summaryResult.text || '');
134
+ } catch (err) {
135
+ logger.warn(`[ForcedSummary] AI call failed: ${err.message}`);
136
+
137
+ if (isSystem) {
138
+ // system 源兜底: 合成 dimensionDigest JSON
139
+ const titles = toolCalls
140
+ .filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
141
+ .map(tc => tc.args?.title || tc.params?.title || 'untitled');
142
+ finalReply = `\`\`\`json
143
+ {
144
+ "dimensionDigest": {
145
+ "summary": "通过 ${toolCalls.length} 次工具调用分析了项目代码,提交了 ${candidateCount} 个候选。",
146
+ "candidateCount": ${candidateCount},
147
+ "keyFindings": ${JSON.stringify(titles.slice(0, 5))},
148
+ "crossRefs": {},
149
+ "gaps": ["AI 服务异常,部分分析未完成"]
150
+ }
151
+ }
152
+ \`\`\``;
153
+ } else {
154
+ // user 源兜底: 合成 Markdown 摘要
155
+ const toolNames = [...new Set(toolCalls.map(tc => tc.tool))];
156
+ const filesRead = toolCalls
157
+ .filter(tc => tc.tool === 'read_project_file')
158
+ .flatMap(tc => {
159
+ const p = tc.args || tc.params || {};
160
+ if (p.filePaths) return p.filePaths;
161
+ if (p.filePath) return [p.filePath];
162
+ return [];
163
+ })
164
+ .slice(0, 10);
165
+ const searches = toolCalls
166
+ .filter(tc => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code')
167
+ .map(tc => {
168
+ const p = tc.args || tc.params || {};
169
+ return p.patterns?.[0] || p.query || p.pattern;
170
+ })
171
+ .filter(Boolean)
172
+ .slice(0, 5);
173
+
174
+ finalReply = `## 分析总结\n\n通过 **${toolCalls.length} 次工具调用**探索了项目代码。\n\n`;
175
+ if (searches.length > 0) {
176
+ finalReply += `### 搜索的关键词\n${searches.map(s => `- \`${s}\``).join('\n')}\n\n`;
177
+ }
178
+ if (filesRead.length > 0) {
179
+ finalReply += `### 读取的文件\n${filesRead.map(f => `- \`${f}\``).join('\n')}\n\n`;
180
+ }
181
+ finalReply += `### 使用的工具\n${toolNames.map(t => `- ${t}`).join('\n')}\n\n`;
182
+ finalReply += '> ⚠️ AI 服务异常,未能生成完整分析。请稍后重试或缩小分析范围。';
183
+ }
184
+ }
185
+
186
+ logger.info(`[ForcedSummary] ✅ forced summary — ${finalReply.length} chars`);
187
+ return { reply: finalReply, tokenUsage: resultTokenUsage };
188
+ }
189
+
190
+ /**
191
+ * 从工具调用记录中提取上下文摘要 (供 user 源强制总结使用)
192
+ * @param {Array} toolCalls
193
+ * @returns {string}
194
+ */
195
+ function buildToolContextForUserSummary(toolCalls) {
196
+ const sections = [];
197
+
198
+ // 目录结构探索
199
+ const structureCalls = toolCalls.filter(tc => tc.tool === 'list_project_structure');
200
+ if (structureCalls.length > 0) {
201
+ const dirs = structureCalls.map(tc => (tc.args || tc.params)?.directory || '/').slice(0, 5);
202
+ sections.push(`**目录探索**: ${dirs.map(d => `\`${d}\``).join(', ')}`);
203
+ }
204
+
205
+ // 项目概况
206
+ const overviewCalls = toolCalls.filter(tc => tc.tool === 'get_project_overview');
207
+ if (overviewCalls.length > 0) {
208
+ sections.push('**项目概况**: 已获取');
209
+ }
210
+
211
+ // 代码搜索
212
+ const searchCalls = toolCalls.filter(
213
+ tc => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code'
214
+ );
215
+ if (searchCalls.length > 0) {
216
+ const queries = searchCalls
217
+ .map(tc => {
218
+ const p = tc.args || tc.params || {};
219
+ return p.patterns?.[0] || p.query || p.pattern;
220
+ })
221
+ .filter(Boolean)
222
+ .slice(0, 8);
223
+ sections.push(`**代码搜索** (${searchCalls.length} 次): ${queries.map(q => `\`${q}\``).join(', ')}`);
224
+ }
225
+
226
+ // 文件读取
227
+ const readCalls = toolCalls.filter(tc => tc.tool === 'read_project_file');
228
+ if (readCalls.length > 0) {
229
+ const files = readCalls
230
+ .flatMap(tc => {
231
+ const p = tc.args || tc.params || {};
232
+ if (p.filePaths) return p.filePaths;
233
+ if (p.filePath) return [p.filePath];
234
+ return [];
235
+ })
236
+ .slice(0, 10);
237
+ sections.push(`**文件读取** (${readCalls.length} 次): ${files.map(f => `\`${f}\``).join(', ')}`);
238
+ }
239
+
240
+ // AST 分析
241
+ const astCalls = toolCalls.filter(tc =>
242
+ ['get_class_hierarchy', 'get_class_info', 'get_protocol_info', 'get_method_overrides', 'get_category_map'].includes(tc.tool)
243
+ );
244
+ if (astCalls.length > 0) {
245
+ const entities = astCalls
246
+ .map(tc => {
247
+ const p = tc.args || tc.params || {};
248
+ return p.className || p.name || p.protocolName || p.rootClass;
249
+ })
250
+ .filter(Boolean)
251
+ .slice(0, 5);
252
+ sections.push(`**AST 结构分析** (${astCalls.length} 次): ${entities.map(e => `\`${e}\``).join(', ')}`);
253
+ }
254
+
255
+ // 知识库搜索
256
+ const kbCalls = toolCalls.filter(tc =>
257
+ ['search_knowledge', 'search_recipes', 'knowledge_overview'].includes(tc.tool)
258
+ );
259
+ if (kbCalls.length > 0) {
260
+ sections.push(`**知识库查询**: ${kbCalls.length} 次`);
261
+ }
262
+
263
+ return sections.length > 0 ? sections.join('\n') : '(工具调用记录为空)';
264
+ }
265
+
266
+ export default produceForcedSummary;