autosnippet 3.0.1 → 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 +655 -260
  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, prompt,
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,13 +1199,23 @@ export class ChatAgent {
950
1199
  * 强制退出后的摘要生成 — 独立方法,避免主循环代码膨胀
951
1200
  * @private
952
1201
  */
953
- async #produceForcedSummary({ source, toolCalls, toolSchemas, ctx, phaseRouter, execStartTime, prompt }) {
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
1212
  const isSystem = source === 'system';
956
- this.#logger.info(`[ChatAgent] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls, source=${source})`);
1213
+ this.#logger.info(
1214
+ `[ChatAgent] ⚠ producing forced summary (${iterations} iters, ${toolCalls.length} calls, source=${source})`
1215
+ );
957
1216
 
958
- const candidateCount = toolCalls.filter(tc =>
959
- 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'
960
1219
  ).length;
961
1220
 
962
1221
  let finalReply;
@@ -964,12 +1223,14 @@ export class ChatAgent {
964
1223
  // 如果熔断器已打开,跳过 AI 调用直接合成摘要(避免无用的失败 + 计数累积)
965
1224
  const isCircuitOpen = this.#aiProvider._circuitState === 'OPEN';
966
1225
  if (isCircuitOpen) {
967
- this.#logger.warn(`[ChatAgent] circuit breaker is OPEN — skipping AI summary, using synthetic ${isSystem ? 'digest' : 'summary'}`);
1226
+ this.#logger.warn(
1227
+ `[ChatAgent] circuit breaker is OPEN — skipping AI summary, using synthetic ${isSystem ? 'digest' : 'summary'}`
1228
+ );
968
1229
  }
969
1230
 
970
1231
  // ── 收集工具调用摘要(user / system 共用) ──
971
1232
  const submitSummary = toolCalls
972
- .filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
1233
+ .filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
973
1234
  .map((tc, i) => `${i + 1}. ${tc.params?.title || tc.params?.category || 'untitled'}`)
974
1235
  .join('\n');
975
1236
 
@@ -977,7 +1238,9 @@ export class ChatAgent {
977
1238
  const toolContextSummary = isSystem ? '' : this.#buildToolContextForUserSummary(toolCalls);
978
1239
 
979
1240
  try {
980
- if (isCircuitOpen) throw new Error('circuit open — skip to synthetic summary');
1241
+ if (isCircuitOpen) {
1242
+ throw new Error('circuit open — skip to synthetic summary');
1243
+ }
981
1244
 
982
1245
  let summaryPrompt;
983
1246
  let systemPrompt;
@@ -1018,21 +1281,19 @@ ${toolContextSummary}
1018
1281
  - 关键发现用列表项罗列
1019
1282
  - 如果发现了架构模式或最佳实践,用简短代码块举例
1020
1283
  - 语言自然流畅,像一份技术分析报告`;
1021
- systemPrompt = '你是项目分析助手。请用纯 Markdown 格式输出结构清晰的分析总结,只输出人类可读的自然语言文档,不要输出 JSON 格式的数据。';
1284
+ systemPrompt =
1285
+ '你是项目分析助手。请用纯 Markdown 格式输出结构清晰的分析总结,只输出人类可读的自然语言文档,不要输出 JSON 格式的数据。';
1022
1286
  }
1023
1287
 
1024
1288
  // 用空 messages 避免累积上下文导致 400
1025
- const summaryResult = await this.#aiProvider.chatWithTools(
1026
- summaryPrompt,
1027
- {
1028
- messages: [],
1029
- toolSchemas,
1030
- toolChoice: 'none',
1031
- systemPrompt,
1032
- temperature: isSystem ? 0.3 : 0.5,
1033
- maxTokens: 8192,
1034
- },
1035
- );
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
+ });
1036
1297
  // 累计 token 用量
1037
1298
  if (summaryResult.usage) {
1038
1299
  this.#currentTokenUsage.input += summaryResult.usage.inputTokens || 0;
@@ -1045,8 +1306,8 @@ ${toolContextSummary}
1045
1306
  if (isSystem) {
1046
1307
  // ── system 源兜底: 合成 dimensionDigest JSON ──
1047
1308
  const titles = toolCalls
1048
- .filter(tc => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
1049
- .map(tc => tc.params?.title || 'untitled');
1309
+ .filter((tc) => tc.tool === 'submit_knowledge' || tc.tool === 'submit_with_check')
1310
+ .map((tc) => tc.params?.title || 'untitled');
1050
1311
  finalReply = `\`\`\`json
1051
1312
  {
1052
1313
  "dimensionDigest": {
@@ -1060,35 +1321,41 @@ ${toolContextSummary}
1060
1321
  \`\`\``;
1061
1322
  } else {
1062
1323
  // ── user 源兜底: 合成人类可读的 Markdown 摘要 ──
1063
- const toolNames = [...new Set(toolCalls.map(tc => tc.tool))];
1324
+ const toolNames = [...new Set(toolCalls.map((tc) => tc.tool))];
1064
1325
  const filesRead = toolCalls
1065
- .filter(tc => tc.tool === 'read_project_file')
1066
- .flatMap(tc => {
1067
- if (tc.params?.filePaths) return tc.params.filePaths;
1068
- if (tc.params?.filePath) return [tc.params.filePath];
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
+ }
1069
1334
  return [];
1070
1335
  })
1071
1336
  .slice(0, 10);
1072
1337
  const searches = toolCalls
1073
- .filter(tc => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code')
1074
- .map(tc => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
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)
1075
1340
  .filter(Boolean)
1076
1341
  .slice(0, 5);
1077
1342
 
1078
1343
  finalReply = `## 分析总结\n\n通过 **${toolCalls.length} 次工具调用**探索了项目代码。\n\n`;
1079
1344
  if (searches.length > 0) {
1080
- finalReply += `### 搜索的关键词\n${searches.map(s => `- \`${s}\``).join('\n')}\n\n`;
1345
+ finalReply += `### 搜索的关键词\n${searches.map((s) => `- \`${s}\``).join('\n')}\n\n`;
1081
1346
  }
