autosnippet 3.0.0 → 3.0.2

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 (290) hide show
  1. package/README.md +230 -324
  2. package/bin/api-server.js +1 -1
  3. package/bin/cli.js +204 -244
  4. package/bin/mcp-server.js +5 -3
  5. package/config/knowledge-base.config.js +132 -132
  6. package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
  7. package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
  8. package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
  9. package/dashboard/dist/index.html +3 -3
  10. package/lib/bootstrap.js +8 -8
  11. package/lib/cli/AiScanService.js +86 -40
  12. package/lib/cli/KnowledgeSyncService.js +113 -74
  13. package/lib/cli/SetupService.js +439 -277
  14. package/lib/cli/UpgradeService.js +63 -100
  15. package/lib/core/AstAnalyzer.js +276 -597
  16. package/lib/core/ast/ProjectGraph.js +101 -40
  17. package/lib/core/ast/ensure-grammars.js +232 -0
  18. package/lib/core/ast/index.js +115 -0
  19. package/lib/core/ast/lang-dart.js +661 -0
  20. package/lib/core/ast/lang-go.js +530 -0
  21. package/lib/core/ast/lang-java.js +435 -0
  22. package/lib/core/ast/lang-javascript.js +272 -0
  23. package/lib/core/ast/lang-kotlin.js +423 -0
  24. package/lib/core/ast/lang-objc.js +388 -0
  25. package/lib/core/ast/lang-python.js +371 -0
  26. package/lib/core/ast/lang-swift.js +337 -0
  27. package/lib/core/ast/lang-typescript.js +503 -0
  28. package/lib/core/capability/CapabilityProbe.js +18 -9
  29. package/lib/core/constitution/Constitution.js +2 -3
  30. package/lib/core/constitution/ConstitutionValidator.js +65 -24
  31. package/lib/core/discovery/DartDiscoverer.js +534 -0
  32. package/lib/core/discovery/DiscovererRegistry.js +83 -0
  33. package/lib/core/discovery/GenericDiscoverer.js +225 -0
  34. package/lib/core/discovery/GoDiscoverer.js +541 -0
  35. package/lib/core/discovery/JvmDiscoverer.js +506 -0
  36. package/lib/core/discovery/NodeDiscoverer.js +466 -0
  37. package/lib/core/discovery/ProjectDiscoverer.js +93 -0
  38. package/lib/core/discovery/PythonDiscoverer.js +338 -0
  39. package/lib/core/discovery/SpmDiscoverer.js +5 -0
  40. package/lib/core/discovery/index.js +53 -0
  41. package/lib/core/enhancement/EnhancementPack.js +71 -0
  42. package/lib/core/enhancement/EnhancementRegistry.js +47 -0
  43. package/lib/core/enhancement/android-enhancement.js +102 -0
  44. package/lib/core/enhancement/django-enhancement.js +70 -0
  45. package/lib/core/enhancement/fastapi-enhancement.js +63 -0
  46. package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
  47. package/lib/core/enhancement/go-web-enhancement.js +201 -0
  48. package/lib/core/enhancement/index.js +65 -0
  49. package/lib/core/enhancement/node-server-enhancement.js +88 -0
  50. package/lib/core/enhancement/react-enhancement.js +86 -0
  51. package/lib/core/enhancement/spring-enhancement.js +112 -0
  52. package/lib/core/enhancement/vue-enhancement.js +96 -0
  53. package/lib/core/gateway/Gateway.js +8 -9
  54. package/lib/core/gateway/GatewayActionRegistry.js +1 -1
  55. package/lib/core/permission/PermissionManager.js +12 -8
  56. package/lib/domain/index.js +13 -9
  57. package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
  58. package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
  59. package/lib/domain/knowledge/Lifecycle.js +22 -22
  60. package/lib/domain/knowledge/index.js +9 -12
  61. package/lib/domain/knowledge/values/Constraints.js +31 -21
  62. package/lib/domain/knowledge/values/Content.js +21 -13
  63. package/lib/domain/knowledge/values/Quality.js +31 -18
  64. package/lib/domain/knowledge/values/Reasoning.js +20 -12
  65. package/lib/domain/knowledge/values/Relations.js +37 -25
  66. package/lib/domain/knowledge/values/Stats.js +18 -12
  67. package/lib/domain/knowledge/values/index.js +4 -3
  68. package/lib/domain/snippet/Snippet.js +35 -10
  69. package/lib/external/ai/AiFactory.js +48 -16
  70. package/lib/external/ai/AiProvider.js +184 -90
  71. package/lib/external/ai/providers/ClaudeProvider.js +25 -12
  72. package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
  73. package/lib/external/ai/providers/MockProvider.js +9 -3
  74. package/lib/external/ai/providers/OpenAiProvider.js +51 -29
  75. package/lib/external/mcp/McpServer.js +66 -36
  76. package/lib/external/mcp/errorHandler.js +23 -11
  77. package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
  78. package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
  79. package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
  80. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
  81. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
  82. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
  83. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
  84. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
  85. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
  86. package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
  87. package/lib/external/mcp/handlers/bootstrap.js +691 -168
  88. package/lib/external/mcp/handlers/browse.js +66 -22
  89. package/lib/external/mcp/handlers/candidate.js +118 -35
  90. package/lib/external/mcp/handlers/consolidated.js +49 -17
  91. package/lib/external/mcp/handlers/guard.js +104 -39
  92. package/lib/external/mcp/handlers/knowledge.js +60 -36
  93. package/lib/external/mcp/handlers/search.js +43 -14
  94. package/lib/external/mcp/handlers/skill.js +120 -45
  95. package/lib/external/mcp/handlers/structure.js +240 -86
  96. package/lib/external/mcp/handlers/system.js +42 -12
  97. package/lib/external/mcp/handlers/wiki.js +58 -33
  98. package/lib/external/mcp/tools.js +306 -123
  99. package/lib/http/HttpServer.js +72 -47
  100. package/lib/http/middleware/RateLimiter.js +5 -3
  101. package/lib/http/middleware/errorHandler.js +6 -1
  102. package/lib/http/middleware/requestLogger.js +14 -3
  103. package/lib/http/middleware/roleResolver.js +30 -23
  104. package/lib/http/routes/ai.js +387 -265
  105. package/lib/http/routes/auth.js +81 -61
  106. package/lib/http/routes/candidates.js +430 -320
  107. package/lib/http/routes/commands.js +289 -189
  108. package/lib/http/routes/extract.js +158 -125
  109. package/lib/http/routes/guardRules.js +309 -217
  110. package/lib/http/routes/knowledge.js +213 -154
  111. package/lib/http/routes/modules.js +578 -0
  112. package/lib/http/routes/monitoring.js +6 -6
  113. package/lib/http/routes/recipes.js +104 -93
  114. package/lib/http/routes/search.js +361 -305
  115. package/lib/http/routes/skills.js +145 -98
  116. package/lib/http/routes/snippets.js +42 -30
  117. package/lib/http/routes/spm.js +3 -405
  118. package/lib/http/routes/violations.js +113 -93
  119. package/lib/http/routes/wiki.js +211 -170
  120. package/lib/http/utils/routeHelpers.js +3 -1
  121. package/lib/http/utils/sse-sessions.js +16 -6
  122. package/lib/http/utils/sse.js +15 -5
  123. package/lib/infrastructure/audit/AuditLogger.js +5 -2
  124. package/lib/infrastructure/audit/AuditStore.js +10 -7
  125. package/lib/infrastructure/cache/CacheService.js +3 -1
  126. package/lib/infrastructure/cache/GraphCache.js +8 -4
  127. package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
  128. package/lib/infrastructure/config/ConfigLoader.js +9 -5
  129. package/lib/infrastructure/config/Defaults.js +30 -10
  130. package/lib/infrastructure/config/Paths.js +28 -8
  131. package/lib/infrastructure/config/TriggerSymbol.js +22 -10
  132. package/lib/infrastructure/database/DatabaseConnection.js +15 -10
  133. package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
  134. package/lib/infrastructure/external/ClipboardManager.js +6 -2
  135. package/lib/infrastructure/external/NativeUi.js +50 -43
  136. package/lib/infrastructure/external/OpenBrowser.js +14 -17
  137. package/lib/infrastructure/external/XcodeAutomation.js +14 -258
  138. package/lib/infrastructure/logging/Logger.js +46 -30
  139. package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
  140. package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
  141. package/lib/infrastructure/paths/HeaderResolver.js +25 -9
  142. package/lib/infrastructure/paths/PathFinder.js +34 -12
  143. package/lib/infrastructure/plugin/PluginManager.js +26 -8
  144. package/lib/infrastructure/realtime/RealtimeService.js +2 -2
  145. package/lib/infrastructure/vector/Chunker.js +22 -7
  146. package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
  147. package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
  148. package/lib/infrastructure/vector/VectorStore.js +28 -10
  149. package/lib/injection/ServiceContainer.js +247 -93
  150. package/lib/platform/ios/index.js +63 -0
  151. package/lib/platform/ios/routes/spm.js +437 -0
  152. package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
  153. package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
  154. package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
  155. package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
  156. package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
  157. package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
  158. package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
  159. package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
  160. package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
  161. package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
  162. package/lib/repository/base/BaseRepository.js +7 -9
  163. package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
  164. package/lib/repository/token/TokenUsageStore.js +4 -2
  165. package/lib/service/automation/ActionPipeline.js +1 -1
  166. package/lib/service/automation/AutomationOrchestrator.js +8 -4
  167. package/lib/service/automation/ContextCollector.js +7 -5
  168. package/lib/service/automation/DirectiveDetector.js +23 -16
  169. package/lib/service/automation/FileWatcher.js +112 -56
  170. package/lib/service/automation/TriggerResolver.js +6 -4
  171. package/lib/service/automation/handlers/AlinkHandler.js +24 -12
  172. package/lib/service/automation/handlers/CreateHandler.js +19 -20
  173. package/lib/service/automation/handlers/DraftHandler.js +14 -8
  174. package/lib/service/automation/handlers/GuardHandler.js +93 -63
  175. package/lib/service/automation/handlers/HeaderHandler.js +1 -6
  176. package/lib/service/automation/handlers/SearchHandler.js +155 -88
  177. package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
  178. package/lib/service/candidate/SimilarityService.js +25 -9
  179. package/lib/service/chat/AnalystAgent.js +50 -24
  180. package/lib/service/chat/CandidateGuardrail.js +143 -17
  181. package/lib/service/chat/ChatAgent.js +759 -243
  182. package/lib/service/chat/ContextWindow.js +116 -71
  183. package/lib/service/chat/ConversationStore.js +77 -36
  184. package/lib/service/chat/EpisodicConsolidator.js +47 -23
  185. package/lib/service/chat/HandoffProtocol.js +98 -22
  186. package/lib/service/chat/Memory.js +34 -14
  187. package/lib/service/chat/ProducerAgent.js +40 -20
  188. package/lib/service/chat/ProjectSemanticMemory.js +109 -78
  189. package/lib/service/chat/ReasoningLayer.js +148 -70
  190. package/lib/service/chat/ReasoningTrace.js +44 -32
  191. package/lib/service/chat/TaskPipeline.js +39 -19
  192. package/lib/service/chat/ToolRegistry.js +48 -29
  193. package/lib/service/chat/WorkingMemory.js +44 -18
  194. package/lib/service/chat/tools.js +1096 -494
  195. package/lib/service/context/RecipeExtractor.js +132 -51
  196. package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
  197. package/lib/service/cursor/KnowledgeCompressor.js +25 -22
  198. package/lib/service/cursor/RulesGenerator.js +13 -7
  199. package/lib/service/cursor/SkillsSyncer.js +77 -27
  200. package/lib/service/cursor/TokenBudget.js +2 -2
  201. package/lib/service/cursor/TopicClassifier.js +54 -20
  202. package/lib/service/guard/ComplianceReporter.js +55 -43
  203. package/lib/service/guard/ExclusionManager.js +67 -29
  204. package/lib/service/guard/GuardCheckEngine.js +381 -86
  205. package/lib/service/guard/GuardFeedbackLoop.js +22 -10
  206. package/lib/service/guard/GuardService.js +29 -19
  207. package/lib/service/guard/RuleLearner.js +55 -23
  208. package/lib/service/guard/SourceFileCollector.js +27 -20
  209. package/lib/service/guard/ViolationsStore.js +43 -38
  210. package/lib/service/knowledge/CodeEntityGraph.js +147 -82
  211. package/lib/service/knowledge/ConfidenceRouter.js +12 -10
  212. package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
  213. package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
  214. package/lib/service/knowledge/KnowledgeService.js +222 -112
  215. package/lib/service/module/ModuleService.js +969 -0
  216. package/lib/service/quality/FeedbackCollector.js +27 -15
  217. package/lib/service/quality/QualityScorer.js +78 -24
  218. package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
  219. package/lib/service/recipe/RecipeParser.js +78 -45
  220. package/lib/service/search/CoarseRanker.js +43 -28
  221. package/lib/service/search/CrossEncoderReranker.js +32 -21
  222. package/lib/service/search/InvertedIndex.js +21 -7
  223. package/lib/service/search/MultiSignalRanker.js +90 -28
  224. package/lib/service/search/RetrievalFunnel.js +45 -24
  225. package/lib/service/search/SearchEngine.js +255 -103
  226. package/lib/service/skills/EventAggregator.js +32 -15
  227. package/lib/service/skills/SignalCollector.js +140 -64
  228. package/lib/service/skills/SkillAdvisor.js +79 -42
  229. package/lib/service/skills/SkillHooks.js +16 -14
  230. package/lib/service/snippet/PlaceholderConverter.js +5 -0
  231. package/lib/service/snippet/SnippetFactory.js +116 -99
  232. package/lib/service/snippet/SnippetInstaller.js +234 -62
  233. package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
  234. package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
  235. package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
  236. package/lib/service/wiki/WikiGenerator.js +637 -263
  237. package/lib/shared/DimensionCopyRegistry.js +472 -0
  238. package/lib/shared/LanguageService.js +399 -0
  239. package/lib/shared/PathGuard.js +45 -28
  240. package/lib/shared/RecipeReadinessChecker.js +72 -12
  241. package/lib/shared/constants.js +41 -41
  242. package/lib/shared/errors/BaseError.js +2 -2
  243. package/lib/shared/errors/index.js +4 -4
  244. package/lib/shared/similarity.js +25 -8
  245. package/lib/shared/token-utils.js +6 -2
  246. package/lib/shared/utils/common.js +12 -4
  247. package/package.json +49 -13
  248. package/scripts/bench-real-projects.mjs +256 -0
  249. package/scripts/build-native-ui.js +30 -30
  250. package/scripts/clear-old-vector-index.js +5 -35
  251. package/scripts/clear-vector-cache.js +7 -37
  252. package/scripts/collect-test-project-stats.mjs +160 -0
  253. package/scripts/diagnose-mcp.js +41 -32
  254. package/scripts/ensure-parse-package.js +6 -9
  255. package/scripts/generate-recipe-drafts.js +116 -77
  256. package/scripts/init-db.js +3 -20
  257. package/scripts/init-snippets.js +305 -0
  258. package/scripts/init-vector-db.js +173 -170
  259. package/scripts/install-cursor-skill.js +148 -104
  260. package/scripts/install-full.js +8 -21
  261. package/scripts/install-vscode-copilot.js +146 -145
  262. package/scripts/migrate-md-to-knowledge.mjs +139 -151
  263. package/scripts/postinstall-safe.js +5 -17
  264. package/scripts/recipe-audit.js +106 -82
  265. package/scripts/release.js +283 -323
  266. package/scripts/setup-mcp-config.js +60 -52
  267. package/scripts/verify-context-api.js +20 -20
  268. package/skills/autosnippet-analysis/SKILL.md +10 -6
  269. package/skills/autosnippet-candidates/SKILL.md +27 -26
  270. package/skills/autosnippet-coldstart/SKILL.md +555 -38
  271. package/skills/autosnippet-concepts/SKILL.md +349 -337
  272. package/skills/autosnippet-create/SKILL.md +5 -5
  273. package/skills/autosnippet-reference-dart/SKILL.md +543 -0
  274. package/skills/autosnippet-reference-go/SKILL.md +539 -0
  275. package/skills/autosnippet-reference-java/SKILL.md +534 -0
  276. package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
  277. package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
  278. package/skills/autosnippet-reference-objc/SKILL.md +29 -6
  279. package/skills/autosnippet-reference-python/SKILL.md +800 -0
  280. package/skills/autosnippet-reference-swift/SKILL.md +70 -14
  281. package/skills/autosnippet-structure/SKILL.md +4 -4
  282. package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
  283. package/templates/recipes-setup/README.md +2 -2
  284. package/templates/recipes-setup/_template.md +1 -1
  285. package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
  286. package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
  287. package/resources/asd-entry/main.swift +0 -159
  288. package/scripts/build-asd-entry.js +0 -51
  289. package/scripts/init-xcode-snippets.js +0 -311
  290. package/template.json +0 -39
