autosnippet 3.2.8 → 3.2.10

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