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
@@ -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,底层连接失败
@@ -32,6 +32,6 @@ export declare class ClaudeProvider extends AiProvider {
32
32
  summarize(code: string): Promise<{}>;
33
33
  embed(_text: string | string[]): Promise<never[]>;
34
34
  supportsEmbedding(): boolean;
35
- _post(url: string, body: Record<string, unknown>): Promise<ApiResponse>;
35
+ _post(url: string, body: Record<string, unknown>, externalSignal?: AbortSignal): Promise<ApiResponse>;
36
36
  }
37
37
  export default ClaudeProvider;
@@ -100,7 +100,7 @@ export class ClaudeProvider extends AiProvider {
100
100
  body.tool_choice = { type: 'auto' };
101
101
  }
102
102
  }
103
- const data = await this._post(`${CLAUDE_BASE}/messages`, body);
103
+ const data = await this._post(`${CLAUDE_BASE}/messages`, body, opts.abortSignal);
104
104
  return this.#parseToolResponse(data);
105
105
  }
106
106
  // ─── 内部转换方法 ──────────────────────
@@ -241,7 +241,7 @@ export class ClaudeProvider extends AiProvider {
241
241
  supportsEmbedding() {
242
242
  return false;
243
243
  }
244
- async _post(url, body) {
244
+ async _post(url, body, externalSignal) {
245
245
  if (!this.apiKey) {
246
246
  const err = new Error('Claude API Key 未配置。请在 .env 中设置 ASD_CLAUDE_API_KEY,或运行 asd setup 完成配置。');
247
247
  err.code = 'API_KEY_MISSING';
@@ -249,6 +249,9 @@ export class ClaudeProvider extends AiProvider {
249
249
  }
250
250
  const controller = new AbortController();
251
251
  const timer = setTimeout(() => controller.abort(), this.timeout);
252
+ // 外部中止信号 → 联动本地 controller
253
+ const onExternalAbort = () => controller.abort();
254
+ externalSignal?.addEventListener('abort', onExternalAbort, { once: true });
252
255
  try {
253
256
  const res = await this._fetch(url, {
254
257
  method: 'POST',
@@ -269,6 +272,7 @@ export class ClaudeProvider extends AiProvider {
269
272
  }
270
273
  finally {
271
274
  clearTimeout(timer);
275
+ externalSignal?.removeEventListener('abort', onExternalAbort);
272
276
  }
273
277
  }
274
278
  }
@@ -34,6 +34,6 @@ export declare class GoogleGeminiProvider extends AiProvider {
34
34
  */
35
35
  chatWithStructuredOutput(prompt: string, opts?: StructuredOutputOptions): Promise<any>;
36
36
  embed(text: string | string[]): Promise<number[] | number[][]>;
37
- _post(url: string, body: Record<string, unknown>): Promise<ApiResponse>;
37
+ _post(url: string, body: Record<string, unknown>, externalSignal?: AbortSignal): Promise<ApiResponse>;
38
38
  }
39
39
  export default GoogleGeminiProvider;
@@ -108,7 +108,7 @@ export class GoogleGeminiProvider extends AiProvider {
108
108
  body.systemInstruction = { parts: [{ text: systemPrompt }] };
109
109
  }
110
110
  const url = `${GEMINI_BASE}/models/${this.model}:generateContent?key=${this.apiKey}`;
111
- const data = await this._post(url, body);
111
+ const data = await this._post(url, body, opts.abortSignal);
112
112
  return this.#parseToolResponse(data);
113
113
  });
114
114
  }
@@ -356,7 +356,7 @@ export class GoogleGeminiProvider extends AiProvider {
356
356
  }
357
357
  return Array.isArray(text) ? results : results[0] || [];
358
358
  }
359
- async _post(url, body) {
359
+ async _post(url, body, externalSignal) {
360
360
  if (!this.apiKey) {
361
361
  const err = new Error('Google Gemini API Key 未配置。请在 .env 中设置 ASD_GOOGLE_API_KEY,或运行 asd setup 完成配置。');
362
362
  err.code = 'API_KEY_MISSING';
@@ -364,6 +364,9 @@ export class GoogleGeminiProvider extends AiProvider {
364
364
  }
365
365
  const controller = new AbortController();
366
366
  const timer = setTimeout(() => controller.abort(), this.timeout);
367
+ // 外部中止信号 → 联动本地 controller
368
+ const onExternalAbort = () => controller.abort();
369
+ externalSignal?.addEventListener('abort', onExternalAbort, { once: true });
367
370
  try {
368
371
  const res = await this._fetch(url, {
369
372
  method: 'POST',
@@ -401,6 +404,7 @@ export class GoogleGeminiProvider extends AiProvider {
401
404
  }
402
405
  finally {
403
406
  clearTimeout(timer);
407
+ externalSignal?.removeEventListener('abort', onExternalAbort);
404
408
  }
405
409
  }
406
410
  }
@@ -37,6 +37,6 @@ export declare class OpenAiProvider extends AiProvider {
37
37
  */
38
38
  chatWithStructuredOutput(prompt: string, opts?: StructuredOutputOptions): Promise<any>;
39
39
  embed(text: string | string[]): Promise<any>;
40
- _post(url: string, body: Record<string, unknown>): Promise<ApiResponse>;
40
+ _post(url: string, body: Record<string, unknown>, externalSignal?: AbortSignal): Promise<ApiResponse>;
41
41
  }
42
42
  export default OpenAiProvider;
@@ -14,7 +14,7 @@ export class OpenAiProvider extends AiProvider {
14
14
  constructor(config = {}) {
15
15
  super(config);
16
16
  this.name = config.name || 'openai';
17
- this.model = config.model || 'gpt-4o-mini';
17
+ this.model = config.model || 'gpt-5.4-mini';
18
18
  this.apiKey = config.apiKey || process.env.ASD_OPENAI_API_KEY || '';
19
19
  this.baseUrl = config.baseUrl || OPENAI_BASE;
20
20
  this.embedModel = config.embedModel || 'text-embedding-3-small';
@@ -129,7 +129,7 @@ export class OpenAiProvider extends AiProvider {
129
129
  else {
130
130
  body.tool_choice = 'auto';
131
131
  }
132
- const data = await this._post(`${this.baseUrl}/chat/completions`, body);
132
+ const data = await this._post(`${this.baseUrl}/chat/completions`, body, opts.abortSignal);
133
133
  return this.#parseToolResponse(data);
134
134
  });
135
135
  }
@@ -251,7 +251,7 @@ export class OpenAiProvider extends AiProvider {
251
251
  return Array.isArray(text) ? texts.map(() => []) : [];
252
252
  }
253
253
  }
254
- async _post(url, body) {
254
+ async _post(url, body, externalSignal) {
255
255
  // Ollama 使用固定 dummy key,不需要校验
256
256
  if (!this.apiKey && this.name !== 'ollama') {
257
257
  const envKey = this.name === 'deepseek' ? 'ASD_DEEPSEEK_API_KEY' : 'ASD_OPENAI_API_KEY';
@@ -261,6 +261,9 @@ export class OpenAiProvider extends AiProvider {
261
261
  }
262
262
  const controller = new AbortController();
263
263
  const timer = setTimeout(() => controller.abort(), this.timeout);
264
+ // 外部中止信号 → 联动本地 controller
265
+ const onExternalAbort = () => controller.abort();
266
+ externalSignal?.addEventListener('abort', onExternalAbort, { once: true });
264
267
  try {
265
268
  const res = await this._fetch(url, {
266
269
  method: 'POST',
@@ -280,6 +283,7 @@ export class OpenAiProvider extends AiProvider {
280
283
  }
281
284
  finally {
282
285
  clearTimeout(timer);
286
+ externalSignal?.removeEventListener('abort', onExternalAbort);
283
287
  }
284
288
  }
285
289
  }
@@ -83,8 +83,25 @@ export class McpServer {
83
83
  async initialize() {
84
84
  if (!this.container) {
85
85
  const { default: Bootstrap } = await import('../../bootstrap.js');
86
- // 路径安全守卫在任何写操作前配置
87
- const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
86
+ // MCP 模式必须显式指定项目目录 process.cwd() 在多根工作区中不可靠
87
+ const projectRoot = process.env.ASD_PROJECT_DIR;
88
+ if (!projectRoot) {
89
+ const msg = `[MCP] 缺少 ASD_PROJECT_DIR 环境变量。MCP server 拒绝启动。\n` +
90
+ `在多根工作区中 process.cwd() 可能指向任意子目录,不能作为项目根目录。\n` +
91
+ `请在 .vscode/mcp.json 的 env 中设置 ASD_PROJECT_DIR 为目标项目的绝对路径。`;
92
+ process.stderr.write(`${msg}\n`);
93
+ throw new Error(msg);
94
+ }
95
+ // ── 排除项目检查 — 防止误配置 ASD_PROJECT_DIR 到不该创建运行时数据的目录 ──
96
+ const { isExcludedProject } = await import('../../shared/isOwnDevRepo.js');
97
+ const exclusion = isExcludedProject(projectRoot);
98
+ if (exclusion.excluded) {
99
+ const msg = `[MCP] projectRoot "${projectRoot}" 是排除项目(${exclusion.reason}),` +
100
+ `MCP server 拒绝在此目录创建运行时数据。\n` +
101
+ `提示: 在 .vscode/mcp.json 的 env 中设置正确的 ASD_PROJECT_DIR。`;
102
+ process.stderr.write(`${msg}\n`);
103
+ throw new Error(msg);
104
+ }
88
105
  // 切换工作目录到项目根 — 确保 DB 等相对路径正确解析
89
106
  if (projectRoot !== process.cwd()) {
90
107
  process.chdir(projectRoot);
@@ -760,11 +760,19 @@ function buildExecutionPlan(activeDimensions) {
760
760
  const scheduler = new TierScheduler();
761
761
  const tiers = scheduler.getTiers();
762
762
  const activeDimIds = new Set(activeDimensions.map((d) => d.id));
763
- const tierLabels = ['基础数据层', '规范 + 架构 + 模式', '流转 + 实践 + 总结'];
763
+ const tierLabels = [
764
+ '基础数据层',
765
+ '规范 + 设计 + 网络',
766
+ '核心质量',
767
+ '领域专项',
768
+ '终端优化 + 总结',
769
+ ];
764
770
  const tierNotes = [
765
771
  '这些维度相互独立,可以任意顺序分析。产出的上下文将帮助后续维度。',
766
772
  '建议利用 Tier 1 中了解到的项目结构和代码特征。',
767
- 'agent-guidelines 应在最后分析 — 综合前序所有维度的发现。',
773
+ '利用前两层建立的架构和规范上下文深入分析。',
774
+ '各维度相对独立,可充分利用并行能力。',
775
+ 'agent-guidelines 应综合前序所有维度的发现。',
768
776
  ];
769
777
  const plan = tiers
770
778
  .map((tierDimIds, index) => {
@@ -374,8 +374,7 @@ export async function fillDimensionsMock(view, dimensions) {
374
374
  message: '🧪 Mock AI 模式 — 基于代码结构自动生成知识候选(非 AI 深度分析)',
375
375
  mockMode: true,
376
376
  });
377
- const knowledgeService = ctx.container.get('knowledgeService');
378
- let totalCreated = 0;
377
+ let totalGenerated = 0;
379
378
  for (const dim of dimensions) {
380
379
  const dimStartTime = Date.now();
381
380
  const dimLabel = dim.label ?? dim.id;
@@ -385,48 +384,30 @@ export async function fillDimensionsMock(view, dimensions) {
385
384
  mockMode: true,
386
385
  });
387
386
  logger.info(`[MockPipeline] ── Dimension "${dim.id}" (${dimLabel}) ──`);
388
- // 生成候选
387
+ // 生成候选(仅在内存中,不写入数据库)
389
388
  const candidates = generateDimensionCandidates(dim.id, dimLabel, allFiles, primaryLang, projectName, astStats, repFiles);
390
- const createdIds = [];
389
+ totalGenerated += candidates.length;
391
390
  for (const candidate of candidates) {
392
- try {
393
- const entry = await knowledgeService.create({
394
- ...candidate,
395
- source: 'mock-bootstrap',
396
- }, { userId: 'mock-ai' });
397
- createdIds.push(entry.id);
398
- totalCreated++;
399
- // 尝试自动评分(非关键路径)
400
- try {
401
- await knowledgeService.updateQuality?.(entry.id, { userId: 'mock-ai' });
402
- }
403
- catch {
404
- /* best effort */
405
- }
406
- logger.info(`[MockPipeline] ✅ Created: "${candidate.title}" → ${entry.id}`);
407
- }
408
- catch (err) {
409
- const msg = err instanceof Error ? err.message : String(err);
410
- logger.warn(`[MockPipeline] ⚠️ Failed to create "${candidate.title}": ${msg}`);
411
- }
391
+ logger.info(`[MockPipeline] 📝 Generated (not persisted): "${candidate.title}"`);
412
392
  }
413
393
  const durationMs = Date.now() - dimStartTime;
414
394
  emitter.emitDimensionComplete(dim.id, {
415
395
  type: 'candidate',
416
396
  extracted: candidates.length,
417
- created: createdIds.length,
397
+ created: 0,
418
398
  status: 'mock-pipeline-complete',
419
399
  degraded: false,
420
400
  durationMs,
421
401
  toolCallCount: 0,
422
402
  source: 'mock-pipeline',
423
403
  });
424
- logger.info(`[MockPipeline] ✅ "${dim.id}": ${createdIds.length}/${candidates.length} candidates, ${durationMs}ms`);
404
+ logger.info(`[MockPipeline] ✅ "${dim.id}": ${candidates.length} candidates generated (mock-only, not persisted), ${durationMs}ms`);
425
405
  }
426
406
  emitter.emitProgress('bootstrap:mock-complete', {
427
- message: `🧪 Mock Bootstrap 完成: ${totalCreated} 个候选知识已生成`,
428
- totalCreated,
407
+ message: `🧪 Mock Bootstrap 完成: ${totalGenerated} 个候选知识已生成(仅预览,未写入数据库)`,
408
+ totalCreated: 0,
409
+ totalGenerated,
429
410
  mockMode: true,
430
411
  });
431
- logger.info(`[MockPipeline] ═══ Mock bootstrap complete — ${totalCreated} candidates from ${dimensions.length} dimensions`);
412
+ logger.info(`[MockPipeline] ═══ Mock bootstrap complete — ${totalGenerated} candidates generated (not persisted) from ${dimensions.length} dimensions`);
432
413
  }
@@ -18,7 +18,7 @@ import path from 'node:path';
18
18
  import { AgentMessage } from '#agent/AgentMessage.js';
19
19
  import { ExplorationTracker } from '#agent/context/ExplorationTracker.js';
20
20
  import { EpisodicConsolidator } from '#agent/domain/EpisodicConsolidator.js';
21
- import { ANALYST_BUDGET } from '#agent/domain/insight-analyst.js';
21
+ import { computeAnalystBudget } from '#agent/domain/insight-analyst.js';
22
22
  import { MemoryCoordinator } from '#agent/memory/MemoryCoordinator.js';
23
23
  import { MemoryEmbeddingStore } from '#agent/memory/MemoryEmbeddingStore.js';
24
24
  import { PersistentMemory } from '#agent/memory/PersistentMemory.js';
@@ -600,9 +600,9 @@ export async function fillDimensionsV3(view, dimensions) {
600
600
  // ── 引擎增强参数 (PipelineStrategy → reactLoop 透传) ──
601
601
  contextWindow: agentFactory?.createContextWindow({ isSystem: true }),
602
602
  // B1 fix: 分析阶段使用 analyst 策略 (SCAN→EXPLORE→VERIFY→SUMMARIZE)
603
- // 而非 bootstrap (EXPLORE→PRODUCE→SUMMARIZE),避免 PRODUCE nudge 浪费轮次
604
- // B3 fix: 透传完整 ANALYST_BUDGET (searchBudget/maxSubmits/softSubmitLimit/idleRoundsToExit)
605
- tracker: ExplorationTracker.resolve({ source: 'system', strategy: 'analyst' }, { ...ANALYST_BUDGET }),
603
+ // B3 fix: 透传完整 ANALYST_BUDGET
604
+ // B4 fix: 自适应预算 根据项目文件数缩放 maxIterations (24~40)
605
+ tracker: ExplorationTracker.resolve({ source: 'system', strategy: 'analyst' }, computeAnalystBudget(projectInfo.fileCount || 0)),
606
606
  trace: memoryCoordinator.getActiveContext(analystScopeId),
607
607
  memoryCoordinator,
608
608
  sharedState: {
@@ -826,6 +826,7 @@ export async function fillDimensionsV3(view, dimensions) {
826
826
  source: 'enhanced-pipeline-strategy',
827
827
  });
828
828
  const dimTokenUsage = combinedTokenUsage;
829
+ const qualityScores = artifact.qualityReport;
829
830
  const dimResult = {
830
831
  candidateCount: producerResult.candidateCount,
831
832
  rejectedCount: producerResult.rejectedCount || 0,
@@ -836,6 +837,13 @@ export async function fillDimensionsV3(view, dimensions) {
836
837
  tokenUsage: dimTokenUsage,
837
838
  analysisText: analysisReport.analysisText,
838
839
  referencedFilesList: analysisReport.referencedFiles || [],
840
+ qualityGate: qualityScores
841
+ ? {
842
+ totalScore: qualityScores.totalScore,
843
+ scores: qualityScores.scores,
844
+ action: gateResult?.action || (runResult?.degraded ? 'degrade' : 'pass'),
845
+ }
846
+ : null,
839
847
  };
840
848
  dimensionStats[dimId] = dimResult;
841
849
  // P3: 保存 checkpoint — 仅当有实质分析内容时(避免 degraded/空结果污染后续 run)
@@ -1061,6 +1069,30 @@ export async function fillDimensionsV3(view, dimensions) {
1061
1069
  `~${consolidationResult.total.updated} UPDATE, ` +
1062
1070
  `⊕${consolidationResult.total.merged} MERGE | ` +
1063
1071
  `Total: ${smStats.total} memories (avg importance: ${smStats.avgImportance})`);
1072
+ // 按类型和来源分布
1073
+ logger.info(`[Insight-v3] Memory by type: ${Object.entries(smStats.byType)
1074
+ .map(([t, n]) => `${t}=${n}`)
1075
+ .join(', ')} | ` +
1076
+ `by source: ${Object.entries(smStats.bySource)
1077
+ .map(([s, n]) => `${s}=${n}`)
1078
+ .join(', ')}`);
1079
+ // 蒸馏管线详情 (来自 EpisodicConsolidator 增强返回值)
1080
+ const cr = consolidationResult;
1081
+ if (cr.perDimension) {
1082
+ const perDim = cr.perDimension;
1083
+ const topDims = Object.entries(perDim)
1084
+ .sort(([, a], [, b]) => b - a)
1085
+ .slice(0, 6);
1086
+ logger.info(`[Insight-v3] Memory per-dimension (top ${topDims.length}): ${topDims.map(([d, n]) => `${d}=${n}`).join(', ')}`);
1087
+ }
1088
+ if (cr.importanceDistribution) {
1089
+ const hist = cr.importanceDistribution;
1090
+ const histStr = Object.entries(hist)
1091
+ .sort(([a], [b]) => Number(a) - Number(b))
1092
+ .map(([k, v]) => `imp${k}=${v}`)
1093
+ .join(' ');
1094
+ logger.info(`[Insight-v3] Importance histogram: ${histStr}`);
1095
+ }
1064
1096
  }
1065
1097
  else {
1066
1098
  logger.warn('[Insight-v3] Database not available — skipping Semantic Memory consolidation');
@@ -1163,6 +1195,7 @@ export async function fillDimensionsV3(view, dimensions) {
1163
1195
  durationMs: stat.durationMs || 0,
1164
1196
  toolCallCount: stat.toolCallCount || 0,
1165
1197
  tokenUsage: stat.tokenUsage || { input: 0, output: 0 },
1198
+ qualityGate: stat.qualityGate || null,
1166
1199
  };
1167
1200
  }
1168
1201
  // Phase E: 附加 Code Entity Graph 拓扑到报告
@@ -70,9 +70,9 @@ router.get('/providers', async (req, res) => {
70
70
  };
71
71
  const providers = [
72
72
  { id: 'google', label: 'Google Gemini', defaultModel: 'gemini-3-flash-preview' },
73
- { id: 'openai', label: 'OpenAI', defaultModel: 'gpt-4o' },
73
+ { id: 'openai', label: 'OpenAI', defaultModel: 'gpt-5.4' },
74
74
  { id: 'deepseek', label: 'DeepSeek', defaultModel: 'deepseek-chat' },
75
- { id: 'claude', label: 'Claude', defaultModel: 'claude-3-5-sonnet-20240620' },
75
+ { id: 'claude', label: 'Claude', defaultModel: 'claude-sonnet-4-20250514' },
76
76
  { id: 'ollama', label: 'Ollama', defaultModel: 'llama3' },
77
77
  { id: 'mock', label: 'Mock (测试)', defaultModel: 'mock-l3' },
78
78
  ].map((p) => ({
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import Database from 'better-sqlite3';
5
- import { isAutoSnippetDevRepo } from '../../shared/isOwnDevRepo.js';
5
+ import { isExcludedProject } from '../../shared/isOwnDevRepo.js';
6
6
  import pathGuard from '../../shared/PathGuard.js';
7
7
  import { initDrizzle } from './drizzle/index.js';
8
8
  const __dirname = import.meta.dirname;
@@ -30,17 +30,18 @@ export class DatabaseConnection {
30
30
  let resolvedDbPath = projectRoot && !path.isAbsolute(dbPath)
31
31
  ? path.resolve(projectRoot, dbPath)
32
32
  : path.resolve(dbPath);
33
- // ── 开发仓库保护 ──────────────────────────────────────────
34
- // 检测 DB 即将落地到 AutoSnippet 源码仓库内 → 重定向到临时目录
35
- // 同时检查 projectRoot(MCP 模式)和 resolvedDbPath 的父目录(测试/CLI 模式)
33
+ // ── 排除项目保护 ──────────────────────────────────────────
34
+ // 检测 DB 即将落地到不适合创建知识库的项目 → 重定向到临时目录
35
+ // 包括:AutoSnippet 源码仓库、生态项目(autosnippet-book 等)、.autosnippet-skip 标记项目
36
36
  const effectiveRoot = projectRoot || path.resolve('.');
37
- if (isAutoSnippetDevRepo(effectiveRoot)) {
37
+ const exclusion = isExcludedProject(effectiveRoot);
38
+ if (exclusion.excluded) {
38
39
  const devDbDir = path.join(os.tmpdir(), 'autosnippet-dev');
39
40
  if (!fs.existsSync(devDbDir)) {
40
41
  fs.mkdirSync(devDbDir, { recursive: true });
41
42
  }
42
43
  resolvedDbPath = path.join(devDbDir, 'autosnippet.db');
43
- process.stderr.write(`[AutoSnippet] Dev repo detected — DB redirected to ${resolvedDbPath}\n`);
44
+ process.stderr.write(`[AutoSnippet] Excluded project detected (${exclusion.reason}) — DB redirected to ${resolvedDbPath}\n`);
44
45
  }
45
46
  else {
46
47
  // 路径安全检查 — 防止 DB 文件创建到项目允许范围外
@@ -170,7 +170,21 @@ export class CursorDeliveryPipeline {
170
170
  catch (e) {
171
171
  this.logger.warn?.(`[CursorDelivery] Failed to load pending entries: ${e.message}`);
172
172
  }
173
- return allEntries;
173
+ // 过滤掉历史遗留的 mock 条目(source=mock-bootstrap/mock-pipeline 或 createdBy=mock-ai)
174
+ const MOCK_SOURCES = new Set(['mock-bootstrap', 'mock-pipeline']);
175
+ const filtered = allEntries.filter((e) => {
176
+ if (MOCK_SOURCES.has(e.source)) {
177
+ return false;
178
+ }
179
+ if (e.createdBy === 'mock-ai') {
180
+ return false;
181
+ }
182
+ return true;
183
+ });
184
+ if (filtered.length < allEntries.length) {
185
+ this.logger.info?.(`[CursorDelivery] Filtered out ${allEntries.length - filtered.length} mock entries`);
186
+ }
187
+ return filtered;
174
188
  }
175
189
  /**
176
190
  * 从 KnowledgeService.list() 返回值提取条目数组
@@ -26,7 +26,7 @@
26
26
  * - 错误不静默:越界写操作抛出 PathGuardError
27
27
  */
28
28
  import path from 'node:path';
29
- import { isAutoSnippetDevRepo } from './isOwnDevRepo.js';
29
+ import { isAutoSnippetDevRepo, isExcludedProject } from './isOwnDevRepo.js';
30
30
  import { DEFAULT_KNOWLEDGE_BASE_DIR, detectKnowledgeBaseDir, } from './ProjectMarkers.js';
31
31
  export class PathGuardError extends Error {
32
32
  projectRoot;
@@ -74,6 +74,10 @@ class PathGuard {
74
74
  #configured = false;
75
75
  /** projectRoot 是否是 AutoSnippet 自身的开发仓库 */
76
76
  #isDevRepo = false;
77
+ /** projectRoot 是否是应排除的项目(开发仓库、生态项目等) */
78
+ #isExcludedProject = false;
79
+ /** 排除原因 */
80
+ #excludeReason = '';
77
81
  /**
78
82
  * 配置 PathGuard(每个进程执行一次)
79
83
  * @param opts.projectRoot 用户项目根目录(绝对路径)
@@ -89,6 +93,9 @@ class PathGuard {
89
93
  this.#packageRoot = packageRoot ? path.resolve(packageRoot) : null;
90
94
  this.#knowledgeBaseDir = knowledgeBaseDir || null; // 延迟解析
91
95
  this.#isDevRepo = isAutoSnippetDevRepo(this.#projectRoot);
96
+ const exclusion = isExcludedProject(this.#projectRoot);
97
+ this.#isExcludedProject = exclusion.excluded;
98
+ this.#excludeReason = exclusion.reason;
92
99
  // 默认白名单:全局缓存 + 平台 Snippets 目录
93
100
  const HOME = process.env.HOME || process.env.USERPROFILE || '';
94
101
  if (HOME) {
@@ -175,19 +182,18 @@ class PathGuard {
175
182
  // 计算相对于 projectRoot 的路径
176
183
  const relative = path.relative(this.#projectRoot, resolved);
177
184
  const firstSegment = relative.split(path.sep)[0];
178
- // ── 开发仓库保护 ──────────────────────────────────
179
- // 如果 projectRoot 是 AutoSnippet 自身源码仓库,
180
- // 禁止写入 .autosnippet/ 和知识库目录(AutoSnippet/),
181
- // 防止在开发仓库内产生运行时数据
182
- if (this.#isDevRepo) {
185
+ // ── 排除项目保护 ──────────────────────────────────
186
+ // 如果 projectRoot 是排除项目(开发仓库、生态项目等),
187
+ // 禁止写入 .autosnippet/ 和知识库目录
188
+ if (this.#isExcludedProject) {
183
189
  if (firstSegment === '.autosnippet') {
184
- throw new PathGuardError(resolved, this.#projectRoot, 'Dev repo 保护: 禁止在 AutoSnippet 源码仓库内创建 .autosnippet/ 运行时数据');
190
+ throw new PathGuardError(resolved, this.#projectRoot, `排除项目保护 (${this.#excludeReason}): 禁止创建 .autosnippet/ 运行时数据`);
185
191
  }
186
192
  const kbDir = this.#resolveKnowledgeBaseDir();
187
193
  if (kbDir && firstSegment === kbDir) {
188
- throw new PathGuardError(resolved, this.#projectRoot, `Dev repo 保护: 禁止在 AutoSnippet 源码仓库内创建 ${kbDir}/ 知识库数据`);
194
+ throw new PathGuardError(resolved, this.#projectRoot, `排除项目保护 (${this.#excludeReason}): 禁止创建 ${kbDir}/ 知识库数据`);
189
195
  }
190
- // 开发仓库内允许写入 .cursor/.vscode/.github 等 IDE 配置
196
+ // 排除项目内仍允许写入 .cursor/.vscode/.github 等 IDE 配置
191
197
  for (const prefix of PROJECT_WRITE_SCOPE_PREFIXES) {
192
198
  if (prefix !== '.autosnippet' && firstSegment === prefix) {
193
199
  return;
@@ -196,7 +202,7 @@ class PathGuard {
196
202
  if (PROJECT_ROOT_WRITABLE_FILES.includes(relative)) {
197
203
  return;
198
204
  }
199
- throw new PathGuardError(resolved, this.#projectRoot, `Dev repo 保护: "${relative}" 不在允许范围内`);
205
+ throw new PathGuardError(resolved, this.#projectRoot, `排除项目保护 (${this.#excludeReason}): "${relative}" 不在允许范围内`);
200
206
  }
201
207
  // 检查是否在允许的前缀中
202
208
  for (const prefix of PROJECT_WRITE_SCOPE_PREFIXES) {