@@ -26,12 +26,11 @@ import fs from 'node:fs';
26
26
  import path from 'node:path';
27
27
  import { fileURLToPath } from 'node:url';
28
28
  import Logger from '../../infrastructure/logging/Logger.js';
29
- import { TaskPipeline } from './TaskPipeline.js';
30
- import { Memory } from './Memory.js';
29
+ import { ContextWindow, limitToolResult, PhaseRouter } from './ContextWindow.js';
31
30
  import { ConversationStore } from './ConversationStore.js';
32
- import { ContextWindow, PhaseRouter, limitToolResult } from './ContextWindow.js';
31
+ import { Memory } from './Memory.js';
33
32
  import { ReasoningLayer } from './ReasoningLayer.js';
34
- import { WorkingMemory } from './WorkingMemory.js';
33
+ import { TaskPipeline } from './TaskPipeline.js';
35
34
 
36
35
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
37
36
  const PROJECT_ROOT = path.resolve(__dirname, '../../..');
@@ -41,7 +40,7 @@ const MAX_ITERATIONS = 6;
41
40
  /** 系统调用 (如 bootstrap) 允许更多迭代,因为每维度需要多次 submit_knowledge */
42
41
  const MAX_ITERATIONS_SYSTEM = 30;
43
42
  /** 原生函数调用模式下,已提交 ≥ MIN_SUBMITS_FOR_EARLY_EXIT 个候选后,连续 N 轮无新提交则提前退出 */
44
- const MIN_SUBMITS_FOR_EARLY_EXIT = 1;
43
+ const _MIN_SUBMITS_FOR_EARLY_EXIT = 1;
45
44
  const IDLE_ROUNDS_TO_EXIT = 2;
46
45
  /** 单个维度最多提交候选数量 — 超过后跳过提交返回提醒 */
47
46
  const MAX_SUBMITS_PER_DIMENSION = 6;
@@ -77,10 +76,16 @@ export class ChatAgent {
77
76
  #conversations = null;
78
77
  /** @type {string|null} 当前 execute 调用的 source — 'user' | 'system' */
79
78
  #currentSource = null;
79
+ /** @type {string|null} 当前 execute 调用的 UI 语言偏好 — 'zh' | 'en' | null */
80
+ #currentLang = null;
81
+ /** @type {string|null} 默认 UI 语言偏好(通过 setLang 设置,bootstrap 等非对话场景使用) */
82
+ #defaultLang = null;
80
83
  /** @type {Array|null} 内存文件缓存(bootstrap 场景注入,search_project_code/read_project_file 优先使用) */
81
84
  #fileCache = null;
82
85
  /** @type {Set<string>} 跨维度已提交候选标题(bootstrap 全局去重) */
83
86
  #globalSubmittedTitles = new Set();
87
+ /** @type {Set<string>} 跨维度已提交代码模式指纹(bootstrap 全局去重) */
88
+ #globalSubmittedPatterns = new Set();
84
89
  /** @type {{ input: number, output: number }} 当前 execute() 累计 token 用量 */
85
90
  #currentTokenUsage = { input: 0, output: 0 };
86
91
  /** @type {import('./ProjectSemanticMemory.js').ProjectSemanticMemory|null} Tier 3 语义记忆 */
@@ -115,13 +120,23 @@ export class ChatAgent {
115
120
  const projectRoot = container?.singletons?._projectRoot || process.cwd();
116
121
  this.#memory = new Memory(projectRoot);
117
122
  this.#conversations = new ConversationStore(projectRoot);
118
- } catch { /* Memory/ConversationStore init failed, degrade silently */ }
123
+ } catch {
124
+ /* Memory/ConversationStore init failed, degrade silently */
125
+ }
119
126
 
120
127
  // v4.1: 尝试初始化 ProjectSemanticMemory (Tier 3)
121
128
  this.#initSemanticMemory(container);
122
129
 
123
130
  // 注册内置 DAG 管线
124
131
  this.#registerBuiltinPipelines();
132
+
133
+ // 从系统环境变量检测默认语言
134
+ const sysLang = (process.env.LANG || '').split('.')[0];
135
+ if (sysLang.startsWith('en')) {
136
+ this.#defaultLang = 'en';
137
+ } else if (sysLang.startsWith('zh')) {
138
+ this.#defaultLang = 'zh';
139
+ }
125
140
  }
126
141
 
127
142
  // ─── 公共 API ─────────────────────────────────────────
@@ -148,6 +163,24 @@ export class ChatAgent {
148
163
  */
149
164
  resetGlobalSubmittedTitles() {
150
165
  this.#globalSubmittedTitles.clear();
166
+ this.#globalSubmittedPatterns.clear();
167
+ }
168
+
169
+ /**
170
+ * 获取当前默认 UI 语言偏好
171
+ * @returns {'zh'|'en'|null}
172
+ */
173
+ getLang() {
174
+ return this.#defaultLang;
175
+ }
176
+
177
+ /**
178
+ * 设置默认 UI 语言偏好(影响 Agent 回复语言)
179
+ * 由前端通过 bootstrap/chat 等 API 设置,后续所有 AI 调用自动继承。
180
+ * @param {'zh'|'en'|null} lang
181
+ */
182
+ setLang(lang) {
183
+ this.#defaultLang = lang || null;
151
184
  }
152
185
 
153
186
  /**
@@ -163,25 +196,38 @@ export class ChatAgent {
163
196
  * @param {string} [opts.projectLanguage] — 项目主语言 (e.g. 'swift', 'objectivec'),注入到 submit tool ctx
164
197
  * @returns {Promise<{reply: string, toolCalls: Array, hasContext: boolean, conversationId?: string}>}
165
198
  */
166
- async execute(prompt, { history = [], conversationId, source = 'user', budget: budgetOverrides, dimensionId, dimensionMeta,
167
- // v3.0: Agent 分离选项
168
- systemPromptOverride, // 覆盖默认 system prompt (Analyst/Producer 各自使用)
169
- allowedTools, // 覆盖默认工具白名单 (string[])
170
- disablePhaseRouter = false, // 禁用 PhaseRouter (Analyst 不需要阶段控制)
171
- temperature: temperatureOverride, // 覆盖默认温度
172
- projectLanguage, // 项目主语言,注入到 submit tool 的 ctx._projectLanguage
173
- // v4.0: Agent Memory 集成
174
- workingMemory, // WorkingMemory 实例 (由 orchestrator 注入)
175
- episodicMemory, // EpisodicMemory 实例 (跨维度情景记忆)
176
- toolResultCache, // ToolResultCache 实例 (跨维度工具结果缓存)
177
- // v5.1: SSE 流式进度回调
178
- onProgress, // (event: {type, ...}) => void 实时推送思考/工具/回答事件
179
- } = {}) {
199
+ async execute(
200
+ prompt,
201
+ {
202
+ history = [],
203
+ conversationId,
204
+ source = 'user',
205
+ budget: budgetOverrides,
206
+ dimensionId,
207
+ dimensionMeta,
208
+ // v3.0: Agent 分离选项
209
+ systemPromptOverride, // 覆盖默认 system prompt (Analyst/Producer 各自使用)
210
+ allowedTools, // 覆盖默认工具白名单 (string[])
211
+ disablePhaseRouter = false, // 禁用 PhaseRouter (Analyst 不需要阶段控制)
212
+ temperature: temperatureOverride, // 覆盖默认温度
213
+ projectLanguage, // 项目主语言,注入到 submit tool 的 ctx._projectLanguage
214
+ lang, // UI 语言偏好 ('zh'|'en'),控制回复语言
215
+ // v4.0: Agent Memory 集成
216
+ workingMemory, // WorkingMemory 实例 (由 orchestrator 注入)
217
+ episodicMemory, // EpisodicMemory 实例 (跨维度情景记忆)
218
+ toolResultCache, // ToolResultCache 实例 (跨维度工具结果缓存)
219
+ // v5.1: SSE 流式进度回调
220
+ onProgress, // (event: {type, ...}) => void — 实时推送思考/工具/回答事件
221
+ } = {}
222
+ ) {
180
223
  this.#currentSource = source;
224
+ this.#currentLang = lang || this.#defaultLang || null;
181
225
  this.#currentTokenUsage = { input: 0, output: 0 };
182
226
  const execStartTime = Date.now();
183
- const promptPreview = prompt.length > 80 ? prompt.substring(0, 80) + '…' : prompt;
184
- this.#logger.info(`[ChatAgent] ▶ execute — source=${source}${dimensionMeta?.id ? ', dim=' + dimensionMeta.id + '(' + dimensionMeta.outputType + ')' : (dimensionId ? ', dim=' + dimensionId : '')}, prompt="${promptPreview}", historyLen=${history.length}${conversationId ? ', convId=' + conversationId.substring(0, 8) : ''}`);
227
+ const promptPreview = prompt.length > 80 ? `${prompt.substring(0, 80)}…` : prompt;
228
+ this.#logger.info(
229
+ `[ChatAgent] ▶ execute — source=${source}${dimensionMeta?.id ? `, dim=${dimensionMeta.id}(${dimensionMeta.outputType})` : dimensionId ? `, dim=${dimensionId}` : ''}, prompt="${promptPreview}", historyLen=${history.length}${conversationId ? `, convId=${conversationId.substring(0, 8)}` : ''}`
230
+ );
185
231
 
186
232
  // 合并预算配置: 默认值 + 外部覆盖
187
233
  const budget = budgetOverrides
@@ -192,7 +238,9 @@ export class ChatAgent {
192
238
  let effectiveHistory = history;
193
239
  if (conversationId && this.#conversations) {
194
240
  effectiveHistory = this.#conversations.load(conversationId);
195
- this.#logger.info(`[ChatAgent] loaded ${effectiveHistory.length} messages from conversation store`);
241
+ this.#logger.info(
242
+ `[ChatAgent] loaded ${effectiveHistory.length} messages from conversation store`
243
+ );
196
244
  this.#conversations.append(conversationId, { role: 'user', content: prompt });
197
245
  }
198
246
 
@@ -206,10 +254,20 @@ export class ChatAgent {
206
254
  this.#logger.info(`[ChatAgent] ✨ using NATIVE tool calling mode (${this.#aiProvider.name})`);
207
255
  let result;
208
256
  result = await this.#executeWithNativeTools(prompt, {
209
- effectiveHistory, conversationId, source, execStartTime, budget, dimensionMeta,
210
- systemPromptOverride, allowedTools, disablePhaseRouter, temperatureOverride,
257
+ effectiveHistory,
258
+ conversationId,
259
+ source,
260
+ execStartTime,
261
+ budget,
262
+ dimensionMeta,
263
+ systemPromptOverride,
264
+ allowedTools,
265
+ disablePhaseRouter,
266
+ temperatureOverride,
211
267
  projectLanguage,
212
- workingMemory, episodicMemory, toolResultCache,
268
+ workingMemory,
269
+ episodicMemory,
270
+ toolResultCache,
213
271
  onProgress,
214
272
  });
215
273
 
@@ -229,8 +287,11 @@ export class ChatAgent {
229
287
  // 持久化 assistant 回复
230
288
  if (conversationId && this.#conversations) {
231
289
  this.#conversations.append(conversationId, { role: 'assistant', content: result.reply });
232
- this.#autoSummarize(conversationId).catch(err => {
233
- this.#logger.debug('[ChatAgent] autoSummarize failed', { conversationId, error: err.message });
290
+ this.#autoSummarize(conversationId).catch((err) => {
291
+ this.#logger.debug('[ChatAgent] autoSummarize failed', {
292
+ conversationId,
293
+ error: err.message,
294
+ });
234
295
  });
