autosnippet 3.3.8 → 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 (42) hide show
  1. package/README.md +1 -1
  2. package/config/default.json +1 -1
  3. package/dashboard/dist/assets/{index-DV8biUkH.js → index-8b1Gf3Bb.js} +4 -4
  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/bootstrap.js +6 -1
  21. package/dist/lib/cli/SetupService.js +5 -4
  22. package/dist/lib/domain/dimension/DimensionRegistry.d.ts +6 -4
  23. package/dist/lib/domain/dimension/DimensionRegistry.js +19 -23
  24. package/dist/lib/external/ai/AiProvider.d.ts +2 -0
  25. package/dist/lib/external/ai/AiProvider.js +4 -0
  26. package/dist/lib/external/ai/providers/ClaudeProvider.d.ts +1 -1
  27. package/dist/lib/external/ai/providers/ClaudeProvider.js +6 -2
  28. package/dist/lib/external/ai/providers/GoogleGeminiProvider.d.ts +1 -1
  29. package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +6 -2
  30. package/dist/lib/external/ai/providers/OpenAiProvider.d.ts +1 -1
  31. package/dist/lib/external/ai/providers/OpenAiProvider.js +7 -3
  32. package/dist/lib/external/mcp/McpServer.js +19 -2
  33. package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +10 -2
  34. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.js +10 -29
  35. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +37 -4
  36. package/dist/lib/http/routes/ai.js +2 -2
  37. package/dist/lib/infrastructure/database/DatabaseConnection.js +7 -6
  38. package/dist/lib/service/delivery/CursorDeliveryPipeline.js +15 -1
  39. package/dist/lib/shared/PathGuard.js +16 -10
  40. package/dist/lib/shared/isOwnDevRepo.d.ts +29 -4
  41. package/dist/lib/shared/isOwnDevRepo.js +64 -4
  42. package/package.json +1 -1
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>AutoSnippet Dashboard</title>
8
- <script type="module" crossorigin src="/assets/index-DV8biUkH.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-8b1Gf3Bb.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/yaml-qRaU8Ldn.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-BZEJEVBn.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/icons-BMNb0V6L.js">
@@ -252,7 +252,7 @@ export class AgentRuntime {
252
252
  // ─── 提取方法: reactLoop 内部阶段 ────────────
253
253
  /** 初始化循环上下文 — 封装 reactLoop 前 ~60 行初始化逻辑 */
254
254
  #initLoop(prompt, opts) {
255
- const { history = [], context = {}, capabilityOverride, budgetOverride, systemPromptOverride, onToolCall, contextWindow, tracker, trace, memoryCoordinator, sharedState, source, toolChoiceOverride, } = opts;
255
+ const { history = [], context = {}, capabilityOverride, budgetOverride, systemPromptOverride, onToolCall, contextWindow, tracker, trace, memoryCoordinator, sharedState, source, toolChoiceOverride, abortSignal, } = opts;
256
256
  // 解析 capabilities
257
257
  const caps = capabilityOverride
258
258
  ? this.#resolveCapabilities(capabilityOverride)
@@ -314,6 +314,7 @@ export class AgentRuntime {
314
314
  context: context || {},
315
315
  contextWindow: contextWindow || null,
316
316
  toolChoiceOverride: toolChoiceOverride || null,
317
+ abortSignal: abortSignal || null,
317
318
  });
318
319
  }
319
320
  /**
@@ -321,6 +322,11 @@ export class AgentRuntime {
321
322
  * @returns true = 应退出循环
322
323
  */
323
324
  #shouldExit(ctx) {
325
+ // 外部中止信号 — 立即退出
326
+ if (ctx.abortSignal?.aborted) {
327
+ this.logger.info('[AgentRuntime] ⛔ abortSignal fired — exiting loop');
328
+ return true;
329
+ }
324
330
  // ExplorationTracker: tick + 退出检查
325
331
  if (ctx.tracker) {
326
332
  ctx.tracker.tick();
@@ -451,6 +457,7 @@ export class AgentRuntime {
451
457
  systemPrompt: effectiveSystemPrompt,
452
458
  temperature: ctx.budget.temperature ?? (ctx.isSystem ? 0.3 : 0.7),
453
459
  maxTokens: ctx.budget.maxTokens ?? (ctx.isSystem ? 8192 : 4096),
460
+ abortSignal: ctx.abortSignal ?? undefined,
454
461
  }));
