autosnippet 3.3.9 → 3.4.0

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 (33) hide show
  1. package/README.md +1 -1
  2. package/config/default.json +1 -1
  3. package/dashboard/dist/assets/{index-DEU4tJtP.js → index-8b1Gf3Bb.js} +1 -1
  4. package/dashboard/dist/index.html +1 -1
  5. package/dist/lib/agent/AgentRuntime.js +13 -1
  6. package/dist/lib/agent/AgentRuntimeTypes.d.ts +2 -0
  7. package/dist/lib/agent/PipelineStrategy.js +32 -2
  8. package/dist/lib/agent/context/ContextWindow.d.ts +2 -1
  9. package/dist/lib/agent/context/ContextWindow.js +18 -4
  10. package/dist/lib/agent/context/ExplorationTracker.js +6 -1
  11. package/dist/lib/agent/context/exploration/ExplorationStrategies.js +2 -1
  12. package/dist/lib/agent/core/LoopContext.d.ts +3 -0
  13. package/dist/lib/agent/core/LoopContext.js +3 -0
  14. package/dist/lib/agent/domain/EpisodicConsolidator.d.ts +5 -0
  15. package/dist/lib/agent/domain/EpisodicConsolidator.js +60 -5
  16. package/dist/lib/agent/domain/insight-analyst.d.ts +16 -0
  17. package/dist/lib/agent/domain/insight-analyst.js +38 -0
  18. package/dist/lib/agent/domain/insight-gate.js +12 -0
  19. package/dist/lib/agent/memory/MemoryConsolidator.js +17 -0
  20. package/dist/lib/domain/dimension/DimensionRegistry.d.ts +6 -4
  21. package/dist/lib/domain/dimension/DimensionRegistry.js +19 -23
  22. package/dist/lib/external/ai/AiProvider.d.ts +2 -0
  23. package/dist/lib/external/ai/AiProvider.js +4 -0
  24. package/dist/lib/external/ai/providers/ClaudeProvider.d.ts +1 -1
  25. package/dist/lib/external/ai/providers/ClaudeProvider.js +6 -2
  26. package/dist/lib/external/ai/providers/GoogleGeminiProvider.d.ts +1 -1
  27. package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +6 -2
  28. package/dist/lib/external/ai/providers/OpenAiProvider.d.ts +1 -1
  29. package/dist/lib/external/ai/providers/OpenAiProvider.js +7 -3
  30. package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +10 -2
  31. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +37 -4
  32. package/dist/lib/http/routes/ai.js +2 -2
  33. package/package.json +1 -1
@@ -208,7 +208,30 @@ export class PipelineStrategy extends Strategy {
208
208
  `tracker: ${stageTracker?.constructor?.name || 'none'}` +
209
209
  `${submitToolName ? `, submitTool: ${submitToolName}` : ''}`);
210
210
  // 执行 reactLoop (含 per-stage 硬超时保护)
211
- const stageResult = await this.#runWithTimeout(runtime, stagePrompt, message, stage, effectiveBudget, ctxWin, stageTracker, strategyContext, phaseResults, bus);
211
+ let stageResult = await this.#runWithTimeout(runtime, stagePrompt, message, stage, effectiveBudget, ctxWin, stageTracker, strategyContext, phaseResults, bus);
212
+ // ── 超时零输出快速重试 ──
213
+ // 当阶段 hard timeout 且 0 tool calls(LLM 完全卡住),
214
+ // 如果有 retryBudget 且本次非 retry,立即以降级预算重跑一次,
215
+ // 跳过 gate 往返,争取在更短时限内拿到输出。
216
+ if (stageResult.timedOut && !stageResult.toolCalls?.length && !isRetry && stage.retryBudget) {
217
+ _pipelineLogger.info(`[PipelineStrategy] ♻️ Stage "${stage.name}" timed out with 0 tool calls — fast-retrying with retryBudget`);
218
+ bus.publish(AgentEvents.PROGRESS, {
219
+ type: 'pipeline_stage_fast_retry',
220
+ stage: stage.name,
221
+ });
222
+ // 重置 ContextWindow (清空上一轮的空消息)
223
+ if (ctxWin) {
224
+ ctxWin.resetForNewStage();
225
+ }
226
+ // 重建 tracker — 用 retryBudget 的更短限制
227
+ const retryTracker = this.#resolveStageTracker(stage, ctx, strategyContext, stage.retryBudget);
228
+ // 构建简化 prompt(如果有 retryPromptBuilder 则使用)
229
+ let retryPrompt = stagePrompt;
230
+ if (typeof stage.retryPromptBuilder === 'function') {
231
+ retryPrompt = stage.retryPromptBuilder({ reason: 'Stage hard timeout with 0 tool calls', artifact: null }, message.content, phaseResults);
232
+ }
233
+ stageResult = await this.#runWithTimeout(runtime, retryPrompt, message, stage, stage.retryBudget, ctxWin, retryTracker, strategyContext, phaseResults, bus);
234
+ }
212
235
  // 累计结果
213
236
  phaseResults[stage.name] = stageResult;
214
237
  ctx.totalToolCalls.push(...(stageResult.toolCalls || []));
@@ -283,6 +306,8 @@ export class PipelineStrategy extends Strategy {
283
306
  }
284
307
  /** 执行 reactLoop 并添加硬超时保护 */
285
308
  async #runWithTimeout(runtime, stagePrompt, message, stage, effectiveBudget, ctxWin, stageTracker, strategyContext, phaseResults, bus) {