235
296
  }
236
297
 
@@ -260,9 +321,13 @@ export class ChatAgent {
260
321
  try {
261
322
  const realtime = this.#container?.get?.('realtimeService');
262
323
  realtime?.broadcastTokenUsageUpdated?.();
263
- } catch { /* optional */ }
324
+ } catch {
325
+ /* optional */
326
+ }
264
327
  }
265
- } catch { /* token logging should never break execution */ }
328
+ } catch {
329
+ /* token logging should never break execution */
330
+ }
266
331
 
267
332
  return { ...result, conversationId };
268
333
  }
@@ -281,15 +346,29 @@ export class ChatAgent {
281
346
  * @param {object} opts
282
347
  * @returns {Promise<{reply: string, toolCalls: Array, hasContext: boolean}>}
283
348
  */
284
- async #executeWithNativeTools(prompt, { effectiveHistory, conversationId, source, execStartTime, budget = DEFAULT_BUDGET, dimensionMeta,
285
- // v3.0: Agent 分离新增选项
286
- systemPromptOverride, allowedTools, disablePhaseRouter = false, temperatureOverride,
287
- projectLanguage,
288
- // v4.0: Agent Memory 集成
289
- workingMemory, episodicMemory, toolResultCache,
290
- // v5.1: SSE 流式进度回调
291
- onProgress,
292
- }) {
349
+ async #executeWithNativeTools(
350
+ prompt,
351
+ {
352
+ effectiveHistory,
353
+ conversationId,
354
+ source,
355
+ execStartTime,
356
+ budget = DEFAULT_BUDGET,
357
+ dimensionMeta,
358
+ // v3.0: Agent 分离新增选项
359
+ systemPromptOverride,
360
+ allowedTools,
361
+ disablePhaseRouter = false,
362
+ temperatureOverride,
363
+ projectLanguage,
364
+ // v4.0: Agent Memory 集成
365
+ workingMemory,
366
+ episodicMemory,
367
+ toolResultCache,
368
+ // v5.1: SSE 流式进度回调
369
+ onProgress,
370
+ }
371
+ ) {
293
372
  const isSystem = source === 'system';
294
373
  const isSkillOnly = dimensionMeta?.outputType === 'skill';
295
374
  const temperature = temperatureOverride ?? (isSystem ? 0.3 : 0.7);
@@ -297,9 +376,7 @@ export class ChatAgent {
297
376
  // ── Layer 1: ContextWindow ──
298
377
  // messages[0] = prompt(不可压缩),历史消息在前面
299
378
  // token 预算按模型动态适配:大窗口模型(Gemini/Claude)给更多预算,小窗口(Ollama)限制预算
300
- const tokenBudget = ContextWindow.resolveTokenBudget(
301
- this.#aiProvider?.model, { isSystem },
302
- );
379
+ const tokenBudget = ContextWindow.resolveTokenBudget(this.#aiProvider?.model, { isSystem });
303
380
  const ctx = new ContextWindow(tokenBudget);
304
381
  for (const h of effectiveHistory) {
305
382
  if (h.role === 'assistant') {
@@ -314,19 +391,33 @@ export class ChatAgent {
314
391
  // ── P5: Pre-check — 首条 prompt 过大时预警 ──
315
392
  const initialUsage = ctx.getTokenUsageRatio();
316
393
  if (initialUsage > 0.7) {
317
- this.#logger.warn(`[ChatAgent] ⚠ initial prompt already at ${(initialUsage * 100).toFixed(0)}% of token budget (${ctx.estimateTokens()}/${ctx.tokenBudget})`);
394
+ this.#logger.warn(
395
+ `[ChatAgent] ⚠ initial prompt already at ${(initialUsage * 100).toFixed(0)}% of token budget (${ctx.estimateTokens()}/${ctx.tokenBudget})`
396
+ );
318
397
  if (initialUsage > 0.9 && isSystem) {
319
398
  // 仅 1 条消息时 compactIfNeeded 无法压缩(需 >4 条),
320
399
  // 依赖 P0/P1 信号限制来控制 prompt 大小
321
- this.#logger.warn(`[ChatAgent] ⚠ prompt exceeds 90% budget — P0/P1 signal limiting should have prevented this. Check PROMPT_LIMITS config.`);
400
+ this.#logger.warn(
401
+ `[ChatAgent] ⚠ prompt exceeds 90% budget — P0/P1 signal limiting should have prevented this. Check PROMPT_LIMITS config.`
402
+ );
322
403
  }
323
404
  }
324
405
 
325
406
  // ── Layer 2: PhaseRouter (仅 system 源且未禁用时使用) ──
326
- const phaseRouter = (isSystem && !disablePhaseRouter) ? new PhaseRouter(budget, isSkillOnly) : null;
407
+ const phaseRouter =
408
+ isSystem && !disablePhaseRouter ? new PhaseRouter(budget, isSkillOnly) : null;
327
409
 
328
410
  // ── 系统提示词 (支持外部覆盖) ──
329
411
  let baseSystemPrompt = systemPromptOverride || this.#buildNativeToolSystemPrompt(budget);
412
+
413
+ // ── 统一注入语言指令(对所有来源生效: user 对话 / system bootstrap / analyst / producer)──
414
+ const effectiveLang = this.#currentLang;
415
+ if (effectiveLang === 'en') {
416
+ baseSystemPrompt += '\n\n## Language\nYou MUST respond in English. All output text, analysis, titles and descriptions must be in English.';
417
+ } else if (effectiveLang === 'zh') {
418
+ baseSystemPrompt += '\n\n## 语言\n你必须使用中文回复。所有输出文本、分析、标题和描述都必须是中文。';
419
+ }
420
+
330
421
  // 统一注入轮次预算,确保 AI 始终知道具体数字和节奏
331
422
  if (isSystem && !baseSystemPrompt.includes('轮次预算')) {
332
423
  const exploreEnd = Math.floor(budget.maxIterations * 0.6);
@@ -335,47 +426,60 @@ export class ChatAgent {
335
426
  }
336
427
 
337
428
  // Bootstrap 场景限制可用工具集 (支持外部覆盖)
338
- const effectiveAllowedTools = allowedTools || (isSystem ? [
339
- 'search_project_code', 'read_project_file',
340
- 'submit_knowledge', 'submit_with_check',
341
- 'list_project_structure', 'get_file_summary', 'semantic_search_code',
342
- // AST 结构化分析工具
343
- 'get_project_overview', 'get_class_hierarchy', 'get_class_info',
344
- 'get_protocol_info', 'get_method_overrides', 'get_category_map',
345
- 'get_previous_analysis',
346
- // Agent Memory 工具 (v4.0)
347
- 'note_finding', 'get_previous_evidence',
348
- ] : null);
429
+ const effectiveAllowedTools =
430
+ allowedTools ||
431
+ (isSystem
432
+ ? [
433
+ 'search_project_code',
434
+ 'read_project_file',
435
+ 'submit_knowledge',
436
+ 'submit_with_check',
437
+ 'list_project_structure',
438
+ 'get_file_summary',
439
+ 'semantic_search_code',
440
+ // AST 结构化分析工具
441
+ 'get_project_overview',
442
+ 'get_class_hierarchy',
443
+ 'get_class_info',
444
+ 'get_protocol_info',
445
+ 'get_method_overrides',
446
+ 'get_category_map',
447
+ 'get_previous_analysis',
448
+ // Agent Memory 工具 (v4.0)
449
+ 'note_finding',
450
+ 'get_previous_evidence',
451
+ ]
452
+ : null);
349
453
  const toolSchemas = this.#toolRegistry.getToolSchemas(effectiveAllowedTools);
350
454
 
351
455
  const toolCalls = [];
352
456
  const maxIter = isSystem ? budget.maxIterations : MAX_ITERATIONS;
353
457
  let consecutiveAiErrors = 0;
354
458
  let consecutiveEmptyResponses = 0;
355
- let iterationCount = 0; // v3: 独立迭代计数器
459
+ let iterationCount = 0; // v3: 独立迭代计数器
356
460
  const submittedTitles = new Set(this.#globalSubmittedTitles);
357
461
  const sharedState = {}; // P2.2: 跨工具调用共享状态(搜索计数器等)
358
462
 
359
463
  // ── 进度感知收敛 (Analyst 专用: disablePhaseRouter=true) ──
360
464
  // 追踪搜索/读取新信息的效率,当探索饱和时提前引导 AI 收敛
361
465
  const explorationMetrics = {
362
- uniqueFiles: new Set(), // 已读取的唯一文件
363
- uniquePatterns: new Set(), // 已搜索的唯一 pattern
364
- uniqueQueries: new Set(), // 已查询的唯一类名/目录(AST + list_project_structure)
365
- totalToolCalls: 0, // 总工具调用数
366
- newInfoRounds: 0, // 最近 N 轮中获取到新信息的轮次
367
- staleRounds: 0, // 连续未获取新信息的轮次
368
- convergenceNudged: false, // 是否已注入收敛 nudge
466
+ uniqueFiles: new Set(), // 已读取的唯一文件
467
+ uniquePatterns: new Set(), // 已搜索的唯一 pattern
468
+ uniqueQueries: new Set(), // 已查询的唯一类名/目录(AST + list_project_structure)
469
+ totalToolCalls: 0, // 总工具调用数
470
+ newInfoRounds: 0, // 最近 N 轮中获取到新信息的轮次
471
+ staleRounds: 0, // 连续未获取新信息的轮次
472
+ convergenceNudged: false, // 是否已注入收敛 nudge
369
473
  };
370
- const MIN_EXPLORE_ITERS = 16; // 最少探索轮次(冷启动质量保障)
371
- const STALE_THRESHOLD = 3; // 连续无新信息轮次触发收敛
474
+ const MIN_EXPLORE_ITERS = 16; // 最少探索轮次(冷启动质量保障)
475
+ const STALE_THRESHOLD = 3; // 连续无新信息轮次触发收敛
372
476
 
373
477
  // ── Layer 4: ReasoningLayer — 推理链采集 + 结构化观察 + 反思 + 规划 ──
374
478
  const reasoning = new ReasoningLayer({
375
479
  enabled: true,
376
- reflectionEnabled: isSystem, // 仅 system 模式启用反思
480
+ reflectionEnabled: isSystem, // 仅 system 模式启用反思
377
481
  reflectionInterval: isSystem ? 5 : 0,
378
- planningEnabled: isSystem, // 仅 system 模式启用规划
482
+ planningEnabled: isSystem, // 仅 system 模式启用规划
379
483
  replanInterval: isSystem ? 8 : 0,
380
484
  deviationThreshold: 0.6,
381
485
  });
@@ -386,49 +490,71 @@ export class ChatAgent {
386
490
  if (phaseRouter) {
387
491
  phaseRouter.tick();
388
492
  if (phaseRouter.shouldExit()) {
389
- this.#logger.info(`[ChatAgent] PhaseRouter exit: phase=${phaseRouter.phase}, iter=${phaseRouter.totalIterations}, submits=${phaseRouter.totalSubmits}`);
493
+ this.#logger.info(
494
+ `[ChatAgent] PhaseRouter exit: phase=${phaseRouter.phase}, iter=${phaseRouter.totalIterations}, submits=${phaseRouter.totalSubmits}`
495
+ );
390
496
  break;
391
497
  }
392
498
  // PhaseRouter 因 maxIterations 强制转入 SUMMARIZE → 注入收尾 nudge
393
499
  if (phaseRouter.consumeForcedSummarize()) {
394
- const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
500
+ const submitCount = toolCalls.filter(
501
+ (tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
502
+ ).length;
395
503
  ctx.appendUserNudge(
396
504
  `⚠️ 轮次即将耗尽 (${phaseRouter.totalIterations}/${budget.maxIterations}),**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
397
- `\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
505
+ `\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
506
+ );
507
+ this.#logger.info(
508
+ '[ChatAgent] 📝 injected forced SUMMARIZE nudge (maxIterations reached)'
398
509
  );
399
- this.#logger.info('[ChatAgent] 📝 injected forced SUMMARIZE nudge (maxIterations reached)');
400
510
  }
401
- } else if (isSystem && !phaseRouter && !ctx.__gracefulExitInjected && !explorationMetrics.convergenceNudged
402
- && iterationCount >= MIN_EXPLORE_ITERS && explorationMetrics.staleRounds >= STALE_THRESHOLD) {
511
+ } else if (
512
+ isSystem &&
513
+ !phaseRouter &&
514
+ !ctx.__gracefulExitInjected &&
515
+ !explorationMetrics.convergenceNudged &&
516
+ iterationCount >= MIN_EXPLORE_ITERS &&
517
+ explorationMetrics.staleRounds >= STALE_THRESHOLD
518
+ ) {
403
519
  // ── 进度感知收敛:探索已饱和,提前引导 AI 总结 ──
404
520
  this.#logger.info(
405
521
  `[ChatAgent] 📊 Exploration saturated at iter ${iterationCount}/${maxIter} — ` +
406
- `files=${explorationMetrics.uniqueFiles.size}, patterns=${explorationMetrics.uniquePatterns.size}, ` +
407
- `queries=${explorationMetrics.uniqueQueries.size}, staleRounds=${explorationMetrics.staleRounds} — nudging convergence`
522
+ `files=${explorationMetrics.uniqueFiles.size}, patterns=${explorationMetrics.uniquePatterns.size}, ` +
523
+ `queries=${explorationMetrics.uniqueQueries.size}, staleRounds=${explorationMetrics.staleRounds} — nudging convergence`
408
524
  );
409
525
  ctx.appendUserNudge(
410
526
  `你已经充分探索了项目代码(${explorationMetrics.uniqueFiles.size} 个文件,${explorationMetrics.uniquePatterns.size} 次不同搜索,${explorationMetrics.uniqueQueries.size} 次结构化查询)。` +
411
- `最近 ${explorationMetrics.staleRounds} 轮没有发现新信息,建议开始撰写分析总结。\n` +
412
- `如果你确信还有重要方面未覆盖,可以继续探索(剩余 ${maxIter - iterationCount} 轮);否则请直接输出你的分析发现。`
527
+ `最近 ${explorationMetrics.staleRounds} 轮没有发现新信息,建议开始撰写分析总结。\n` +
528
+ `如果你确信还有重要方面未覆盖,可以继续探索(剩余 ${maxIter - iterationCount} 轮);否则请直接输出你的分析发现。`
413
529
  );
414
530
  explorationMetrics.convergenceNudged = true;
415
531
  // 不 break,不设 toolChoice=none — 这是软 nudge,AI 仍可继续探索
416
- } else if (isSystem && !phaseRouter && !ctx.__budgetWarningInjected
417
- && iterationCount >= Math.floor(maxIter * 0.75)) {
532
+ } else if (
533
+ isSystem &&
534
+ !phaseRouter &&
535
+ !ctx.__budgetWarningInjected &&
536
+ iterationCount >= Math.floor(maxIter * 0.75)
537
+ ) {
418
538
  // ── 预算感知提醒:75% 预算消耗时轻量提示 ──
419
539
  ctx.appendUserNudge(
420
- `📌 进度提醒:你已使用 ${iterationCount}/${maxIter} 轮次(${Math.round(iterationCount / maxIter * 100)}%)。` +
421
- `请确保核心方面已覆盖,开始准备总结。剩余 ${maxIter - iterationCount} 轮,优先填补最重要的分析空白。`
540
+ `📌 进度提醒:你已使用 ${iterationCount}/${maxIter} 轮次(${Math.round((iterationCount / maxIter) * 100)}%)。` +
541
+ `请确保核心方面已覆盖,开始准备总结。剩余 ${maxIter - iterationCount} 轮,优先填补最重要的分析空白。`
422
542
  );
423
543
  ctx.__budgetWarningInjected = true;
424
- this.#logger.info(`[ChatAgent] 📌 Budget warning at ${iterationCount}/${maxIter} (${Math.round(iterationCount / maxIter * 100)}%)`);
544
+ this.#logger.info(
545
+ `[ChatAgent] 📌 Budget warning at ${iterationCount}/${maxIter} (${Math.round((iterationCount / maxIter) * 100)}%)`
546
+ );
425
547
  } else if (isSystem && iterationCount >= maxIter && !ctx.__gracefulExitInjected) {
426
548
  // 达到上限 → 注入收尾消息让 AI 快速总结(而非硬中断)
427
- this.#logger.info(`[ChatAgent] Iteration cap reached (${iterationCount}/${maxIter}) — injecting graceful exit nudge`);
428
- const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
549
+ this.#logger.info(
550
+ `[ChatAgent] Iteration cap reached (${iterationCount}/${maxIter}) injecting graceful exit nudge`
551
+ );
552
+ const submitCount = toolCalls.filter(
553
+ (tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
554
+ ).length;
429
555
  ctx.appendUserNudge(
430
556
  `⚠️ 你已使用 ${iterationCount}/${maxIter} 轮次,**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
431
- `\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
557
+ `\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。`
432
558
  );
433
559
  ctx.__gracefulExitInjected = true;
434
560
  // 不 break,给 AI 2 轮 grace 来产出总结
@@ -459,7 +585,9 @@ export class ChatAgent {
459
585
 
460
586
  // ── ReasoningLayer Hook 1: beforeAICall — 开始新轮次 + 反思检查 ──
461
587
  const reflectionNudge = reasoning.beforeAICall(iterationCount, {
462
- explorationMetrics, budget, phase: phaseRouter?.phase,
588
+ explorationMetrics,
589
+ budget,
590
+ phase: phaseRouter?.phase,
463
591
  });
464
592
  if (reflectionNudge) {
465
593
  ctx.appendUserNudge(reflectionNudge);
@@ -469,7 +597,9 @@ export class ChatAgent {
469
597
  // ── 压缩检查 (每次 AI 调用前) ──
470
598
  const compactResult = ctx.compactIfNeeded();
471
599
  if (compactResult.level > 0) {
472
- this.#logger.info(`[ChatAgent] context compacted: L${compactResult.level}, removed ${compactResult.removed} items`);
600
+ this.#logger.info(
601
+ `[ChatAgent] context compacted: L${compactResult.level}, removed ${compactResult.removed} items`
602
+ );
473
603
  }
474
604
 
475
605
  // ── 构建 systemPrompt (含阶段提示) ──
@@ -499,7 +629,7 @@ export class ChatAgent {
499
629
  if (workingMemory && isSystem) {
500
630
  const wmContext = workingMemory.buildContext();
501
631
  if (wmContext) {
502
- systemPrompt += '\n\n' + wmContext;
632
+ systemPrompt += `\n\n${wmContext}`;
503
633
  }
504
634
  }
505
635
 
@@ -508,10 +638,17 @@ export class ChatAgent {
508
638
  try {
509
639
  const messages = ctx.toMessages();
510
640
  const currentPhase = phaseRouter?.phase || 'user';
511
- this.#logger.info(`[ChatAgent] 🔄 iteration ${currentIter}/${maxIter} — phase=${currentPhase}, ${messages.length} msgs, toolChoice=${currentChoice}, tokens~${ctx.estimateTokens()}`);
641
+ this.#logger.info(
642
+ `[ChatAgent] 🔄 iteration ${currentIter}/${maxIter} — phase=${currentPhase}, ${messages.length} msgs, toolChoice=${currentChoice}, tokens~${ctx.estimateTokens()}`
643
+ );
512
644
 
513
645
  // SSE: 推送步骤开始
514
- onProgress?.({ type: 'step:start', step: currentIter, maxSteps: maxIter, phase: currentPhase });
646
+ onProgress?.({
647
+ type: 'step:start',
648
+ step: currentIter,
649
+ maxSteps: maxIter,
650
+ phase: currentPhase,
651
+ });
515
652
 
516
653
  aiResult = await this.#aiProvider.chatWithTools(prompt, {
517
654
  messages,
@@ -531,10 +668,14 @@ export class ChatAgent {
531
668
  }
532
669
 
533
670
  if (aiResult.functionCalls?.length > 0) {
534
- this.#logger.info(`[ChatAgent] ✓ AI returned ${aiResult.functionCalls.length} function calls in ${aiDuration}ms: [${aiResult.functionCalls.map(fc => fc.name).join(', ')}]`);
671
+ this.#logger.info(
672
+ `[ChatAgent] ✓ AI returned ${aiResult.functionCalls.length} function calls in ${aiDuration}ms: [${aiResult.functionCalls.map((fc) => fc.name).join(', ')}]`
673
+ );
535
674
  } else {
536
675
  const textPreview = (aiResult.text || '').substring(0, 120).replace(/\n/g, '↵');
537
- this.#logger.info(`[ChatAgent] ✓ AI returned text in ${aiDuration}ms (${(aiResult.text || '').length} chars) — "${textPreview}…"`);
676
+ this.#logger.info(
677
+ `[ChatAgent] ✓ AI returned text in ${aiDuration}ms (${(aiResult.text || '').length} chars) — "${textPreview}…"`
678
+ );
538
679
  }
539
680
  consecutiveAiErrors = 0;
540
681
 
@@ -542,7 +683,9 @@ export class ChatAgent {
542
683
  reasoning.afterAICall(aiResult);
543
684
  } catch (aiErr) {
544
685
  consecutiveAiErrors++;
545
- this.#logger.warn(`[ChatAgent] AI call failed (attempt ${consecutiveAiErrors}): ${aiErr.message}`);
686
+ this.#logger.warn(
687
+ `[ChatAgent] AI call failed (attempt ${consecutiveAiErrors}): ${aiErr.message}`
688
+ );
546
689
 
547
690
  // 熔断器已打开时立即跳出 — 不要继续浪费重试、也避免失败计数加速累积
548
691
  if (aiErr.code === 'CIRCUIT_OPEN') {
@@ -562,7 +705,9 @@ export class ChatAgent {
562
705
 
563
706
  if (consecutiveAiErrors >= 2) {
564
707
  if (isSystem) {
565
- this.#logger.warn(`[ChatAgent] 🛑 2 consecutive AI errors — resetting context, breaking to summary`);
708
+ this.#logger.warn(
709
+ `[ChatAgent] 🛑 2 consecutive AI errors — resetting context, breaking to summary`
710
+ );
566
711
  ctx.resetToPromptOnly();
567
712
  break;
568
713
  }
@@ -576,7 +721,7 @@ export class ChatAgent {
576
721
  reasoningQuality: reasoning.getQualityMetrics(),
577
722
  };
578
723
  }
579
- await new Promise(r => setTimeout(r, 2000));
724
+ await new Promise((r) => setTimeout(r, 2000));
580
725
  continue;
581
726
  }
582
727
 
@@ -585,13 +730,23 @@ export class ChatAgent {
585
730
  // ── Graceful exit 保护: Gemini 有时会无视 toolChoice='none' 继续返回工具调用
586
731
  // 强制忽略工具调用,将附带文本视为最终回复
587
732
  if (ctx.__gracefulExitInjected) {
588
- this.#logger.warn(`[ChatAgent] ⚠ AI returned ${aiResult.functionCalls.length} tool calls despite toolChoice=none (graceful exit) — ignoring tools, treating as text`);
733
+ this.#logger.warn(
734
+ `[ChatAgent] ⚠ AI returned ${aiResult.functionCalls.length} tool calls despite toolChoice=none (graceful exit) — ignoring tools, treating as text`
735
+ );
589
736
  if (aiResult.text) {
590
737
  const reply = this.#cleanFinalAnswer(aiResult.text);
591
738
  const totalDuration = Date.now() - execStartTime;
592
739
  reasoning.afterRound();
593
- this.#logger.info(`[ChatAgent] ✅ final answer (graceful exit, forced) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`);
594
- return { reply, toolCalls, hasContext: toolCalls.length > 0, reasoningTrace: reasoning.trace, reasoningQuality: reasoning.getQualityMetrics() };
740
+ this.#logger.info(
741
+ `[ChatAgent] final answer (graceful exit, forced) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
742
+ );
743
+ return {
744
+ reply,
745
+ toolCalls,
746
+ hasContext: toolCalls.length > 0,
747
+ reasoningTrace: reasoning.trace,
748
+ reasoningQuality: reasoning.getQualityMetrics(),
749
+ };
595
750
  }
596
751
  // 无文本时继续循环,下一轮硬上限兜底
597
752
  continue;
@@ -601,7 +756,9 @@ export class ChatAgent {
601
756
  const MAX_TOOL_CALLS_PER_ITER = 8;
602
757
  let activeCalls = aiResult.functionCalls;
603
758
  if (activeCalls.length > MAX_TOOL_CALLS_PER_ITER) {
604
- this.#logger.warn(`[ChatAgent] ⚠ ${activeCalls.length} tool calls, capping to ${MAX_TOOL_CALLS_PER_ITER}`);
759
+ this.#logger.warn(
760
+ `[ChatAgent] ⚠ ${activeCalls.length} tool calls, capping to ${MAX_TOOL_CALLS_PER_ITER}`
761
+ );
605
762
  activeCalls = activeCalls.slice(0, MAX_TOOL_CALLS_PER_ITER);
606
763
  }
607
764
 
@@ -609,14 +766,21 @@ export class ChatAgent {
609
766
  ctx.appendAssistantWithToolCalls(aiResult.text || null, activeCalls);
610
767
 
611
768
  let roundSubmitCount = 0;
612
- let roundHasNewInfo = false; // 本轮是否获取到新信息
769
+ let roundHasNewInfo = false; // 本轮是否获取到新信息
613
770
 
614
771
  for (const fc of activeCalls) {
615
772
  const toolStartTime = Date.now();
616
- this.#logger.info(`[ChatAgent] 🔧 ${fc.name}(${JSON.stringify(fc.args).substring(0, 100)})`);
773
+ this.#logger.info(
774
+ `[ChatAgent] 🔧 ${fc.name}(${JSON.stringify(fc.args).substring(0, 100)})`
775
+ );
617
776
 
618
777
  // SSE: 推送工具调用开始
619
- onProgress?.({ type: 'tool:start', id: `tc_${fc.name}_${Date.now()}`, tool: fc.name, args: fc.args });
778
+ onProgress?.({
779
+ type: 'tool:start',
780
+ id: `tc_${fc.name}_${Date.now()}`,
781
+ tool: fc.name,
782
+ args: fc.args,
783
+ });
620
784
 
621
785
  let toolResult;
622
786
  let cacheHit = false;
@@ -637,28 +801,48 @@ export class ChatAgent {
637
801
  fc.name,
638
802
  fc.args,
639
803
  this.#getToolContext({
640
- _sessionToolCalls: toolCalls, _dimensionMeta: dimensionMeta,
641
- _submittedTitles: submittedTitles, _sharedState: sharedState,
804
+ _sessionToolCalls: toolCalls,
805
+ _dimensionMeta: dimensionMeta,
806
+ _submittedTitles: submittedTitles,
807
+ _submittedPatterns: this.#globalSubmittedPatterns,
808
+ _sharedState: sharedState,
642
809
  _projectLanguage: projectLanguage,
643
810
  // v4.0: Agent Memory
644
811
  _workingMemory: workingMemory || null,
645
812
  _episodicMemory: episodicMemory || null,
646
813
  _toolResultCache: toolResultCache || null,
647
814
  _currentRound: iterationCount,
648
- }),
815
+ })
649
816
  );
650
817
  const toolDuration = Date.now() - toolStartTime;
651
- const resultSize = typeof toolResult === 'string' ? toolResult.length : JSON.stringify(toolResult).length;
652
- this.#logger.info(`[ChatAgent] 🔧 done: ${fc.name} → ${resultSize} chars in ${toolDuration}ms`);
818
+ const resultSize =
819
+ typeof toolResult === 'string'
820
+ ? toolResult.length
821
+ : JSON.stringify(toolResult).length;
822
+ this.#logger.info(
823
+ `[ChatAgent] 🔧 done: ${fc.name} → ${resultSize} chars in ${toolDuration}ms`
824
+ );
653
825
 
654
826
  // SSE: 推送工具调用完成
655
- onProgress?.({ type: 'tool:end', tool: fc.name, status: 'ok', resultSize, duration: toolDuration });
827
+ onProgress?.({
828
+ type: 'tool:end',
829
+ tool: fc.name,
830
+ status: 'ok',
831
+ resultSize,
832
+ duration: toolDuration,
833
+ });
656
834
  } catch (toolErr) {
657
835
  this.#logger.warn(`[ChatAgent] 🔧 FAILED: ${fc.name} — ${toolErr.message}`);
658
836
  toolResult = { error: `tool "${fc.name}" failed: ${toolErr.message}` };
659
837
 
660
838
  // SSE: 推送工具调用失败
661
- onProgress?.({ type: 'tool:end', tool: fc.name, status: 'error', error: toolErr.message, duration: Date.now() - toolStartTime });
839
+ onProgress?.({
840
+ type: 'tool:end',
841
+ tool: fc.name,
842
+ status: 'error',
843
+ error: toolErr.message,
844
+ duration: Date.now() - toolStartTime,
845
+ });
662
846
  }
663
847
  }
664
848
 
@@ -710,7 +894,7 @@ export class ChatAgent {
710
894
  }
711
895
  }
712
896
  for (const sub of Object.values(batchResults)) {
713
- for (const m of (sub.matches || [])) {
897
+ for (const m of sub.matches || []) {
714
898
  if (m.file && !explorationMetrics.uniqueFiles.has(m.file)) {
715
899
  explorationMetrics.uniqueFiles.add(m.file);
716
900
  foundNewInfo = true;
@@ -739,11 +923,16 @@ export class ChatAgent {
739
923
  explorationMetrics.uniqueQueries.add(qKey);
740
924
  foundNewInfo = true;
741
925
  }
742
- } else if (fc.name === 'get_class_info' || fc.name === 'get_class_hierarchy'
743
- || fc.name === 'get_protocol_info' || fc.name === 'get_method_overrides'
744
- || fc.name === 'get_category_map') {
926
+ } else if (
927
+ fc.name === 'get_class_info' ||
928
+ fc.name === 'get_class_hierarchy' ||
929
+ fc.name === 'get_protocol_info' ||
930
+ fc.name === 'get_method_overrides' ||
931
+ fc.name === 'get_category_map'
932
+ ) {
745
933
  // AST 结构化查询:同一类名/协议名只算一次新信息
746
- const queryTarget = fc.args?.className || fc.args?.protocolName || fc.args?.name || '';
934
+ const queryTarget =
935
+ fc.args?.className || fc.args?.protocolName || fc.args?.name || '';
747
936
  const qKey = `${fc.name}:${queryTarget}`;
748
937
  if (!explorationMetrics.uniqueQueries.has(qKey)) {
749
938
  explorationMetrics.uniqueQueries.add(qKey);
@@ -780,19 +969,31 @@ export class ChatAgent {
780
969
  if (fc.name === 'submit_knowledge' || fc.name === 'submit_with_check') {
781
970
  const title = fc.args?.title || fc.args?.category || '';
782
971
  const isRejected = typeof toolResult === 'object' && toolResult?.status === 'rejected';
783
- const isError = typeof toolResult === 'object' && (toolResult?.error || toolResult?.status === 'error');
972
+ const isError =
973
+ typeof toolResult === 'object' &&
974
+ (toolResult?.error || toolResult?.status === 'error');
784
975
 
785
976
  if (isRejected) {
786
977
  this.#logger.info(`[ChatAgent] 🚫 off-topic rejected: "${title}"`);
787
978
  } else if (isError) {
788
979
  // 候选创建失败(如 validation error)— 不加入 submittedTitles,允许 AI 重试
789
- this.#logger.info(`[ChatAgent] ⚠ submit error: "${title}" — ${toolResult.error || 'unknown'}`);
980
+ this.#logger.info(
981
+ `[ChatAgent] ⚠ submit error: "${title}" — ${toolResult.error || 'unknown'}`
982
+ );
790
983
  } else if (submittedTitles.has(title)) {
791
984
  resultStr = `⚠ 重复提交: "${title}" 已存在。`;
792
985
  this.#logger.info(`[ChatAgent] 🔁 duplicate: "${title}"`);
793
986
  } else {
794
987
  submittedTitles.add(title);
795
988
  this.#globalSubmittedTitles.add(title);
989
+ // 记录代码模式指纹用于跨维度去重
990
+ const pattern = fc.args?.content?.pattern || '';
991
+ if (pattern.length >= 30) {
992
+ const fp = pattern.replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, '').replace(/[\s]+/g, '').toLowerCase().slice(0, 200);
993
+ if (fp.length >= 20) {
994
+ this.#globalSubmittedPatterns.add(fp);
995
+ }
996
+ }
796
997
  roundSubmitCount++;
797
998
  }
798
999
  }
@@ -836,7 +1037,9 @@ export class ChatAgent {
836
1037
  // skill-only 维度从 EXPLORE 直接进入 SUMMARIZE (跳过 PRODUCE),
837
1038
  // 需要明确告知 AI 输出 dimensionDigest JSON
838
1039
  if (transition.transitioned && transition.newPhase === 'SUMMARIZE') {
839
- const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
1040
+ const submitCount = toolCalls.filter(
1041
+ (tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
1042
+ ).length;
840
1043
  ctx.appendUserNudge(
841
1044
  `你已完成分析探索。请在回复中直接输出 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 留空数组 \`[]\`。如果有未来得及处理的信号,请在此标记,系统会在下次运行时续传。`
842
1045
  );
@@ -855,8 +1058,10 @@ export class ChatAgent {
855
1058
  // 空响应重试(Gemini 偶发)
856
1059
  if (!aiResult.text && isSystem && consecutiveEmptyResponses < 2) {
857
1060
  consecutiveEmptyResponses++;
858
- this.#logger.warn(`[ChatAgent] ⚠ empty response from system source — retrying (${consecutiveEmptyResponses}/2)`);
859
- await new Promise(r => setTimeout(r, 1500));
1061
+ this.#logger.warn(
1062
+ `[ChatAgent] empty response from system source — retrying (${consecutiveEmptyResponses}/2)`
1063
+ );
1064
+ await new Promise((r) => setTimeout(r, 1500));
860
1065
  continue;
861
1066
  }
862
1067
  // 收到非空响应时重置空响应计数器
@@ -878,19 +1083,31 @@ export class ChatAgent {
878
1083
  // 注入 nudge 让 AI 再输出一次 digest JSON
879
1084
  if (transition.transitioned) {
880
1085
  ctx.appendAssistantText(aiResult.text || '');
881
- const submitCount = toolCalls.filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check').length;
1086
+ const submitCount = toolCalls.filter(
1087
+ (tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
1088
+ ).length;
882
1089
  ctx.appendUserNudge(
883
1090
  `请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹):\n\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理的信号","reason":"原因","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 记录未来得及处理的信号。已全部覆盖则留空 \`[]\`。`
884
1091
  );
885
- this.#logger.info('[ChatAgent] 📝 injected SUMMARIZE nudge (text-triggered transition)');
1092
+ this.#logger.info(
1093
+ '[ChatAgent] 📝 injected SUMMARIZE nudge (text-triggered transition)'
1094
+ );
886
1095
  continue;
887
1096
  }
888
1097
  // 已在 SUMMARIZE 阶段 — 这就是最终回答
889
1098
  const reply = this.#cleanFinalAnswer(aiResult.text || '');
890
1099
  const totalDuration = Date.now() - execStartTime;
891
1100
  reasoning.afterRound();
892
- this.#logger.info(`[ChatAgent] ✅ final answer — ${reply.length} chars, ${phaseRouter.totalIterations} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`);
893
- return { reply, toolCalls, hasContext: toolCalls.length > 0, reasoningTrace: reasoning.trace, reasoningQuality: reasoning.getQualityMetrics() };
1101
+ this.#logger.info(
1102
+ `[ChatAgent] final answer ${reply.length} chars, ${phaseRouter.totalIterations} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`
1103
+ );
1104
+ return {
1105
+ reply,
1106
+ toolCalls,
1107
+ hasContext: toolCalls.length > 0,
1108
+ reasoningTrace: reasoning.trace,
1109
+ reasoningQuality: reasoning.getQualityMetrics(),
1110
+ };
894
1111
  }
895
1112
 
896
1113
  if (!transition.transitioned) {
@@ -902,7 +1119,9 @@ export class ChatAgent {
902
1119
  ctx.appendUserNudge(
903
1120
  '你的分析很好。请继续调用 submit_knowledge 提交你发现的知识候选,每个值得记录的模式/实践都应该提交。'
904
1121
  );
905
- this.#logger.info('[ChatAgent] 📝 injected submit nudge (text in PRODUCE, not transitioning)');
1122
+ this.#logger.info(
1123
+ '[ChatAgent] 📝 injected submit nudge (text in PRODUCE, not transitioning)'
1124
+ );
906
1125
  }
907
1126
  continue;
908
1127
  }
@@ -910,8 +1129,16 @@ export class ChatAgent {
910
1129
  const reply = this.#cleanFinalAnswer(aiResult.text || '');
911
1130
  const totalDuration = Date.now() - execStartTime;
912
1131
  reasoning.afterRound();
913
- this.#logger.info(`[ChatAgent] ✅ final answer — ${reply.length} chars, ${phaseRouter.totalIterations} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`);
914
- return { reply, toolCalls, hasContext: toolCalls.length > 0, reasoningTrace: reasoning.trace, reasoningQuality: reasoning.getQualityMetrics() };
1132
+ this.#logger.info(
1133
+ `[ChatAgent] final answer ${reply.length} chars, ${phaseRouter.totalIterations} iters, ${toolCalls.length} tool calls, ${totalDuration}ms`
1134
+ );
1135
+ return {
1136
+ reply,
1137
+ toolCalls,
1138
+ hasContext: toolCalls.length > 0,
1139
+ reasoningTrace: reasoning.trace,
1140
+ reasoningQuality: reasoning.getQualityMetrics(),
1141
+ };
915
1142
  }
916
1143
 
917
1144
  // 其他阶段的文字回答 → 继续循环(PhaseRouter 已自动转换阶段)
@@ -924,22 +1151,44 @@ export class ChatAgent {
924
1151
  const reply = this.#cleanFinalAnswer(aiResult.text || '');
925
1152
  const totalDuration = Date.now() - execStartTime;
926
1153
  reasoning.afterRound();
927
- this.#logger.info(`[ChatAgent] ✅ final answer (${ctx.__gracefulExitInjected ? 'graceful exit' : 'user'}) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`);
928
- return { reply, toolCalls, hasContext: toolCalls.length > 0, reasoningTrace: reasoning.trace, reasoningQuality: reasoning.getQualityMetrics() };
1154
+ this.#logger.info(
1155
+ `[ChatAgent] ✅ final answer (${ctx.__gracefulExitInjected ? 'graceful exit' : 'user'}) — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
1156
+ );
1157
+ return {
1158
+ reply,
1159
+ toolCalls,
1160
+ hasContext: toolCalls.length > 0,
1161
+ reasoningTrace: reasoning.trace,
1162
+ reasoningQuality: reasoning.getQualityMetrics(),
1163
+ };
929
1164
  }
930
1165
 
931
1166
  // system 模式非 graceful exit 的文字回答(理论上不应到这里)
932
1167
  const reply = this.#cleanFinalAnswer(aiResult.text || '');
933
1168
  const totalDuration = Date.now() - execStartTime;
934
1169
  reasoning.afterRound();
935
- this.#logger.info(`[ChatAgent] ✅ final answer — ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`);
936
- return { reply, toolCalls, hasContext: toolCalls.length > 0, reasoningTrace: reasoning.trace, reasoningQuality: reasoning.getQualityMetrics() };
1170
+ this.#logger.info(
1171
+ `[ChatAgent] final answer ${reply.length} chars, ${toolCalls.length} tool calls, ${totalDuration}ms`
1172
+ );
1173
+ return {
1174
+ reply,
1175
+ toolCalls,
1176
+ hasContext: toolCalls.length > 0,
1177
+ reasoningTrace: reasoning.trace,
1178
+ reasoningQuality: reasoning.getQualityMetrics(),
1179
+ };
937
1180
  }
938
1181
 
939
1182
  // ── 循环退出: 产出 dimensionDigest 总结 ──
940
1183
  reasoning.afterRound();
941
1184
  const forcedResult = await this.#produceForcedSummary({
942
- source, toolCalls, toolSchemas, ctx, phaseRouter, execStartTime,
1185
+ source,
1186
+ toolCalls,
1187
+ toolSchemas,
1188
+ ctx,
1189
+ phaseRouter,
1190
+ execStartTime,
1191
+ prompt,
943
1192
  });
944
1193
  forcedResult.reasoningTrace = reasoning.trace;
945
1194
  forcedResult.reasoningQuality = reasoning.getQualityMetrics();
@@ -950,32 +1199,55 @@ export class ChatAgent {
950
1199
  * 强制退出后的摘要生成 — 独立方法,避免主循环代码膨胀
951
1200
  * @private
952
1201
  */
953
- async #produceForcedSummary({ source, toolCalls, toolSchemas, ctx, phaseRouter, execStartTime }) {
1202
+ async #produceForcedSummary({
1203
+ source,
1204
+ toolCalls,
1205
+ toolSchemas,
1206
+ ctx,
1207
+ phaseRouter,
1208
+ execStartTime,
1209
+ prompt,
1210
+ }) {
954
1211
  const iterations = phaseRouter?.totalIterations || 0;
955
- this.#logger.info(`[ChatAgent] producing forced summary (${iterations} iters, ${toolCalls.length} calls)`);
1212
+ const isSystem = source === 'system';
1213
+ this.#logger.info(
1214
+ `[ChatAgent] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls, source=${source})`
1215
+ );
956
1216
 
957
- const candidateCount = toolCalls.filter(tc =>
958
- tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
1217
+ const candidateCount = toolCalls.filter(
1218
+ (tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check'
959
1219
  ).length;
960
1220
 
961
1221
  let finalReply;
962
1222
 
963
- // 如果熔断器已打开,跳过 AI 调用直接合成 digest(避免无用的失败 + 计数累积)
1223
+ // 如果熔断器已打开,跳过 AI 调用直接合成摘要(避免无用的失败 + 计数累积)
964
1224
  const isCircuitOpen = this.#aiProvider._circuitState === 'OPEN';
965
1225
  if (isCircuitOpen) {
966
- this.#logger.warn(`[ChatAgent] circuit breaker is OPEN — skipping AI summary, using synthetic digest`);
1226
+ this.#logger.warn(
1227
+ `[ChatAgent] circuit breaker is OPEN — skipping AI summary, using synthetic ${isSystem ? 'digest' : 'summary'}`
1228
+ );
967
1229
  }
968
1230
 
1231
+ // ── 收集工具调用摘要(user / system 共用) ──
1232
+ const submitSummary = toolCalls
1233
+ .filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
1234
+ .map((tc, i) => `${i + 1}. ${tc.params?.title || tc.params?.category || 'untitled'}`)
1235
+ .join('\n');
1236
+
1237
+ // ── 收集工具调用上下文(user 源需要更丰富的上下文来生成自然语言总结) ──
1238
+ const toolContextSummary = isSystem ? '' : this.#buildToolContextForUserSummary(toolCalls);
1239
+
969
1240
  try {
970
- if (isCircuitOpen) throw new Error('circuit open — skip to synthetic digest');
1241
+ if (isCircuitOpen) {
1242
+ throw new Error('circuit open — skip to synthetic summary');
1243
+ }
971
1244
 
972
- const submitSummary = toolCalls
973
- .filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
974
- .map((tc, i) => `${i + 1}. ${tc.params?.title || tc.params?.category || 'untitled'}`)
975
- .join('\n');
1245
+ let summaryPrompt;
1246
+ let systemPrompt;
976
1247
 
977
- const summaryPrompt = source === 'system'
978
- ? `你已完成 ${iterations} 轮工具调用(共 ${toolCalls.length} 次),提交了 ${candidateCount} 个候选。
1248
+ if (isSystem) {
1249
+ // ── system 源: 输出 dimensionDigest JSON(供 Bootstrap 管线消费) ──
1250
+ summaryPrompt = `你已完成 ${iterations} 轮工具调用(共 ${toolCalls.length} 次),提交了 ${candidateCount} 个候选。
979
1251
  ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
980
1252
  **必须**输出 dimensionDigest JSON(用 \`\`\`json 包裹):
981
1253
  \`\`\`json
@@ -992,21 +1264,36 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
992
1264
  }
993
1265
  }
994
1266
  \`\`\`
995
- > remainingTasks: 列出本次未来得及处理的信号/主题。已全部覆盖则留空 \`[]\`。`
996
- : `Completed ${iterations} iterations with ${toolCalls.length} tool calls. Please summarize.`;
1267
+ > remainingTasks: 列出本次未来得及处理的信号/主题。已全部覆盖则留空 \`[]\`。`;
1268
+ systemPrompt = '直接输出 dimensionDigest JSON 总结,不要调用工具。';
1269
+ } else {
1270
+ // ── user 源: 输出人类可读的 Markdown 结构化总结(前端 AI Chat 展示) ──
1271
+ const userQuestion = prompt ? `用户的原始问题:「${prompt.slice(0, 500)}」\n\n` : '';
1272
+ summaryPrompt = `${userQuestion}你刚才通过 ${toolCalls.length} 次工具调用分析了项目代码。以下是你调用过的工具和获取到的关键信息:
1273
+
1274
+ ${toolContextSummary}
1275
+
1276
+ 请基于以上收集到的信息,用**清晰易读的 Markdown** 格式撰写分析总结,直接回答用户的问题。
1277
+
1278
+ 要求:
1279
+ - 使用二级/三级标题组织内容
1280
+ - 要有具体的代码文件路径、类名、模式名称等细节
1281
+ - 关键发现用列表项罗列
1282
+ - 如果发现了架构模式或最佳实践,用简短代码块举例
1283
+ - 语言自然流畅,像一份技术分析报告`;
1284
+ systemPrompt =
1285
+ '你是项目分析助手。请用纯 Markdown 格式输出结构清晰的分析总结,只输出人类可读的自然语言文档,不要输出 JSON 格式的数据。';
1286
+ }
997
1287
 
998
1288
  // 用空 messages 避免累积上下文导致 400
999
- const summaryResult = await this.#aiProvider.chatWithTools(
1000
- summaryPrompt,
1001
- {
1002
- messages: [],
1003
- toolSchemas,
1004
- toolChoice: 'none',
1005
- systemPrompt: '直接输出 dimensionDigest JSON 总结,不要调用工具。',
1006
- temperature: 0.3,
1007
- maxTokens: 8192,
1008
- },
1009
- );
1289
+ const summaryResult = await this.#aiProvider.chatWithTools(summaryPrompt, {
1290
+ messages: [],
1291
+ toolSchemas,
1292
+ toolChoice: 'none',
1293
+ systemPrompt,
1294
+ temperature: isSystem ? 0.3 : 0.5,
1295
+ maxTokens: 8192,
1296
+ });
1010
1297
  // 累计 token 用量
1011
1298
  if (summaryResult.usage) {
1012
1299
  this.#currentTokenUsage.input += summaryResult.usage.inputTokens || 0;
@@ -1015,11 +1302,13 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1015
1302
  finalReply = this.#cleanFinalAnswer(summaryResult.text || '');
1016
1303
  } catch (err) {
1017
1304
  this.#logger.warn(`[ChatAgent] forced summary AI call failed: ${err.message}`);
1018
- // 合成 digest 兜底
1019
- const titles = toolCalls
1020
- .filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
1021
- .map(tc => tc.params?.title || 'untitled');
1022
- finalReply = `\`\`\`json
1305
+
1306
+ if (isSystem) {
1307
+ // ── system 源兜底: 合成 dimensionDigest JSON ──
1308
+ const titles = toolCalls
1309
+ .filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
1310
+ .map((tc) => tc.params?.title || 'untitled');
1311
+ finalReply = `\`\`\`json
1023
1312
  {
1024
1313
  "dimensionDigest": {
1025
1314
  "summary": "通过 ${toolCalls.length} 次工具调用分析了项目代码,提交了 ${candidateCount} 个候选。",
@@ -1030,13 +1319,136 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1030
1319
  }
1031
1320
  }
1032
1321
  \`\`\``;
1322
+ } else {
1323
+ // ── user 源兜底: 合成人类可读的 Markdown 摘要 ──
1324
+ const toolNames = [...new Set(toolCalls.map((tc) => tc.tool))];
1325
+ const filesRead = toolCalls
1326
+ .filter((tc) => tc.tool === 'read_project_file')
1327
+ .flatMap((tc) => {
1328
+ if (tc.params?.filePaths) {
1329
+ return tc.params.filePaths;
1330
+ }
1331
+ if (tc.params?.filePath) {
1332
+ return [tc.params.filePath];
1333
+ }
1334
+ return [];
1335
+ })
1336
+ .slice(0, 10);
1337
+ const searches = toolCalls
1338
+ .filter((tc) => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code')
1339
+ .map((tc) => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
1340
+ .filter(Boolean)
1341
+ .slice(0, 5);
1342
+
1343
+ finalReply = `## 分析总结\n\n通过 **${toolCalls.length} 次工具调用**探索了项目代码。\n\n`;
1344
+ if (searches.length > 0) {
1345
+ finalReply += `### 搜索的关键词\n${searches.map((s) => `- \`${s}\``).join('\n')}\n\n`;
1346
+ }
1347
+ if (filesRead.length > 0) {
1348
+ finalReply += `### 读取的文件\n${filesRead.map((f) => `- \`${f}\``).join('\n')}\n\n`;
1349
+ }
1350
+ finalReply += `### 使用的工具\n${toolNames.map((t) => `- ${t}`).join('\n')}\n\n`;
1351
+ finalReply += `> ⚠️ AI 服务异常,未能生成完整分析。请稍后重试或缩小分析范围。`;
1352
+ }
1033
1353
  }
1034
1354
 
1035
1355
  const totalDuration = Date.now() - execStartTime;
1036
- this.#logger.info(`[ChatAgent] ✅ forced summary — ${finalReply.length} chars, ${totalDuration}ms total`);
1356
+ this.#logger.info(
1357
+ `[ChatAgent] ✅ forced summary — ${finalReply.length} chars, ${totalDuration}ms total`
1358
+ );
1037
1359
  return { reply: finalReply, toolCalls, hasContext: toolCalls.length > 0 };