1082
1347
  if (filesRead.length > 0) {
1083
- finalReply += `### 读取的文件\n${filesRead.map(f => `- \`${f}\``).join('\n')}\n\n`;
1348
+ finalReply += `### 读取的文件\n${filesRead.map((f) => `- \`${f}\``).join('\n')}\n\n`;
1084
1349
  }
1085
- finalReply += `### 使用的工具\n${toolNames.map(t => `- ${t}`).join('\n')}\n\n`;
1350
+ finalReply += `### 使用的工具\n${toolNames.map((t) => `- ${t}`).join('\n')}\n\n`;
1086
1351
  finalReply += `> ⚠️ AI 服务异常,未能生成完整分析。请稍后重试或缩小分析范围。`;
1087
1352
  }
1088
1353
  }
1089
1354
 
1090
1355
  const totalDuration = Date.now() - execStartTime;
1091
- 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
+ );
1092
1359
  return { reply: finalReply, toolCalls, hasContext: toolCalls.length > 0 };
1093
1360
  }
1094
1361
 
@@ -1100,55 +1367,79 @@ ${toolContextSummary}
1100
1367
  const sections = [];
1101
1368
 
1102
1369
  // 目录结构探索
1103
- const structureCalls = toolCalls.filter(tc => tc.tool === 'list_project_structure');
1370
+ const structureCalls = toolCalls.filter((tc) => tc.tool === 'list_project_structure');
1104
1371
  if (structureCalls.length > 0) {
1105
- const dirs = structureCalls.map(tc => tc.params?.directory || '/').slice(0, 5);
1106
- sections.push(`**目录探索**: ${dirs.map(d => `\`${d}\``).join(', ')}`);
1372
+ const dirs = structureCalls.map((tc) => tc.params?.directory || '/').slice(0, 5);
1373
+ sections.push(`**目录探索**: ${dirs.map((d) => `\`${d}\``).join(', ')}`);
1107
1374
  }
1108
1375
 
1109
1376
  // 项目概况
1110
- const overviewCalls = toolCalls.filter(tc => tc.tool === 'get_project_overview');
1377
+ const overviewCalls = toolCalls.filter((tc) => tc.tool === 'get_project_overview');
1111
1378
  if (overviewCalls.length > 0) {
1112
1379
  sections.push('**项目概况**: 已获取');
1113
1380
  }
1114
1381
 
1115
1382
  // 代码搜索
1116
- const searchCalls = toolCalls.filter(tc => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code');
1383
+ const searchCalls = toolCalls.filter(
1384
+ (tc) => tc.tool === 'search_project_code' || tc.tool === 'semantic_search_code'
1385
+ );
1117
1386
  if (searchCalls.length > 0) {
1118
1387
  const queries = searchCalls
1119
- .map(tc => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
1388
+ .map((tc) => tc.params?.patterns?.[0] || tc.params?.query || tc.params?.pattern)
1120
1389
  .filter(Boolean)
1121
1390
  .slice(0, 8);
1122
- sections.push(`**代码搜索** (${searchCalls.length} 次): ${queries.map(q => `\`${q}\``).join(', ')}`);
1391
+ sections.push(
1392
+ `**代码搜索** (${searchCalls.length} 次): ${queries.map((q) => `\`${q}\``).join(', ')}`
1393
+ );
1123
1394
  }
1124
1395
 
1125
1396
  // 文件读取
1126
- const readCalls = toolCalls.filter(tc => tc.tool === 'read_project_file');
1397
+ const readCalls = toolCalls.filter((tc) => tc.tool === 'read_project_file');
1127
1398
  if (readCalls.length > 0) {
1128
1399
  const files = readCalls
1129
- .flatMap(tc => {
1130
- if (tc.params?.filePaths) return tc.params.filePaths;
1131
- if (tc.params?.filePath) return [tc.params.filePath];
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
+ }
1132
1407
  return [];
1133
1408
  })
1134
1409
  .slice(0, 10);
1135
- sections.push(`**文件读取** (${readCalls.length} 次): ${files.map(f => `\`${f}\``).join(', ')}`);
1410
+ sections.push(
1411
+ `**文件读取** (${readCalls.length} 次): ${files.map((f) => `\`${f}\``).join(', ')}`
1412
+ );
1136
1413
  }
1137
1414
 
1138
1415
  // AST 分析
1139
- const astCalls = toolCalls.filter(tc =>
1140
- ['get_class_hierarchy', 'get_class_info', 'get_protocol_info', 'get_method_overrides', 'get_category_map'].includes(tc.tool)
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)
1141
1424
  );
1142
1425
  if (astCalls.length > 0) {
1143
1426
  const entities = astCalls
1144
- .map(tc => tc.params?.className || tc.params?.name || tc.params?.protocolName || tc.params?.rootClass)
1427
+ .map(
1428
+ (tc) =>
1429
+ tc.params?.className ||
1430
+ tc.params?.name ||
1431
+ tc.params?.protocolName ||
1432
+ tc.params?.rootClass
1433
+ )
1145
1434
  .filter(Boolean)
1146
1435
  .slice(0, 5);
1147
- sections.push(`**AST 结构分析** (${astCalls.length} 次): ${entities.map(e => `\`${e}\``).join(', ')}`);
1436
+ sections.push(
1437
+ `**AST 结构分析** (${astCalls.length} 次): ${entities.map((e) => `\`${e}\``).join(', ')}`
1438
+ );
1148
1439
  }
1149
1440
 
1150
1441
  // 知识库搜索
1151
- const kbCalls = toolCalls.filter(tc =>
1442
+ const kbCalls = toolCalls.filter((tc) =>
1152
1443
  ['search_knowledge', 'search_recipes', 'knowledge_overview'].includes(tc.tool)
1153
1444
  );
1154
1445
  if (kbCalls.length > 0) {
@@ -1185,7 +1476,9 @@ ${toolContextSummary}
1185
1476
  * @returns {string} conversationId
1186
1477
  */
1187
1478
  createConversation({ category = 'user', title = '' } = {}) {
1188
- if (!this.#conversations) return null;
1479
+ if (!this.#conversations) {
1480
+ return null;
1481
+ }
1189
1482
  return this.#conversations.create({ category, title });
1190
1483
  }
1191
1484
 
@@ -1197,7 +1490,9 @@ ${toolContextSummary}
1197
1490
  * @returns {Array}
1198
1491
  */
1199
1492
  getConversations({ category, limit = 20 } = {}) {
1200
- if (!this.#conversations) return [];
1493
+ if (!this.#conversations) {
1494
+ return [];
1495
+ }
1201
1496
  return this.#conversations.list({ category, limit });
1202
1497
  }
1203
1498
 
@@ -1221,12 +1516,18 @@ ${toolContextSummary}
1221
1516
  }
1222
1517
  // 降级到硬编码任务(复杂交互逻辑无法用 DAG 表达的场景)
1223
1518
  switch (taskName) {
1224
- case 'check_and_submit': return this.#taskCheckAndSubmit(params);
1225
- case 'discover_all_relations': return this.#taskDiscoverAllRelations(params);
1226
- case 'full_enrich': return this.#taskFullEnrich(params);
1227
- case 'quality_audit': return this.#taskQualityAudit(params);
1228
- case 'guard_full_scan': return this.#taskGuardFullScan(params);
1229
- 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}`);
1230
1531
  }
