autosnippet 3.2.4 → 3.2.6

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 (64) hide show
  1. package/README.md +2 -4
  2. package/bin/cli.js +164 -145
  3. package/config/constitution.yaml +2 -0
  4. package/dashboard/dist/assets/{index-DNOHYBhy.css → index-BaGY7kJI.css} +1 -1
  5. package/dashboard/dist/assets/{index-6itPuGFl.js → index-DfHY_3ln.js} +25 -25
  6. package/dashboard/dist/index.html +2 -2
  7. package/lib/cli/CliLogger.js +78 -0
  8. package/lib/cli/SetupService.js +9 -718
  9. package/lib/cli/UpgradeService.js +23 -398
  10. package/lib/cli/deploy/FileDeployer.js +562 -0
  11. package/lib/cli/deploy/FileManifest.js +272 -0
  12. package/lib/external/mcp/McpServer.js +22 -26
  13. package/lib/external/mcp/autoApproveInjector.js +1 -0
  14. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +5 -5
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +25 -3
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +6 -6
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +4 -0
  18. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +5 -5
  19. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +89 -44
  20. package/lib/external/mcp/handlers/consolidated.js +8 -9
  21. package/lib/external/mcp/handlers/dimension-complete-external.js +4 -4
  22. package/lib/external/mcp/handlers/guard.js +283 -5
  23. package/lib/external/mcp/handlers/task.js +183 -9
  24. package/lib/external/mcp/tools.js +32 -81
  25. package/lib/http/routes/task.js +55 -0
  26. package/lib/service/chat/AnalystAgent.js +12 -12
  27. package/lib/service/chat/ChatAgent.js +227 -545
  28. package/lib/service/chat/ChatAgentPrompts.js +9 -11
  29. package/lib/service/chat/ContextWindow.js +2 -296
  30. package/lib/service/chat/EpisodicConsolidator.js +15 -15
  31. package/lib/service/chat/ExplorationTracker.js +1262 -0
  32. package/lib/service/chat/HandoffProtocol.js +8 -9
  33. package/lib/service/chat/Memory.js +4 -0
  34. package/lib/service/chat/ProducerAgent.js +9 -6
  35. package/lib/service/chat/ProjectSemanticMemory.js +4 -0
  36. package/lib/service/chat/ReasoningTrace.js +182 -0
  37. package/lib/service/chat/WorkingMemory.js +4 -0
  38. package/lib/service/chat/memory/ActiveContext.js +910 -0
  39. package/lib/service/chat/memory/MemoryCoordinator.js +662 -0
  40. package/lib/service/chat/memory/PersistentMemory.js +450 -0
  41. package/lib/service/chat/memory/SessionStore.js +896 -0
  42. package/lib/service/chat/memory/index.js +13 -0
  43. package/lib/service/chat/tools/ast-graph.js +17 -16
  44. package/lib/service/cursor/AgentInstructionsGenerator.js +76 -47
  45. package/lib/service/cursor/FileProtection.js +4 -1
  46. package/lib/service/guard/GuardCheckEngine.js +10 -3
  47. package/lib/service/task/TaskGraphService.js +3 -3
  48. package/lib/shared/LanguageService.js +2 -1
  49. package/package.json +1 -1
  50. package/skills/autosnippet-intent/SKILL.md +1 -3
  51. package/skills/autosnippet-recipes/SKILL.md +1 -3
  52. package/templates/claude-code/commands/prime.md +19 -0
  53. package/templates/claude-code/hooks/autosnippet-session.sh +63 -0
  54. package/templates/claude-code/settings.json +21 -0
  55. package/templates/copilot-instructions.md +64 -177
  56. package/templates/cursor-hooks/commands/prime.md +12 -0
  57. package/templates/cursor-hooks/hooks/session-start.sh +10 -0
  58. package/templates/cursor-hooks/hooks.json +11 -0
  59. package/templates/cursor-rules/autosnippet-conventions.mdc +52 -3
  60. package/templates/cursor-rules/autosnippet-workflow.mdc +51 -27
  61. package/lib/external/mcp/handlers/decide.js +0 -109
  62. package/lib/external/mcp/handlers/ready.js +0 -42
  63. package/lib/service/chat/ReasoningLayer.js +0 -888
  64. package/templates/claude-hooks.yaml +0 -19