1038
1360
  }
1039
1361
 
1362
+ /**
1363
+ * 从工具调用记录中提取上下文摘要(供 user 源强制总结使用)
1364
+ * @private
1365
+ */
1366
+ #buildToolContextForUserSummary(toolCalls) {
1367
+ const sections = [];
1368
+
1369
+ // 目录结构探索
1370
+ const structureCalls = toolCalls.filter((tc) => tc.tool === 'list_project_structure');
1371
+ if (structureCalls.length > 0) {
1372
+ const dirs = structureCalls.map((tc) => tc.params?.directory || '/').slice(0, 5);
1373
+ sections.push(`**目录探索**: ${dirs.map((d) => `\`${d}\``).join(', ')}`);
1374
+ }
1375
+
1376
+ // 项目概况
1377
+ const overviewCalls = toolCalls.filter((tc) => tc.tool === 'get_project_overview');
1378
+ if (overviewCalls.length > 0) {
1379
+ sections.push('**项目概况**: 已获取');
1380
+ }
1381
+
1382
+ // 代码搜索
1383
+ const searchCalls = toolCalls.filter(
1384
+ (tc) => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code'
1385
+ );
1386
+ if (searchCalls.length > 0) {
1387
+ const queries = searchCalls
1388
+ .map((tc) => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
1389
+ .filter(Boolean)
1390
+ .slice(0, 8);
1391
+ sections.push(
1392
+ `**代码搜索** (${searchCalls.length} 次): ${queries.map((q) => `\`${q}\``).join(', ')}`
1393
+ );
1394
+ }
1395
+
1396
+ // 文件读取
1397
+ const readCalls = toolCalls.filter((tc) => tc.tool === 'read_project_file');
1398
+ if (readCalls.length > 0) {
1399
+ const files = readCalls
1400
+ .flatMap((tc) => {
1401
+ if (tc.params?.filePaths) {
1402
+ return tc.params.filePaths;
1403
+ }
1404
+ if (tc.params?.filePath) {
1405
+ return [tc.params.filePath];
1406
+ }
1407
+ return [];
1408
+ })
1409
+ .slice(0, 10);
1410
+ sections.push(
1411
+ `**文件读取** (${readCalls.length} 次): ${files.map((f) => `\`${f}\``).join(', ')}`
1412
+ );
1413
+ }
1414
+
1415
+ // AST 分析
1416
+ const astCalls = toolCalls.filter((tc) =>
1417
+ [
1418
+ 'get_class_hierarchy',
1419
+ 'get_class_info',
1420
+ 'get_protocol_info',
1421
+ 'get_method_overrides',
1422
+ 'get_category_map',
1423
+ ].includes(tc.tool)
1424
+ );
1425
+ if (astCalls.length > 0) {
1426
+ const entities = astCalls
1427
+ .map(
1428
+ (tc) =>
1429
+ tc.params?.className ||
1430
+ tc.params?.name ||
1431
+ tc.params?.protocolName ||
1432
+ tc.params?.rootClass
1433
+ )
1434
+ .filter(Boolean)
1435
+ .slice(0, 5);
1436
+ sections.push(
1437
+ `**AST 结构分析** (${astCalls.length} 次): ${entities.map((e) => `\`${e}\``).join(', ')}`
1438
+ );
1439
+ }
1440
+
1441
+ // 知识库搜索
1442
+ const kbCalls = toolCalls.filter((tc) =>
1443
+ ['search_knowledge', 'search_recipes', 'knowledge_overview'].includes(tc.tool)
1444
+ );
1445
+ if (kbCalls.length > 0) {
1446
+ sections.push(`**知识库查询**: ${kbCalls.length} 次`);
1447
+ }
1448
+
1449
+ return sections.length > 0 ? sections.join('\n') : '(工具调用记录为空)';
1450
+ }
1451
+
1040
1452
  // ─── Text Parsing 已移除 (v5.0) ────────────────────────
1041
1453
  // 所有 Provider 统一走 chatWithTools() 原生函数调用路径。
1042
1454
  // 不支持 native tool calling 的 Provider 在基类 chatWithTools()
@@ -1064,7 +1476,9 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1064
1476
  * @returns {string} conversationId
1065
1477
  */
1066
1478
  createConversation({ category = 'user', title = '' } = {}) {
1067
- if (!this.#conversations) return null;
1479
+ if (!this.#conversations) {
1480
+ return null;
1481
+ }
1068
1482
  return this.#conversations.create({ category, title });
1069
1483
  }
1070
1484
 
@@ -1076,7 +1490,9 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1076
1490
  * @returns {Array}
1077
1491
  */
1078
1492
  getConversations({ category, limit = 20 } = {}) {
1079
- if (!this.#conversations) return [];
1493
+ if (!this.#conversations) {
1494
+ return [];
1495
+ }
1080
1496
  return this.#conversations.list({ category, limit });
1081
1497
  }
1082
1498
 
@@ -1100,12 +1516,18 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1100
1516
  }
1101
1517
  // 降级到硬编码任务(复杂交互逻辑无法用 DAG 表达的场景)
1102
1518
  switch (taskName) {
1103
- case 'check_and_submit': return this.#taskCheckAndSubmit(params);
1104
- case 'discover_all_relations': return this.#taskDiscoverAllRelations(params);
1105
- case 'full_enrich': return this.#taskFullEnrich(params);
1106
- case 'quality_audit': return this.#taskQualityAudit(params);
1107
- case 'guard_full_scan': return this.#taskGuardFullScan(params);
1108
- default: throw new Error(`Unknown task: ${taskName}`);
1519
+ case 'check_and_submit':
1520
+ return this.#taskCheckAndSubmit(params);
1521
+ case 'discover_all_relations':
1522
+ return this.#taskDiscoverAllRelations(params);
1523
+ case 'full_enrich':
1524
+ return this.#taskFullEnrich(params);
1525
+ case 'quality_audit':
1526
+ return this.#taskQualityAudit(params);
1527
+ case 'guard_full_scan':
1528
+ return this.#taskGuardFullScan(params);
1529
+ default:
1530
+ throw new Error(`Unknown task: ${taskName}`);
1109
1531
  }
1110
1532
  }
1111
1533
 
@@ -1131,7 +1553,9 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1131
1553
  */
1132
1554
  async runPipeline(pipelineId, inputs = {}) {
1133
1555
  const pipeline = this.#pipelines.get(pipelineId);
1134
- if (!pipeline) throw new Error(`Pipeline '${pipelineId}' not found`);
1556
+ if (!pipeline) {
1557
+ throw new Error(`Pipeline '${pipelineId}' not found`);
1558
+ }
1135
1559
  const executor = (toolName, params) => this.executeTool(toolName, params);
1136
1560
  return pipeline.execute(executor, inputs);
1137
1561
  }
@@ -1140,7 +1564,7 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1140
1564
  * 获取已注册的管线列表
1141
1565
  */
1142
1566
  getPipelines() {
1143
- return [...this.#pipelines.values()].map(p => p.describe());
1567
+ return [...this.#pipelines.values()].map((p) => p.describe());
1144
1568
  }
1145
1569
 
1146
1570
  /**
@@ -1155,7 +1579,11 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1155
1579
  { name: 'full_enrich', description: '批量 AI 语义补全候选字段' },
1156
1580
  { name: 'quality_audit', description: '批量质量审计全部 Recipe,标记低分项' },
1157
1581
  { name: 'guard_full_scan', description: '用全部 Guard 规则扫描指定代码,生成完整报告' },
1158
- { name: 'bootstrap_full_pipeline', description: '冷启动全流程 DAG: bootstrap(纯启发式) → enrich(AI结构补齐) + loadSkill(并行) → refine(AI内容润色)' },
1582
+ {
1583
+ name: 'bootstrap_full_pipeline',
1584
+ description:
1585
+ '冷启动全流程 DAG: bootstrap(纯启发式) → enrich(AI结构补齐) + loadSkill(并行) → refine(AI内容润色)',
1586
+ },
1159
1587
  ],
1160
1588
  pipelines: this.getPipelines(),
1161
1589
  };
@@ -1177,7 +1605,7 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1177
1605
  });
1178
1606
 
1179
1607
  // Step 2: 如果有高相似度,使用 AI 分析是否真正重复
1180
- const highSim = (duplicates.similar || []).filter(d => d.similarity >= 0.7);
1608
+ const highSim = (duplicates.similar || []).filter((d) => d.similarity >= 0.7);
1181
1609
  let aiVerdict = null;
1182
1610
  if (highSim.length > 0 && this.#aiProvider) {
1183
1611
  const verdictPrompt = `以下新候选代码与已有 Recipe 高度相似,请判断是否真正重复。
