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.
- package/README.md +1 -1
- package/config/default.json +1 -1
- package/dashboard/dist/assets/{index-DV8biUkH.js → index-8b1Gf3Bb.js} +4 -4
- package/dashboard/dist/index.html +1 -1
- package/dist/lib/agent/AgentRuntime.js +13 -1
- package/dist/lib/agent/AgentRuntimeTypes.d.ts +2 -0
- package/dist/lib/agent/PipelineStrategy.js +32 -2
- package/dist/lib/agent/context/ContextWindow.d.ts +2 -1
- package/dist/lib/agent/context/ContextWindow.js +18 -4
- package/dist/lib/agent/context/ExplorationTracker.js +6 -1
- package/dist/lib/agent/context/exploration/ExplorationStrategies.js +2 -1
- package/dist/lib/agent/core/LoopContext.d.ts +3 -0
- package/dist/lib/agent/core/LoopContext.js +3 -0
- package/dist/lib/agent/domain/EpisodicConsolidator.d.ts +5 -0
- package/dist/lib/agent/domain/EpisodicConsolidator.js +60 -5
- package/dist/lib/agent/domain/insight-analyst.d.ts +16 -0
- package/dist/lib/agent/domain/insight-analyst.js +38 -0
- package/dist/lib/agent/domain/insight-gate.js +12 -0
- package/dist/lib/agent/memory/MemoryConsolidator.js +17 -0
- package/dist/lib/bootstrap.js +6 -1
- package/dist/lib/cli/SetupService.js +5 -4
- package/dist/lib/domain/dimension/DimensionRegistry.d.ts +6 -4
- package/dist/lib/domain/dimension/DimensionRegistry.js +19 -23
- package/dist/lib/external/ai/AiProvider.d.ts +2 -0
- package/dist/lib/external/ai/AiProvider.js +4 -0
- package/dist/lib/external/ai/providers/ClaudeProvider.d.ts +1 -1
- package/dist/lib/external/ai/providers/ClaudeProvider.js +6 -2
- package/dist/lib/external/ai/providers/GoogleGeminiProvider.d.ts +1 -1
- package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +6 -2
- package/dist/lib/external/ai/providers/OpenAiProvider.d.ts +1 -1
- package/dist/lib/external/ai/providers/OpenAiProvider.js +7 -3
- package/dist/lib/external/mcp/McpServer.js +19 -2
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +10 -2
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.js +10 -29
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +37 -4
- package/dist/lib/http/routes/ai.js +2 -2
- package/dist/lib/infrastructure/database/DatabaseConnection.js +7 -6
- package/dist/lib/service/delivery/CursorDeliveryPipeline.js +15 -1
- package/dist/lib/shared/PathGuard.js +16 -10
- package/dist/lib/shared/isOwnDevRepo.d.ts +29 -4
- package/dist/lib/shared/isOwnDevRepo.js +64 -4
- 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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
573
|
-
* -
|
|
574
|
-
* -
|
|
575
|
-
* -
|
|
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
|
|
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
|
|
584
|
-
|
|
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
|
-
|
|
588
|
+
// 按 tier 编号升序排列,过滤空层
|
|
589
|
+
return [...tierMap.entries()].sort(([a], [b]) => a - b).map(([, dims]) => dims);
|
|
594
590
|
}
|
|
595
591
|
/**
|
|
596
592
|
* 将 Recipe 分类到最匹配的维度
|
|
@@ -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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
389
|
+
totalGenerated += candidates.length;
|
|
391
390
|
for (const candidate of candidates) {
|
|
392
|
-
|
|
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:
|
|
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}": ${
|
|
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 完成: ${
|
|
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 — ${
|
|
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 {
|
|
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
|
-
//
|
|
604
|
-
//
|
|
605
|
-
tracker: ExplorationTracker.resolve({ source: 'system', strategy: 'analyst' },
|
|
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-
|
|
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-
|
|
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 {
|
|
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
|
|
35
|
-
//
|
|
33
|
+
// ── 排除项目保护 ──────────────────────────────────────────
|
|
34
|
+
// 检测 DB 即将落地到不适合创建知识库的项目 → 重定向到临时目录
|
|
35
|
+
// 包括:AutoSnippet 源码仓库、生态项目(autosnippet-book 等)、.autosnippet-skip 标记项目
|
|
36
36
|
const effectiveRoot = projectRoot || path.resolve('.');
|
|
37
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
|
180
|
-
// 禁止写入 .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,
|
|
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,
|
|
194
|
+
throw new PathGuardError(resolved, this.#projectRoot, `排除项目保护 (${this.#excludeReason}): 禁止创建 ${kbDir}/ 知识库数据`);
|
|
189
195
|
}
|
|
190
|
-
//
|
|
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,
|
|
205
|
+
throw new PathGuardError(resolved, this.#projectRoot, `排除项目保护 (${this.#excludeReason}): "${relative}" 不在允许范围内`);
|
|
200
206
|
}
|
|
201
207
|
// 检查是否在允许的前缀中
|
|
202
208
|
for (const prefix of PROJECT_WRITE_SCOPE_PREFIXES) {
|