autosnippet 3.2.7 → 3.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/bin/cli.js +13 -5
  2. package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
  3. package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +26 -29
  6. package/lib/cli/SetupService.js +1 -1
  7. package/lib/core/AstAnalyzer.js +27 -5
  8. package/lib/core/analysis/CallEdgeResolver.js +402 -0
  9. package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
  10. package/lib/core/analysis/CallSiteExtractor.js +629 -0
  11. package/lib/core/analysis/DataFlowInferrer.js +57 -0
  12. package/lib/core/analysis/ImportPathResolver.js +189 -0
  13. package/lib/core/analysis/ImportRecord.js +105 -0
  14. package/lib/core/analysis/SymbolTableBuilder.js +211 -0
  15. package/lib/core/ast/ProjectGraph.js +8 -0
  16. package/lib/core/ast/lang-dart.js +352 -5
  17. package/lib/core/ast/lang-go.js +212 -10
  18. package/lib/core/ast/lang-java.js +205 -1
  19. package/lib/core/ast/lang-kotlin.js +330 -1
  20. package/lib/core/ast/lang-python.js +31 -2
  21. package/lib/core/ast/lang-rust.js +284 -3
  22. package/lib/core/ast/lang-swift.js +180 -1
  23. package/lib/core/ast/lang-typescript.js +290 -1
  24. package/lib/core/discovery/index.js +2 -2
  25. package/lib/external/ai/AiProvider.js +66 -172
  26. package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
  27. package/lib/external/mcp/McpServer.js +1 -0
  28. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
  29. package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
  30. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
  31. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
  32. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
  33. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
  34. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  35. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
  36. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
  37. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
  38. package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
  39. package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
  40. package/lib/external/mcp/handlers/consolidated.js +9 -0
  41. package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
  42. package/lib/external/mcp/handlers/guard.js +3 -3
  43. package/lib/external/mcp/handlers/structure.js +62 -0
  44. package/lib/external/mcp/handlers/wiki-external.js +66 -3
  45. package/lib/external/mcp/tools.js +36 -1
  46. package/lib/http/HttpServer.js +1 -1
  47. package/lib/http/middleware/requestLogger.js +1 -0
  48. package/lib/http/routes/ai.js +240 -35
  49. package/lib/http/routes/candidates.js +2 -3
  50. package/lib/http/routes/extract.js +13 -11
  51. package/lib/http/routes/modules.js +2 -2
  52. package/lib/http/routes/recipes.js +9 -5
  53. package/lib/http/routes/remote.js +149 -270
  54. package/lib/http/routes/violations.js +0 -54
  55. package/lib/http/utils/sse-sessions.js +1 -1
  56. package/lib/infrastructure/logging/Logger.js +5 -4
  57. package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
  58. package/lib/injection/ServiceContainer.js +70 -28
  59. package/lib/platform/ScreenCaptureService.js +177 -0
  60. package/lib/platform/ios/index.js +2 -2
  61. package/lib/platform/ios/routes/spm.js +2 -2
  62. package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
  63. package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
  64. package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
  65. package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
  66. package/lib/service/agent/AgentEventBus.js +207 -0
  67. package/lib/service/agent/AgentFactory.js +490 -0
  68. package/lib/service/agent/AgentMessage.js +240 -0
  69. package/lib/service/agent/AgentRouter.js +228 -0
  70. package/lib/service/agent/AgentRuntime.js +1016 -0
  71. package/lib/service/agent/AgentState.js +217 -0
  72. package/lib/service/agent/IntentClassifier.js +331 -0
  73. package/lib/service/agent/LarkTransport.js +389 -0
  74. package/lib/service/agent/capabilities.js +408 -0
  75. package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
  76. package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
  77. package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
  78. package/lib/service/agent/core/LoopContext.js +170 -0
  79. package/lib/service/agent/core/MessageAdapter.js +223 -0
  80. package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
  81. package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
  82. package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
  83. package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
  84. package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
  85. package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
  86. package/lib/service/agent/domain/insight-producer.js +267 -0
  87. package/lib/service/agent/domain/scan-prompts.js +105 -0
  88. package/lib/service/agent/forced-summary.js +266 -0
  89. package/lib/service/agent/index.js +91 -0
  90. package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
  91. package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
  92. package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
  93. package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
  94. package/lib/service/{chat → agent}/memory/index.js +1 -1
  95. package/lib/service/agent/policies.js +442 -0
  96. package/lib/service/agent/presets.js +303 -0
  97. package/lib/service/agent/strategies.js +717 -0
  98. package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
  99. package/lib/service/agent/tools/ai-analysis.js +75 -0
  100. package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
  101. package/lib/service/{chat → agent}/tools/composite.js +2 -1
  102. package/lib/service/{chat → agent}/tools/guard.js +1 -121
  103. package/lib/service/{chat → agent}/tools/index.js +33 -22
  104. package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
  105. package/lib/service/agent/tools/knowledge-graph.js +112 -0
  106. package/lib/service/agent/tools/scan-recipe.js +189 -0
  107. package/lib/service/agent/tools/system-interaction.js +476 -0
  108. package/lib/service/automation/DirectiveDetector.js +0 -1
  109. package/lib/service/automation/FileWatcher.js +0 -8
  110. package/lib/service/automation/handlers/CreateHandler.js +7 -3
  111. package/lib/service/automation/handlers/DraftHandler.js +7 -6
  112. package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
  113. package/lib/service/knowledge/CodeEntityGraph.js +327 -2
  114. package/lib/service/knowledge/KnowledgeService.js +5 -1
  115. package/lib/service/module/ModuleService.js +49 -73
  116. package/lib/service/skills/SignalCollector.js +26 -19
  117. package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
  118. package/lib/service/wiki/WikiGenerator.js +1 -1
  119. package/lib/shared/FieldSpec.js +1 -1
  120. package/lib/shared/PathGuard.js +1 -1
  121. package/lib/shared/StyleGuide.js +1 -1
  122. package/package.json +4 -1
  123. package/resources/native-ui/screenshot.swift +228 -0
  124. package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
  125. package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
  126. package/lib/core/discovery/SpmDiscoverer.js +0 -5
  127. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
  128. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
  129. package/lib/http/routes/spm.js +0 -5
  130. package/lib/infrastructure/external/XcodeAutomation.js +0 -15
  131. package/lib/service/chat/ChatAgent.js +0 -1602
  132. package/lib/service/chat/Memory.js +0 -161
  133. package/lib/service/chat/ProducerAgent.js +0 -431
  134. package/lib/service/chat/ReasoningTrace.js +0 -523
  135. package/lib/service/chat/TaskPipeline.js +0 -357
  136. package/lib/service/chat/WorkingMemory.js +0 -357
  137. package/lib/service/chat/memory/PersistentMemory.js +0 -450
  138. package/lib/service/chat/tools/ai-analysis.js +0 -267
  139. package/lib/service/chat/tools/knowledge-graph.js +0 -234
  140. package/lib/service/chat/tools.js +0 -18
  141. package/lib/service/snippet/PlaceholderConverter.js +0 -5
  142. package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
  143. /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
  144. /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
  145. /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
  146. /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
  147. /package/lib/service/{chat → agent}/tools/query.js +0 -0