1231
1532
  }
1232
1533
 
@@ -1252,7 +1553,9 @@ ${toolContextSummary}
1252
1553
  */
1253
1554
  async runPipeline(pipelineId, inputs = {}) {
1254
1555
  const pipeline = this.#pipelines.get(pipelineId);
1255
- if (!pipeline) throw new Error(`Pipeline '${pipelineId}' not found`);
1556
+ if (!pipeline) {
1557
+ throw new Error(`Pipeline '${pipelineId}' not found`);
1558
+ }
1256
1559
  const executor = (toolName, params) => this.executeTool(toolName, params);
1257
1560
  return pipeline.execute(executor, inputs);
1258
1561
  }
@@ -1261,7 +1564,7 @@ ${toolContextSummary}
1261
1564
  * 获取已注册的管线列表
1262
1565
  */
1263
1566
  getPipelines() {
1264
- return [...this.#pipelines.values()].map(p => p.describe());
1567
+ return [...this.#pipelines.values()].map((p) => p.describe());
1265
1568
  }
1266
1569
 
1267
1570
  /**
@@ -1276,7 +1579,11 @@ ${toolContextSummary}
1276
1579
  { name: 'full_enrich', description: '批量 AI 语义补全候选字段' },
1277
1580
  { name: 'quality_audit', description: '批量质量审计全部 Recipe,标记低分项' },
1278
1581
  { name: 'guard_full_scan', description: '用全部 Guard 规则扫描指定代码,生成完整报告' },
1279
- { 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
+ },
1280
1587
  ],
1281
1588
  pipelines: this.getPipelines(),
1282
1589
  };
@@ -1298,7 +1605,7 @@ ${toolContextSummary}
1298
1605
  });
1299
1606
 
1300
1607
  // Step 2: 如果有高相似度,使用 AI 分析是否真正重复
1301
- const highSim = (duplicates.similar || []).filter(d => d.similarity >= 0.7);
1608
+ const highSim = (duplicates.similar || []).filter((d) => d.similarity >= 0.7);
1302
1609
  let aiVerdict = null;
1303
1610
  if (highSim.length > 0 && this.#aiProvider) {
1304
1611
  const verdictPrompt = `以下新候选代码与已有 Recipe 高度相似,请判断是否真正重复。
@@ -1308,23 +1615,28 @@ ${toolContextSummary}
1308
1615
  - Code: ${(candidate.code || '').substring(0, 1000)}
1309
1616
 
1310
1617
  相似 Recipe:
1311
- ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1618
+ ${highSim.map((s) => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1312
1619
 
1313
1620
  请回答: DUPLICATE(真正重复)/ SIMILAR(相似但不同,建议保留并标注关系)/ UNIQUE(误判,可放心提交)
1314
1621
  只回答一个词。`;
1315
1622
  try {
1316
1623
  const raw = await this.#aiProvider.chat(verdictPrompt, { temperature: 0, maxTokens: 20 });
1317
1624
  aiVerdict = (raw || '').trim().toUpperCase().split(/\s/)[0];
1318
- } catch { /* ignore */ }
1625
+ } catch {
1626
+ /* ignore */
1627
+ }
1319
1628
  }