@@ -1187,23 +1615,28 @@ ${submitSummary ? `已提交候选:\n${submitSummary}\n` : ''}
1187
1615
  - Code: ${(candidate.code || '').substring(0, 1000)}
1188
1616
 
1189
1617
  相似 Recipe:
1190
- ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1618
+ ${highSim.map((s) => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1191
1619
 
1192
1620
  请回答: DUPLICATE(真正重复)/ SIMILAR(相似但不同,建议保留并标注关系)/ UNIQUE(误判,可放心提交)
1193
1621
  只回答一个词。`;
1194
1622
  try {
1195
1623
  const raw = await this.#aiProvider.chat(verdictPrompt, { temperature: 0, maxTokens: 20 });
1196
1624
  aiVerdict = (raw || '').trim().toUpperCase().split(/\s/)[0];
1197
- } catch { /* ignore */ }
1625
+ } catch {
1626
+ /* ignore */
1627
+ }
1198
1628
  }
1199
1629
 
1200
1630
  return {
1201
1631
  duplicates: duplicates.similar || [],
1202
1632
  highSimilarity: highSim,
1203
1633
  aiVerdict,
1204
- recommendation: highSim.length === 0
1205
- ? 'safe_to_submit'
1206
- : aiVerdict === 'DUPLICATE' ? 'block_duplicate' : 'review_suggested',
1634
+ recommendation:
1635
+ highSim.length === 0
1636
+ ? 'safe_to_submit'
1637
+ : aiVerdict === 'DUPLICATE'
1638
+ ? 'block_duplicate'
1639
+ : 'review_suggested',
1207
1640
  };
1208
1641
  }
1209
1642
 
@@ -1214,14 +1647,27 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1214
1647
  async #taskDiscoverAllRelations({ batchSize = 20 } = {}) {
1215
1648
  const ctx = this.#getToolContext();
1216
1649
  const knowledgeService = ctx.container.get('knowledgeService');
1217
- if (!knowledgeService) throw new Error('KnowledgeService 不可用');
1650
+ if (!knowledgeService) {
1651
+ throw new Error('KnowledgeService 不可用');
1652
+ }
1218
1653
 
1219
- if (!ctx.aiProvider) throw new Error('AI Provider 未配置,请先设置 API Key');
1654
+ if (!ctx.aiProvider) {
1655
+ throw new Error('AI Provider 未配置,请先设置 API Key');
1656
+ }
1220
1657
 
1221
1658
  // 获取所有活跃知识条目
1222
- const { items = [], data = [] } = await knowledgeService.list({ lifecycle: 'active' }, { page: 1, pageSize: 500 });
1659
+ const { items = [], data = [] } = await knowledgeService.list(
1660
+ { lifecycle: 'active' },
1661
+ { page: 1, pageSize: 500 }
1662
+ );
1223
1663
  const recipes = items.length > 0 ? items : data;
1224
- if (recipes.length < 2) return { discovered: 0, totalPairs: 0, message: `只有 ${recipes.length} 条 Recipe,至少需要 2 条` };
1664
+ if (recipes.length < 2) {
1665
+ return {
1666
+ discovered: 0,
1667
+ totalPairs: 0,
1668
+ message: `只有 ${recipes.length} 条 Recipe,至少需要 2 条`,
1669
+ };
1670
+ }
1225
1671
 
1226
1672
  // 按 batch 分组分析
1227
1673
  const pairs = [];
@@ -1241,14 +1687,28 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1241
1687
  try {
1242
1688
  const result = await this.executeTool('discover_relations', {
1243
1689
  recipePairs: batch.map(([a, b]) => ({
1244
- a: { id: a.id, title: a.title, category: a.category, language: a.language, code: String(a.content || a.code || '').substring(0, 500) },
1245
- b: { id: b.id, title: b.title, category: b.category, language: b.language, code: String(b.content || b.code || '').substring(0, 500) },
1690
+ a: {
1691
+ id: a.id,
1692
+ title: a.title,
1693
+ category: a.category,
1694
+ language: a.language,
1695
+ code: String(a.content || a.code || '').substring(0, 500),
1696
+ },
1697
+ b: {
1698
+ id: b.id,
1699
+ title: b.title,
1700
+ category: b.category,
1701
+ language: b.language,
1702
+ code: String(b.content || b.code || '').substring(0, 500),
1703
+ },
1246
1704
  })),
1247
1705
  });
1248
1706
 
1249
1707
  if (result.error) {
1250
1708
  batchErrors++;
1251
- this.#logger.warn(`[DiscoverRelations] Batch ${Math.floor(b / batchSize) + 1} error: ${result.error}`);
1709
+ this.#logger.warn(
1710
+ `[DiscoverRelations] Batch ${Math.floor(b / batchSize) + 1} error: ${result.error}`
1711
+ );
1252
1712
  continue;
1253
1713
  }
1254
1714
  if (result.relations) {
@@ -1257,7 +1717,9 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1257
1717
  }
1258
1718
  } catch (err) {
1259
1719
  batchErrors++;
1260
- this.#logger.warn(`[DiscoverRelations] Batch ${Math.floor(b / batchSize) + 1} threw: ${err.message}`);
1720
+ this.#logger.warn(
1721
+ `[DiscoverRelations] Batch ${Math.floor(b / batchSize) + 1} threw: ${err.message}`
1722
+ );
1261
1723
  }
1262
1724
  }
1263
1725
 
@@ -1278,21 +1740,26 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1278
1740
  const knowledgeService = ctx.container.get('knowledgeService');
1279
1741
 
1280
1742
  const { items = [], data = [] } = await knowledgeService.list(
1281
- { lifecycle: status }, { page: 1, pageSize: maxCount }
1743
+ { lifecycle: status },
1744
+ { page: 1, pageSize: maxCount }
1282
1745
  );
1283
1746
  const candidates = items.length > 0 ? items : data;
1284
- if (candidates.length === 0) return { enriched: 0, message: 'No candidates to enrich' };
1747
+ if (candidates.length === 0) {
1748
+ return { enriched: 0, message: 'No candidates to enrich' };
1749
+ }
1285
1750
 
1286
1751
  // 筛选缺失语义字段的候选
1287
- const needEnrich = candidates.filter(c => {
1752
+ const needEnrich = candidates.filter((c) => {
1288
1753
  const m = c.metadata || {};
1289
1754
  return !m.rationale || !m.knowledgeType || !m.complexity;
1290
1755
  });
1291
1756
 
1292
- if (needEnrich.length === 0) return { enriched: 0, message: 'All candidates already enriched' };
1757
+ if (needEnrich.length === 0) {
1758
+ return { enriched: 0, message: 'All candidates already enriched' };
1759
+ }
1293
1760
 
1294
1761
  const result = await this.executeTool('enrich_candidate', {
1295
- candidateIds: needEnrich.map(c => c.id).slice(0, 20),
1762
+ candidateIds: needEnrich.map((c) => c.id).slice(0, 20),
1296
1763
  });
1297
1764
 
1298
1765
  return result;
@@ -1307,17 +1774,22 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1307
1774
  const knowledgeService = ctx.container.get('knowledgeService');
1308
1775
 
1309
1776
  const { items = [], data = [] } = await knowledgeService.list(
1310
- { lifecycle: 'active' }, { page: 1, pageSize: maxCount }
1777
+ { lifecycle: 'active' },
1778
+ { page: 1, pageSize: maxCount }
1311
1779
  );
1312
1780
  const recipes = items.length > 0 ? items : data;
1313
- if (recipes.length === 0) return { total: 0, lowQuality: [], message: 'No active recipes' };
1781
+ if (recipes.length === 0) {
1782
+ return { total: 0, lowQuality: [], message: 'No active recipes' };
1783
+ }
1314
1784
 
1315
1785
  const lowQuality = [];
1316
1786
  const gradeDistribution = { A: 0, B: 0, C: 0, D: 0, F: 0 };
1317
1787
 
1318
1788
  for (const recipe of recipes) {
1319
1789
  const scoreResult = await this.executeTool('quality_score', { recipe });
1320
- if (scoreResult.grade) gradeDistribution[scoreResult.grade] = (gradeDistribution[scoreResult.grade] || 0) + 1;
1790
+ if (scoreResult.grade) {
1791
+ gradeDistribution[scoreResult.grade] = (gradeDistribution[scoreResult.grade] || 0) + 1;
1792
+ }
1321
1793
  if (scoreResult.score < threshold) {
1322
1794
  lowQuality.push({
1323
1795
  id: recipe.id,
@@ -1345,11 +1817,15 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1345
1817
  * 对代码运行全部 Guard 规则 + 生成修复建议
1346
1818
  */
1347
1819
  async #taskGuardFullScan({ code, language, filePath } = {}) {
1348
- if (!code) return { error: 'code is required' };
1820
+ if (!code) {
1821
+ return { error: 'code is required' };
1822
+ }
1349
1823
 
1350
1824
  // Step 1: 静态检查
1351
1825
  const checkResult = await this.executeTool('guard_check_code', {
1352
- code, language: language || 'unknown', scope: 'project',
1826
+ code,
1827
+ language: language || 'unknown',
1828
+ scope: 'project',
1353
1829
  });
1354
1830
 
1355
1831
  // Step 2: 如果有违规且 AI 可用,生成修复建议
@@ -1358,7 +1834,10 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1358
1834
  try {
1359
1835
  const violationSummary = (checkResult.violations || [])
1360
1836
  .slice(0, 5)
1361
- .map(v => `- [${v.severity}] ${v.message || v.ruleName} (line ${v.line || v.matches?.[0]?.line || '?'})`)
1837
+ .map(
1838
+ (v) =>
1839
+ `- [${v.severity}] ${v.message || v.ruleName} (line ${v.line || v.matches?.[0]?.line || '?'})`
1840
+ )
1362
1841
  .join('\n');
1363
1842
 
1364
1843
  const prompt = `以下代码存在 Guard 规则违规。请为每个违规提供修复建议。
@@ -1373,10 +1852,15 @@ ${code.substring(0, 3000)}
1373
1852
 
1374
1853
  请用 JSON 数组格式返回建议: [{"violation": "...", "suggestion": "...", "fixExample": "..."}]`;
1375
1854
 
1376
- suggestions = await this.#aiProvider.chatWithStructuredOutput(prompt, {
1377
- openChar: '[', closeChar: ']', temperature: 0.3,
1378
- }) || [];
1379
- } catch { /* AI suggestions optional */ }
1855
+ suggestions =
1856
+ (await this.#aiProvider.chatWithStructuredOutput(prompt, {
1857
+ openChar: '[',
1858
+ closeChar: ']',
1859
+ temperature: 0.3,
1860
+ })) || [];
1861
+ } catch {
1862
+ /* AI suggestions optional */
1863
+ }
1380
1864
  }
1381
1865
 
1382
1866
  return {
@@ -1403,18 +1887,20 @@ ${code.substring(0, 3000)}
1403
1887
  // ── bootstrap_full_pipeline (v6 简化版) ──────────────────
1404
1888
  // 只做启发式 Phase 1-5.5 (含 ChatAgent per-dimension production)
1405
1889
  // 不再需要 enrich/refine 后置步骤
1406
- this.registerPipeline(new TaskPipeline('bootstrap_full_pipeline', [
1407
- {
1408
- name: 'bootstrap',
1409
- tool: 'bootstrap_knowledge',
1410
- params: {
1411
- maxFiles: (ctx) => ctx._inputs.maxFiles || 500,
1412
- skipGuard: (ctx) => ctx._inputs.skipGuard || false,
1413
- contentMaxLines: (ctx) => ctx._inputs.contentMaxLines || 120,
1414
- loadSkills: true,
1890
+ this.registerPipeline(
1891
+ new TaskPipeline('bootstrap_full_pipeline', [
1892
+ {
1893
+ name: 'bootstrap',
1894
+ tool: 'bootstrap_knowledge',
1895
+ params: {
1896
+ maxFiles: (ctx) => ctx._inputs.maxFiles || 500,
1897
+ skipGuard: (ctx) => ctx._inputs.skipGuard || false,
1898
+ contentMaxLines: (ctx) => ctx._inputs.contentMaxLines || 120,
1899
+ loadSkills: true,
1900
+ },
1415
1901
  },
1416
- },
1417
- ]));
1902
+ ])
1903
+ );
1418
1904
  }
1419
1905
 
1420
1906
  // ─── Native Tool Calling 内部方法 ──────────────────────
@@ -1434,9 +1920,11 @@ ${code.substring(0, 3000)}
1434
1920
  let soulSection = '';
1435
1921
  try {
1436
1922
  if (fs.existsSync(SOUL_PATH)) {
1437
- soulSection = '\n' + fs.readFileSync(SOUL_PATH, 'utf-8').trim() + '\n';
1923
+ soulSection = `\n${fs.readFileSync(SOUL_PATH, 'utf-8').trim()}\n`;
1438
1924
  }
1439
- } catch { /* SOUL.md not available */ }
1925
+ } catch {
1926
+ /* SOUL.md not available */
1927
+ }
1440
1928
 
1441
1929
  return `${soulSection}
1442
1930
  你是 AutoSnippet 项目的统一 AI 中心。项目内所有 AI 推理和分析都通过你执行。
@@ -1446,8 +1934,7 @@ ${this.#projectBriefingCache}${this.#memory?.toPromptSection({ source: 'user' })
1446
1934
  1. 当需要查询数据时,直接调用相应工具。
1447
1935
  2. 工具参数严格按照工具声明中的 schema 传递。
1448
1936
  3. 对于代码分析任务,先 search_project_code 搜索,再 read_project_file 读取。
1449
- 4. 回答时使用用户的语言(中文/英文)。
1450
- 5. 当工具返回错误时,尝试不同参数或方法。`;
1937
+ 4. 当工具返回错误时,尝试不同参数或方法。`;
1451
1938
  }
1452
1939
 
1453
1940
  // Bootstrap 系统模式: LLM 以领域大脑的能力处理任务
@@ -1490,7 +1977,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1490
1977
  * 清理最终回答(去除 Thought/preamble + MEMORY 标签)
1491
1978
  */
1492
1979
  #cleanFinalAnswer(response) {
1493
- if (!response) return '';
1980
+ if (!response) {
1981
+ return '';
1982
+ }
1494
1983
  return response
1495
1984
  .replace(/^(Final Answer|最终回答|Answer)\s*[::]\s*/i, '')
1496
1985
  .replace(/\[MEMORY:\w+\]\s*[\s\S]*?\s*\[\/MEMORY\]/g, '')
@@ -1537,9 +2026,10 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1537
2026
  */
1538
2027
  #loadSkillsFromDir(dir, skillMap) {
1539
2028
  try {
1540
- const dirs = fs.readdirSync(dir, { withFileTypes: true })
1541
- .filter(d => d.isDirectory())
1542
- .map(d => d.name);
2029
+ const dirs = fs
2030
+ .readdirSync(dir, { withFileTypes: true })
2031
+ .filter((d) => d.isDirectory())
2032
+ .map((d) => d.name);
1543
2033
  for (const name of dirs) {
1544
2034
  const skillPath = path.join(dir, name, 'SKILL.md');
1545
2035
  let summary = '';
@@ -1553,15 +2043,19 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1553
2043
  for (const line of lines) {
1554
2044
  const trimmed = line.trim();
1555
2045
  if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('---')) {
1556
- summary = trimmed.length > 80 ? trimmed.substring(0, 80) + '...' : trimmed;
2046
+ summary = trimmed.length > 80 ? `${trimmed.substring(0, 80)}...` : trimmed;
1557
2047
  break;
1558
2048
  }
1559
2049
  }
1560
2050
  }
1561
- } catch { /* SKILL.md not found */ }
2051
+ } catch {
2052
+ /* SKILL.md not found */
2053
+ }
1562
2054
  skillMap.set(name, { name, summary });