@@ -0,0 +1,272 @@
1
+ /**
2
+ * FileManifest — 所有可部署文件的单一真实来源
3
+ *
4
+ * Setup 和 Upgrade 共享同一份清单,由 FileDeployer 按策略执行。
5
+ *
6
+ * 字段说明:
7
+ * id — 唯一标识(用于日志和结果报告)
8
+ * src — 模板相对路径(相对于 templates/),null 表示需要 generate 函数
9
+ * dest — 目标相对路径(相对于 projectRoot)
10
+ * strategy — 部署策略(见 FileDeployer.STRATEGIES)
11
+ * on — 适用场景:'both' | 'setup' | 'upgrade'
12
+ * chmod — 是否需要 chmod +x(.sh 文件)
13
+ * generate — 自定义生成函数名(strategy 为 'generate' 时使用)
14
+ * category — 分组标签(用于 stepIDE 结果汇报)
15
+ */
16
+
17
+ /**
18
+ * 部署策略:
19
+ * 'overwrite' — AutoSnippet 完全拥有,始终覆盖
20
+ * 'overwrite-dir' — 递归覆盖整个目录(只覆盖 AutoSnippet 的文件)
21
+ * 'signature-safe' — 检查 AutoSnippet 签名再覆盖(保护用户自建文件)
22
+ * 'create-only' — 仅在文件不存在时创建(不更新)
23
+ * 'merge-json' — JSON 深度合并(只写入 autosnippet 键)
24
+ * 'merge-gitignore' — 增量追加缺失的 gitignore 规则
25
+ * 'backup-overwrite' — 备份旧文件后覆盖
26
+ * 'generate' — 自定义生成逻辑(由 generate 函数处理)
27
+ * 'inject-marker' — 在 <!-- autosnippet:begin/end --> 标记间注入/替换
28
+ */
29
+
30
+ export const MANIFEST = [
31
+ // ═══ MCP Config ═══════════════════════════════════════
32
+ {
33
+ id: 'cursor-mcp',
34
+ dest: '.cursor/mcp.json',
35
+ strategy: 'merge-json',
36
+ on: 'both',
37
+ category: 'mcp',
38
+ jsonKey: 'mcpServers',
39
+ },
40
+ {
41
+ id: 'vscode-mcp',
42
+ dest: '.vscode/mcp.json',
43
+ strategy: 'merge-json',
44
+ on: 'both',
45
+ category: 'mcp',
46
+ jsonKey: 'servers',
47
+ },
48
+
49
+ // ═══ Cursor Rules(AutoSnippet 完全拥有) ═══════════
50
+ {
51
+ id: 'cursor-conventions',
52
+ src: 'cursor-rules/autosnippet-conventions.mdc',
53
+ dest: '.cursor/rules/autosnippet-conventions.mdc',
54
+ strategy: 'overwrite',
55
+ on: 'both',
56
+ category: 'cursor-rules',
57
+ },
58
+ {
59
+ id: 'cursor-skills-template',
60
+ src: 'cursor-rules/autosnippet-skills.mdc',
61
+ dest: '.cursor/rules/autosnippet-skills.mdc',
62
+ strategy: 'overwrite',
63
+ on: 'both',
64
+ category: 'cursor-rules',
65
+ },
66
+ {
67
+ id: 'cursor-workflow',
68
+ src: 'cursor-rules/autosnippet-workflow.mdc',
69
+ dest: '.cursor/rules/autosnippet-workflow.mdc',
70
+ strategy: 'overwrite',
71
+ on: 'both',
72
+ category: 'cursor-rules',
73
+ },
74
+
75
+ // ═══ Cursor Hooks ═════════════════════════════════════
76
+ {
77
+ id: 'cursor-hooks-json',
78
+ src: 'cursor-hooks/hooks.json',
79
+ dest: '.cursor/hooks.json',
80
+ strategy: 'overwrite',
81
+ on: 'both',
82
+ category: 'cursor-hooks',
83
+ },
84
+ {
85
+ id: 'cursor-hooks-dir',
86
+ src: 'cursor-hooks/hooks/',
87
+ dest: '.cursor/hooks/',
88
+ strategy: 'overwrite-dir',
89
+ on: 'both',
90
+ category: 'cursor-hooks',
91
+ chmod: true,
92
+ },
93
+
94
+ // ═══ Cursor Commands ══════════════════════════════════
95
+ {
96
+ id: 'cursor-commands',
97
+ src: 'cursor-hooks/commands/',
98
+ dest: '.cursor/commands/',
99
+ strategy: 'overwrite-dir',
100
+ on: 'both',
101
+ category: 'cursor-commands',
102
+ },
103
+
104
+ // ═══ Claude Code ══════════════════════════════════════
105
+ {
106
+ id: 'claude-code',
107
+ src: 'claude-code/',
108
+ dest: '.claude/',
109
+ strategy: 'overwrite-dir',
110
+ on: 'both',
111
+ category: 'claude-hooks',
112
+ chmod: true,
113
+ cleanup: ['.claude/hooks.yaml'], // 清理旧格式文件
114
+ },
115
+
116
+ // ═══ Agent Instructions(签名保护)════════════════════
117
+ {
118
+ id: 'copilot-instructions',
119
+ src: 'copilot-instructions.md',
120
+ dest: '.github/copilot-instructions.md',
121
+ strategy: 'signature-safe',
122
+ on: 'both',
123
+ category: 'copilot-instructions',
124
+ fallback: 'inject-marker', // 签名保护失败时,用标记注入
125
+ },
126
+ {
127
+ id: 'agents-md',
128
+ strategy: 'generate',
129
+ generate: 'generateAgentsMd',
130
+ dest: 'AGENTS.md',
131
+ on: 'setup',
132
+ category: 'agent-instructions',
133
+ },
134
+
135
+ // ═══ CI/CD ════════════════════════════════════════════
136
+ {
137
+ id: 'guard-ci',
138
+ src: 'guard-ci.yml',
139
+ dest: '.github/workflows/autosnippet-guard.yml',
140
+ strategy: 'signature-safe',
141
+ on: 'both',
142
+ category: 'guard-ci',
143
+ },
144
+ {
145
+ id: 'pre-commit',
146
+ src: 'pre-commit-guard.sh',
147
+ dest: null, // 动态决定:.husky/pre-commit 或 .git/hooks/pre-commit
148
+ strategy: 'create-only',
149
+ on: 'setup',
150
+ category: 'pre-commit-hook',
151
+ chmod: true,
152
+ resolveDest: 'resolvePreCommitDest',
153
+ },
154
+
155
+ // ═══ Constitution ═════════════════════════════════════
156
+ // setup 由 stepCoreRepo 处理(create-only 语义)
157
+ // upgrade 时备份旧文件后覆盖
158
+ {
159
+ id: 'constitution',
160
+ src: 'constitution.yaml',
161
+ dest: 'AutoSnippet/constitution.yaml',
162
+ strategy: 'backup-overwrite',
163
+ on: 'upgrade',
164
+ category: 'constitution',
165
+ requireDir: 'AutoSnippet',
166
+ },
167
+
168
+ // ═══ Gitignore ════════════════════════════════════════
169
+ {
170
+ id: 'gitignore',
171
+ strategy: 'merge-gitignore',
172
+ dest: '.gitignore',
173
+ on: 'both',
174
+ category: 'gitignore',
175
+ },
176
+
177
+ // ═══ Skills ═══════════════════════════════════════════
178
+ {
179
+ id: 'skills-install',
180
+ strategy: 'generate',
181
+ generate: 'installSkills',
182
+ on: 'both',
183
+ category: 'skills',
184
+ },
185
+ {
186
+ id: 'skills-ensure-dir',
187
+ strategy: 'generate',
188
+ generate: 'ensureSkillsDir',
189
+ on: 'both',
190
+ category: 'skills',
191
+ },
192
+
193
+ // ═══ Dynamic Agent Instructions (requires DB) ════════
194
+ {
195
+ id: 'cursor-delivery',
196
+ strategy: 'generate',
197
+ generate: 'triggerCursorDelivery',
198
+ on: 'upgrade',
199
+ category: 'agent-instructions',
200
+ },
201
+
202
+ // ═══ Auto-approve injection ═══════════════════════════
203
+ // setup 不注入 autoApprove — 让用户首次使用时亲眼授权每个工具
204
+ // bootstrap 成功后由 bootstrap-external.js 自动注入
205
+ {
206
+ id: 'auto-approve',
207
+ strategy: 'generate',
208
+ generate: 'injectAutoApprove',
209
+ on: 'upgrade',
210
+ category: 'mcp',
211
+ },
212
+
213
+ // ═══ VSCode Extension ═════════════════════════════════
214
+ {
215
+ id: 'vscode-extension',
216
+ strategy: 'generate',
217
+ generate: 'installVSCodeExtension',
218
+ on: 'setup',
219
+ category: 'vscode-extension',
220
+ },
221
+ ];
222
+
223
+ /**
224
+ * .gitignore 规则清单 — Setup 和 Upgrade 共用
225
+ * 每条规则:{ pattern, comment, negation? }
226
+ */
227
+ export const GITIGNORE_RULES = [
228
+ // 核心运行时
229
+ { pattern: '.autosnippet/*', comment: 'AutoSnippet 运行时缓存(不入库)' },
230
+ { pattern: '!.autosnippet/config.json', negation: true },
231
+
232
+ // 环境变量
233
+ { pattern: '.env', comment: 'AutoSnippet 环境变量(含 API Key,不入库)' },
234
+
235
+ // 日志
236
+ { pattern: 'logs/', comment: 'AutoSnippet 运行日志' },
237
+
238
+ // AI 草稿
239
+ { pattern: '.autosnippet-drafts/', comment: 'AutoSnippet AI 草稿(临时)' },
240
+ { pattern: '_draft_*.md', comment: 'AutoSnippet AI 草稿文件(项目根目录临时文件)' },
241
+
242
+ // 系统 / 编辑器临时文件
243
+ { pattern: '.DS_Store', comment: 'macOS 元数据' },
244
+ { pattern: 'nohup.out' },
245
+ { pattern: '*.sw[a-p]' },
246
+ ];
247
+
248
+ /**
249
+ * .gitignore 迁移规则 — 升级时清理旧格式
250
+ */
251
+ export const GITIGNORE_MIGRATIONS = [
252
+ // v2.4.0: ".autosnippet/" → ".autosnippet/*"
253
+ { find: /^\.autosnippet\/$/m, replace: '.autosnippet/*' },
254
+ // Skills 已迁移到 AutoSnippet/skills/,清理旧 negation
255
+ { find: /^!?\.autosnippet\/skills\/.*\n?/gm, replace: '' },
256
+ ];
257
+
258
+ /**
259
+ * MCP Server 配置生成器
260
+ * @param {string} projectRoot
261
+ * @param {'cursor'|'vscode'} ide
262
+ */
263
+ export function buildMcpServerEntry(projectRoot, ide) {
264
+ const base = {
265
+ command: 'asd-mcp',
266
+ env: { ASD_PROJECT_DIR: projectRoot },
267
+ };
268
+ if (ide === 'vscode') {
269
+ return { type: 'stdio', ...base };
270
+ }
271
+ return base;
272
+ }
@@ -38,9 +38,7 @@ import * as systemHandlers from './handlers/system.js';
38
38
  // ─── External Agent Bootstrap 新 handler ──────────────────────