1320
1629
 
1321
1630
  return {
1322
1631
  duplicates: duplicates.similar || [],
1323
1632
  highSimilarity: highSim,
1324
1633
  aiVerdict,
1325
- recommendation: highSim.length === 0
1326
- ? 'safe_to_submit'
1327
- : 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',
1328
1640
  };
1329
1641
  }
1330
1642
 
@@ -1335,14 +1647,27 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1335
1647
  async #taskDiscoverAllRelations({ batchSize = 20 } = {}) {
1336
1648
  const ctx = this.#getToolContext();
1337
1649
  const knowledgeService = ctx.container.get('knowledgeService');
1338
- if (!knowledgeService) throw new Error('KnowledgeService 不可用');
1650
+ if (!knowledgeService) {
1651
+ throw new Error('KnowledgeService 不可用');
1652
+ }
1339
1653
 
1340
- if (!ctx.aiProvider) throw new Error('AI Provider 未配置,请先设置 API Key');
1654
+ if (!ctx.aiProvider) {
1655
+ throw new Error('AI Provider 未配置,请先设置 API Key');
1656
+ }
1341
1657
 
1342
1658
  // 获取所有活跃知识条目
1343
- 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
+ );
1344
1663
  const recipes = items.length > 0 ? items : data;
1345
- 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
+ }
1346
1671
 