1563
2055
  }
1564
- } catch { /* directory not found */ }
2056
+ } catch {
2057
+ /* directory not found */
2058
+ }
1565
2059
  }
1566
2060
 
1567
2061
  /**
@@ -1571,13 +2065,16 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1571
2065
  async #buildProjectBriefing() {
1572
2066
  try {
1573
2067
  const db = this.#container?.get('database');
1574
- if (!db) return '';
2068
+ if (!db) {
2069
+ return '';
2070
+ }
1575
2071
  // knowledgeType → kind 映射:
1576
2072
  // rule: code-standard, code-style, best-practice, boundary-constraint
1577
2073
  // pattern: code-pattern, architecture, solution
1578
2074
  // fact: code-relation, inheritance, call-chain, data-flow, module-dependency
1579
2075
  // V3: knowledge_entries 统一表(candidates 已合并,lifecycle 替代 status)
1580
- const stats = db.prepare(`
2076
+ const stats = db
2077
+ .prepare(`
1581
2078
  SELECT
1582
2079
  (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active') as recipeCount,
1583
2080
  (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledgeType IN ('code-standard','code-style','best-practice','boundary-constraint')) as ruleCount,
@@ -1586,7 +2083,8 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1586
2083
  (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledgeType = 'boundary-constraint') as guardRuleCount,
1587
2084
  (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'pending') as pendingCandidates,
1588
2085
  (SELECT COUNT(*) FROM knowledge_entries) as totalCandidates
1589
- `).get();
2086
+ `)
2087
+ .get();
1590
2088
  if (!stats || stats.recipeCount === 0) {
1591
2089
  return '\n## 项目状态\n⚠️ 知识库为空。建议先执行冷启动(bootstrap_knowledge)。\n';
1592
2090
  }
@@ -1608,11 +2106,17 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1608
2106
  try {
1609
2107
  const db = container?.get?.('database');
1610
2108
  if (db) {
1611
- import('./ProjectSemanticMemory.js').then(({ ProjectSemanticMemory }) => {
1612
- this.#semanticMemory = new ProjectSemanticMemory(db, { logger: this.#logger });
1613
- }).catch(() => { /* Semantic Memory not available */ });
2109
+ import('./ProjectSemanticMemory.js')
2110
+ .then(({ ProjectSemanticMemory }) => {
2111
+ this.#semanticMemory = new ProjectSemanticMemory(db, { logger: this.#logger });
2112
+ })
2113
+ .catch(() => {
2114
+ /* Semantic Memory not available */
2115
+ });
1614
2116
  }
