autosnippet 3.2.7 → 3.2.8
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/bin/cli.js +7 -0
- package/dashboard/dist/assets/index-D5jiDBQG.css +1 -0
- package/dashboard/dist/assets/{index-DfHY_3ln.js → index-e5OKj-Ni.js} +38 -38
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +3 -3
- package/lib/core/AstAnalyzer.js +26 -4
- package/lib/core/analysis/CallEdgeResolver.js +402 -0
- package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
- package/lib/core/analysis/CallSiteExtractor.js +629 -0
- package/lib/core/analysis/DataFlowInferrer.js +57 -0
- package/lib/core/analysis/ImportPathResolver.js +189 -0
- package/lib/core/analysis/ImportRecord.js +105 -0
- package/lib/core/analysis/SymbolTableBuilder.js +211 -0
- package/lib/core/ast/ProjectGraph.js +8 -0
- package/lib/core/ast/lang-dart.js +352 -5
- package/lib/core/ast/lang-go.js +212 -10
- package/lib/core/ast/lang-java.js +205 -1
- package/lib/core/ast/lang-kotlin.js +330 -1
- package/lib/core/ast/lang-python.js +31 -2
- package/lib/core/ast/lang-rust.js +284 -3
- package/lib/core/ast/lang-swift.js +180 -1
- package/lib/core/ast/lang-typescript.js +290 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +21 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +5 -4
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +70 -4
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +95 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +17 -6
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/routes/remote.js +15 -15
- package/lib/injection/ServiceContainer.js +6 -11
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
- package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
- package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
- package/lib/service/chat/ChatAgent.js +1 -1
- package/lib/service/chat/ChatAgentPrompts.js +13 -1
- package/lib/service/chat/ExplorationTracker.js +52 -8
- package/lib/service/chat/HandoffProtocol.js +19 -1
- package/lib/service/chat/WorkingMemory.js +3 -1
- package/lib/service/chat/memory/ActiveContext.js +3 -1
- package/lib/service/chat/memory/SessionStore.js +4 -3
- package/lib/service/chat/tools/ast-graph.js +229 -32
- package/lib/service/chat/tools/index.js +6 -1
- package/lib/service/chat/tools/infrastructure.js +5 -0
- package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
- package/lib/service/knowledge/CodeEntityGraph.js +327 -2
- package/lib/service/knowledge/KnowledgeService.js +5 -1
- package/lib/service/module/ModuleService.js +9 -0
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
|
@@ -465,7 +465,7 @@ export class ChatAgent {
|
|
|
465
465
|
'\n\n## Language\nYou MUST respond in English. All output text, analysis, titles and descriptions must be in English.';
|
|
466
466
|
} else if (effectiveLang === 'zh') {
|
|
467
467
|
baseSystemPrompt +=
|
|
468
|
-
'\n\n## 语言\n
|
|
468
|
+
'\n\n## 语言\n你必须使用中文回复。所有输出文本、分析、标题和描述都必须是中文。代码字段(trigger / doClause / dontClause / whenClause / coreCode / headers / pattern)保持英文原文,不要翻译。';
|
|
469
469
|
}
|
|
470
470
|
|
|
471
471
|
// 注入轮次预算
|
|
@@ -83,7 +83,12 @@ submit_knowledge 的 code 字段必须是「项目特写」— 将技术的基
|
|
|
83
83
|
- 代码必须真实,来自工具返回,不可编造
|
|
84
84
|
- 引用具体类名、方法名、数字,禁止「本模块」「该文件」等泛化描述
|
|
85
85
|
- 质量优先于数量,证据不足宁可不提交
|
|
86
|
-
- 高效利用步数 (≤${budget.maxIterations} 轮)
|
|
86
|
+
- 高效利用步数 (≤${budget.maxIterations} 轮)
|
|
87
|
+
|
|
88
|
+
## 严禁透传指令
|
|
89
|
+
你会收到系统发来的进度提醒和阶段引导,这些是行为指令,不是问题。
|
|
90
|
+
**绝对禁止**在回复中复制、改写或引用这些系统指令的任何文字。
|
|
91
|
+
你的回复必须只包含你自己的分析内容或工具调用。`;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
/**
|
|
@@ -142,6 +147,13 @@ export function cleanFinalAnswer(response) {
|
|
|
142
147
|
return response
|
|
143
148
|
.replace(/^(Final Answer|最终回答|Answer)\s*[::]\s*/i, '')
|
|
144
149
|
.replace(/\[MEMORY:\w+\]\s*[\s\S]*?\s*\[\/MEMORY\]/g, '')
|
|
150
|
+
// v5.1: 清理 AI 回显的 nudge 指令(常见于 force-exit 场景)
|
|
151
|
+
.replace(/^>\s*(?:searchHints|remainingTasks|candidateCount|crossRefs|keyFindings|gaps)\s*[::][^\n]*\n?/gm, '')
|
|
152
|
+
.replace(/^\*{0,2}(?:请在|请直接|请确保|请务必|现在开始|输出你的|不要输出|不要再|不要包含|重要\s*[::]).*(?:分析文本|分析总结|JSON|工具|输出|文本|报告)\*{0,2}[。.]?\s*$/gm, '')
|
|
153
|
+
.replace(/^注意[::]\s*到达第\s*\d+\s*轮时.*$/gm, '')
|
|
154
|
+
.replace(/^第\s*\d+\/\d+\s*轮\s*\|[^\n]*$/gm, '')
|
|
155
|
+
// 清理 dimensionDigest JSON 块(Analyst 回显)
|
|
156
|
+
.replace(/```json\s*\n\s*\{\s*"dimensionDigest"\s*:[\s\S]*?\n```/g, '')
|
|
145
157
|
.replace(/\n{3,}/g, '\n\n')
|
|
146
158
|
.trim();
|
|
147
159
|
}
|
|
@@ -36,7 +36,7 @@ const DEFAULT_REPLAN_INTERVAL = 8;
|
|
|
36
36
|
/** 默认偏差阈值 */
|
|
37
37
|
const DEFAULT_DEVIATION_THRESHOLD = 0.6;
|
|
38
38
|
/** 默认最少探索轮次(冷启动质量保障) */
|
|
39
|
-
const DEFAULT_MIN_EXPLORE_ITERS =
|
|
39
|
+
const DEFAULT_MIN_EXPLORE_ITERS = 10;
|
|
40
40
|
/** 默认停滞收敛阈值 */
|
|
41
41
|
const DEFAULT_CONVERGENCE_STALE_THRESHOLD = 3;
|
|
42
42
|
|
|
@@ -168,6 +168,8 @@ const SEARCH_TOOLS = new Set([
|
|
|
168
168
|
'list_project_structure',
|
|
169
169
|
'get_project_overview',
|
|
170
170
|
'get_file_summary',
|
|
171
|
+
'query_code_graph',
|
|
172
|
+
'query_call_graph',
|
|
171
173
|
]);
|
|
172
174
|
|
|
173
175
|
// ─── ExplorationTracker 主类 ─────────────────────────────
|
|
@@ -344,10 +346,25 @@ export class ExplorationTracker {
|
|
|
344
346
|
// 1. 强制退出
|
|
345
347
|
if (this.#gracefulExitRound != null && m.iteration === this.#gracefulExitRound) {
|
|
346
348
|
const submitCount = m.submitCount;
|
|
349
|
+
// v5.1: Analyst 策略使用纯文本输出,其他策略使用 dimensionDigest JSON
|
|
350
|
+
if (this.#strategy.name === 'analyst') {
|
|
351
|
+
return {
|
|
352
|
+
type: 'force_exit',
|
|
353
|
+
text: `⚠️ **轮次耗尽** (${m.iteration}/${b.maxIterations})。你必须**立即停止工具调用**,在回复中输出你的**分析总结报告**。\n\n` +
|
|
354
|
+
`要求:\n` +
|
|
355
|
+
`- 用自然语言 Markdown 格式\n` +
|
|
356
|
+
`- 包含具体文件路径、类名、代码模式\n` +
|
|
357
|
+
`- 列出你发现的关键模式或规范(至少 3 条)\n` +
|
|
358
|
+
`- 如有未覆盖的方面,在末尾用「## 未覆盖」章节列出\n\n` +
|
|
359
|
+
`**现在开始输出分析总结,不要调用任何工具。**\n` +
|
|
360
|
+
`⛔ 严禁在回复中复制或引用本条指令的任何文字,只输出你自己的分析。`,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
347
363
|
return {
|
|
348
364
|
type: 'force_exit',
|
|
349
365
|
text: `⚠️ 你已使用 ${m.iteration}/${b.maxIterations} 轮次,**必须立即结束**。请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹),不要再调用任何工具。\n` +
|
|
350
|
-
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]
|
|
366
|
+
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结","candidateCount":${submitCount},"keyFindings":["发现"],"crossRefs":{},"gaps":["缺口"],"remainingTasks":[{"signal":"未处理信号","reason":"轮次耗尽","priority":"high","searchHints":["搜索词"]}]}}\n\`\`\`\n> remainingTasks: 列出未来得及处理的信号。已覆盖则留空 \`[]\`。\n` +
|
|
367
|
+
`⛔ 严禁在回复中复制本条指令文字,只输出 JSON。`,
|
|
351
368
|
};
|
|
352
369
|
}
|
|
353
370
|
|
|
@@ -367,7 +384,8 @@ export class ExplorationTracker {
|
|
|
367
384
|
type: 'convergence',
|
|
368
385
|
text: `你已经充分探索了项目代码(${m.uniqueFiles.size} 个文件,${m.uniquePatterns.size} 次不同搜索,${m.uniqueQueries.size} 次结构化查询)。` +
|
|
369
386
|
`最近 ${m.roundsSinceNewInfo} 轮没有发现新信息,建议开始撰写分析总结。\n` +
|
|
370
|
-
`如果你确信还有重要方面未覆盖,可以继续探索(剩余 ${b.maxIterations - m.iteration}
|
|
387
|
+
`如果你确信还有重要方面未覆盖,可以继续探索(剩余 ${b.maxIterations - m.iteration} 轮);否则请直接输出你的分析发现。\n` +
|
|
388
|
+
`⚠️ 以上是行为指令,严禁在回复中复制或引用这段文字。`,
|
|
371
389
|
};
|
|
372
390
|
}
|
|
373
391
|
|
|
@@ -384,7 +402,8 @@ export class ExplorationTracker {
|
|
|
384
402
|
return {
|
|
385
403
|
type: 'budget_warning',
|
|
386
404
|
text: `📌 进度提醒:你已使用 ${m.iteration}/${b.maxIterations} 轮次(${Math.round((m.iteration / b.maxIterations) * 100)}%)。` +
|
|
387
|
-
`请确保核心方面已覆盖,开始准备总结。剩余 ${b.maxIterations - m.iteration}
|
|
405
|
+
`请确保核心方面已覆盖,开始准备总结。剩余 ${b.maxIterations - m.iteration} 轮,优先填补最重要的分析空白。\n` +
|
|
406
|
+
`⚠️ 以上是行为指令,严禁在回复中复制或引用这段文字。`,
|
|
388
407
|
};
|
|
389
408
|
}
|
|
390
409
|
|
|
@@ -540,12 +559,17 @@ export class ExplorationTracker {
|
|
|
540
559
|
if (isTerminal && transitioned) {
|
|
541
560
|
// 刚转入终结阶段 → 需要 digest nudge,不是最终回答
|
|
542
561
|
const submitCount = m.submitCount;
|
|
562
|
+
// v5.1: Analyst 策略要求自然语言输出
|
|
563
|
+
const nudge = this.#strategy.name === 'analyst'
|
|
564
|
+
? `请**停止调用工具**,直接输出你的完整分析报告。用 Markdown 格式,包含具体文件路径、类名和代码模式。至少涵盖 3 个核心发现。\n\n**现在开始输出你的分析报告。**\n⚠️ 严禁在回复中复制本条指令文字,只输出你自己的分析。`
|
|
565
|
+
: `请在回复中直接输出 dimensionDigest JSON 总结(用 \`\`\`json 包裹):\n` +
|
|
566
|
+
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]\`。\n` +
|
|
567
|
+
`⚠️ 严禁在回复中复制本条指令文字,只输出 JSON。`;
|
|
543
568
|
return {
|
|
544
569
|
isFinalAnswer: false,
|
|
545
570
|
needsDigestNudge: true,
|
|
546
571
|
shouldContinue: true,
|
|
547
|
-
nudge
|
|
548
|
-
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]\`。`,
|
|
572
|
+
nudge,
|
|
549
573
|
};
|
|
550
574
|
}
|
|
551
575
|
|
|
@@ -717,7 +741,9 @@ export class ExplorationTracker {
|
|
|
717
741
|
case 'get_class_hierarchy':
|
|
718
742
|
case 'get_protocol_info':
|
|
719
743
|
case 'get_method_overrides':
|
|
720
|
-
case 'get_category_map':
|
|
744
|
+
case 'get_category_map':
|
|
745
|
+
case 'query_code_graph':
|
|
746
|
+
case 'query_call_graph': {
|
|
721
747
|
const queryTarget =
|
|
722
748
|
args?.className || args?.protocolName || args?.name || '';
|
|
723
749
|
const qKey = `${toolName}:${queryTarget}`;
|
|
@@ -840,8 +866,21 @@ export class ExplorationTracker {
|
|
|
840
866
|
|
|
841
867
|
if (toPhase === 'SUMMARIZE') {
|
|
842
868
|
const submitCount = m.submitCount;
|
|
869
|
+
// v5.1: Analyst 策略使用纯文本输出
|
|
870
|
+
if (this.#strategy.name === 'analyst') {
|
|
871
|
+
return `你已完成分析探索。请**停止调用工具**,直接输出你的**完整分析报告**。\n\n` +
|
|
872
|
+
`要求:\n` +
|
|
873
|
+
`- 用 Markdown 格式组织内容(二级/三级标题)\n` +
|
|
874
|
+
`- 包含具体的文件路径、类名、方法名、代码模式\n` +
|
|
875
|
+
`- 每个关键发现都要给出证据(文件路径 + 代码片段或行为描述)\n` +
|
|
876
|
+
`- 至少涵盖 3 个核心发现\n` +
|
|
877
|
+
`- 如有未覆盖的方面,在末尾用 「## 待探索」 章节列出\n\n` +
|
|
878
|
+
`**现在开始输出你的分析报告。不要再调用任何工具。**\n` +
|
|
879
|
+
`⚠️ 以上是行为指令,严禁在回复中复制或引用这段文字,只输出你自己的分析内容。`;
|
|
880
|
+
}
|
|
843
881
|
return `你已完成分析探索。请在回复中直接输出 dimensionDigest JSON(用 \`\`\`json 包裹),包含以下字段:\n` +
|
|
844
|
-
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因(如:提交上限已达)","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]
|
|
882
|
+
`\`\`\`json\n{"dimensionDigest":{"summary":"分析总结(100-200字)","candidateCount":${submitCount},"keyFindings":["关键发现"],"crossRefs":{},"gaps":["未覆盖方面"],"remainingTasks":[{"signal":"未处理的信号/主题","reason":"未完成原因(如:提交上限已达)","priority":"high|medium|low","searchHints":["建议搜索词"]}]}}\n\`\`\`\n> 如果所有信号都已覆盖,remainingTasks 留空数组 \`[]\`。如果有未来得及处理的信号,请在此标记,系统会在下次运行时续传。\n` +
|
|
883
|
+
`⚠️ 严禁在回复中复制本条指令文字,只输出 JSON。`;
|
|
845
884
|
}
|
|
846
885
|
|
|
847
886
|
if (toPhase === 'EXPLORE' && fromPhase === 'SCAN') {
|
|
@@ -988,6 +1027,7 @@ export class ExplorationTracker {
|
|
|
988
1027
|
);
|
|
989
1028
|
}
|
|
990
1029
|
|
|
1030
|
+
parts.push(`\n⚠️ 以上是行为指令,严禁在回复中复制或引用这段文字,用你自己的分析内容回答。`);
|
|
991
1031
|
const reflectionText = parts.join('\n');
|
|
992
1032
|
trace?.setReflection?.(reflectionText);
|
|
993
1033
|
this.#logger.info(
|
|
@@ -1189,6 +1229,10 @@ export class ExplorationTracker {
|
|
|
1189
1229
|
toolName === 'search_project_code' &&
|
|
1190
1230
|
(desc.includes('搜索') || desc.includes('search') || desc.includes('查找') || desc.includes('分析'))
|
|
1191
1231
|
) return step;
|
|
1232
|
+
if (
|
|
1233
|
+
(toolName === 'query_code_graph' || toolName === 'query_call_graph') &&
|
|
1234
|
+
(desc.includes('图谱') || desc.includes('graph') || desc.includes('调用') || desc.includes('call') || desc.includes('关系') || desc.includes('依赖'))
|
|
1235
|
+
) return step;
|
|
1192
1236
|
}
|
|
1193
1237
|
|
|
1194
1238
|
return null;
|
|
@@ -89,6 +89,23 @@ function sanitizeAnalysisText(text) {
|
|
|
89
89
|
|
|
90
90
|
// 纯数字编号残留行(清理被上面 pattern 删除后留下的孤立编号)
|
|
91
91
|
/^\s*\d+\.\s*$/gm,
|
|
92
|
+
|
|
93
|
+
// ── v5.1: Nudge 指令回显清理 ──
|
|
94
|
+
|
|
95
|
+
// AI 回显的 dimensionDigest 模板说明行("> searchHints: ...", "> candidateCount: ...")
|
|
96
|
+
/^>\s*(?:searchHints|remainingTasks|candidateCount|crossRefs|keyFindings|gaps)\s*[::][^\n]*\n?/gm,
|
|
97
|
+
|
|
98
|
+
// AI 回显的输出指令("请在 JSON 后紧跟...", "请直接输出...", "现在开始输出...", "输出你的...")
|
|
99
|
+
/^\*{0,2}(?:请在|请直接|请确保|请务必|现在开始|输出你的|不要输出|不要再|不要包含)\s*[^。\n]*(?:分析文本|分析总结|分析报告|JSON|工具|输出|文本|报告)[^。\n]*[。.]?\s*\*{0,2}$/gm,
|
|
100
|
+
|
|
101
|
+
// AI 回显的"重要"强调指令
|
|
102
|
+
/^\*{0,2}重要\s*[::][^。\n]*\*{0,2}$/gm,
|
|
103
|
+
|
|
104
|
+
// AI 回显的"注意"行("注意:到达第 N 轮时...")
|
|
105
|
+
/^注意[::]\s*到达第\s*\d+\s*轮时[^\n]*$/gm,
|
|
106
|
+
|
|
107
|
+
// AI 回显的轮次进度行("第 19/24 轮 | ⚠ 总结阶段 — 请停止工具调用,直接输出分析文本 | 剩余 5 轮")
|
|
108
|
+
/^第\s*\d+\/\d+\s*轮\s*\|[^\n]*$/gm,
|
|
92
109
|
];
|
|
93
110
|
let cleaned = text;
|
|
94
111
|
for (const pat of patterns) {
|
|
@@ -239,7 +256,8 @@ export function buildAnalysisArtifact(analystResult, dimensionId, projectGraph =
|
|
|
239
256
|
const distilled = activeContext?.distill() || { keyFindings: [], toolCallSummary: [] };
|
|
240
257
|
const findings = distilled.keyFindings.map((f) => ({
|
|
241
258
|
finding: f.finding,
|
|
242
|
-
|
|
259
|
+
// P0 Fix: evidence 可能是 array/object,强制 string
|
|
260
|
+
evidence: typeof f.evidence === 'string' ? f.evidence : Array.isArray(f.evidence) ? f.evidence.join(', ') : f.evidence ? String(f.evidence) : '',
|
|
243
261
|
importance: f.importance,
|
|
244
262
|
}));
|
|
245
263
|
|
|
@@ -203,9 +203,11 @@ export class WorkingMemory {
|
|
|
203
203
|
* @param {number} [round=0] - 当前轮次
|
|
204
204
|
*/
|
|
205
205
|
noteKeyFinding(finding, evidence = '', importance = 5, round = 0) {
|
|
206
|
+
// P0 Fix: 防御性保证 evidence 是 string (AI 可能传入 array/object)
|
|
207
|
+
const safeEvidence = typeof evidence === 'string' ? evidence : Array.isArray(evidence) ? evidence.join(', ') : evidence ? String(evidence) : '';
|
|
206
208
|
this.#scratchpad.push({
|
|
207
209
|
finding,
|
|
208
|
-
evidence,
|
|
210
|
+
evidence: safeEvidence,
|
|
209
211
|
importance: Math.min(10, Math.max(1, importance)),
|
|
210
212
|
round,
|
|
211
213
|
});
|
|
@@ -322,9 +322,11 @@ export class ActiveContext {
|
|
|
322
322
|
* @param {number} [round=0] - 当前轮次
|
|
323
323
|
*/
|
|
324
324
|
noteKeyFinding(finding, evidence = '', importance = 5, round = 0) {
|
|
325
|
+
// P0 Fix: 防御性保证 evidence 是 string (AI 可能传入 array/object)
|
|
326
|
+
const safeEvidence = typeof evidence === 'string' ? evidence : Array.isArray(evidence) ? evidence.join(', ') : evidence ? String(evidence) : '';
|
|
325
327
|
this.#scratchpad.push({
|
|
326
328
|
finding,
|
|
327
|
-
evidence,
|
|
329
|
+
evidence: safeEvidence,
|
|
328
330
|
importance: Math.min(10, Math.max(1, importance)),
|
|
329
331
|
round,
|
|
330
332
|
});
|
|
@@ -153,10 +153,10 @@ export class SessionStore {
|
|
|
153
153
|
*/
|
|
154
154
|
storeDimensionReport(dimId, report) {
|
|
155
155
|
// findings 统一形状: { finding: string, evidence: string, importance: number }
|
|
156
|
-
//
|
|
156
|
+
// P0 Fix: evidence 可能是 array/object,强制 string
|
|
157
157
|
const findings = (report.findings || []).map((f) => ({
|
|
158
158
|
finding: f.finding || '',
|
|
159
|
-
evidence: f.evidence
|
|
159
|
+
evidence: typeof f.evidence === 'string' ? f.evidence : Array.isArray(f.evidence) ? f.evidence.join(', ') : f.evidence ? String(f.evidence) : '',
|
|
160
160
|
importance: f.importance || 5,
|
|
161
161
|
}));
|
|
162
162
|
|
|
@@ -174,7 +174,8 @@ export class SessionStore {
|
|
|
174
174
|
// 自动提取文件级 Evidence
|
|
175
175
|
for (const f of findings) {
|
|
176
176
|
if (f.evidence) {
|
|
177
|
-
const
|
|
177
|
+
const ev = typeof f.evidence === 'string' ? f.evidence : String(f.evidence);
|
|
178
|
+
const filePath = ev.split(':')[0];
|
|
178
179
|
this.addEvidence(filePath, {
|
|
179
180
|
dimId,
|
|
180
181
|
finding: f.finding,
|