1347
1672
  // 按 batch 分组分析
1348
1673
  const pairs = [];
@@ -1362,14 +1687,28 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1362
1687
  try {
1363
1688
  const result = await this.executeTool('discover_relations', {
1364
1689
  recipePairs: batch.map(([a, b]) => ({
1365
- a: { id: a.id, title: a.title, category: a.category, language: a.language, code: String(a.content || a.code || '').substring(0, 500) },
1366
- 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
+ },
1367
1704
  })),
1368
1705
  });
1369
1706
 
1370
1707
  if (result.error) {
1371
1708
  batchErrors++;
1372
- 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
+ );
1373
1712
  continue;
1374
1713
  }
1375
1714
  if (result.relations) {
@@ -1378,7 +1717,9 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1378
1717
  }
1379
1718
  } catch (err) {
1380
1719
  batchErrors++;
1381
- 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
+ );
1382
1723
  }
1383
1724
  }
1384
1725
 
@@ -1399,21 +1740,26 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1399
1740
  const knowledgeService = ctx.container.get('knowledgeService');
1400
1741
 
1401
1742
  const { items = [], data = [] } = await knowledgeService.list(
1402
- { lifecycle: status }, { page: 1, pageSize: maxCount }
1743
+ { lifecycle: status },
1744
+ { page: 1, pageSize: maxCount }
1403
1745
  );
1404
1746
  const candidates = items.length > 0 ? items : data;
1405
- 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
+ }
1406
1750
 
1407
1751
  // 筛选缺失语义字段的候选
1408
- const needEnrich = candidates.filter(c => {
1752
+ const needEnrich = candidates.filter((c) => {
1409
1753
  const m = c.metadata || {};
1410
1754
  return !m.rationale || !m.knowledgeType || !m.complexity;
1411
1755
  });
1412
1756
 
1413
- 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
+ }
1414
1760
 
1415
1761
  const result = await this.executeTool('enrich_candidate', {
1416
- candidateIds: needEnrich.map(c => c.id).slice(0, 20),
1762
+ candidateIds: needEnrich.map((c) => c.id).slice(0, 20),
1417
1763
  });
1418
1764
 
1419
1765
  return result;
@@ -1428,17 +1774,22 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1428
1774
  const knowledgeService = ctx.container.get('knowledgeService');
1429
1775
 
1430
1776
  const { items = [], data = [] } = await knowledgeService.list(
1431
- { lifecycle: 'active' }, { page: 1, pageSize: maxCount }
1777
+ { lifecycle: 'active' },
1778
+ { page: 1, pageSize: maxCount }
1432
1779
  );
1433
1780
  const recipes = items.length > 0 ? items : data;
1434
- 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
+ }
1435
1784
 
1436
1785
  const lowQuality = [];
1437
1786
  const gradeDistribution = { A: 0, B: 0, C: 0, D: 0, F: 0 };
1438
1787
 