1615
- } catch { /* container.get failed, degrade silently */ }
2117
+ } catch {
2118
+ /* container.get failed, degrade silently */
2119
+ }
1616
2120
  }
1617
2121
 
1618
2122
  /**
@@ -1625,7 +2129,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1625
2129
  * source 隔离: 标记 memory 来源,避免系统分析污染用户记忆
1626
2130
  */
1627
2131
  #extractMemory(prompt, reply) {
1628
- if (!this.#memory && !this.#semanticMemory) return;
2132
+ if (!this.#memory && !this.#semanticMemory) {
2133
+ return;
2134
+ }
1629
2135
  const source = this.#currentSource || 'user';
1630
2136
 
1631
2137
  try {
@@ -1638,7 +2144,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1638
2144
  /remember\s+(to|that)/i,
1639
2145
  /our\s+(convention|standard|rule)\s+is/i,
1640
2146
  ];
1641
- if (prefPatterns.some(p => p.test(prompt))) {
2147
+ if (prefPatterns.some((p) => p.test(prompt))) {
1642
2148
  const entry = {
1643
2149
  type: 'preference',
1644
2150
  content: prompt.substring(0, 200),
@@ -1656,7 +2162,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1656
2162
  /let'?s\s+(go\s+with|use|adopt)/i,
1657
2163
  /approved|confirmed|decided/i,
1658
2164
  ];
1659
- if (decisionPatterns.some(p => p.test(prompt))) {
2165
+ if (decisionPatterns.some((p) => p.test(prompt))) {
1660
2166
  const entry = {
1661
2167
  type: 'decision',
1662
2168
  content: prompt.substring(0, 200),
@@ -1687,7 +2193,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1687
2193
  }
1688
2194
  }
1689
2195
  }
1690
- } catch { /* memory write failure is non-critical */ }
2196
+ } catch {
2197
+ /* memory write failure is non-critical */
2198
+ }
1691
2199
  }