455
462
  ctx.consecutiveAiErrors = 0;
456
463
  }
@@ -518,6 +525,11 @@ export class AgentRuntime {
518
525
  * @returns continueResult() 或 null (退出)
519
526
  */
520
527
  async #handleAiError(ctx, aiErr) {
528
+ // AbortError — 外部中止信号已触发,不计入错误计数,立即退出
529
+ if (ctx.abortSignal?.aborted) {
530
+ this.logger.info('[AgentRuntime] ⛔ abortSignal fired during LLM call — exiting');
531
+ return null;
532
+ }
521
533
  ctx.consecutiveAiErrors++;
522
534
  this.logger.warn(`[AgentRuntime] AI call failed (attempt ${ctx.consecutiveAiErrors}): ${aiErr.message}`);
523
535
  ctx.tracker?.rollbackTick?.();
@@ -106,6 +106,8 @@ export interface ReactLoopOpts {
106
106
  sharedState?: Record<string, unknown>;
107
107
  source?: string;
108
108
  toolChoiceOverride?: string | null;
109
+ /** 外部中止信号 — PipelineStrategy hard timeout 时取消进行中的 LLM 调用 */
110
+ abortSignal?: AbortSignal;
109
111
  [key: string]: unknown;
110
112
  }
111
113
  /** 单次迭代允许的最大工具调用数 */
@@ -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) {
@@ -44,7 +44,12 @@ export class Bootstrap {
44
44
  // 0.5 确保 PathGuard 已配置(如果调用方未提前配置)
45
45
  // MCP 服务器会在 initialize() 之前配置,但 CLI/测试可能跳过
46
46
  if (!pathGuard.configured) {
47
- const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
47
+ const isMcpMode = process.env.ASD_MCP_MODE === '1';
48
+ const projectRoot = process.env.ASD_PROJECT_DIR || (isMcpMode ? undefined : process.cwd());
49
+ if (!projectRoot) {
50
+ throw new Error('[Bootstrap] MCP 模式下缺少 ASD_PROJECT_DIR 环境变量,' +
51
+ '且 PathGuard 未提前配置。请在 .vscode/mcp.json 中设置 ASD_PROJECT_DIR。');
52
+ }
48
53
  Bootstrap.configurePathGuard(projectRoot);
49
54
  }
50
55
  // 1. 加载配置
@@ -46,7 +46,7 @@
46
46
  import { execSync } from 'node:child_process';
47
47
  import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmdirSync, rmSync, writeFileSync, } from 'node:fs';
48
48
  import { join, resolve } from 'node:path';
49
- import { isAutoSnippetDevRepo } from '../shared/isOwnDevRepo.js';
49
+ import { isExcludedProject } from '../shared/isOwnDevRepo.js';
50
50
  import { DEFAULT_KNOWLEDGE_BASE_DIR, DEFAULT_SUB_REPO_DIR, isGitRepo, } from '../shared/ProjectMarkers.js';
51
51
  import { PACKAGE_ROOT } from '../shared/package-root.js';
52
52
  import { FileDeployer } from './deploy/FileDeployer.js';
@@ -81,9 +81,10 @@ export class SetupService {
81
81
  this.seed = options.seed || false;
82
82
  this.subRepoDir = options.subRepoDir || DEFAULT_SUB_REPO_DIR;
83
83
  this.subRepoUrl = options.subRepoUrl;
84
- // ── 开发仓库保护 ──────────────────────────────────
85
- if (isAutoSnippetDevRepo(this.projectRoot)) {
86
- throw new Error('[SetupService] 检测到当前目录是 AutoSnippet 源码开发仓库,' +
84
+ // ── 排除项目保护 ──────────────────────────────────
85
+ const exclusion = isExcludedProject(this.projectRoot);
86
+ if (exclusion.excluded) {
87
+ throw new Error(`[SetupService] 检测到当前目录是排除项目(${exclusion.reason}),` +
87
88
  '拒绝执行 setup 以避免创建 .autosnippet/ 和 AutoSnippet/ 运行时数据。' +
88
89
  '\n提示: 请在用户项目目录中运行 asd setup。');
89
90
  }
@@ -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
  /**