@@ -1,749 +0,0 @@
1
- /**
2
- * EpisodicMemory — Bootstrap 会话级情景记忆
3
- *
4
- * @deprecated Phase 4: 已合并入 SessionStore (lib/service/chat/memory/SessionStore.js)
5
- * - 全部维度报告/证据/反思逻辑已迁入 SessionStore
6
- * - 本文件保留做向后兼容,新代码请使用 SessionStore
7
- *
8
- * 内部 Agent 和外部 Agent 共享此模块。
9
- *
10
- * 提供跨维度上下文:
11
- * - 完整的维度分析报告 (含分析文本 + 代码证据)
12
- * - 结构化 Evidence Store (文件→发现 的映射)
13
- * - 跨维度引用 (CrossReference)
14
- * - Tier 级 Reflection (每个 Tier 完成后的综合洞察)
15
- *
16
- * 调用方:
17
- * - orchestrator.js (内部 Agent) — AnalystAgent 使用 buildContextForDimension()
18
- * - BootstrapSession.js (外部 Agent) — 每个 Session 创建独立 EpisodicMemory 实例,
19
- * 通过 storeDimensionReport() 记录外部 Agent 提交的分析报告
20
- *
21
- * 生命周期: 与 Bootstrap 会话一致。
22
- * 持久化: 通过 saveCheckpoint / loadCheckpoint 实现断点续传。
23
- *
24
- * @module EpisodicMemory
25
- */
26
-
27
- import fs from 'node:fs';
28
- import path from 'node:path';
29
- import Logger from '../../../../../infrastructure/logging/Logger.js';
30
-
31
- // ──────────────────────────────────────────────────────────────
32
- // 数据结构定义
33
- // ──────────────────────────────────────────────────────────────
34
-
35
- /**
36
- * @typedef {object} DimensionReport
37
- * @property {string} dimId — 维度 ID
38
- * @property {number} completedAt — 完成时间戳
39
- * @property {string} analysisText — Analyst 分析全文
40
- * @property {Array<Finding>} findings — 结构化发现列表
41
- * @property {string[]} referencedFiles — 引用文件清单
42
- * @property {Array<CandidateSummary>} candidatesSummary — 候选产出汇总
43
- * @property {object|null} workingMemoryDistilled — WorkingMemory 蒸馏数据
44
- * @property {object|null} digest — DimensionDigest (兼容旧格式)
45
- */
46
-
47
- /**
48
- * @typedef {object} Finding
49
- * @property {string} finding — 发现描述
50
- * @property {string} [evidence] — 证据 (文件路径:行号)
51
- * @property {number} importance — 重要性 1-10
52
- */
53
-
54
- /**
55
- * @typedef {object} CandidateSummary
56
- * @property {string} dimId
57
- * @property {string} title
58
- * @property {string} subTopic
59
- * @property {string} summary
60
- */
61
-
62
- /**
63
- * @typedef {object} CrossReference
64
- * @property {string} from — 来源维度 ID
65
- * @property {string} to — 目标维度 ID
66
- * @property {string} relation — 关系类型 (suggests/references/conflicts)
67
- * @property {string} detail — 具体内容
68
- */
69
-
70
- /**
71
- * @typedef {object} TierReflection
72
- * @property {number} tierIndex
73
- * @property {string[]} completedDimensions
74
- * @property {Array<Finding>} topFindings
75
- * @property {string[]} crossDimensionPatterns
76
- * @property {string[]} suggestionsForNextTier
77
- */
78
-
79
- // ──────────────────────────────────────────────────────────────
80
- // EpisodicMemory 类
81
- // ──────────────────────────────────────────────────────────────
82
-
83
- export class EpisodicMemory {
84
- /** @type {Map<string, DimensionReport>} dimId → DimensionReport */
85
- #dimensionReports = new Map();
86
-
87
- /** @type {Map<string, Finding[]>} filePath → Evidence[] */
88
- #evidenceStore = new Map();
89
-
90
- /** @type {CrossReference[]} */
91
- #crossReferences = [];
92
-
93
- /** @type {TierReflection[]} */
94
- #tierReflections = [];
95
-
96
- /** @type {Map<string, CandidateSummary[]>} dimId → candidates */
97
- #submittedCandidates = new Map();
98
-
99
- /** @type {object} 项目上下文 (不变信息) */
100
- #projectContext;
101
-
102
- /** @type {import('../../../../../infrastructure/logging/Logger.js').default} */
103
- #logger;
104
-
105
- /**
106
- * @param {object} projectContext - 项目基础信息
107
- * @param {string} projectContext.projectName
108
- * @param {string} projectContext.primaryLang
109
- * @param {number} projectContext.fileCount
110
- * @param {string[]} [projectContext.modules]
111
- * @param {object} [projectContext.depGraph]
112
- * @param {object} [projectContext.astMetrics]
113
- * @param {object} [projectContext.guardSummary]
114
- */
115
- constructor(projectContext = {}) {
116
- this.#projectContext = projectContext;
117
- this.#logger = Logger.getInstance();
118
- }
119
-
120
- // ─── 维度报告 (DimensionReport) ────────────────────────
121
-
122
- /**
123
- * 维度完成后存储完整报告
124
- *
125
- * @param {string} dimId
126
- * @param {object} report
127
- * @param {string} report.analysisText — Analyst 分析全文
128
- * @param {Array<Finding>} [report.findings] — 结构化发现
129
- * @param {string[]} [report.referencedFiles] — 引用文件
130
- * @param {Array<CandidateSummary>} [report.candidatesSummary] — 候选汇总
131
- * @param {object} [report.workingMemoryDistilled] — WorkingMemory 蒸馏
132
- * @param {object} [report.digest] — DimensionDigest (兼容)
133
- */
134
- storeDimensionReport(dimId, report) {
135
- // findings 统一形状: { finding: string, evidence: string, importance: number }
136
- // 源头 buildAnalysisArtifact() 和 ActiveContext.distill() 已保证一致
137
- const findings = (report.findings || []).map((f) => ({
138
- finding: f.finding || '',
139
- evidence: f.evidence || '',
140
- importance: f.importance || 5,
141
- }));
142
-
143
- this.#dimensionReports.set(dimId, {
144
- dimId,
145
- completedAt: Date.now(),
146
- analysisText: report.analysisText || '',
147
- findings,
148
- referencedFiles: report.referencedFiles || [],
149
- candidatesSummary: report.candidatesSummary || [],
150
- workingMemoryDistilled: report.workingMemoryDistilled || null,
151
- digest: report.digest || null,
152
- });
153
-
154
- // 自动提取文件级 Evidence
155
- for (const f of findings) {
156
- if (f.evidence) {
157
- const filePath = f.evidence.split(':')[0]; // "file.m:123" → "file.m"
158
- this.addEvidence(filePath, {
159
- dimId,
160
- finding: f.finding,
161
- importance: f.importance,
162
- });
163
- }
164
- }
165
-
166
- // 从 digest 中提取 crossRefs
167
- if (report.digest?.crossRefs) {
168
- for (const [targetDim, detail] of Object.entries(report.digest.crossRefs)) {
169
- if (detail) {
170
- this.#crossReferences.push({
171
- from: dimId,
172
- to: targetDim,
173
- relation: 'suggests',
174
- detail: String(detail),
175
- });
176
- }
177
- }
178
- }
179
-
180
- this.#logger.info(
181
- `[EpisodicMemory] Stored report for "${dimId}": ` +
182
- `${report.findings?.length || 0} findings, ` +
183
- `${report.referencedFiles?.length || 0} files`
184
- );
185
- }
186
-
187
- /**
188
- * 获取维度报告
189
- * @param {string} dimId
190
- * @returns {DimensionReport|undefined}
191
- */
192
- getDimensionReport(dimId) {
193
- return this.#dimensionReports.get(dimId);
194
- }
195
-
196
- // ─── Evidence Store (文件→发现 映射) ───────────────────
197
-
198
- /**
199
- * 记录代码证据
200
- * @param {string} filePath
201
- * @param {object} evidence
202
- * @param {string} evidence.dimId
203
- * @param {string} evidence.finding
204
- * @param {number} [evidence.importance]
205
- */
206
- addEvidence(filePath, evidence) {
207
- if (!this.#evidenceStore.has(filePath)) {
208
- this.#evidenceStore.set(filePath, []);
209
- }
210
- this.#evidenceStore.get(filePath).push({
211
- ...evidence,
212
- timestamp: Date.now(),
213
- });
214
- }
215
-
216
- /**
217
- * 获取指定文件的所有证据
218
- * @param {string} filePath
219
- * @returns {Finding[]}
220
- */
221
- getEvidenceForFile(filePath) {
222
- return this.#evidenceStore.get(filePath) || [];
223
- }
224
-
225
- /**
226
- * 搜索证据 — 模糊匹配文件名和发现内容
227
- * @param {string} query
228
- * @param {string} [dimId] — 限定维度
229
- * @returns {Array<{filePath: string, evidence: object}>}
230
- */
231
- searchEvidence(query, dimId) {
232
- const results = [];
233
- const lowerQuery = query.toLowerCase();
234
-
235
- for (const [filePath, evidences] of this.#evidenceStore) {
236
- for (const ev of evidences) {
237
- if (dimId && ev.dimId !== dimId) {
238
- continue;
239
- }
240
-
241
- const matchesFile = filePath.toLowerCase().includes(lowerQuery);
242
- const matchesFinding = (ev.finding || '').toLowerCase().includes(lowerQuery);
243
-
244
- if (matchesFile || matchesFinding) {
245
- results.push({ filePath, evidence: ev });
246
- }
247
- }
248
- }
249
-
250
- return results.sort((a, b) => (b.evidence.importance || 5) - (a.evidence.importance || 5));
251
- }
252
-
253
- // ─── 已提交候选 (兼容 DimensionContext) ────────────────
254
-
255
- /**
256
- * 记录已提交的候选 (兼容 DimensionContext.addSubmittedCandidate)
257
- * @param {string} dimId
258
- * @param {CandidateSummary} candidate
259
- */
260
- addSubmittedCandidate(dimId, candidate) {
261
- if (!this.#submittedCandidates.has(dimId)) {
262
- this.#submittedCandidates.set(dimId, []);
263
- }
264
- this.#submittedCandidates.get(dimId).push({
265
- dimId,
266
- title: candidate.title || '',
267
- subTopic: candidate.subTopic || '',
268
- summary: candidate.summary || '',
269
- });
270
- }
271
-
272
- // ─── DimensionDigest 兼容层 ───────────────────────────
273
-
274
- /**
275
- * 添加维度摘要 (兼容 DimensionContext.addDimensionDigest)
276
- * 如果已有 DimensionReport 则合并 digest;否则创建最小 Report
277
- *
278
- * @param {string} dimId
279
- * @param {object} digest
280
- */
281
- addDimensionDigest(dimId, digest) {
282
- const existing = this.#dimensionReports.get(dimId);
283
- if (existing) {
284
- existing.digest = digest;
285
- } else {
286
- this.#dimensionReports.set(dimId, {
287
- dimId,
288
- completedAt: Date.now(),
289
- analysisText: digest.summary || '',
290
- findings: (digest.keyFindings || []).map((f) => ({
291
- finding: typeof f === 'string' ? f : f.finding || '',
292
- evidence: '',
293
- importance: 5,
294
- })),
295
- referencedFiles: [],
296
- candidatesSummary: [],
297
- workingMemoryDistilled: null,
298
- digest,
299
- });
300
- }
301
-
302
- // 提取 crossRefs
303
- if (digest.crossRefs) {
304
- for (const [targetDim, detail] of Object.entries(digest.crossRefs)) {
305
- if (detail) {
306
- // 避免重复
307
- const exists = this.#crossReferences.some(
308
- (cr) => cr.from === dimId && cr.to === targetDim
309
- );
310
- if (!exists) {
311
- this.#crossReferences.push({
312
- from: dimId,
313
- to: targetDim,
314
- relation: 'suggests',
315
- detail: String(detail),
316
- });
317
- }
318
- }
319
- }
320
- }
321
- }
322
-
323
- // ─── Tier Reflection ──────────────────────────────────
324
-
325
- /**
326
- * 添加 Tier 级 Reflection
327
- * @param {number} tierIndex
328
- * @param {TierReflection} reflection
329
- */
330
- addTierReflection(tierIndex, reflection) {
331
- this.#tierReflections.push(reflection);
332
- this.#logger.info(
333
- `[EpisodicMemory] Tier ${tierIndex + 1} reflection: ` +
334
- `${reflection.topFindings?.length || 0} top findings, ` +
335
- `${reflection.crossDimensionPatterns?.length || 0} patterns`
336
- );
337
- }
338
-
339
- /**
340
- * 获取与当前维度相关的 Tier Reflections
341
- * @param {string} currentDimId
342
- * @returns {string|null} - 格式化的 Markdown 文本
343
- */
344
- getRelevantReflections(currentDimId) {
345
- if (this.#tierReflections.length === 0) {
346
- return null;
347
- }
348
-
349
- const parts = [];
350
- for (const ref of this.#tierReflections) {
351
- parts.push(`### Tier ${ref.tierIndex + 1} 综合洞察`);
352
-
353
- if (ref.topFindings?.length > 0) {
354
- parts.push('**核心发现**:');
355
- for (const f of ref.topFindings.slice(0, 5)) {
356
- parts.push(`- [${f.importance || 5}/10] ${f.finding}`);
357
- }
358
- }
359
-
360
- if (ref.crossDimensionPatterns?.length > 0) {
361
- parts.push('**跨维度模式**:');
362
- for (const p of ref.crossDimensionPatterns) {
363
- parts.push(`- ${p}`);
364
- }
365
- }
366
-
367
- if (ref.suggestionsForNextTier?.length > 0) {
368
- parts.push('**对后续维度的建议**:');
369
- for (const s of ref.suggestionsForNextTier) {
370
- parts.push(`- ${s}`);
371
- }
372
- }
373
- }
374
-
375
- return parts.length > 0 ? parts.join('\n') : null;
376
- }
377
-
378
- // ─── 上下文构建 (核心: 替代 DimensionContext) ──────────
379
-
380
- /**
381
- * 构建给 Analyst 的跨维度上下文 (替代 DimensionContext.buildContextForDimension)
382
- *
383
- * 比 DimensionContext 更丰富:
384
- * - 注入具体 findings (而非仅 summary)
385
- * - 汇总已读文件清单 (避免重复读取)
386
- * - 注入跨维度引用建议
387
- * - 注入 Tier Reflection 洞察
388
- *
389
- * @param {string} currentDimId — 当前正在分析的维度 ID
390
- * @param {string[]} [focusKeywords] — 当前维度的聚焦关键词 (用于过滤相关发现)
391
- * @returns {string} Markdown 格式的上下文块
392
- */
393
- buildContextForDimension(currentDimId, focusKeywords = []) {
394
- const parts = [];
395
- const completedDims = [...this.#dimensionReports.entries()].filter(
396
- ([id]) => id !== currentDimId
397
- );
398
-
399
- if (completedDims.length === 0 && this.#tierReflections.length === 0) {
400
- return '';
401
- }
402
-
403
- parts.push('## 前序维度分析成果(避免重复探索)');
404
-
405
- // §1: 前序维度的关键发现 (比 summary 更具体)
406
- for (const [dimId, report] of completedDims) {
407
- parts.push(`### ${dimId}`);
408
-
409
- // 摘要
410
- if (report.digest?.summary) {
411
- parts.push(report.digest.summary);
412
- } else if (report.analysisText) {
413
- parts.push(`${report.analysisText.substring(0, 300)}…`);
414
- }
415
-
416
- // P0 Fix (B1): 优先使用 report.findings,为空时从 workingMemoryDistilled.keyFindings 补充
417
- let findings = report.findings;
418
- if ((!findings || findings.length === 0) && report.workingMemoryDistilled?.keyFindings) {
419
- findings = report.workingMemoryDistilled.keyFindings.map((f) => ({
420
- finding: f.finding || '',
421
- evidence: f.evidence || '',
422
- importance: f.importance || 5,
423
- }));
424
- }
425
-
426
- // 选择与当前维度相关的 findings
427
- const relevantFindings = this.#selectRelevantFindings(findings, focusKeywords, 5);
428
- if (relevantFindings.length > 0) {
429
- parts.push('**具体发现**:');
430
- for (const f of relevantFindings) {
431
- let line = `- [${f.importance}/10] ${f.finding}`;
432
- if (f.evidence) {
433
- line += ` _(${f.evidence})_`;
434
- }
435
- parts.push(line);
436
- }
437
- }
438
-
439
- // 候选数量
440
- const candidates = this.#submittedCandidates.get(dimId) || [];
441
- if (candidates.length > 0) {
442
- parts.push(
443
- `已提交 ${candidates.length} 个候选: ${candidates.map((c) => c.title).join(', ')}`
444
- );
445
- }
446
- }
447
-
448
- // §2: 已读文件汇总 (帮助下游维度避免重复读取)
449
- const allReadFiles = new Set();
450
- for (const report of this.#dimensionReports.values()) {
451
- for (const f of report.referencedFiles) {
452
- allReadFiles.add(f);
453
- }
454
- }
455
- if (allReadFiles.size > 0) {
456
- parts.push(`### 前序维度已扫描的文件 (${allReadFiles.size} 个)`);
457
- const fileList = [...allReadFiles].slice(0, 30).join(', ');
458
- parts.push(fileList);
459
- if (allReadFiles.size > 30) {
460
- parts.push(`…还有 ${allReadFiles.size - 30} 个文件`);
461
- }
462
- }
463
-
464
- // §3: 跨维度引用建议
465
- const relevantCrossRefs = this.#crossReferences.filter((cr) => cr.to === currentDimId);
466
- if (relevantCrossRefs.length > 0) {
467
- parts.push(`### 其他维度对 ${currentDimId} 的建议`);
468
- for (const cr of relevantCrossRefs) {
469
- parts.push(`- [来自 ${cr.from}] ${cr.detail}`);
470
- }
471
- }
472
-
473
- // §4: Tier Reflection 洞察
474
- const reflections = this.getRelevantReflections(currentDimId);
475
- if (reflections) {
476
- parts.push(reflections);
477
- }
478
-
479
- return parts.join('\n');
480
- }
481
-
482
- /**
483
- * 兼容 DimensionContext.buildContextForDimension 返回格式
484
- * 返回 { previousDimensions, submittedCandidates } 对象
485
- *
486
- * @param {string} currentDimId
487
- * @returns {object}
488
- */
489
- buildContextSnapshot(currentDimId) {
490
- const previousDimensions = {};
491
- for (const [dimId, report] of this.#dimensionReports) {
492
- if (dimId === currentDimId) {
493
- continue;
494
- }
495
- previousDimensions[dimId] = report.digest || {
496
- summary: report.analysisText?.substring(0, 300) || '',
497
- candidateCount: report.candidatesSummary?.length || 0,
498
- keyFindings: report.findings?.map((f) => f.finding) || [],
499
- crossRefs: {},
500
- gaps: [],
501
- };
502
- }
503
-
504
- const submittedCandidates = [];
505
- for (const [, candidates] of this.#submittedCandidates) {
506
- submittedCandidates.push(...candidates);
507
- }
508
-
509
- return { previousDimensions, submittedCandidates };
510
- }
511
-
512
- // ─── 持久化 (断点续传) ────────────────────────────────
513
-
514
- /**
515
- * 保存到磁盘 (断点续传)
516
- * @param {string} projectRoot
517
- */
518
- async saveCheckpoint(projectRoot) {
519
- const checkpointDir = path.join(projectRoot, '.autosnippet', 'bootstrap-checkpoint');
520
- try {
521
- fs.mkdirSync(checkpointDir, { recursive: true });
522
-
523
- const data = {
524
- version: 1,
525
- savedAt: Date.now(),
526
- dimensionReports: Object.fromEntries(
527
- [...this.#dimensionReports].map(([k, v]) => [
528
- k,
529
- {
530
- ...v,
531
- // 不保存 analysisText 全文 (太大), 只保存 digest + findings
532
- analysisText: v.analysisText?.substring(0, 500) || '',
533
- },
534
- ])
535
- ),
536
- crossReferences: this.#crossReferences,
537
- tierReflections: this.#tierReflections,
538
- submittedCandidates: Object.fromEntries(this.#submittedCandidates),
539
- evidenceIndex: [...this.#evidenceStore.keys()],
540
- };
541
-
542
- fs.writeFileSync(
543
- path.join(checkpointDir, 'episodic-memory.json'),
544
- JSON.stringify(data, null, 2),
545
- 'utf-8'
546
- );
547
-
548
- this.#logger.info(
549
- `[EpisodicMemory] Checkpoint saved: ${this.#dimensionReports.size} reports`
550
- );
551
- } catch (err) {
552
- this.#logger.warn(`[EpisodicMemory] Failed to save checkpoint: ${err.message}`);
553
- }
554
- }
555
-
556
- /**
557
- * 从磁盘加载 (断点恢复)
558
- * @param {string} projectRoot
559
- * @returns {boolean} 是否成功加载
560
- */
561
- async loadCheckpoint(projectRoot) {
562
- const checkpointPath = path.join(
563
- projectRoot,
564
- '.autosnippet',
565
- 'bootstrap-checkpoint',
566
- 'episodic-memory.json'
567
- );
568
-
569
- try {
570
- if (!fs.existsSync(checkpointPath)) {
571
- return false;
572
- }
573
-
574
- const raw = fs.readFileSync(checkpointPath, 'utf-8');
575
- const data = JSON.parse(raw);
576
-
577
- // 版本检查
578
- if (data.version !== 1) {
579
- this.#logger.warn(`[EpisodicMemory] Unsupported checkpoint version: ${data.version}`);
580
- return false;
581
- }
582
-
583
- // 有效期检查 (1 小时)
584
- if (Date.now() - data.savedAt > 3600_000) {
585
- this.#logger.info(`[EpisodicMemory] Checkpoint expired (>1h), ignoring`);
586
- return false;
587
- }
588
-
589
- // 恢复数据
590
- if (data.dimensionReports) {
591
- for (const [dimId, report] of Object.entries(data.dimensionReports)) {
592
- this.#dimensionReports.set(dimId, report);
593
- }
594
- }
595
- if (data.crossReferences) {
596
- this.#crossReferences = data.crossReferences;
597
- }
598
- if (data.tierReflections) {
599
- this.#tierReflections = data.tierReflections;
600
- }
601
- if (data.submittedCandidates) {
602
- for (const [dimId, candidates] of Object.entries(data.submittedCandidates)) {
603
- this.#submittedCandidates.set(dimId, candidates);
604
- }
605
- }
606
-
607
- this.#logger.info(
608
- `[EpisodicMemory] Checkpoint loaded: ${this.#dimensionReports.size} reports`
609
- );
610
- return true;
611
- } catch (err) {
612
- this.#logger.warn(`[EpisodicMemory] Failed to load checkpoint: ${err.message}`);
613
- return false;
614
- }
615
- }
616
-
617
- // ─── 序列化 (兼容 DimensionContext.toJSON/fromJSON) ────
618
-
619
- /**
620
- * 序列化为 JSON
621
- * @returns {object}
622
- */
623
- toJSON() {
624
- return {
625
- dimensionReports: Object.fromEntries(this.#dimensionReports),
626
- crossReferences: this.#crossReferences,
627
- tierReflections: this.#tierReflections,
628
- submittedCandidates: Object.fromEntries(this.#submittedCandidates),
629
- projectContext: this.#projectContext,
630
- };
631
- }
632
-
633
- /**
634
- * 从 JSON 恢复
635
- * @param {object} json
636
- * @returns {EpisodicMemory}
637
- */
638
- static fromJSON(json) {
639
- const em = new EpisodicMemory(json.projectContext || {});
640
- if (json.dimensionReports) {
641
- for (const [k, v] of Object.entries(json.dimensionReports)) {
642
- em.#dimensionReports.set(k, v);
643
- }
644
- }
645
- if (json.crossReferences) {
646
- em.#crossReferences = json.crossReferences;
647
- }
648
- if (json.tierReflections) {
649
- em.#tierReflections = json.tierReflections;
650
- }
651
- if (json.submittedCandidates) {
652
- for (const [k, v] of Object.entries(json.submittedCandidates)) {
653
- em.#submittedCandidates.set(k, v);
654
- }
655
- }
656
- return em;
657
- }
658
-
659
- // ─── 统计 ─────────────────────────────────────────────
660
-
661
- /**
662
- * 获取已完成的维度列表
663
- * @returns {string[]}
664
- */
665
- getCompletedDimensions() {
666
- return [...this.#dimensionReports.keys()];
667
- }
668
-
669
- /**
670
- * 获取所有已引用文件 (去重)
671
- * @returns {Set<string>}
672
- */
673
- getAllReferencedFiles() {
674
- const files = new Set();
675
- for (const report of this.#dimensionReports.values()) {
676
- for (const f of report.referencedFiles) {
677
- files.add(f);
678
- }
679
- }
680
- return files;
681
- }
682
-
683
- /**
684
- * 获取统计数据
685
- * @returns {object}
686
- */
687
- getStats() {
688
- const totalFindings = [...this.#dimensionReports.values()].reduce(
689
- (sum, r) => sum + r.findings.length,
690
- 0
691
- );
692
- const totalEvidence = [...this.#evidenceStore.values()].reduce(
693
- (sum, arr) => sum + arr.length,
694
- 0
695
- );
696
- const totalCandidates = [...this.#submittedCandidates.values()].reduce(
697
- (sum, arr) => sum + arr.length,
698
- 0
699
- );
700
-
701
- return {
702
- completedDimensions: this.#dimensionReports.size,
703
- totalFindings,
704
- totalEvidence,
705
- totalCandidates,
706
- crossReferences: this.#crossReferences.length,
707
- tierReflections: this.#tierReflections.length,
708
- referencedFiles: this.getAllReferencedFiles().size,
709
- };
710
- }
711
-
712
- // ─── 内部 ─────────────────────────────────────────────
713
-
714
- /**
715
- * 从 findings 中选择与当前焦点最相关的
716
- * @param {Finding[]} findings
717
- * @param {string[]} focusKeywords
718
- * @param {number} limit
719
- * @returns {Finding[]}
720
- */
721
- #selectRelevantFindings(findings, focusKeywords, limit) {
722
- if (!findings || findings.length === 0) {
723
- return [];
724
- }
725
-
726
- if (!focusKeywords || focusKeywords.length === 0) {
727
- // 无聚焦关键词: 按重要性排序
728
- return [...findings]
729
- .sort((a, b) => (b.importance || 5) - (a.importance || 5))
730
- .slice(0, limit);
731
- }
732
-
733
- // 有聚焦关键词: 综合重要性 + 关键词匹配
734
- return [...findings]
735
- .map((f) => {
736
- const relevance = focusKeywords.some((kw) =>
737
- (f.finding || '').toLowerCase().includes(kw.toLowerCase())
738
- )
739
- ? 1
740
- : 0;
741
- return { ...f, _score: relevance * 10 + (f.importance || 5) };
742
- })
743
- .sort((a, b) => b._score - a._score)
744
- .slice(0, limit)
745
- .map(({ _score, ...rest }) => rest); // 移除临时 _score
746
- }
747
- }
748
-
749
- export default EpisodicMemory;