309
+ // 创建 AbortController — hard timeout 时取消进行中的 LLM 请求
310
+ const abortController = new AbortController();
286
311
  const reactPromise = runtime.reactLoop(stagePrompt, {
287
312
  history: message.history,
288
313
  context: {
@@ -300,6 +325,7 @@ export class PipelineStrategy extends Strategy {
300
325
  memoryCoordinator: strategyContext.memoryCoordinator || null,
301
326
  sharedState: strategyContext.sharedState || null,
302
327
  source: strategyContext.source || null,
328
+ abortSignal: abortController.signal,
303
329
  });
304
330
  const stageTimeoutMs = effectiveBudget?.timeoutMs;
305
331
  if (!stageTimeoutMs) {
@@ -311,7 +337,11 @@ export class PipelineStrategy extends Strategy {
311
337
  return Promise.race([
312
338
  reactPromise,
313
339
  new Promise((_, reject) => {
314
- hardTimer = setTimeout(() => reject(new Error('__STAGE_HARD_TIMEOUT__')), hardLimitMs);
340
+ hardTimer = setTimeout(() => {
341
+ // 先中止进行中的 LLM HTTP 请求,再触发 reject
342
+ abortController.abort();
343
+ reject(new Error('__STAGE_HARD_TIMEOUT__'));
344
+ }, hardLimitMs);
315
345
  }),
316
346
  ])
317
347
  .catch((err) => {
@@ -56,12 +56,13 @@ export declare class ContextWindow {
56
56
  * 根据模型名称解析合适的 ContextWindow token 预算。
57
57
  *
58
58
  * 策略: 取模型最大上下文窗口的一个安全分片,
59
+ * - 超大窗口 (≥400k): 预算 48000(1M 级模型可容纳更多上下文)
59
60
  * - 大窗口 (≥200k): 预算 32000(tool schemas + system prompt 占显著空间)
60
61
  * - 中窗口 (≥64k): 预算 24000
61
62
  * - 小窗口 (≥16k): 预算 12000
62
63
  * - 微窗口 (<16k): 预算 = 窗口 × 0.7(留 30% 给 prompt/tool schema)
63
64
  *
64
- * @param modelName 模型名称,如 'gemini-3-flash-preview', 'gpt-4o-mini'
65
+ * @param modelName 模型名称,如 'gemini-3-flash-preview', 'gpt-5.4-mini'
65
66
  * @param [opts] - isSystem 为 true 时给予更高预算
66
67
  * @returns 建议的 token 预算
67
68
  */
@@ -53,6 +53,8 @@ export class ContextWindow {
53
53
  [/gemini-1\.0/i, 32_000],
54
54
  [/gemini/i, 1_000_000], // 未知版本回退
55
55
  // ── OpenAI ──
56
+ [/gpt-5\.4-(?:mini|nano)/i, 400_000],
57
+ [/gpt-5/i, 1_000_000],
56
58
  [/gpt-4o/i, 128_000],
57
59
  [/gpt-4-turbo/i, 128_000],
58
60
  [/gpt-4-(?!turbo)/i, 8_192],
@@ -60,6 +62,7 @@ export class ContextWindow {
60
62
  [/gpt-3\.5/i, 4_096],
61
63
  [/o1|o3|o4/i, 200_000], // OpenAI reasoning models
62
64
  // ── Anthropic ──
65
+ [/claude-(?:opus|sonnet)-4[.-]6/i, 1_000_000], // Opus 4.6 / Sonnet 4.6
63
66
  [/claude-.*sonnet-4/i, 200_000],
64
67
  [/claude-3[.-]5/i, 200_000],
65
68
  [/claude-3[.-]opus/i, 200_000],
@@ -81,12 +84,13 @@ export class ContextWindow {
81
84
  * 根据模型名称解析合适的 ContextWindow token 预算。
82
85
  *
83
86
  * 策略: 取模型最大上下文窗口的一个安全分片,
87
+ * - 超大窗口 (≥400k): 预算 48000(1M 级模型可容纳更多上下文)
84
88
  * - 大窗口 (≥200k): 预算 32000(tool schemas + system prompt 占显著空间)
85
89
  * - 中窗口 (≥64k): 预算 24000
86
90
  * - 小窗口 (≥16k): 预算 12000
87
91
  * - 微窗口 (<16k): 预算 = 窗口 × 0.7(留 30% 给 prompt/tool schema)
88
92
  *
89
- * @param modelName 模型名称,如 'gemini-3-flash-preview', 'gpt-4o-mini'
93
+ * @param modelName 模型名称,如 'gemini-3-flash-preview', 'gpt-5.4-mini'
90
94
  * @param [opts] - isSystem 为 true 时给予更高预算
91
95
  * @returns 建议的 token 预算
92
96
  */
@@ -104,7 +108,10 @@ export class ContextWindow {
104
108
  }
105
109
  // 2. 按分级策略计算 token 预算
106
110
  let budget;
107
- if (contextSize >= 200_000) {
111
+ if (contextSize >= 400_000) {
112
+ budget = isSystem ? 48_000 : 36_000;
113
+ }
114
+ else if (contextSize >= 200_000) {
108
115
  budget = isSystem ? 32_000 : 24_000;
109
116
  }
110
117
  else if (contextSize >= 64_000) {
@@ -209,7 +216,10 @@ export class ContextWindow {
209
216
  }
210
217
  }
211
218
  if (truncated > 0) {
212
- this.#logger.info(`[ContextWindow] L1 compact: truncated ${truncated} tool results`);
219
+ const afterTokens = this.estimateTokens();
220
+ const ratio = this.getTokenUsageRatio();
221
+ this.#logger.info(`[ContextWindow] L1 compact: truncated ${truncated} tool results | ` +
222
+ `tokens≈${afterTokens}/${this.#tokenBudget} (${(ratio * 100).toFixed(1)}%)`);
213
223
  }
214
224
  return { level: 1, removed: truncated };
215
225
  }
@@ -285,7 +295,11 @@ export class ContextWindow {
285
295
  });
286
296
  const removedCount = keepFrom - 1;
287
297
  this.#compactionLog.push(`L${level}: removed ${removedCount} messages (${toolCallCount} rounds)`);
288
- this.#logger.info(`[ContextWindow] L${level} compact: removed ${removedCount} messages, kept last ${level === 2 ? 2 : 1} rounds`);
298
+ const afterTokens = this.estimateTokens();
299
+ const ratio = this.getTokenUsageRatio();
300
+ this.#logger.info(`[ContextWindow] L${level} compact: removed ${removedCount} messages (${toolCallCount} rounds), ` +
301
+ `kept last ${level === 2 ? 2 : 1} round(s) | ` +
302
+ `tokens≈${afterTokens}/${this.#tokenBudget} (${(ratio * 100).toFixed(1)}%)`);
289
303
  return { level, removed: removedCount };
290
304
  }
291
305
  // ─── 查询 API ─────────────────────────────────────────
@@ -70,6 +70,8 @@ export class ExplorationTracker {
70
70
  #submitToolName = 'submit_knowledge';
71
71
  /** 管线类型标识 — 统一场景判别(替代 submitToolName / strategy.name 字符串比较) */
72
72
  #pipelineType;
73
+ /** 当前阶段开始时间(用于 dwell time 统计) */
74
+ #phaseStartTime = Date.now();
73
75
  /**
74
76
  * @param strategy 策略配置对象
75
77
  * @param budget 预算配置 { maxIterations, searchBudget, ... }
@@ -446,8 +448,10 @@ export class ExplorationTracker {
446
448
  }
447
449
  #transitionTo(newPhase) {
448
450
  const oldPhase = this.#phase;
451
+ const dwellMs = Date.now() - this.#phaseStartTime;
449
452
  this.#transitionFromPhase = oldPhase;
450
453
  this.#phase = newPhase;
454
+ this.#phaseStartTime = Date.now();
451
455
  this.#metrics.phaseRounds = 0;
452
456
  this.#metrics.searchRoundsInPhase = 0;
453
457
  // 重置停滞计数器 — 防止跨阶段累积导致级联式过早转换
@@ -456,7 +460,8 @@ export class ExplorationTracker {
456
460
  this.#metrics.roundsSinceSubmit = 0;
457
461
  this.#metrics.consecutiveIdleRounds = 0;
458
462
  this.#justTransitioned = true;
459
- this.#logger.info(`[ExplorationTracker] ${oldPhase} → ${newPhase} (iter=${this.#metrics.iteration}, submits=${this.#metrics.submitCount}, phaseRounds=${this.#metrics.phaseRounds}, idleRounds=${this.#metrics.consecutiveIdleRounds})`);
463
+ this.#logger.info(`[ExplorationTracker] ${oldPhase} → ${newPhase} (iter=${this.#metrics.iteration}, submits=${this.#metrics.submitCount}, ` +
464
+ `dwellMs=${dwellMs}, files=${this.#metrics.uniqueFiles.size}, patterns=${this.#metrics.uniquePatterns.size})`);
460
465
  // Phase 3: 发射阶段转换信号
461
466
  if (this.#signalBus) {
462
467
  const terminalPhase = this.#getTerminalPhase();
@@ -66,7 +66,8 @@ export const STRATEGY_ANALYST = {
66
66
  phases: ['SCAN', 'EXPLORE', 'VERIFY', 'SUMMARIZE'],
67
67
  transitions: {
68
68
  'SCAN→EXPLORE': {
69
- onMetrics: (m) => m.iteration >= 3,
69
+ // 2 轮结构扫描足够获取项目骨架(目录 + 关键文件列表),将 1 轮还给 EXPLORE
70
+ onMetrics: (m) => m.iteration >= 2,
70
71
  onTextResponse: false,
71
72
  },
72
73
  'EXPLORE→VERIFY': {
@@ -62,6 +62,7 @@ interface LoopContextConfig {
62
62
  context?: Record<string, unknown>;
63
63
  contextWindow?: ContextWindow | null;
64
64
  toolChoiceOverride?: string | null;
65
+ abortSignal?: AbortSignal | null;
65
66
  }
66
67
  export declare class LoopContext {
67
68
  /** 统一消息适配器 */
@@ -110,6 +111,8 @@ export declare class LoopContext {
110
111
  contextWindow: ContextWindow | null;
111
112
  /** 首轮 toolChoice 覆盖 ('required'/'auto'/'none') */
112
113
  toolChoiceOverride: string | null;
114
+ /** 外部中止信号 — hard timeout 时取消进行中的 LLM 调用 */
115
+ abortSignal: AbortSignal | null;
113
116
  constructor(config: LoopContextConfig);
114
117
  /** 是否为 system 场景 */
115
118
  get isSystem(): boolean;
@@ -60,6 +60,8 @@ export class LoopContext {
60
60
  contextWindow;
61
61
  /** 首轮 toolChoice 覆盖 ('required'/'auto'/'none') */
62
62
  toolChoiceOverride;
63
+ /** 外部中止信号 — hard timeout 时取消进行中的 LLM 调用 */
64
+ abortSignal;
63
65
  constructor(config) {
64
66
  this.messages = config.messages;
65
67
  this.tracker = (config.tracker || null);
@@ -76,6 +78,7 @@ export class LoopContext {
76
78
  this.context = config.context || {};
77
79
  this.contextWindow = config.contextWindow || null;
78
80
  this.toolChoiceOverride = config.toolChoiceOverride || null;
81
+ this.abortSignal = (config.abortSignal || null);
79
82
  this.loopStartTime = Date.now();
80
83
  }
81
84
  // ─── 计算属性 ───
@@ -97,6 +97,11 @@ export declare class EpisodicConsolidator {
97
97
  };
98
98
  total: ConsolidateResult;
99
99
  durationMs: number;
100
+ perDimension: {
101
+ [k: string]: number;
102
+ };
103
+ importanceDistribution: Record<number, number>;
104
+ entityCount: number;
100
105
  };
101
106
  }
102
107
  export default EpisodicConsolidator;
@@ -25,16 +25,18 @@ import Logger from '#infra/logging/Logger.js';
25
25
  * - "XX 采用了 YY"
26
26
  */
27
27
  const FACT_PATTERNS = [
28
- // "项目使用/采用了 XXX"
28
+ // Chinese
29
29
  /(?:项目|工程|代码库)(?:使用|采用|基于|遵循)了?\s*([^,。,.\n]{5,60})/g,
30
- // "主要/核心 XXX 是 YYY"
31
30
  /(?:主要|核心|主|主力)\s*(\S+)\s*(?:是|为|使用)\s*([^,。,.\n]{3,40})/g,
32
- // "发现了? N 个 XXX"
33
31
  /(?:发现|找到|扫描到|识别|共有|包含)\s*了?\s*(\d+)\s*个?\s*([^,。,.\n]{2,30})/g,
34
- // "XXX 是唯一的/主要的 YYY"
35
32
  /(\S{2,20})\s*是\s*(?:唯一的?|主要的?|核心的?|全局的?)\s*([^,。,.\n]{3,30})/g,
36
- // "使用了 XXX 前缀/后缀/命名"
37
33
  /(?:使用|采用|遵循)了?\s*(\S{1,10})\s*(?:前缀|后缀|命名|约定|规范)/g,
34
+ // English
35
+ /(?:the\s+)?project\s+(?:uses?|adopts?|relies\s+on|follows?)\s+([^.,\n]{5,60})/gi,
36
+ /(?:found|discovered|identified|detected)\s+(\d+)\s+([^.,\n]{3,40})/gi,
37
+ /(?:the\s+)?(?:primary|main|core)\s+(\S+)\s+(?:is|are)\s+([^.,\n]{3,40})/gi,
38
+ /(?:all|every)\s+([^.,\n]{3,30})\s+(?:use|adopt|follow|implement)\s+([^.,\n]{3,40})/gi,
39
+ /(?:there\s+(?:is|are))\s+(\d+)\s+([^.,\n]{3,40})/gi,
38
40
  ];
39
41
  /**
40
42
  * 匹配洞察性陈述:
@@ -43,9 +45,14 @@ const FACT_PATTERNS = [
43
45
  * - "建议/推荐 XXX"
44
46
  */
45
47
  const INSIGHT_PATTERNS = [
48
+ // Chinese
46
49
  /([^,。,.\n]{5,40})(?:暗示|表明|说明|意味着|揭示)\s*([^,。,.\n]{5,60})/g,
47
50
  /([^,。,.\n]{3,20})\s*(?:与|和)\s*([^,。,.\n]{3,20})\s*(?:耦合|关联|存在依赖|有关系)/g,
48
51
  /(?:建议|推荐|应该|需要)\s*([^,。,.\n]{5,60})/g,
52
+ // English
53
+ /([^.,\n]{5,40})\s+(?:suggests?|indicates?|implies?|reveals?)\s+(?:that\s+)?([^.,\n]{5,60})/gi,
54
+ /([^.,\n]{3,20})\s+(?:is|are)\s+(?:tightly\s+)?(?:coupled|linked|related)\s+(?:to|with)\s+([^.,\n]{3,30})/gi,
55
+ /(?:recommend|should|consider|suggest)\s+([^.,\n]{5,60})/gi,
49
56
  ];
50
57
  // ──────────────────────────────────────────────────────────────
51
58
  // EpisodicConsolidator 类
@@ -81,9 +88,15 @@ export class EpisodicConsolidator {
81
88
  const textFactMemories = this.#extractFromAnalysisText(sessionStore);
82
89
  // 5. 合并所有候选, 使用 consolidate 去重
83
90
  const allCandidates = [...findingMemories, ...insightMemories, ...textFactMemories];
91
+ // ── 结构化统计日志 ──
92
+ const dimStats = this.#computeDimStats(allCandidates);
93
+ const importanceDist = this.#computeImportanceDistribution(allCandidates);
94
+ const entityCount = allCandidates.reduce((sum, c) => sum + (c.relatedEntities?.length || 0), 0);
84
95
  this.#logger.info(`[Consolidator] Extracted ${allCandidates.length} candidate memories: ` +
85
96
  `${findingMemories.length} findings, ${insightMemories.length} insights, ` +
86
97
  `${textFactMemories.length} text facts`);
98
+ this.#logger.info(`[Consolidator] Per-dimension: ${dimStats.map((d) => `${d.dim}=${d.count}`).join(', ')}`);
99
+ this.#logger.info(`[Consolidator] Importance distribution: ${importanceDist} | Entities extracted: ${entityCount}`);
87
100
  const result = this.#semanticMemory.consolidate(allCandidates, { bootstrapSession });
88
101
  const durationMs = Date.now() - t0;
89
102
  this.#logger.info(`[Consolidator] Consolidation complete in ${durationMs}ms: ` +
@@ -95,6 +108,9 @@ export class EpisodicConsolidator {
95
108
  textFacts: { extracted: textFactMemories.length },
96
109
  total: result,
97
110
  durationMs,
111
+ perDimension: Object.fromEntries(dimStats.map((d) => [d.dim, d.count])),
112
+ importanceDistribution: this.#importanceHistogram(allCandidates),
113
+ entityCount,
98
114
  };
99
115
  }
100
116
  // ─── 提取器 ───────────────────────────────────────────
@@ -273,6 +289,45 @@ export class EpisodicConsolidator {
273
289
  return memories;
274
290
  }
275
291
  // ─── 辅助方法 ─────────────────────────────────────────
292
+ /** 按维度聚合候选数量 */
293
+ #computeDimStats(candidates) {
294
+ const counts = new Map();
295
+ for (const c of candidates) {
296
+ const dim = c.sourceDimension || 'unknown';
297
+ counts.set(dim, (counts.get(dim) || 0) + 1);
298
+ }
299
+ return [...counts.entries()]
300
+ .map(([dim, count]) => ({ dim, count }))
301
+ .sort((a, b) => b.count - a.count);
302
+ }
303
+ /** 生成重要性分布字符串: "[1-3]=N [4-6]=N [7-10]=N" */
304
+ #computeImportanceDistribution(candidates) {
305
+ let low = 0;
306
+ let mid = 0;
307
+ let high = 0;
308
+ for (const c of candidates) {
309
+ const imp = c.importance || 5;
310
+ if (imp <= 3) {
311
+ low++;
312
+ }
313
+ else if (imp <= 6) {
314
+ mid++;
315
+ }
316
+ else {
317
+ high++;
318
+ }
319
+ }
320
+ return `[1-3]=${low} [4-6]=${mid} [7-10]=${high}`;
321
+ }
322
+ /** 构建重要性直方图对象 (供返回值使用) */
323
+ #importanceHistogram(candidates) {
324
+ const hist = {};
325
+ for (const c of candidates) {
326
+ const imp = c.importance || 5;
327
+ hist[imp] = (hist[imp] || 0) + 1;
328
+ }
329
+ return hist;
330
+ }
276
331
  /**
277
332
  * 从文本中提取实体名 (类名/文件名/模块名)
278
333
  *
@@ -61,6 +61,7 @@ interface CodeEntityGraphLike {
61
61
  }
62
62
  export declare const ANALYST_SYSTEM_PROMPT = "\u4F60\u662F\u4E00\u4F4D\u9AD8\u7EA7\u8F6F\u4EF6\u67B6\u6784\u5E08\uFF0C\u6B63\u5728\u6DF1\u5EA6\u5206\u6790\u4E00\u4E2A\u771F\u5B9E\u9879\u76EE\u7684\u67D0\u4E2A\u7EF4\u5EA6\u3002\n\n## \u6267\u884C\u8BA1\u5212\n\u4F60\u6709 **N \u8F6E**\u5DE5\u5177\u8C03\u7528\u673A\u4F1A\uFF08\u7CFB\u7EDF\u4F1A\u544A\u77E5\u5177\u4F53\u6570\u5B57\uFF09\u3002\u8BF7\u4E25\u683C\u6309\u4EE5\u4E0B\u8282\u594F\u5206\u914D\uFF1A\n\n| \u9636\u6BB5 | \u8F6E\u6B21\u5360\u6BD4 | \u76EE\u6807 |\n|------|---------|------|\n| 1. \u5168\u5C40\u626B\u63CF | \u7B2C 1-3 \u8F6E | get_project_overview + list_project_structure \u4E86\u89E3\u9879\u76EE\u7ED3\u6784 |\n| 2. \u7ED3\u6784\u5316\u63A2\u7D22 | \u7B2C 4-N\u00D760% \u8F6E | get_class_hierarchy / get_class_info \u7406\u89E3\u6838\u5FC3\u7C7B\uFF1Bsearch_project_code \u6279\u91CF\u641C\u7D22\u5173\u952E\u6A21\u5F0F |\n| 3. \u6DF1\u5EA6\u9A8C\u8BC1 | \u7B2C N\u00D760%-N\u00D780% \u8F6E | read_project_file \u9605\u8BFB\u5173\u952E\u5B9E\u73B0\uFF0C\u786E\u8BA4\u7EC6\u8282 |\n| 4. \u8F93\u51FA\u603B\u7ED3 | \u6700\u540E 20% | **\u505C\u6B62\u8C03\u7528\u5DE5\u5177**\uFF0C\u76F4\u63A5\u8F93\u51FA\u4F60\u7684\u5206\u6790\u6587\u672C |\n\n## \u5173\u952E\u89C4\u5219\n- **\u5230\u8FBE 80% \u8F6E\u6B21\u65F6\u5FC5\u987B\u5F00\u59CB\u5199\u603B\u7ED3**\uFF0C\u4E0D\u8981\u7B49\u7CFB\u7EDF\u63D0\u9192\n- \u6BCF\u4E00\u8F6E\u90FD\u5FC5\u987B\u8C03\u7528\u5DE5\u5177\u83B7\u53D6\u65B0\u4FE1\u606F\uFF0C\u4E0D\u8981\u82B1\u8F6E\u6B21\u5728\u7EAF\u6587\u672C\u601D\u8003\u4E0A\n- \u4E0D\u8981\u91CD\u590D\u641C\u7D22\u76F8\u540C\u5173\u952E\u8BCD\u6216\u8BFB\u53D6\u76F8\u540C\u6587\u4EF6\uFF08\u7CFB\u7EDF\u4F1A\u8FD4\u56DE\u7F13\u5B58\u5E76\u6263\u8F6E\u6B21\uFF09\n\n## \u5DE5\u5177\u6548\u7387\n- **\u6279\u91CF\u641C\u7D22**: search_project_code({ patterns: [\"keywordA\", \"keywordB\", \"keywordC\"] }) \u2014 \u4E00\u6B21\u641C 3-5 \u4E2A\n- **\u6279\u91CF\u8BFB\u6587\u4EF6**: read_project_file({ filePaths: [\"a.m\", \"b.m\", \"c.m\"] }) \u2014 \u4E00\u6B21\u8BFB 3-5 \u4E2A\n- **\u7ED3\u6784\u5316\u67E5\u8BE2\u4F18\u5148**: get_class_hierarchy / get_class_info \u6BD4\u6587\u672C\u641C\u7D22\u66F4\u7CBE\u786E\u9AD8\u6548\n\n## \u8F93\u51FA\u8981\u6C42\n\u8F93\u51FA\u4F60\u7684\u5206\u6790\u53D1\u73B0\uFF0C\u5305\u62EC\u5177\u4F53\u7684\u6587\u4EF6\u5B8C\u6574\u76F8\u5BF9\u8DEF\u5F84\uFF08\u4ECE\u9879\u76EE\u6839\u76EE\u5F55\u5F00\u59CB\uFF09\u548C\u884C\u53F7\u3002\n\u6BCF\u4E2A\u6587\u4EF6\u5F15\u7528\u683C\u5F0F: (\u6765\u6E90: Full/Relative/Path/FileName.ext:\u884C\u53F7)\n\u7981\u6B62\u53EA\u5199\u6587\u4EF6\u540D\uFF0C\u5FC5\u987B\u5199\u4ECE\u9879\u76EE\u6839\u5F00\u59CB\u7684\u5B8C\u6574\u8DEF\u5F84\u3002\n\u6807\u6CE8\u6BCF\u4E2A\u53D1\u73B0\u6240\u5C5E\u7684\u6A21\u5757/\u5305\u540D\u3002\n\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u4F60\u7684\u7406\u89E3\uFF0C\u4E0D\u9700\u8981\u7279\u5B9A\u683C\u5F0F\u3002";
63
63
  export declare const ANALYST_TOOLS: string[];
64
+ /** 默认 Analyst 预算(24 轮基线) */
64
65
  export declare const ANALYST_BUDGET: {
65
66
  maxIterations: number;
66
67
  searchBudget: number;
@@ -69,6 +70,21 @@ export declare const ANALYST_BUDGET: {
69
70
  softSubmitLimit: number;
70
71
  idleRoundsToExit: number;
71
72
  };
73
+ /**
74
+ * 根据项目规模自适应计算 Analyst 预算
75
+ *
76
+ * 策略: 以文件数为主要缩放因子,保持 searchBudget/maxIterations 的比例关系。
77
+ * - ≤40 文件: 基线 24 轮(小型项目无需额外预算)
78
+ * - 41~100 文件: 线性插值到 32 轮
79
+ * - 101~200 文件: 线性插值到 40 轮
80
+ * - >200 文件: 封顶 40 轮(避免单维度成本失控)
81
+ *
82
+ * searchBudget 按比例随 maxIterations 缩放(保持 75%)。
83
+ * timeoutMs 按比例随 maxIterations 缩放(基线 300s 对应 24 轮)。
84
+ */
85
+ export declare function computeAnalystBudget(fileCount: number): typeof ANALYST_BUDGET & {
86
+ timeoutMs: number;
87
+ };
72
88
  /** Panorama context — module role, layer, coupling, gaps */
73
89
  interface PanoramaContextLike {
74
90
  moduleRole: string | null;
@@ -72,6 +72,7 @@ export const ANALYST_TOOLS = [
72
72
  // ──────────────────────────────────────────────────────────────────
73
73
  // Analyst 预算 — 使用 analyst 策略(自由探索,无阶段约束)
74
74
  // ──────────────────────────────────────────────────────────────────
75
+ /** 默认 Analyst 预算(24 轮基线) */
75
76
  export const ANALYST_BUDGET = {
76
77
  maxIterations: 24,
77
78
  searchBudget: 18,
@@ -80,6 +81,43 @@ export const ANALYST_BUDGET = {
80
81
  softSubmitLimit: 0,
81
82
  idleRoundsToExit: 2,
82
83
  };
84
+ /**
85
+ * 根据项目规模自适应计算 Analyst 预算
86
+ *
87
+ * 策略: 以文件数为主要缩放因子,保持 searchBudget/maxIterations 的比例关系。
88
+ * - ≤40 文件: 基线 24 轮(小型项目无需额外预算)
89
+ * - 41~100 文件: 线性插值到 32 轮
90
+ * - 101~200 文件: 线性插值到 40 轮
91
+ * - >200 文件: 封顶 40 轮(避免单维度成本失控)
92
+ *
93
+ * searchBudget 按比例随 maxIterations 缩放(保持 75%)。
94
+ * timeoutMs 按比例随 maxIterations 缩放(基线 300s 对应 24 轮)。
95
+ */
96
+ export function computeAnalystBudget(fileCount) {
97
+ const clamped = Math.max(0, fileCount);
98
+ let maxIter;
99
+ if (clamped <= 40) {
100
+ maxIter = 24;
101
+ }
102
+ else if (clamped <= 100) {
103
+ // 40→100 文件: 24→32 轮(线性插值)
104
+ maxIter = Math.round(24 + ((clamped - 40) / 60) * 8);
105
+ }
106
+ else if (clamped <= 200) {
107
+ // 100→200 文件: 32→40 轮
108
+ maxIter = Math.round(32 + ((clamped - 100) / 100) * 8);
109
+ }
110
+ else {
111
+ maxIter = 40;
112
+ }
113
+ return {
114
+ ...ANALYST_BUDGET,
115
+ maxIterations: maxIter,
116
+ searchBudget: Math.round(maxIter * 0.75),
117
+ // 超时随轮次等比缩放: 24轮→300s, 40轮→500s
118
+ timeoutMs: Math.round((maxIter / 24) * 300_000),
119
+ };
120
+ }
83
121
  /**
84
122
  * 构建 Analyst Prompt
85
123
  *
@@ -14,7 +14,9 @@
14
14
  *
15
15
  * @module insight-gate
16
16
  */
17
+ import Logger from '#infra/logging/Logger.js';
17
18
  import { EvidenceCollector, } from './EvidenceCollector.js';
19
+ const logger = Logger.getInstance();
18
20
  // ──────────────────────────────────────────────────────────────────
19
21
  // AnalysisReport 构建
20
22
  // ──────────────────────────────────────────────────────────────────
@@ -376,6 +378,16 @@ export function insightGateEvaluator(source, phaseResults, strategyContext = {})
376
378
  ? buildAnalysisArtifact(source, dimId, projectGraph, activeContext)
377
379
  : buildAnalysisReport(source, dimId, projectGraph);
378
380
  const gate = analysisQualityGate(artifact, { outputType: outputType || 'analysis' });
381
+ const qr = artifact.qualityReport;
382
+ if (qr?.scores) {
383
+ logger.info(`[QualityGate] dim="${dimId}" action=${gate.pass ? 'pass' : gate.action} ` +
384
+ `total=${qr.totalScore} depth=${qr.scores.depthScore} breadth=${qr.scores.breadthScore} ` +
385
+ `evidence=${qr.scores.evidenceScore} coherence=${qr.scores.coherenceScore}` +
386
+ (qr.suggestions.length > 0 ? ` suggestions=[${qr.suggestions.join('; ')}]` : ''));
387
+ }
388
+ else {
389
+ logger.info(`[QualityGate] dim="${dimId}" action=${gate.pass ? 'pass' : gate.action} reason="${gate.reason || 'v1-rules'}" (v1 fallback)`);
390
+ }
379
391
  return {
380
392
  action: gate.action || (gate.pass ? 'pass' : 'retry'),
381
393
  reason: gate.reason || '',
@@ -17,6 +17,8 @@ import { MemoryStore } from './MemoryStore.js';
17
17
  /** 相似度阈值 */
18
18
  const SIMILARITY_UPDATE = 0.85; // ≥85% 同义 → UPDATE
19
19
  const SIMILARITY_MERGE = 0.6; // ≥60% 相关 → MERGE
20
+ /** 详细日志开关 (合并时记录每次 MERGE/UPDATE/REPLACE 的内容摘要) */
21
+ const VERBOSE_CONSOLIDATION = true;
20
22
  // ─── 矛盾检测模式 (Mem0 风格冲突解决) ─────────────────
21
23
  /** 中文否定/禁止模式 */
22
24
  const NEGATION_PATTERNS_ZH = /不(再)?使用|不(再)?用|禁止|废弃|移除|取消|停止|不要|不采用|弃用|淘汰/;
@@ -68,6 +70,9 @@ export class MemoryConsolidator {
68
70
  accessCount: topMatch.access_count + 1,
69
71
  });
70
72
  stats.updated++;
73
+ if (VERBOSE_CONSOLIDATION) {
74
+ this.#logDebug(`UPDATE sim=${(topMatch.similarity ?? 0).toFixed(2)}: "${content.substring(0, 40)}..." → existing "${topMatch.content.substring(0, 40)}..."`);
75
+ }
71
76
  }
72
77
  else if ((topMatch.similarity ?? 0) >= SIMILARITY_MERGE) {
73
78
  // MERGE: 相关但不同 → 合并信息
@@ -79,6 +84,9 @@ export class MemoryConsolidator {
79
84
  relatedMemories: [...existingRelated, `merged:${Date.now()}`],
80
85
  });
81
86
  stats.merged++;
87
+ if (VERBOSE_CONSOLIDATION) {
88
+ this.#logDebug(`MERGE sim=${(topMatch.similarity ?? 0).toFixed(2)}: "${content.substring(0, 40)}..." ⊕ "${topMatch.content.substring(0, 40)}..."`);
89
+ }
82
90
  }
83
91
  else {
84
92
  this.#store.add({ ...candidate, bootstrapSession });
@@ -299,6 +307,15 @@ export class MemoryConsolidator {
299
307
  return 'fact';
300
308
  }
301
309
  }
310
+ #logDebug(msg) {
311
+ const formatted = `[MemoryConsolidator] ${msg}`;
312
+ if (this.#logger?.debug) {
313
+ this.#logger.debug(formatted);
314
+ }
315
+ else if (this.#logger?.info) {
316
+ this.#logger.info(formatted);
317
+ }
318
+ }
302
319
  #log(msg) {
303
320
  const formatted = `[MemoryConsolidator] ${msg}`;
304
321
  if (this.#logger?.info) {
@@ -38,10 +38,12 @@ export declare function resolveActiveDimensions(primaryLang: string, detectedFra
38
38
  /**
39
39
  * 构建 Tier 分层调度计划
40
40
  *
41
- * 基于每个维度的 tierHint 字段将活跃维度分为 3 层:
42
- * - Tier 1 (tierHint=1): 基础数据层 — architecture + 语言/框架条件维度
43
- * - Tier 2 (tierHint=2): 规范+设计层 — coding-standards, design-patterns
44
- * - Tier 3 (tierHint=3): 实践+质量层 — 其余所有维度
41
+ * 基于每个维度的 tierHint 字段动态分为 N 层 (不再硬编码 3 层):
42
+ * - tierHint=1: 基础数据层 — architecture + 语言/框架条件维度
43
+ * - tierHint=2: 规范+设计层 — coding-standards, design-patterns
44
+ * - tierHint=3+: 实践+质量层 — 按声明值自动分桶
45
+ *
46
+ * 未声明 tierHint 的维度默认归入最后一层 (tierHint=max 或 3)。
45
47
  */
46
48
  export declare function buildTierPlan(activeDims?: readonly UnifiedDimension[]): string[][];
47
49
  /**
@@ -145,7 +145,7 @@ const D7_NETWORKING_API = {
145
145
  weight: 0.7,
146
146
  suggestedTopics: ['api-contract', 'retry-strategy', 'request-pattern'],
147
147
  relatedRoles: ['networking'],
148
- tierHint: 3,
148
+ tierHint: 2,
149
149
  displayGroup: 'data-event-flow',
150
150
  };
151
151
  const D8_UI_INTERACTION = {
@@ -163,7 +163,7 @@ const D8_UI_INTERACTION = {
163
163
  weight: 0.7,
164
164
  suggestedTopics: ['component-pattern', 'lifecycle', 'navigation'],
165
165
  relatedRoles: ['ui', 'feature'],
166
- tierHint: 3,
166
+ tierHint: 4,
167
167
  displayGroup: 'architecture',
168
168
  };
169
169
  const D9_TESTING_QUALITY = {
@@ -181,7 +181,7 @@ const D9_TESTING_QUALITY = {
181
181
  weight: 0.9,
182
182
  suggestedTopics: ['unit-test', 'mock-strategy', 'ci-cd'],
183
183
  relatedRoles: [],
184
- tierHint: 3,
184
+ tierHint: 4,
185
185
  displayGroup: 'best-practice',
186
186
  };
187
187
  const D10_SECURITY_AUTH = {
@@ -199,7 +199,7 @@ const D10_SECURITY_AUTH = {
199
199
  weight: 1.0,
200
200
  suggestedTopics: ['authentication', 'authorization', 'encryption'],
201
201
  relatedRoles: ['networking', 'service'],
202
- tierHint: 3,
202
+ tierHint: 4,
203
203
  displayGroup: 'best-practice',
204
204
  };
205
205
  const D11_PERFORMANCE_OPTIMIZATION = {
@@ -217,7 +217,7 @@ const D11_PERFORMANCE_OPTIMIZATION = {
217
217
  weight: 0.8,
218
218
  suggestedTopics: ['memory-management', 'lazy-loading', 'rendering'],
219
219
  relatedRoles: ['ui', 'storage'],
220
- tierHint: 3,
220
+ tierHint: 5,
221
221
  displayGroup: 'data-event-flow',
222
222
  };
223
223
  const D12_OBSERVABILITY_LOGGING = {
@@ -235,7 +235,7 @@ const D12_OBSERVABILITY_LOGGING = {
235
235
  weight: 0.7,
236
236
  suggestedTopics: ['logging-standard', 'event-tracking', 'diagnostics'],
237
237
  relatedRoles: ['service', 'core'],
238
- tierHint: 3,
238
+ tierHint: 5,
239
239
  displayGroup: 'data-event-flow',
240
240
  };
241
241
  const D13_AGENT_GUIDELINES = {
@@ -253,7 +253,7 @@ const D13_AGENT_GUIDELINES = {
253
253
  weight: 0.6,
254
254
  suggestedTopics: ['constraints', 'deprecated-api', 'agent-rules'],
255
255
  relatedRoles: [],
256
- tierHint: 3,
256
+ tierHint: 5,
257
257
  displayGroup: 'best-practice',
258
258
  };
259
259
  // ═══════════════════════════════════════════════════════════
@@ -569,28 +569,24 @@ export function resolveActiveDimensions(primaryLang, detectedFrameworks = []) {
569
569
  /**
570
570
  * 构建 Tier 分层调度计划
571
571
  *
572
- * 基于每个维度的 tierHint 字段将活跃维度分为 3 层:
573
- * - Tier 1 (tierHint=1): 基础数据层 — architecture + 语言/框架条件维度
574
- * - Tier 2 (tierHint=2): 规范+设计层 — coding-standards, design-patterns
575
- * - Tier 3 (tierHint=3): 实践+质量层 — 其余所有维度
572
+ * 基于每个维度的 tierHint 字段动态分为 N 层 (不再硬编码 3 层):
573
+ * - tierHint=1: 基础数据层 — architecture + 语言/框架条件维度
574
+ * - tierHint=2: 规范+设计层 — coding-standards, design-patterns
575
+ * - tierHint=3+: 实践+质量层 — 按声明值自动分桶
576
+ *
577
+ * 未声明 tierHint 的维度默认归入最后一层 (tierHint=max 或 3)。
576
578
  */
577
579
  export function buildTierPlan(activeDims = DIMENSION_REGISTRY) {
578
- const tier1 = [];
579
- const tier2 = [];
580
- const tier3 = [];
580
+ const tierMap = new Map();
581
581
  for (const dim of activeDims) {
582
582
  const hint = dim.tierHint ?? 3;
583
- if (hint === 1) {
584
- tier1.push(dim.id);
585
- }
586
- else if (hint === 2) {
587
- tier2.push(dim.id);
588
- }
589
- else {
590
- tier3.push(dim.id);
583
+ if (!tierMap.has(hint)) {
584
+ tierMap.set(hint, []);
591
585
  }
586
+ tierMap.get(hint).push(dim.id);
592
587
  }
593
- return [tier1, tier2, tier3].filter((t) => t.length > 0);
588
+ // tier 编号升序排列,过滤空层
589
+ return [...tierMap.entries()].sort(([a], [b]) => a - b).map(([, dims]) => dims);
594
590
  }
595
591
  /**
596
592
  * 将 Recipe 分类到最匹配的维度
@@ -57,6 +57,8 @@ export interface ChatWithToolsOptions {
57
57
  systemPrompt?: string;
58
58
  temperature?: number;
59
59
  maxTokens?: number;
60
+ /** 外部中止信号 — hard timeout 时取消进行中的 LLM 请求 */
61
+ abortSignal?: AbortSignal;
60
62
  }
61
63
  /** 函数调用结果 */
62
64
  export interface FunctionCallResult {
@@ -684,6 +684,10 @@ ${items}`;
684
684
  }
685
685
  catch (err) {
686
686
  const e = err;
687
+ // AbortError — 外部主动中止(如 PipelineStrategy hard timeout),不重试直接抛出
688
+ if (e.name === 'AbortError' || e.cause?.name === 'AbortError') {
689
+ throw e;
690
+ }
687
691
  // ── 综合判断是否为可重试的网络/服务端错误 ──
688
692
  const causeCode = e.cause?.code || '';
689
693
  // 网络级错误:无 HTTP status,底层连接失败