1692
2200
 
1693
2201
  /**
@@ -1695,7 +2203,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1695
2203
  * 当对话消息数超过 12 条时触发 AI 摘要压缩
1696
2204
  */
1697
2205
  async #autoSummarize(conversationId) {
1698
- if (!this.#conversations || !this.#aiProvider) return;
2206
+ if (!this.#conversations || !this.#aiProvider) {
2207
+ return;
2208
+ }
1699
2209
  try {
1700
2210
  const messages = this.#conversations.load(conversationId, { tokenBudget: Infinity });
1701
2211
  if (messages.length >= 12) {
@@ -1735,17 +2245,23 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1735
2245
  * 截断长文本
1736
2246
  */
1737
2247
  #truncate(text, maxLen = 4000) {
1738
- if (!text || text.length <= maxLen) return text;
1739
- return text.substring(0, maxLen) + `\n...(truncated, ${text.length - maxLen} chars omitted)`;
2248
+ if (!text || text.length <= maxLen) {
2249
+ return text;
2250
+ }
2251
+ return `${text.substring(0, maxLen)}\n...(truncated, ${text.length - maxLen} chars omitted)`;
1740
2252
  }
1741
2253
 
1742
2254
  /**
1743
2255
  * 精简工具结果(避免过长的 observation)
1744
2256
  */
1745
2257
  #summarizeResult(result) {
1746
- if (!result) return null;
2258
+ if (!result) {
2259
+ return null;
2260
+ }
1747
2261
  const str = typeof result === 'string' ? result : JSON.stringify(result);
1748
- if (str.length <= 500) return result;
2262
+ if (str.length <= 500) {
2263
+ return result;
2264
+ }
1749
2265
  // 返回截断版
1750
2266
  if (typeof result === 'object') {
1751
2267
  if (Array.isArray(result)) {
@@ -1757,7 +2273,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1757
2273
  for (const k of keys) {
1758
2274
  const v = result[k];
1759
2275
  if (typeof v === 'string' && v.length > 200) {
1760
- summary[k] = v.substring(0, 200) + '...';
2276
+ summary[k] = `${v.substring(0, 200)}...`;
1761
2277
  } else if (Array.isArray(v)) {
1762
2278
  summary[k] = { _count: v.length, first2: v.slice(0, 2) };
1763
2279
  } else {