1439
1788
  for (const recipe of recipes) {
1440
1789
  const scoreResult = await this.executeTool('quality_score', { recipe });
1441
- 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
+ }
1442
1793
  if (scoreResult.score < threshold) {
1443
1794
  lowQuality.push({
1444
1795
  id: recipe.id,
@@ -1466,11 +1817,15 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1466
1817
  * 对代码运行全部 Guard 规则 + 生成修复建议
1467
1818
  */
1468
1819
  async #taskGuardFullScan({ code, language, filePath } = {}) {
1469
- if (!code) return { error: 'code is required' };
1820
+ if (!code) {
1821
+ return { error: 'code is required' };
1822
+ }
1470
1823
 
1471
1824
  // Step 1: 静态检查
1472
1825
  const checkResult = await this.executeTool('guard_check_code', {
1473
- code, language: language || 'unknown', scope: 'project',
1826
+ code,
1827
+ language: language || 'unknown',
1828
+ scope: 'project',
1474
1829
  });
1475
1830
 
1476
1831
  // Step 2: 如果有违规且 AI 可用,生成修复建议
@@ -1479,7 +1834,10 @@ ${highSim.map(s => `- ${s.title} (相似度: ${s.similarity})`).join('\n')}
1479
1834
  try {
1480
1835
  const violationSummary = (checkResult.violations || [])
1481
1836
  .slice(0, 5)
1482
- .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
+ )
1483
1841
  .join('\n');
1484
1842
 
1485
1843
  const prompt = `以下代码存在 Guard 规则违规。请为每个违规提供修复建议。
@@ -1494,10 +1852,15 @@ ${code.substring(0, 3000)}
1494
1852
 
1495
1853
  请用 JSON 数组格式返回建议: [{"violation": "...", "suggestion": "...", "fixExample": "..."}]`;
1496
1854
 
1497
- suggestions = await this.#aiProvider.chatWithStructuredOutput(prompt, {
1498
- openChar: '[', closeChar: ']', temperature: 0.3,
1499
- }) || [];
1500
- } 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
+ }
1501
1864
  }
1502
1865
 
1503
1866
  return {
@@ -1524,18 +1887,20 @@ ${code.substring(0, 3000)}
1524
1887
  // ── bootstrap_full_pipeline (v6 简化版) ──────────────────
1525
1888
  // 只做启发式 Phase 1-5.5 (含 ChatAgent per-dimension production)
1526
1889
  // 不再需要 enrich/refine 后置步骤
1527
- this.registerPipeline(new TaskPipeline('bootstrap_full_pipeline', [
1528
- {
1529
- name: 'bootstrap',
1530
- tool: 'bootstrap_knowledge',
1531
- params: {
1532
- maxFiles: (ctx) => ctx._inputs.maxFiles || 500,
1533
- skipGuard: (ctx) => ctx._inputs.skipGuard || false,
1534
- contentMaxLines: (ctx) => ctx._inputs.contentMaxLines || 120,
1535
- 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
+ },
1536
1901
  },
1537
- },
1538
- ]));
1902
+ ])
1903
+ );
1539
1904
  }
1540
1905
 
1541
1906
  // ─── Native Tool Calling 内部方法 ──────────────────────
@@ -1555,9 +1920,11 @@ ${code.substring(0, 3000)}
1555
1920
  let soulSection = '';
1556
1921
  try {
1557
1922
  if (fs.existsSync(SOUL_PATH)) {
1558
- soulSection = '\n' + fs.readFileSync(SOUL_PATH, 'utf-8').trim() + '\n';
1923
+ soulSection = `\n${fs.readFileSync(SOUL_PATH, 'utf-8').trim()}\n`;
1559
1924
  }
1560
- } catch { /* SOUL.md not available */ }
1925
+ } catch {
1926
+ /* SOUL.md not available */
1927
+ }
1561
1928
 