39
39
 
40
40
  import { bootstrapExternal } from './handlers/bootstrap-external.js';
41
- import { decideHandler } from './handlers/decide.js';
42
41
  import { dimensionComplete } from './handlers/dimension-complete-external.js';
43
- import { readyHandler } from './handlers/ready.js';
44
42
  import { taskHandler } from './handlers/task.js';
45
43
  import { wikiFinalize, wikiPlan } from './handlers/wiki-external.js';
46
44
 
@@ -185,6 +183,12 @@ export class McpServer {
185
183
  }
186
184
 
187
185
  const wrapped = wrapHandler(name, handler);
186
+
187
+ // Track task operation for _injectDecisions
188
+ if (name === 'autosnippet_task') {
189
+ this._lastTaskOperation = args.operation || '';
190
+ }
191
+
188
192
  const result = await wrapped(ctx, args);
189
193
 
190
194
  // ── P0+P3: Decision 注入 + Session 追踪 ──
@@ -211,10 +215,10 @@ export class McpServer {
211
215
  * 在工具返回结果中注入 decisions 摘要 + 更新 session 统计
212
216
  *
213
217
  * 策略:
214
- * - ready: 刷新缓存,不额外注入(response 本身含 decisions)
215
- * - decide 写操作: invalidate 缓存(下次查询拉最新)
218
+ * - prime: 刷新缓存,不额外注入(response 本身含 decisions)
219
+ * - decision 写操作 (record/revise/unpin): invalidate 缓存(下次查询拉最新)
216
220
  * - 其他工具: 注入 _activeDecisions 摘要
217
- * - 首次未调 ready 的工具: 注入更强提醒
221
+ * - 首次未调 prime 的工具: 注入更强提醒
218
222
  *
219
223
  * @param {string} toolName
220
224
  * @param {object} result — handler 返回的 envelope 对象
@@ -225,26 +229,20 @@ export class McpServer {
225
229
  this._session.toolsUsed.add(toolName);
226
230
  this._session.lastActivityAt = Date.now();
227
231
 
228
- // 1) ready 工具:刷新缓存 + 标记 session
229
- if (toolName === 'autosnippet_ready') {
230
- this._session.readyCalled = true;
231
- this._refreshCacheFromReady(result);
232
- return result;
233
- }
234
-
235
- // 2) decide 写操作:invalidate 缓存(record/revise/unpin 改变了 decisions)
236
- if (toolName === 'autosnippet_decide') {
237
- this._decisionCache.fetchedAt = 0;
238
- this._decisionCache._pending = null;
239
- return result;
240
- }
241
-
242
- // 3) autosnippet_task 也不注入(task 操作返回值已足够,避免 token 膨胀)
232
+ // 1) autosnippet_task: prime 刷新缓存,decision 写操作 invalidate 缓存
243
233
  if (toolName === 'autosnippet_task') {
234
+ const op = this._lastTaskOperation;
235
+ if (op === 'prime') {
236
+ this._session.readyCalled = true;
237
+ this._refreshCacheFromReady(result);
238
+ } else if (['record_decision', 'revise_decision', 'unpin_decision'].includes(op)) {
239
+ this._decisionCache.fetchedAt = 0;
240
+ this._decisionCache._pending = null;
241
+ }
244
242
  return result;
245
243
  }
246
244
 
247
- // 4) 对非 ready/decide 工具:注入 decisions 摘要
245
+ // 4) 对非 task 操作工具:注入 decisions 摘要
248
246
  const decisions = await this._getDecisionsSummary();
249
247
  if (decisions.length > 0 && typeof result === 'object' && result !== null) {
250
248
  result._activeDecisions = decisions;
@@ -252,11 +250,11 @@ export class McpServer {
252
250
  // P3: 如果 ready 从未被调用,注入更强提醒
253
251
  if (!this._session.readyCalled) {
254
252
  result._decisionReminder =
255
- '⚠️ You have NOT called autosnippet_ready() yet this session. ' +
256
- 'These decisions may affect your work. Call autosnippet_ready() for full context.';
253
+ '⚠️ You have NOT called autosnippet_task({ operation: "prime" }) yet this session. ' +
254
+ 'These decisions may affect your work. Call autosnippet_task({ operation: "prime" }) for full context.';
257
255
  } else {
258
256
  result._decisionReminder =
259
- 'Respect these team decisions. Call autosnippet_decide({ operation: "list" }) for full details.';
257
+ 'Respect these team decisions. Call autosnippet_task({ operation: "list_decisions" }) for full details.';
260
258
  }
261
259
  }
262
260
 
@@ -362,8 +360,6 @@ export class McpServer {
362
360
  knowledgeHandlers.submitKnowledgeBatch(ctx, args),
363
361
  autosnippet_save_document: (ctx, args) => knowledgeHandlers.saveDocument(ctx, args),
364
362
  autosnippet_skill: (ctx, args) => consolidated.consolidatedSkill(ctx, args),
365
- autosnippet_ready: (ctx, args) => readyHandler(ctx, args),
366
- autosnippet_decide: (ctx, args) => decideHandler(ctx, args),
367
363
  autosnippet_task: (ctx, args) => taskHandler(ctx, args),
368
364
  // ── External Agent Bootstrap (v3.1) ──
369
365
  autosnippet_bootstrap: (ctx, _args) => bootstrapExternal(ctx),
@@ -36,6 +36,7 @@ const AUTO_APPROVE_TOOLS = [
36
36
  'autosnippet_submit_knowledge_batch',
37
37
  'autosnippet_save_document',
38
38
  'autosnippet_skill',
39
+ 'autosnippet_task',
39
40
  'autosnippet_bootstrap',
40
41
  'autosnippet_dimension_complete',
41
42
  'autosnippet_wiki_plan',
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import crypto from 'node:crypto';
19
- import { EpisodicMemory } from './pipeline/EpisodicMemory.js';
19
+ import { SessionStore } from '../../../../service/chat/memory/SessionStore.js';
20
20
  import { ExternalSubmissionTracker } from './ExternalSubmissionTracker.js';
21
21
 
22
22
  // ── 常量 ────────────────────────────────────────────────────
@@ -37,7 +37,7 @@ export class BootstrapSession {
37
37
  this.projectRoot = projectRoot;
38
38
  this.dimensions = dimensions;
39
39
  this.completedDimensions = new Map(); // dimId → { report, completedAt, recipeIds }
40
- this.episodicMemory = new EpisodicMemory(projectContext);
40
+ this.sessionStore = new SessionStore(projectContext);
41
41
 
42
42
  /** 外部 Agent 提交追踪 (v2: 对标内部 Agent 的 EvidenceCollector) */
43
43
  this.submissionTracker = new ExternalSubmissionTracker();
@@ -98,9 +98,9 @@ export class BootstrapSession {
98
98
  completedAt: Date.now(),
99
99
  });
100
100
 
101
- // 写入 EpisodicMemory
102
- // keyFindings 是字符串数组,需转换为 EpisodicMemory 期望的 { finding, importance } 格式
103
- this.episodicMemory.storeDimensionReport(dimId, {
101
+ // 写入 SessionStore
102
+ // keyFindings 是字符串数组,需转换为 SessionStore 期望的 { finding, importance } 格式
103
+ this.sessionStore.storeDimensionReport(dimId, {
104
104
  analysisText: report.analysisText,
105
105
  findings: (report.keyFindings || []).map((f) => ({ finding: f, importance: 7 })),
106
106
  referencedFiles: report.referencedFiles || [],
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * EpisodicMemory — Bootstrap 会话级情景记忆
3
3
  *
4
+ * @deprecated Phase 4: 已合并入 SessionStore (lib/service/chat/memory/SessionStore.js)
5
+ * - 全部维度报告/证据/反思逻辑已迁入 SessionStore
6
+ * - 本文件保留做向后兼容,新代码请使用 SessionStore
7
+ *
4
8
  * 内部 Agent 和外部 Agent 共享此模块。
5
9
  *
6
10
  * 提供跨维度上下文:
@@ -128,11 +132,19 @@ export class EpisodicMemory {
128
132
  * @param {object} [report.digest] — DimensionDigest (兼容)
129
133
  */
130
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
+
131
143
  this.#dimensionReports.set(dimId, {
132
144
  dimId,
133
145
  completedAt: Date.now(),
134
146
  analysisText: report.analysisText || '',
135
- findings: report.findings || [],
147
+ findings,
136
148
  referencedFiles: report.referencedFiles || [],
137
149
  candidatesSummary: report.candidatesSummary || [],
138
150
  workingMemoryDistilled: report.workingMemoryDistilled || null,
@@ -140,7 +152,7 @@ export class EpisodicMemory {
140
152
  });
141
153
 
142
154
  // 自动提取文件级 Evidence
143
- for (const f of report.findings || []) {
155
+ for (const f of findings) {
144
156
  if (f.evidence) {
145
157
  const filePath = f.evidence.split(':')[0]; // "file.m:123" → "file.m"
146
158
  this.addEvidence(filePath, {
@@ -401,8 +413,18 @@ export class EpisodicMemory {
401
413
  parts.push(`${report.analysisText.substring(0, 300)}…`);
402
414
  }
403
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
+
404
426
  // 选择与当前维度相关的 findings
405
- const relevantFindings = this.#selectRelevantFindings(report.findings, focusKeywords, 5);
427
+ const relevantFindings = this.#selectRelevantFindings(findings, focusKeywords, 5);
406
428
  if (relevantFindings.length > 0) {
407
429
  parts.push('**具体发现**:');
408
430
  for (const f of relevantFindings) {
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import { BootstrapSnapshot } from './BootstrapSnapshot.js';
19
- import { EpisodicMemory } from './EpisodicMemory.js';
19
+ import { SessionStore } from '../../../../../service/chat/memory/SessionStore.js';
20
20
 
21
21
  // ──────────────────────────────────────────────────────────────
22
22
  // IncrementalBootstrap 类
@@ -106,16 +106,16 @@ export class IncrementalBootstrap {
106
106
  };
107
107
  }
108
108
 
109
- // 4. 增量可行 → 尝试恢复 EpisodicMemory
109
+ // 4. 增量可行 → 尝试恢复 SessionStore
110
110
  let restoredEpisodic = null;
111
111
  if (previousSnapshot.episodicData) {
112
112
  try {
113
- restoredEpisodic = EpisodicMemory.fromJSON(previousSnapshot.episodicData);
113
+ restoredEpisodic = SessionStore.fromJSON(previousSnapshot.episodicData);
114
114
  this.#log(
115
- `Restored EpisodicMemory: ${restoredEpisodic.getCompletedDimensions().length} dimensions`
115
+ `Restored SessionStore: ${restoredEpisodic.getCompletedDimensions().length} dimensions`
116
116
  );
117
117
  } catch (err) {
118
- this.#log(`Failed to restore EpisodicMemory: ${err.message}`, 'warn');
118
+ this.#log(`Failed to restore SessionStore: ${err.message}`, 'warn');
119
119
  }
120
120
  }
121
121
 
@@ -156,7 +156,7 @@ export class IncrementalBootstrap {
156
156
  * @param {string} params.sessionId
157
157
  * @param {Array} params.allFiles
158
158
  * @param {object} params.dimensionStats
159
- * @param {EpisodicMemory} [params.episodicMemory]
159
+ * @param {SessionStore} [params.episodicMemory]
160
160
  * @param {object} [params.meta] — { durationMs, candidateCount, primaryLang }
161
161
  * @param {IncrementalPlan} [params.plan] — evaluate() 返回的计划 (增量时)
162
162
  * @returns {string} 快照 ID
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * ToolResultCache — 跨维度工具结果缓存
3
3
  *
4
+ * @deprecated Phase 4: 已合并入 SessionStore (lib/service/chat/memory/SessionStore.js)
5
+ * - LRU 缓存逻辑 (仅只读工具) 已迁入 SessionStore
6
+ * - 本文件保留做向后兼容,新代码请使用 SessionStore
7
+ *
4
8
  * 缓存 search_project_code 和 read_project_file 的调用结果,
5
9
  * 避免后续维度重复执行已知搜索/读取操作。
6
10
  *
@@ -86,16 +86,16 @@ export function getFullDimensionConfig(dimId) {
86
86
  *
87
87
  * @param {number} tierIndex — Tier 索引 (0-based)
88
88
  * @param {Map<string, object>} tierResults — 本 Tier 的维度结果
89
- * @param {import('./EpisodicMemory.js').EpisodicMemory} episodicMemory
89
+ * @param {import('../../../../../service/chat/memory/SessionStore.js').SessionStore} sessionStore
90
90
  * @returns {object} TierReflection
91
91
  */
92
- export function buildTierReflection(tierIndex, tierResults, episodicMemory) {
92
+ export function buildTierReflection(tierIndex, tierResults, sessionStore) {
93
93
  const completedDimensions = [...tierResults.keys()];
94
94
 
95
95
  // 收集本 Tier 所有维度的 findings
96
96
  const allFindings = [];
97
97
  for (const dimId of completedDimensions) {
98
- const report = episodicMemory.getDimensionReport(dimId);
98
+ const report = sessionStore.getDimensionReport(dimId);
99
99
  if (report?.findings) {
100
100
  for (const f of report.findings) {
101
101
  allFindings.push({ dimId, ...f });
@@ -148,7 +148,7 @@ export function buildTierReflection(tierIndex, tierResults, episodicMemory) {
148
148
 
149
149
  // 找出 gaps (各维度报告的未覆盖方面)
150
150
  for (const dimId of completedDimensions) {
151
- const report = episodicMemory.getDimensionReport(dimId);
151
+ const report = sessionStore.getDimensionReport(dimId);
152
152
  const gaps = report?.digest?.gaps || [];
153
153
  for (const gap of gaps) {
154
154
  if (gap && typeof gap === 'string' && gap.length > 5) {
@@ -159,7 +159,7 @@ export function buildTierReflection(tierIndex, tierResults, episodicMemory) {
159
159
 
160
160
  // remainingTasks
161
161
  for (const dimId of completedDimensions) {
162
- const report = episodicMemory.getDimensionReport(dimId);
162
+ const report = sessionStore.getDimensionReport(dimId);
163
163
  const remaining = report?.digest?.remainingTasks || [];
164
164
  for (const task of remaining) {
165
165
  if (task?.signal) {