autosnippet 3.2.3 → 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 +3 -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 -719
  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 +75 -40
  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 +66 -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
@@ -1,888 +0,0 @@
1
- /**
2
- * ReasoningLayer — ChatAgent ReAct 循环的推理中间件
3
- *
4
- * 职责:
5
- * 1. 管理 ReasoningTrace 生命周期
6
- * 2. 从 AI 响应中提取 Thought
7
- * 3. 从工具结果中构建结构化 Observation
8
- * 4. 在合适时机触发 Reflection(反思)
9
- * 5. 在合适时机触发 Planning(规划/重规划)
10
- * 6. 提供推理质量指标
11
- *
12
- * 不拥有的职责:
13
- * - AI 调用 (仍由 ChatAgent 直接调 aiProvider)
14
- * - 工具执行 (仍由 ChatAgent 通过 ToolRegistry)
15
- * - 上下文压缩 (仍由 ContextWindow)
16
- * - 阶段控制 (仍由 PhaseRouter)
17
- *
18
- * ChatAgent 在主循环的 4 个生命周期点调用:
19
- * 1. beforeAICall(iteration, opts) — 开始新轮次 + 可选注入反思/规划
20
- * 2. afterAICall(aiResult) — 提取 Thought + 提取 Plan
21
- * 3. afterToolExec(name, args, result, metrics) — 构建 Observation
22
- * 4. afterRound(roundResults) — 关闭轮次 + 写入摘要 + 更新计划进度
23
- *
24
- * 回滚策略: new ReasoningLayer({ enabled: false }) 一键禁用全部功能
25
- *
26
- * @module ReasoningLayer
27
- */
28
-
29
- import Logger from '../../infrastructure/logging/Logger.js';
30
- import { ReasoningTrace } from './ReasoningTrace.js';
31
-
32
- /** 反思间隔(每 N 轮触发一次) */
33
- const DEFAULT_REFLECTION_INTERVAL = 5;
34
- /** 连续无新信息 N 轮触发停滞反思 */
35
- const DEFAULT_STALE_THRESHOLD = 2;
36
- /** 最少经过 N 轮后才允许触发停滞反思 */
37
- const MIN_ITERS_FOR_STALE_REFLECTION = 4;
38
- /** 默认重规划间隔 */
39
- const DEFAULT_REPLAN_INTERVAL = 8;
40
- /** 默认偏差阈值 */
41
- const DEFAULT_DEVIATION_THRESHOLD = 0.6;
42
-
43
- export class ReasoningLayer {
44
- /** @type {ReasoningTrace} */
45
- #trace;
46
- /** @type {object} */
47
- #config;
48
- /** @type {object} */
49
- #logger;
50
- /** @type {object} */
51
- #planProgress;
52
- /** @type {boolean} */
53
- #pendingReplan = false;
54
-
55
- /**
56
- * @param {object} [config]
57
- * @param {boolean} [config.enabled=true] — 总开关
58
- * @param {boolean} [config.reflectionEnabled=true] — 反思开关
59
- * @param {number} [config.reflectionInterval=5] — 周期性反思间隔轮次
60
- * @param {number} [config.staleThreshold=2] — 停滞触发阈值(连续无新信息轮次)
61
- * @param {boolean} [config.planningEnabled=false] — 规划开关
62
- * @param {number} [config.replanInterval=8] — 周期性重规划间隔轮次
63
- * @param {number} [config.deviationThreshold=0.6] — 偏差触发重规划的阈值
64
- */
65
- constructor(config = {}) {
66
- this.#config = {
67
- enabled: config.enabled !== false,
68
- reflectionEnabled: config.reflectionEnabled !== false,
69
- reflectionInterval: config.reflectionInterval ?? DEFAULT_REFLECTION_INTERVAL,
70
- staleThreshold: config.staleThreshold ?? DEFAULT_STALE_THRESHOLD,
71
- planningEnabled: config.planningEnabled ?? false,
72
- replanInterval: config.replanInterval ?? DEFAULT_REPLAN_INTERVAL,
73
- deviationThreshold: config.deviationThreshold ?? DEFAULT_DEVIATION_THRESHOLD,
74
- };
75
- this.#trace = new ReasoningTrace();
76
- this.#logger = Logger.getInstance();
77
- this.#planProgress = {
78
- coveredSteps: 0,
79
- totalSteps: 0,
80
- deviationScore: 0,
81
- unplannedActions: 0,
82
- lastReplanIteration: null,
83
- consecutiveOffPlan: 0,
84
- };
85
- }
86
-
87
- /**
88
- * 获取推理链引用(只读)
89
- * @returns {ReasoningTrace}
90
- */
91
- get trace() {
92
- return this.#trace;
93
- }
94
-
95
- // ─── 生命周期 Hook ────────────────────────────────────
96
-
97
- /**
98
- * Hook 1: AI 调用前
99
- * - 开始新轮次
100
- * - 检查是否应触发 Planning(第 1 轮 plan elicitation,或周期性 replan)
101
- * - 检查是否应触发 Reflection
102
- * - Planning 和 Reflection 同时触发时合并为一个 nudge
103
- *
104
- * @param {number} iteration — 当前迭代轮次
105
- * @param {object} [opts]
106
- * @param {object} [opts.explorationMetrics] — 探索指标
107
- * @param {object} [opts.budget] — 预算配置 { maxIterations }
108
- * @param {string} [opts.phase] — 当前 PhaseRouter 阶段
109
- * @returns {string|null} — 反思/规划提示(null = 不触发)
110
- */
111
- beforeAICall(iteration, { explorationMetrics, budget, phase } = {}) {
112
- if (!this.#config.enabled) {
113
- return null;
114
- }
115
-
116
- this.#trace.startRound(iteration);
117
-
118
- // ── Planning: 第 1 轮注入 plan elicitation ──
119
- if (this.#config.planningEnabled && iteration === 1) {
120
- return this.#buildPlanElicitationPrompt(budget);
121
- }
122
-
123
- // ── Planning: 周期性/偏差触发 replan ──
124
- let replanNudge = null;
125
- if (this.#config.planningEnabled && this.#trace.getPlan()) {
126
- replanNudge = this.#checkReplan(iteration, explorationMetrics, budget, phase);
127
- }
128
-
129
- // ── Reflection ──
130
- let reflectionNudge = null;
131
- if (this.#config.reflectionEnabled) {
132
- reflectionNudge = this.#checkReflection({ iteration, explorationMetrics, budget, phase });
133
- }
134
-
135
- // ── 合并 replan + reflection ──
136
- if (replanNudge && reflectionNudge) {
137
- // 两者同时触发 → 合并,避免两条独立 nudge
138
- return `${replanNudge}\n\n${reflectionNudge}`;
139
- }
140
- return replanNudge || reflectionNudge || null;
141
- }
142
-
143
- /**
144
- * Hook 2: AI 调用后
145
- * - 从 AI 响应中提取 Thought
146
- * - 从 AI 响应中提取 Plan(首次 / replan 后)
147
- *
148
- * @param {object} aiResult — AI 返回结果 (native tool calling)
149
- */
150
- afterAICall(aiResult) {
151
- if (!this.#config.enabled) {
152
- return;
153
- }
154
-
155
- let extractedText = null;
156
-
157
- // Native 模式: 当 AI 同时返回文本和工具调用时,文本就是 thought
158
- if (aiResult?.text && aiResult?.functionCalls?.length > 0) {
159
- this.#trace.setThought(aiResult.text);
160
- extractedText = aiResult.text;
161
- this.#logger.info(
162
- `[ReasoningLayer] 💭 thought: ${aiResult.text.substring(0, 150).replace(/\n/g, '↵')}…`
163
- );
164
- } else if (aiResult?.text) {
165
- extractedText = aiResult.text;
166
- }
167
-
168
- // ── Planning: 从 AI 响应中提取 plan ──
169
- if (this.#config.planningEnabled && extractedText) {
170
- if (!this.#trace.getPlan()) {
171
- // 首次: 提取初始 plan
172
- const planText = this.#extractPlanFromText(extractedText);
173
- if (planText) {
174
- const iteration = this.#trace.getCurrentIteration() || 1;
175
- this.#trace.setPlan(planText, iteration);
176
- const plan = this.#trace.getPlan();
177
- this.#planProgress.totalSteps = plan.steps.length;
178
- this.#logger.info(`[ReasoningLayer] 📋 plan extracted (${plan.steps.length} steps)`);
179
- }
180
- } else if (this.#pendingReplan) {
181
- // replan 后: 提取更新的 plan
182
- const replanText = this.#extractPlanFromText(extractedText);
183
- if (replanText) {
184
- const iteration = this.#trace.getCurrentIteration() || 1;
185
- this.#trace.updatePlan(replanText, iteration);
186
- const plan = this.#trace.getPlan();
187
- // 重置进度追踪
188
- this.#planProgress.coveredSteps = plan.steps.filter((s) => s.status === 'done').length;
189
- this.#planProgress.totalSteps = plan.steps.length;
190
- this.#planProgress.unplannedActions = 0;
191
- this.#planProgress.consecutiveOffPlan = 0;
192
- this.#pendingReplan = false;
193
- this.#logger.info(`[ReasoningLayer] 📋 plan updated (${plan.steps.length} steps)`);
194
- }
195
- }
196
- }
197
- }
198
-
199
- /**
200
- * Hook 3: 单个工具执行后
201
- * - 记录 Action
202
- * - 构建结构化 Observation
203
- *
204
- * @param {string} toolName
205
- * @param {object} args
206
- * @param {any} result — 工具返回的原始结果
207
- * @param {object} [explorationMetrics] — 探索指标(用于判断是否新信息)
208
- */
209
- afterToolExec(toolName, args, result, explorationMetrics) {
210
- if (!this.#config.enabled) {
211
- return;
212
- }
213
-
214
- this.#trace.addAction(toolName, args);
215
-
216
- const meta = this.#buildObservationMeta(toolName, args, result, explorationMetrics);
217
- this.#trace.addObservation(toolName, meta);
218
- }
219
-
220
- /**
221
- * Hook 4: 本轮所有工具执行后
222
- * - 写入轮次摘要
223
- * - 更新计划进度
224
- * - 关闭当前轮次
225
- *
226
- * @param {object} [roundResults]
227
- * @param {number} [roundResults.newInfoCount] — 本轮获取新信息的工具数
228
- * @param {number} [roundResults.totalCalls] — 本轮工具调用总数
229
- * @param {number} [roundResults.submitCount] — 本轮提交候选数
230
- * @param {object} [roundResults.explorationMetrics] — 探索指标
231
- */
232
- afterRound({ newInfoCount, totalCalls, submitCount, explorationMetrics } = {}) {
233
- if (!this.#config.enabled) {
234
- return;
235
- }
236
-
237
- this.#trace.setRoundSummary({
238
- newInfoCount: newInfoCount || 0,
239
- totalCalls: totalCalls || 0,
240
- submits: submitCount || 0,
241
- cumulativeFiles: explorationMetrics?.uniqueFiles?.size || 0,
242
- cumulativePatterns: explorationMetrics?.uniquePatterns?.size || 0,
243
- });
244
-
245
- // ── Planning: 更新 plan 进度 ──
246
- if (this.#config.planningEnabled && this.#trace.getPlan()) {
247
- this.#updatePlanProgress();
248
- }
249
-
250
- this.#trace.endRound();
251
- }
252
-
253
- /**
254
- * 推理质量评分(最终回传给调用方)
255
- *
256
- * 评分维度:
257
- * Planning 未启用时:
258
- * - thoughtRatio: 有推理文本的轮次占比 (权重 40%)
259
- * - reflectionRatio: 有反思的轮次占比 (权重 20%)
260
- * - actionEfficiency: 平均每轮 action 数 (权重 20%)
261
- * - observationCoverage: 有结构化观察的轮次占比 (权重 20%)
262
- * Planning 启用时 (有 plan):
263
- * - thoughtRatio: 30%, reflectionRatio: 15%, actionEfficiency: 15%
264
- * - observationCoverage: 15%, planScore: 25%
265
- *
266
- * @returns {{ score: number, breakdown: object }}
267
- */
268
- getQualityMetrics() {
269
- const stats = this.#trace.getStats();
270
- const totalRounds = stats.totalRounds || 1; // 避免除零
271
-
272
- const thoughtRatio = stats.thoughtCount / totalRounds;
273
- const reflectionRatio = stats.reflectionCount / totalRounds;
274
- const actionEfficiency = Math.min(stats.totalActions / totalRounds / 3, 1); // 每轮 3 个 action 为满分
275
- const observationCoverage = stats.totalObservations > 0 ? 1 : 0;
276
-
277
- // ── Planning 指标 ──
278
- const plan = this.#trace.getPlan();
279
- const hasPlan = plan && plan.steps.length > 0;
280
- let planScore = 0;
281
- if (hasPlan) {
282
- const completionRate =
283
- this.#planProgress.totalSteps > 0
284
- ? this.#planProgress.coveredSteps / this.#planProgress.totalSteps
285
- : 0;
286
- const adherenceRate = 1 - (this.#planProgress.deviationScore || 0);
287
- planScore = completionRate * 0.6 + adherenceRate * 0.4;
288
- }
289
-
290
- const score = hasPlan
291
- ? Math.round(
292
- (thoughtRatio * 0.3 +
293
- reflectionRatio * 0.15 +
294
- actionEfficiency * 0.15 +
295
- observationCoverage * 0.15 +
296
- planScore * 0.25) *
297
- 100
298
- )
299
- : Math.round(
300
- (thoughtRatio * 0.4 +
301
- reflectionRatio * 0.2 +
302
- actionEfficiency * 0.2 +
303
- observationCoverage * 0.2) *
304
- 100
305
- );
306
-
307
- const breakdown = {
308
- ...stats,
309
- thoughtRatio: Math.round(thoughtRatio * 100),
310
- reflectionRatio: Math.round(reflectionRatio * 100),
311
- actionEfficiency: Math.round(actionEfficiency * 100),
312
- observationCoverage: Math.round(observationCoverage * 100),
313
- };
314
-
315
- if (hasPlan) {
316
- breakdown.planCompletion = Math.round(
317
- (this.#planProgress.totalSteps > 0
318
- ? this.#planProgress.coveredSteps / this.#planProgress.totalSteps
319
- : 0) * 100
320
- );
321
- breakdown.planAdherence = Math.round((1 - (this.#planProgress.deviationScore || 0)) * 100);
322
- breakdown.planScore = Math.round(planScore * 100);
323
- }
324
-
325
- return { score, breakdown };
326
- }
327
-
328
- /**
329
- * 获取当前计划进度(供外部查询)
330
- * @returns {object}
331
- */
332
- getPlanProgress() {
333
- return { ...this.#planProgress };
334
- }
335
-
336
- // ─── 内部方法 ──────────────────────────────────────────
337
-
338
- /**
339
- * 判断是否应该触发反思 + 生成反思内容
340
- *
341
- * 触发条件(任一满足):
342
- * 1. 周期性: 每 reflectionInterval 轮
343
- * 2. 停滞: 连续 N 轮无新信息 (staleRounds >= staleThreshold)
344
- *
345
- * @param {object} opts
346
- * @returns {string|null}
347
- * @private
348
- */
349
- #checkReflection({ iteration, explorationMetrics, budget, phase }) {
350
- const interval = this.#config.reflectionInterval;
351
- const staleThreshold = this.#config.staleThreshold;
352
-
353
- // 触发条件
354
- const periodicTrigger = iteration > 1 && interval > 0 && iteration % interval === 0;
355
- const staleTrigger =
356
- explorationMetrics?.staleRounds >= staleThreshold &&
357
- iteration >= MIN_ITERS_FOR_STALE_REFLECTION;
358
-
359
- if (!periodicTrigger && !staleTrigger) {
360
- return null;
361
- }
362
-
363
- const summary = this.#trace.getRecentSummary(interval || 3);
364
- if (!summary) {
365
- return null;
366
- }
367
-
368
- const stats = this.#trace.getStats();
369
- const maxIter = budget?.maxIterations || 30;
370
- const remaining = maxIter - iteration;
371
- const progressPct = Math.round((iteration / maxIter) * 100);
372
-
373
- // ── 构建反思提示 ──
374
- const parts = [];
375
-
376
- if (staleTrigger) {
377
- parts.push(
378
- `📊 停滞反思 (第 ${iteration}/${maxIter} 轮, 连续 ${explorationMetrics.staleRounds} 轮无新信息):`
379
- );
380
- } else {
381
- parts.push(`📊 中期反思 (第 ${iteration}/${maxIter} 轮, ${progressPct}% 预算):`);
382
- }
383
-
384
- // 过去推理回顾
385
- if (summary.thoughts.length > 0) {
386
- parts.push(
387
- `\n你最近的思考方向:\n${summary.thoughts.map((t, i) => ` ${i + 1}. ${t}`).join('\n')}`
388
- );
389
- }
390
-
391
- // 行动效率统计
392
- parts.push(
393
- `\n行动效率: 最近 ${summary.roundCount} 轮中 ${Math.round(summary.newInfoRatio * 100)}% 获取到新信息`
394
- );
395
-
396
- // 累计进度
397
- parts.push(
398
- `累计: ${explorationMetrics?.uniqueFiles?.size || 0} 文件, ${explorationMetrics?.uniquePatterns?.size || 0} 搜索模式, ${stats.totalActions} 次工具调用`
399
- );
400
-
401
- // ── Planning 进度附加 ──
402
- if (this.#config.planningEnabled) {
403
- const plan = this.#trace.getPlan();
404
- if (plan && plan.steps.length > 0) {
405
- const doneCount = plan.steps.filter((s) => s.status === 'done').length;
406
- parts.push(`\n📋 计划进度: ${doneCount}/${plan.steps.length} 步骤已完成`);
407
- }
408
- }
409
-
410
- // 阶段化评估问题
411
- if (phase === 'EXPLORE' || !phase) {
412
- parts.push(
413
- `\n请评估:\n1. 到目前为止最重要的发现是什么?\n2. 还有哪些关键方面未覆盖?\n3. 剩余 ${remaining} 轮,最有价值的下一步是什么?`
414
- );
415
- } else if (phase === 'PRODUCE') {
416
- parts.push(`\n请评估:\n1. 已提交的候选是否覆盖了核心发现?\n2. 是否有高价值知识点被遗漏?`);
417
- }
418
-
419
- const reflectionText = parts.join('\n');
420
-
421
- // 记录到 trace
422
- this.#trace.setReflection(reflectionText);
423
-
424
- this.#logger.info(
425
- `[ReasoningLayer] 💭 reflection triggered at iteration ${iteration} (${staleTrigger ? 'stale' : 'periodic'})`
426
- );
427
-
428
- return reflectionText;
429
- }
430
-
431
- /**
432
- * 从工具执行结果中提取结构化观察元数据
433
- *
434
- * 不改变工具结果传给 AI 的内容,只影响 ReasoningTrace 记录
435
- *
436
- * @param {string} toolName
437
- * @param {object} args
438
- * @param {any} result
439
- * @param {object} [metrics] — explorationMetrics
440
- * @returns {object}
441
- * @private
442
- */
443
- #buildObservationMeta(toolName, args, result, metrics) {
444
- const meta = {
445
- gotNewInfo: false,
446
- resultType: 'unknown',
447
- keyFacts: [],
448
- resultSize: 0,
449
- };
450
-
451
- const resultStr = typeof result === 'string' ? result : JSON.stringify(result || '');
452
- meta.resultSize = resultStr.length;
453
-
454
- switch (toolName) {
455
- case 'search_project_code': {
456
- meta.resultType = 'search';
457
- const matches = result?.matches || [];
458
- const batchResults = result?.batchResults;
459
- const totalMatches = batchResults
460
- ? Object.values(batchResults).reduce((s, br) => s + (br.matches?.length || 0), 0)
461
- : matches.length;
462
- meta.keyFacts.push(`${totalMatches} matches found`);
463
- // 新文件检测
464
- if (metrics?.uniqueFiles) {
465
- const allFiles = [];
466
- for (const m of matches) {
467
- if (m.file) {
468
- allFiles.push(m.file);
469
- }
470
- }
471
- if (batchResults) {
472
- for (const sub of Object.values(batchResults)) {
473
- for (const m of sub.matches || []) {
474
- if (m.file) {
475
- allFiles.push(m.file);
476
- }
477
- }
478
- }
479
- }
480
- const newFiles = allFiles.filter((f) => !metrics.uniqueFiles.has(f));
481
- meta.gotNewInfo = newFiles.length > 0;
482
- if (newFiles.length > 0) {
483
- meta.keyFacts.push(`${newFiles.length} new files`);
484
- }
485
- } else {
486
- meta.gotNewInfo = totalMatches > 0;
487
- }
488
- break;
489
- }
490
-
491
- case 'read_project_file': {
492
- meta.resultType = 'file_content';
493
- const fp = args?.filePath || '';
494
- const fps = args?.filePaths || [];
495
- const allPaths = fp ? [fp, ...fps] : fps;
496
- if (metrics?.uniqueFiles) {
497
- const newPaths = allPaths.filter((p) => !metrics.uniqueFiles.has(p));
498
- meta.gotNewInfo = newPaths.length > 0;
499
- } else {
500
- meta.gotNewInfo = allPaths.length > 0;
501
- }
502
- meta.keyFacts.push(`read ${allPaths.length} file(s)`);
503
- break;
504
- }
505
-
506
- case 'submit_knowledge':
507
- case 'submit_with_check': {
508
- meta.resultType = 'submit';
509
- meta.gotNewInfo = true; // submit 本身就是进展
510
- const status = typeof result === 'object' ? result?.status || 'ok' : 'ok';
511
- const title = args?.title || '(untitled)';
512
- meta.keyFacts.push(`submit "${title}": ${status}`);
513
- break;
514
- }
515
-
516
- case 'list_project_structure': {
517
- meta.resultType = 'structure';
518
- const dir = args?.directory || '/';
519
- if (metrics?.uniqueQueries) {
520
- const qKey = `list:${dir}`;
521
- meta.gotNewInfo = !metrics.uniqueQueries.has(qKey);
522
- } else {
523
- meta.gotNewInfo = true;
524
- }
525
- meta.keyFacts.push(`list ${dir}`);
526
- break;
527
- }
528
-
529
- case 'get_class_info':
530
- case 'get_class_hierarchy':
531
- case 'get_protocol_info':
532
- case 'get_method_overrides':
533
- case 'get_category_map': {
534
- meta.resultType = 'ast_query';
535
- const target = args?.className || args?.protocolName || args?.name || '';
536
- if (metrics?.uniqueQueries) {
537
- const qKey = `${toolName}:${target}`;
538
- meta.gotNewInfo = !metrics.uniqueQueries.has(qKey);
539
- } else {
540
- meta.gotNewInfo = true;
541
- }
542
- meta.keyFacts.push(`${toolName}(${target})`);
543
- break;
544
- }
545
-
546
- case 'get_project_overview': {
547
- meta.resultType = 'overview';
548
- if (metrics?.uniqueQueries) {
549
- meta.gotNewInfo = !metrics.uniqueQueries.has('overview');
550
- } else {
551
- meta.gotNewInfo = true;
552
- }
553
- meta.keyFacts.push('project overview');
554
- break;
555
- }
556
-
557
- case 'semantic_search_code':
558
- case 'get_file_summary':
559
- case 'get_previous_analysis': {
560
- meta.resultType = 'query';
561
- meta.gotNewInfo = true; // 保守假设
562
- meta.keyFacts.push(`${toolName}`);
563
- break;
564
- }
565
-
566
- default: {
567
- meta.resultType = 'other';
568
- meta.gotNewInfo = true; // 保守假设
569
- meta.keyFacts.push(`${toolName}`);
570
- }
571
- }
572
-
573
- return meta;
574
- }
575
-
576
- // ─── Planning 内部方法 ─────────────────────────────────
577
-
578
- /**
579
- * 构建第 1 轮的 plan elicitation prompt
580
- * @param {object} [budget]
581
- * @returns {string}
582
- * @private
583
- */
584
- #buildPlanElicitationPrompt(budget) {
585
- const maxIter = budget?.maxIterations || 30;
586
- return [
587
- `📋 在开始探索前,请先制定一个简要的探索计划。`,
588
- ``,
589
- `你有 ${maxIter} 轮工具调用机会。请在你的回复中用编号列表简述 3-6 个探索步骤:`,
590
- `- 每个步骤应描述要搜索/阅读的目标(具体的类名、模式、文件路径)`,
591
- `- 步骤应从宏观到微观递进(先概览 → 再搜索关键模式 → 再深入关键文件)`,
592
- `- 最后一步应是"总结分析发现"`,
593
- ``,
594
- `例如:`,
595
- `1. 获取项目概览和目录结构,识别核心模块`,
596
- `2. 搜索网络请求相关类,分析请求模式`,
597
- `3. 搜索错误处理和响应解析模式`,
598
- `4. 深入阅读 3-5 个典型实现文件,确认关键细节`,
599
- `5. 总结分析发现`,
600
- ``,
601
- `制定计划后请立即开始执行第 1 步(可在同一轮中同时输出计划文本并调用工具)。`,
602
- ].join('\n');
603
- }
604
-
605
- /**
606
- * 检查是否应触发重规划(replan)
607
- *
608
- * 触发条件(任一满足):
609
- * 1. 周期性: 距上次 replan 超过 replanInterval 轮
610
- * 2. 偏差: 连续 3 轮执行计划外行为 或 偏差分数超阈值
611
- *
612
- * @param {number} iteration
613
- * @param {object} [explorationMetrics]
614
- * @param {object} [budget]
615
- * @param {string} [phase]
616
- * @returns {string|null}
617
- * @private
618
- */
619
- #checkReplan(iteration, explorationMetrics, budget, phase) {
620
- const plan = this.#trace.getPlan();
621
- if (!plan) {
622
- return null;
623
- }
624
-
625
- const progress = this.#planProgress;
626
- const interval = this.#config.replanInterval;
627
- const deviationThreshold = this.#config.deviationThreshold;
628
-
629
- // 触发条件
630
- const baseIteration = progress.lastReplanIteration || plan.createdAtIteration;
631
- const periodicTrigger = interval > 0 && iteration > 1 && iteration - baseIteration >= interval;
632
- const deviationTrigger =
633
- progress.consecutiveOffPlan >= 3 ||
634
- (progress.totalSteps > 0 && progress.deviationScore > deviationThreshold);
635
-
636
- if (!periodicTrigger && !deviationTrigger) {
637
- return null;
638
- }
639
-
640
- // 构建 replan nudge
641
- const maxIter = budget?.maxIterations || 30;
642
- const remaining = maxIter - iteration;
643
-
644
- const parts = [];
645
-
646
- if (deviationTrigger) {
647
- parts.push(`📋 计划偏差检查 (第 ${iteration}/${maxIter} 轮):`);
648
- if (progress.consecutiveOffPlan >= 3) {
649
- parts.push(`你的行为已连续 ${progress.consecutiveOffPlan} 轮偏离原定计划。`);
650
- }
651
- } else {
652
- parts.push(`📋 计划进度回顾 (第 ${iteration}/${maxIter} 轮):`);
653
- }
654
-
655
- // 步骤完成情况
656
- const doneSteps = plan.steps.filter((s) => s.status === 'done');
657
- const pendingSteps = plan.steps.filter((s) => s.status === 'pending');
658
-
659
- if (doneSteps.length > 0) {
660
- parts.push(`\n✅ 已完成 (${doneSteps.length}/${plan.steps.length}):`);
661
- for (const s of doneSteps) {
662
- parts.push(` - ${s.description}`);
663
- }
664
- }
665
- if (pendingSteps.length > 0) {
666
- parts.push(`\n⏳ 未完成 (${pendingSteps.length}/${plan.steps.length}):`);
667
- for (const s of pendingSteps) {
668
- parts.push(` - ${s.description}`);
669
- }
670
- }
671
- if (progress.unplannedActions > 0) {
672
- parts.push(`\n⚡ 计划外行为: ${progress.unplannedActions} 次`);
673
- }
674
-
675
- parts.push(`\n剩余 ${remaining} 轮。请评估:`);
676
- parts.push(`1. 未完成的步骤是否仍然相关?`);
677
- parts.push(`2. 是否需要根据新发现调整后续步骤?`);
678
- parts.push(`3. 请更新你的探索计划(用编号列表)。`);
679
-
680
- progress.lastReplanIteration = iteration;
681
- this.#pendingReplan = true;
682
-
683
- this.#logger.info(
684
- `[ReasoningLayer] 📋 replan triggered at iteration ${iteration} (${deviationTrigger ? 'deviation' : 'periodic'})`
685
- );
686
-
687
- return parts.join('\n');
688
- }
689
-
690
- /**
691
- * 每轮结束后更新计划进度
692
- * - 将本轮工具调用与 plan 步骤进行模糊匹配
693
- * - 更新步骤状态和偏差指标
694
- * @private
695
- */
696
- #updatePlanProgress() {
697
- const steps = this.#trace.getPlanStepsMutable();
698
- if (steps.length === 0) {
699
- return;
700
- }
701
-
702
- const actions = this.#trace.getCurrentRoundActions();
703
- if (actions.length === 0) {
704
- return;
705
- }
706
-
707
- let matchedThisRound = false;
708
-
709
- for (const action of actions) {
710
- const matchedStep = this.#findMatchingStep(steps, action);
711
- if (matchedStep) {
712
- matchedStep.status = 'done';
713
- matchedThisRound = true;
714
- } else {
715
- this.#planProgress.unplannedActions++;
716
- }
717
- }
718
-
719
- // 更新偏差追踪
720
- if (matchedThisRound) {
721
- this.#planProgress.consecutiveOffPlan = 0;
722
- } else {
723
- this.#planProgress.consecutiveOffPlan++;
724
- }
725
-
726
- // 重新计算进度
727
- this.#planProgress.coveredSteps = steps.filter((s) => s.status === 'done').length;
728
- this.#planProgress.totalSteps = steps.length;
729
- this.#planProgress.deviationScore =
730
- steps.length > 0 ? 1 - this.#planProgress.coveredSteps / steps.length : 0;
731
- }
732
-
733
- /**
734
- * 模糊匹配: 将工具调用匹配到 plan 步骤
735
- *
736
- * 匹配策略:
737
- * 1. 步骤关键词出现在工具参数中
738
- * 2. 工具类型匹配步骤描述的语义
739
- *
740
- * @param {Array} steps — plan steps (mutable reference)
741
- * @param {object} action — { tool, params }
742
- * @returns {object|null} — 匹配到的 step,或 null
743
- * @private
744
- */
745
- #findMatchingStep(steps, action) {
746
- const toolName = action.tool;
747
- const argsStr = JSON.stringify(action.params || {}).toLowerCase();
748
-
749
- for (const step of steps) {
750
- if (step.status === 'done') {
751
- continue;
752
- }
753
-
754
- // 策略 1: 关键词匹配
755
- if (step.keywords?.length > 0) {
756
- const matched = step.keywords.some((kw) => argsStr.includes(kw.toLowerCase()));
757
- if (matched) {
758
- return step;
759
- }
760
- }
761
-
762
- // 策略 2: 工具类型 → 步骤描述的语义匹配
763
- const desc = step.description.toLowerCase();
764
- if (
765
- toolName === 'get_project_overview' &&
766
- (desc.includes('概览') ||
767
- desc.includes('overview') ||
768
- desc.includes('结构') ||
769
- desc.includes('项目'))
770
- ) {
771
- return step;
772
- }
773
- if (
774
- toolName === 'list_project_structure' &&
775
- (desc.includes('目录') || desc.includes('结构') || desc.includes('structure'))
776
- ) {
777
- return step;
778
- }
779
- if (
780
- (toolName === 'get_class_info' || toolName === 'get_class_hierarchy') &&
781
- (desc.includes('继承') ||
782
- desc.includes('类') ||
783
- desc.includes('hierarchy') ||
784
- desc.includes('class'))
785
- ) {
786
- return step;
787
- }
788
- if (
789
- toolName === 'read_project_file' &&
790
- (desc.includes('阅读') ||
791
- desc.includes('read') ||
792
- desc.includes('深入') ||
793
- desc.includes('查看') ||
794
- desc.includes('文件'))
795
- ) {
796
- return step;
797
- }
798
- // search_project_code 匹配更宽松:任何含"搜索/查找/search"的待处理步骤
799
- if (
800
- toolName === 'search_project_code' &&
801
- (desc.includes('搜索') ||
802
- desc.includes('search') ||
803
- desc.includes('查找') ||
804
- desc.includes('分析'))
805
- ) {
806
- return step;
807
- }
808
- }
809
-
810
- return null;
811
- }
812
-
813
- /**
814
- * 从 AI 响应文本中提取"计划"部分
815
- *
816
- * 策略:
817
- * 1. 查找"计划/plan/步骤"标记后的编号列表
818
- * 2. 如果没有明确标记,提取文本前部分的编号列表
819
- *
820
- * @param {string} text — AI 完整响应文本
821
- * @returns {string|null} — 提取的 plan 文本,或 null
822
- * @private
823
- */
824
- #extractPlanFromText(text) {
825
- if (!text || text.length < 30) {
826
- return null;
827
- }
828
-
829
- // 在文本的前 2000 字符中搜索计划
830
- const searchArea = text.substring(0, 2000);
831
-
832
- // 策略 1: 查找"计划"标记
833
- const planMarkers = [
834
- /(?:探索|分析)?计划[::\s]/i,
835
- /(?:my\s+)?plan[::\s]/i,
836
- /步骤[::\s]/i,
837
- /以下是.*(?:计划|步骤)/i,
838
- ];
839
-
840
- let planStart = -1;
841
- for (const marker of planMarkers) {
842
- const match = searchArea.match(marker);
843
- if (match) {
844
- planStart = match.index + match[0].length;
845
- break;
846
- }
847
- }
848
-
849
- // 策略 2: 如果没有标记,查找第一个编号列表的起始位置
850
- if (planStart === -1) {
851
- const listMatch = searchArea.match(/\n\s*1[.)]\s+/);
852
- if (listMatch) {
853
- planStart = listMatch.index;
854
- }
855
- }
856
-
857
- if (planStart === -1) {
858
- return null;
859
- }
860
-
861
- // 从 planStart 开始提取到列表结束
862
- const remaining = searchArea.substring(planStart);
863
- const lines = remaining.split('\n');
864
- const planLines = [];
865
- let inList = false;
866
-
867
- for (const line of lines) {
868
- if (/^\s*(?:\d+[.)]\s+|[-*]\s+)/.test(line)) {
869
- inList = true;
870
- planLines.push(line);
871
- } else if (inList && line.trim() === '') {
872
- // 空行 — 列表可能结束
873
- break;
874
- } else if (inList) {
875
- // 非列表行 — 列表结束
876
- break;
877
- }
878
- }
879
-
880
- if (planLines.length < 2) {
881
- return null;
882
- }
883
-
884
- return planLines.join('\n').trim();
885
- }
886
- }
887
-
888
- export default ReasoningLayer;