1562
1929
  return `${soulSection}
1563
1930
  你是 AutoSnippet 项目的统一 AI 中心。项目内所有 AI 推理和分析都通过你执行。
@@ -1567,8 +1934,7 @@ ${this.#projectBriefingCache}${this.#memory?.toPromptSection({ source: 'user' })
1567
1934
  1. 当需要查询数据时,直接调用相应工具。
1568
1935
  2. 工具参数严格按照工具声明中的 schema 传递。
1569
1936
  3. 对于代码分析任务,先 search_project_code 搜索,再 read_project_file 读取。
1570
- 4. 回答时使用用户的语言(中文/英文)。
1571
- 5. 当工具返回错误时,尝试不同参数或方法。`;
1937
+ 4. 当工具返回错误时,尝试不同参数或方法。`;
1572
1938
  }
1573
1939
 
1574
1940
  // Bootstrap 系统模式: LLM 以领域大脑的能力处理任务
@@ -1611,7 +1977,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1611
1977
  * 清理最终回答(去除 Thought/preamble + MEMORY 标签)
1612
1978
  */
1613
1979
  #cleanFinalAnswer(response) {
1614
- if (!response) return '';
1980
+ if (!response) {
1981
+ return '';
1982
+ }
1615
1983
  return response
1616
1984
  .replace(/^(Final Answer|最终回答|Answer)\s*[::]\s*/i, '')
1617
1985
  .replace(/\[MEMORY:\w+\]\s*[\s\S]*?\s*\[\/MEMORY\]/g, '')
@@ -1658,9 +2026,10 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1658
2026
  */
1659
2027
  #loadSkillsFromDir(dir, skillMap) {
1660
2028
  try {
1661
- const dirs = fs.readdirSync(dir, { withFileTypes: true })
1662
- .filter(d => d.isDirectory())
1663
- .map(d => d.name);
2029
+ const dirs = fs
2030
+ .readdirSync(dir, { withFileTypes: true })
2031
+ .filter((d) => d.isDirectory())
2032
+ .map((d) => d.name);
1664
2033
  for (const name of dirs) {
1665
2034
  const skillPath = path.join(dir, name, 'SKILL.md');
1666
2035
  let summary = '';
@@ -1674,15 +2043,19 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1674
2043
  for (const line of lines) {
1675
2044
  const trimmed = line.trim();
1676
2045
  if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('---')) {
1677
- summary = trimmed.length > 80 ? trimmed.substring(0, 80) + '...' : trimmed;
2046
+ summary = trimmed.length > 80 ? `${trimmed.substring(0, 80)}...` : trimmed;
1678
2047
  break;
1679
2048
  }
1680
2049
  }
1681
2050
  }
1682
- } catch { /* SKILL.md not found */ }
2051
+ } catch {
2052
+ /* SKILL.md not found */
2053
+ }
1683
2054
  skillMap.set(name, { name, summary });
1684
2055
  }
1685
- } catch { /* directory not found */ }
2056
+ } catch {
2057
+ /* directory not found */
2058
+ }
1686
2059
  }
1687
2060
 
1688
2061
  /**
@@ -1692,13 +2065,16 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1692
2065
  async #buildProjectBriefing() {
1693
2066
  try {
1694
2067
  const db = this.#container?.get('database');
1695
- if (!db) return '';
2068
+ if (!db) {
2069
+ return '';
2070
+ }
1696
2071
  // knowledgeType → kind 映射:
1697
2072
  // rule: code-standard, code-style, best-practice, boundary-constraint
1698
2073
  // pattern: code-pattern, architecture, solution
1699
2074
  // fact: code-relation, inheritance, call-chain, data-flow, module-dependency
1700
2075
  // V3: knowledge_entries 统一表(candidates 已合并,lifecycle 替代 status)
1701
- const stats = db.prepare(`
2076
+ const stats = db
2077
+ .prepare(`
1702
2078
  SELECT
1703
2079
  (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active') as recipeCount,
1704
2080
  (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledgeType IN ('code-standard','code-style','best-practice','boundary-constraint')) as ruleCount,
@@ -1707,7 +2083,8 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1707
2083
  (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'active' AND knowledgeType = 'boundary-constraint') as guardRuleCount,
1708
2084
  (SELECT COUNT(*) FROM knowledge_entries WHERE lifecycle = 'pending') as pendingCandidates,
1709
2085
  (SELECT COUNT(*) FROM knowledge_entries) as totalCandidates
1710
- `).get();
2086
+ `)
2087
+ .get();
1711
2088
  if (!stats || stats.recipeCount === 0) {
1712
2089
  return '\n## 项目状态\n⚠️ 知识库为空。建议先执行冷启动(bootstrap_knowledge)。\n';
1713
2090
  }
@@ -1729,11 +2106,17 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1729
2106
  try {
1730
2107
  const db = container?.get?.('database');
1731
2108
  if (db) {
1732
- import('./ProjectSemanticMemory.js').then(({ ProjectSemanticMemory }) => {
1733
- this.#semanticMemory = new ProjectSemanticMemory(db, { logger: this.#logger });
1734
- }).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
+ });
1735
2116
  }
1736
- } catch { /* container.get failed, degrade silently */ }
2117
+ } catch {
2118
+ /* container.get failed, degrade silently */
2119
+ }
1737
2120
  }
1738
2121
 
1739
2122
  /**
@@ -1746,7 +2129,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1746
2129
  * source 隔离: 标记 memory 来源,避免系统分析污染用户记忆
1747
2130
  */
1748
2131
  #extractMemory(prompt, reply) {
1749
- if (!this.#memory && !this.#semanticMemory) return;
2132
+ if (!this.#memory && !this.#semanticMemory) {
2133
+ return;
2134
+ }
1750
2135
  const source = this.#currentSource || 'user';
1751
2136
 
1752
2137
  try {
@@ -1759,7 +2144,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1759
2144
  /remember\s+(to|that)/i,
1760
2145
  /our\s+(convention|standard|rule)\s+is/i,
1761
2146
  ];
1762
- if (prefPatterns.some(p => p.test(prompt))) {
2147
+ if (prefPatterns.some((p) => p.test(prompt))) {
1763
2148
  const entry = {
1764
2149
  type: 'preference',
1765
2150
  content: prompt.substring(0, 200),
@@ -1777,7 +2162,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1777
2162
  /let'?s\s+(go\s+with|use|adopt)/i,
1778
2163
  /approved|confirmed|decided/i,
1779
2164
  ];
1780
- if (decisionPatterns.some(p => p.test(prompt))) {
2165
+ if (decisionPatterns.some((p) => p.test(prompt))) {
1781
2166
  const entry = {
1782
2167
  type: 'decision',
1783
2168
  content: prompt.substring(0, 200),
@@ -1808,7 +2193,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1808
2193
  }
1809
2194
  }
1810
2195
  }
1811
- } catch { /* memory write failure is non-critical */ }
2196
+ } catch {
2197
+ /* memory write failure is non-critical */
2198
+ }
1812
2199
  }
1813
2200
 
1814
2201
  /**
@@ -1816,7 +2203,9 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1816
2203
  * 当对话消息数超过 12 条时触发 AI 摘要压缩
1817
2204
  */
1818
2205
  async #autoSummarize(conversationId) {
1819
- if (!this.#conversations || !this.#aiProvider) return;
2206
+ if (!this.#conversations || !this.#aiProvider) {
2207
+ return;
2208
+ }
1820
2209
  try {
1821
2210
  const messages = this.#conversations.load(conversationId, { tokenBudget: Infinity });
1822
2211
  if (messages.length >= 12) {
@@ -1856,17 +2245,23 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1856
2245
  * 截断长文本
1857
2246
  */
1858
2247
  #truncate(text, maxLen = 4000) {
1859
- if (!text || text.length <= maxLen) return text;
1860
- 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)`;
1861
2252
  }
1862
2253
 
1863
2254
  /**
1864
2255
  * 精简工具结果(避免过长的 observation)
1865
2256
  */
1866
2257
  #summarizeResult(result) {
1867
- if (!result) return null;
2258
+ if (!result) {
2259
+ return null;
2260
+ }
1868
2261
  const str = typeof result === 'string' ? result : JSON.stringify(result);
1869
- if (str.length <= 500) return result;
2262
+ if (str.length <= 500) {
2263
+ return result;
2264
+ }
1870
2265
  // 返回截断版
1871
2266
  if (typeof result === 'object') {
1872
2267
  if (Array.isArray(result)) {
@@ -1878,7 +2273,7 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
1878
2273
  for (const k of keys) {
1879
2274
  const v = result[k];
1880
2275
  if (typeof v === 'string' && v.length > 200) {
1881
- summary[k] = v.substring(0, 200) + '...';
2276
+ summary[k] = `${v.substring(0, 200)}...`;
1882
2277
  } else if (Array.isArray(v)) {
1883
2278
  summary[k] = { _count: v.length, first2: v.slice(0, 2) };
1884
2279
  } else {