autosnippet 3.3.0 → 3.3.3
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/dashboard/dist/assets/icons-BJ2mUBi8.js +1 -0
- package/dashboard/dist/assets/index-B659K9t5.js +128 -0
- package/dashboard/dist/assets/index-NCm40PMD.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/dist/bin/cli.d.ts +1 -0
- package/dist/bin/cli.js +284 -142
- package/dist/lib/agent/context/ExplorationTracker.d.ts +2 -0
- package/dist/lib/agent/context/ExplorationTracker.js +21 -3
- package/dist/lib/agent/core/ToolExecutionPipeline.d.ts +3 -1
- package/dist/lib/agent/core/ToolExecutionPipeline.js +8 -1
- package/dist/lib/agent/forge/DynamicComposer.d.ts +58 -0
- package/dist/lib/agent/forge/DynamicComposer.js +99 -0
- package/dist/lib/agent/forge/SandboxRunner.d.ts +60 -0
- package/dist/lib/agent/forge/SandboxRunner.js +251 -0
- package/dist/lib/agent/forge/TemporaryToolRegistry.d.ts +76 -0
- package/dist/lib/agent/forge/TemporaryToolRegistry.js +154 -0
- package/dist/lib/agent/forge/ToolForge.d.ts +92 -0
- package/dist/lib/agent/forge/ToolForge.js +239 -0
- package/dist/lib/agent/forge/ToolRequirementAnalyzer.d.ts +44 -0
- package/dist/lib/agent/forge/ToolRequirementAnalyzer.js +119 -0
- package/dist/lib/agent/tools/ToolRegistry.d.ts +2 -0
- package/dist/lib/agent/tools/ToolRegistry.js +4 -0
- package/dist/lib/agent/tools/composite.js +0 -1
- package/dist/lib/agent/tools/index.d.ts +2 -50
- package/dist/lib/agent/tools/index.js +2 -3
- package/dist/lib/agent/tools/lifecycle.d.ts +1 -58
- package/dist/lib/agent/tools/lifecycle.js +2 -75
- package/dist/lib/cli/KnowledgeSyncService.d.ts +26 -0
- package/dist/lib/cli/KnowledgeSyncService.js +33 -1
- package/dist/lib/cli/deploy/FileManifest.d.ts +0 -21
- package/dist/lib/cli/deploy/FileManifest.js +0 -11
- package/dist/lib/domain/knowledge/KnowledgeEntry.d.ts +10 -0
- package/dist/lib/domain/knowledge/KnowledgeEntry.js +2 -0
- package/dist/lib/domain/knowledge/Lifecycle.d.ts +19 -2
- package/dist/lib/domain/knowledge/Lifecycle.js +32 -6
- package/dist/lib/domain/knowledge/UnifiedValidator.d.ts +1 -5
- package/dist/lib/domain/knowledge/UnifiedValidator.js +7 -44
- package/dist/lib/domain/knowledge/values/Stats.d.ts +29 -0
- package/dist/lib/domain/knowledge/values/Stats.js +41 -0
- package/dist/lib/external/mcp/McpServer.d.ts +19 -38
- package/dist/lib/external/mcp/McpServer.js +145 -117
- package/dist/lib/external/mcp/autoApproveInjector.js +0 -2
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.d.ts +26 -1
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +41 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +49 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +3 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +27 -0
- package/dist/lib/external/mcp/handlers/bootstrap/skills.js +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap-external.js +1 -0
- package/dist/lib/external/mcp/handlers/bootstrap-internal.js +2 -0
- package/dist/lib/external/mcp/handlers/browse.d.ts +1 -0
- package/dist/lib/external/mcp/handlers/browse.js +2 -1
- package/dist/lib/external/mcp/handlers/consolidated.d.ts +117 -6
- package/dist/lib/external/mcp/handlers/consolidated.js +251 -71
- package/dist/lib/external/mcp/handlers/guard.d.ts +150 -0
- package/dist/lib/external/mcp/handlers/guard.js +239 -5
- package/dist/lib/external/mcp/handlers/knowledge.d.ts +0 -29
- package/dist/lib/external/mcp/handlers/knowledge.js +1 -76
- package/dist/lib/external/mcp/handlers/panorama.d.ts +36 -0
- package/dist/lib/external/mcp/handlers/panorama.js +156 -0
- package/dist/lib/external/mcp/handlers/system.d.ts +2 -54
- package/dist/lib/external/mcp/handlers/system.js +3 -113
- package/dist/lib/external/mcp/handlers/task.d.ts +13 -24
- package/dist/lib/external/mcp/handlers/task.js +218 -557
- package/dist/lib/external/mcp/handlers/types.d.ts +91 -8
- package/dist/lib/external/mcp/handlers/types.js +18 -1
- package/dist/lib/external/mcp/handlers/wiki-external.d.ts +18 -1
- package/dist/lib/external/mcp/handlers/wiki-external.js +16 -1
- package/dist/lib/external/mcp/tools.d.ts +18 -24
- package/dist/lib/external/mcp/tools.js +132 -159
- package/dist/lib/http/HttpServer.js +52 -0
- package/dist/lib/http/middleware/validate.js +7 -3
- package/dist/lib/http/routes/audit.d.ts +8 -0
- package/dist/lib/http/routes/audit.js +51 -0
- package/dist/lib/http/routes/guardReport.d.ts +10 -0
- package/dist/lib/http/routes/guardReport.js +143 -0
- package/dist/lib/http/routes/knowledge.js +32 -1
- package/dist/lib/http/routes/panorama.d.ts +11 -0
- package/dist/lib/http/routes/panorama.js +322 -0
- package/dist/lib/http/routes/signals.d.ts +10 -0
- package/dist/lib/http/routes/signals.js +104 -0
- package/dist/lib/http/routes/task.d.ts +2 -3
- package/dist/lib/http/routes/task.js +17 -347
- package/dist/lib/http/routes/violations.js +1 -1
- package/dist/lib/infrastructure/audit/AuditLogger.d.ts +6 -1
- package/dist/lib/infrastructure/audit/AuditLogger.js +14 -1
- package/dist/lib/infrastructure/database/drizzle/schema.d.ts +202 -504
- package/dist/lib/infrastructure/database/drizzle/schema.js +38 -69
- package/dist/lib/infrastructure/database/migrations/004_evolution_proposals.d.ts +8 -0
- package/dist/lib/infrastructure/database/migrations/004_evolution_proposals.js +43 -0
- package/dist/lib/infrastructure/database/migrations/005_recipe_source_refs.d.ts +9 -0
- package/dist/lib/infrastructure/database/migrations/005_recipe_source_refs.js +24 -0
- package/dist/lib/infrastructure/logging/Logger.d.ts +2 -0
- package/dist/lib/infrastructure/logging/Logger.js +34 -7
- package/dist/lib/infrastructure/monitoring/ErrorTracker.js +3 -1
- package/dist/lib/infrastructure/monitoring/PerformanceMonitor.d.ts +2 -2
- package/dist/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -10
- package/dist/lib/infrastructure/notification/LarkNotifier.d.ts +24 -0
- package/dist/lib/infrastructure/notification/LarkNotifier.js +97 -0
- package/dist/lib/infrastructure/report/ReportStore.d.ts +45 -0
- package/dist/lib/infrastructure/report/ReportStore.js +133 -0
- package/dist/lib/infrastructure/signal/SignalAggregator.d.ts +18 -0
- package/dist/lib/infrastructure/signal/SignalAggregator.js +84 -0
- package/dist/lib/infrastructure/signal/SignalBridge.d.ts +13 -0
- package/dist/lib/infrastructure/signal/SignalBridge.js +20 -0
- package/dist/lib/infrastructure/signal/SignalBus.d.ts +63 -0
- package/dist/lib/infrastructure/signal/SignalBus.js +106 -0
- package/dist/lib/infrastructure/signal/SignalTraceWriter.d.ts +36 -0
- package/dist/lib/infrastructure/signal/SignalTraceWriter.js +130 -0
- package/dist/lib/infrastructure/vector/HnswVectorAdapter.js +18 -2
- package/dist/lib/injection/ServiceContainer.js +8 -0
- package/dist/lib/injection/ServiceMap.d.ts +16 -10
- package/dist/lib/injection/modules/AgentModule.d.ts +1 -1
- package/dist/lib/injection/modules/AgentModule.js +7 -1
- package/dist/lib/injection/modules/AppModule.d.ts +1 -1
- package/dist/lib/injection/modules/AppModule.js +4 -13
- package/dist/lib/injection/modules/GuardModule.js +27 -2
- package/dist/lib/injection/modules/InfraModule.d.ts +0 -1
- package/dist/lib/injection/modules/InfraModule.js +9 -7
- package/dist/lib/injection/modules/KnowledgeModule.d.ts +5 -0
- package/dist/lib/injection/modules/KnowledgeModule.js +131 -0
- package/dist/lib/injection/modules/PanoramaModule.d.ts +18 -0
- package/dist/lib/injection/modules/PanoramaModule.js +76 -0
- package/dist/lib/injection/modules/SignalModule.d.ts +10 -0
- package/dist/lib/injection/modules/SignalModule.js +84 -0
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.d.ts +1 -0
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.js +6 -0
- package/dist/lib/service/bootstrap/BootstrapTaskManager.d.ts +3 -1
- package/dist/lib/service/bootstrap/BootstrapTaskManager.js +20 -1
- package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +45 -0
- package/dist/lib/service/bootstrap/UiStartupTasks.js +101 -0
- package/dist/lib/service/delivery/AgentInstructionsGenerator.js +4 -5
- package/dist/lib/service/delivery/CursorDeliveryPipeline.d.ts +3 -1
- package/dist/lib/service/delivery/CursorDeliveryPipeline.js +13 -10
- package/dist/lib/service/delivery/RulesGenerator.js +3 -2
- package/dist/lib/service/evolution/ConsolidationAdvisor.d.ts +114 -0
- package/dist/lib/service/evolution/ConsolidationAdvisor.js +542 -0
- package/dist/lib/service/evolution/ContradictionDetector.d.ts +54 -0
- package/dist/lib/service/evolution/ContradictionDetector.js +253 -0
- package/dist/lib/service/evolution/DecayDetector.d.ts +71 -0
- package/dist/lib/service/evolution/DecayDetector.js +244 -0
- package/dist/lib/service/evolution/EnhancementSuggester.d.ts +38 -0
- package/dist/lib/service/evolution/EnhancementSuggester.js +220 -0
- package/dist/lib/service/evolution/KnowledgeMetabolism.d.ts +82 -0
- package/dist/lib/service/evolution/KnowledgeMetabolism.js +167 -0
- package/dist/lib/service/evolution/RedundancyAnalyzer.d.ts +53 -0
- package/dist/lib/service/evolution/RedundancyAnalyzer.js +210 -0
- package/dist/lib/service/evolution/StagingManager.d.ts +57 -0
- package/dist/lib/service/evolution/StagingManager.js +201 -0
- package/dist/lib/service/guard/ComplianceReporter.d.ts +42 -2
- package/dist/lib/service/guard/ComplianceReporter.js +43 -5
- package/dist/lib/service/guard/CoverageAnalyzer.d.ts +54 -0
- package/dist/lib/service/guard/CoverageAnalyzer.js +149 -0
- package/dist/lib/service/guard/GuardCheckEngine.d.ts +42 -0
- package/dist/lib/service/guard/GuardCheckEngine.js +465 -14
- package/dist/lib/service/guard/GuardFeedbackLoop.d.ts +3 -0
- package/dist/lib/service/guard/GuardFeedbackLoop.js +9 -0
- package/dist/lib/service/guard/ReverseGuard.d.ts +73 -0
- package/dist/lib/service/guard/ReverseGuard.js +256 -0
- package/dist/lib/service/guard/RuleLearner.d.ts +12 -0
- package/dist/lib/service/guard/RuleLearner.js +38 -0
- package/dist/lib/service/guard/UncertaintyCollector.d.ts +83 -0
- package/dist/lib/service/guard/UncertaintyCollector.js +149 -0
- package/dist/lib/service/guard/ViolationsStore.d.ts +1 -0
- package/dist/lib/service/guard/ViolationsStore.js +33 -3
- package/dist/lib/service/knowledge/ConfidenceRouter.d.ts +13 -0
- package/dist/lib/service/knowledge/ConfidenceRouter.js +14 -0
- package/dist/lib/service/knowledge/KnowledgeService.js +22 -4
- package/dist/lib/service/knowledge/SourceRefReconciler.d.ts +68 -0
- package/dist/lib/service/knowledge/SourceRefReconciler.js +309 -0
- package/dist/lib/service/panorama/CouplingAnalyzer.d.ts +27 -0
- package/dist/lib/service/panorama/CouplingAnalyzer.js +192 -0
- package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +28 -0
- package/dist/lib/service/panorama/DimensionAnalyzer.js +320 -0
- package/dist/lib/service/panorama/LayerInferrer.d.ts +19 -0
- package/dist/lib/service/panorama/LayerInferrer.js +182 -0
- package/dist/lib/service/panorama/ModuleDiscoverer.d.ts +24 -0
- package/dist/lib/service/panorama/ModuleDiscoverer.js +185 -0
- package/dist/lib/service/panorama/PanoramaAggregator.d.ts +29 -0
- package/dist/lib/service/panorama/PanoramaAggregator.js +228 -0
- package/dist/lib/service/panorama/PanoramaScanner.d.ts +52 -0
- package/dist/lib/service/panorama/PanoramaScanner.js +188 -0
- package/dist/lib/service/panorama/PanoramaService.d.ts +125 -0
- package/dist/lib/service/panorama/PanoramaService.js +363 -0
- package/dist/lib/service/panorama/PanoramaTypes.d.ts +134 -0
- package/dist/lib/service/panorama/PanoramaTypes.js +6 -0
- package/dist/lib/service/panorama/RoleRefiner.d.ts +48 -0
- package/dist/lib/service/panorama/RoleRefiner.js +535 -0
- package/dist/lib/service/search/BM25Scorer.d.ts +2 -2
- package/dist/lib/service/search/CoarseRanker.d.ts +7 -6
- package/dist/lib/service/search/CoarseRanker.js +11 -10
- package/dist/lib/service/search/FieldWeightedScorer.d.ts +81 -0
- package/dist/lib/service/search/FieldWeightedScorer.js +318 -0
- package/dist/lib/service/search/MultiSignalRanker.d.ts +3 -2
- package/dist/lib/service/search/MultiSignalRanker.js +17 -1
- package/dist/lib/service/search/SearchEngine.d.ts +9 -7
- package/dist/lib/service/search/SearchEngine.js +67 -10
- package/dist/lib/service/search/SearchTypes.d.ts +25 -3
- package/dist/lib/service/search/SearchTypes.js +6 -1
- package/dist/lib/service/signal/HitRecorder.d.ts +68 -0
- package/dist/lib/service/signal/HitRecorder.js +173 -0
- package/dist/lib/service/skills/SignalCollector.d.ts +3 -1
- package/dist/lib/service/skills/SignalCollector.js +31 -1
- package/dist/lib/service/task/IntentExtractor.d.ts +66 -0
- package/dist/lib/service/task/IntentExtractor.js +256 -0
- package/dist/lib/service/task/PrimeSearchPipeline.d.ts +54 -0
- package/dist/lib/service/task/PrimeSearchPipeline.js +113 -0
- package/dist/lib/service/vector/VectorService.d.ts +3 -0
- package/dist/lib/service/vector/VectorService.js +38 -4
- package/dist/lib/shared/schemas/mcp-tools.d.ts +41 -96
- package/dist/lib/shared/schemas/mcp-tools.js +59 -119
- package/dist/scripts/analyze-signals.d.ts +20 -0
- package/dist/scripts/analyze-signals.js +155 -0
- package/dist/scripts/diagnose-mcp.js +1 -1
- package/package.json +1 -1
- package/skills/autosnippet-create/SKILL.md +98 -89
- package/skills/autosnippet-devdocs/SKILL.md +55 -57
- package/templates/claude-code/hooks/autosnippet-session.sh +10 -15
- package/templates/cursor-hooks/hooks/session-start.sh +1 -1
- package/templates/guard-ci.yml +2 -2
- package/templates/instructions/agent-static.md +2 -1
- package/templates/instructions/conventions.md +5 -6
- package/templates/recipes-setup/README.md +1 -2
- package/templates/recipes-setup/_template.md +39 -39
- package/dashboard/dist/assets/icons-BofcEZ3f.js +0 -1
- package/dashboard/dist/assets/index-D0whuycy.css +0 -1
- package/dashboard/dist/assets/index-SiN1GChm.js +0 -128
- package/dist/lib/domain/task/Task.d.ts +0 -140
- package/dist/lib/domain/task/Task.js +0 -254
- package/dist/lib/domain/task/TaskDependency.d.ts +0 -23
- package/dist/lib/domain/task/TaskDependency.js +0 -34
- package/dist/lib/domain/task/TaskIdGenerator.d.ts +0 -40
- package/dist/lib/domain/task/TaskIdGenerator.js +0 -75
- package/dist/lib/domain/task/index.d.ts +0 -4
- package/dist/lib/domain/task/index.js +0 -4
- package/dist/lib/infrastructure/database/migrations/002_add_tasks.d.ts +0 -11
- package/dist/lib/infrastructure/database/migrations/002_add_tasks.js +0 -86
- package/dist/lib/repository/task/TaskRepository.impl.d.ts +0 -171
- package/dist/lib/repository/task/TaskRepository.impl.js +0 -347
- package/dist/lib/service/task/TaskGraphService.d.ts +0 -222
- package/dist/lib/service/task/TaskGraphService.js +0 -597
- package/dist/lib/service/task/TaskKnowledgeBridge.d.ts +0 -95
- package/dist/lib/service/task/TaskKnowledgeBridge.js +0 -298
- package/dist/lib/service/task/TaskReadyEngine.d.ts +0 -84
- package/dist/lib/service/task/TaskReadyEngine.js +0 -115
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConsolidationAdvisor — 提交前融合顾问
|
|
3
|
+
*
|
|
4
|
+
* 解决问题:Agent 逐条提交 Recipe 导致碎片化、低价值条目激增。
|
|
5
|
+
*
|
|
6
|
+
* 设计思路:在新知识提交前分析已有知识库,给出 4 种建议之一:
|
|
7
|
+
* create — 独立有价值,正常新建(走正常可信度判断)
|
|
8
|
+
* merge — 与 1 条 Recipe 相似,将候选内容合并到已有 Recipe,合并后 Recipe → staging
|
|
9
|
+
* reorganize — 与多条 Recipe 交叉重叠,将候选功能拆分到已有 Recipe 上,被修改的 Recipe → staging
|
|
10
|
+
* insufficient — 独立价值不足且已有足够 Recipe 覆盖,交给 Agent 与开发者决定
|
|
11
|
+
*
|
|
12
|
+
* 分析维度:
|
|
13
|
+
* 1. 结构相似度 — 复用 RedundancyAnalyzer 的 4 维算法
|
|
14
|
+
* 2. 语义域覆盖 — category + trigger 是否落在已有 Recipe 管辖范围
|
|
15
|
+
* 3. 独立价值 — 内容长度、具体性、是否有独立 coreCode
|
|
16
|
+
*/
|
|
17
|
+
var _a;
|
|
18
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
19
|
+
import { ContradictionDetector } from './ContradictionDetector.js';
|
|
20
|
+
/* ────────────────────── Constants ────────────────────── */
|
|
21
|
+
/** 低于此阈值的 Recipe 被视为内容不足 / 碎片化 */
|
|
22
|
+
const MIN_SUBSTANCE_SCORE = 0.3;
|
|
23
|
+
/** 结构相似度达到此阈值 → enhance 建议 */
|
|
24
|
+
const ENHANCE_THRESHOLD = 0.4;
|
|
25
|
+
/** 结构相似度达到此阈值 → 判定为高度重叠 */
|
|
26
|
+
const HIGH_OVERLAP_THRESHOLD = 0.65;
|
|
27
|
+
/** 最多分析多少条同域 Recipe(控制性能) */
|
|
28
|
+
const MAX_CANDIDATES_PER_ANALYSIS = 30;
|
|
29
|
+
const WEIGHTS = { title: 0.2, clause: 0.3, code: 0.3, guard: 0.2 };
|
|
30
|
+
/* ────────────────────── Class ────────────────────── */
|
|
31
|
+
export class ConsolidationAdvisor {
|
|
32
|
+
#db;
|
|
33
|
+
#logger = Logger.getInstance();
|
|
34
|
+
constructor(db) {
|
|
35
|
+
this.#db = db;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 分析候选知识与现有知识库的关系,返回融合建议。
|
|
39
|
+
*
|
|
40
|
+
* @param candidate - 待提交的候选数据
|
|
41
|
+
* @returns ConsolidationAdvice — 建议 + 理由 + 上下文
|
|
42
|
+
*/
|
|
43
|
+
analyze(candidate) {
|
|
44
|
+
// ── Step 1: 独立价值评估 ──
|
|
45
|
+
const substanceScore = this.#assessSubstance(candidate);
|
|
46
|
+
// ── Step 2: 加载同域 / 相关 Recipe ──
|
|
47
|
+
const related = this.#loadRelatedRecipes(candidate);
|
|
48
|
+
// ── Step 3: insufficient — 独立价值不足,交给 Agent 与开发者决定 ──
|
|
49
|
+
if (substanceScore < MIN_SUBSTANCE_SCORE) {
|
|
50
|
+
if (related.length > 0) {
|
|
51
|
+
const scored = related.map((r) => ({
|
|
52
|
+
recipe: r,
|
|
53
|
+
similarity: this.#computeSimilarity(candidate, r),
|
|
54
|
+
}));
|
|
55
|
+
scored.sort((a, b) => b.similarity - a.similarity);
|
|
56
|
+
return {
|
|
57
|
+
action: 'insufficient',
|
|
58
|
+
confidence: 0.85,
|
|
59
|
+
reason: this.#buildInsufficientReason(candidate, substanceScore, scored.slice(0, 3)),
|
|
60
|
+
coveredBy: scored.slice(0, 5).map((s) => ({
|
|
61
|
+
id: s.recipe.id,
|
|
62
|
+
title: s.recipe.title,
|
|
63
|
+
similarity: Math.round(s.similarity * 100) / 100,
|
|
64
|
+
})),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
action: 'insufficient',
|
|
69
|
+
confidence: 0.8,
|
|
70
|
+
reason: this.#buildInsufficientReason(candidate, substanceScore, []),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// ── Step 4: 无相关 Recipe → 正常新建 ──
|
|
74
|
+
if (related.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
action: 'create',
|
|
77
|
+
confidence: 0.95,
|
|
78
|
+
reason: `在 ${candidate.category || '全库'} 中未找到相关 Recipe,可安全新建。`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// ── Step 5: 结构相似度分析 ──
|
|
82
|
+
const scored = related.map((r) => ({
|
|
83
|
+
recipe: r,
|
|
84
|
+
similarity: this.#computeSimilarity(candidate, r),
|
|
85
|
+
}));
|
|
86
|
+
scored.sort((a, b) => b.similarity - a.similarity);
|
|
87
|
+
const top = scored[0];
|
|
88
|
+
const highOverlaps = scored.filter((s) => s.similarity >= HIGH_OVERLAP_THRESHOLD);
|
|
89
|
+
const moderateOverlaps = scored.filter((s) => s.similarity >= ENHANCE_THRESHOLD && s.similarity < HIGH_OVERLAP_THRESHOLD);
|
|
90
|
+
// ── Step 6: 多条高度重叠 → reorganize(合并重新拆分,旧 Recipe 状态回退) ──
|
|
91
|
+
if (highOverlaps.length >= 2) {
|
|
92
|
+
return {
|
|
93
|
+
action: 'reorganize',
|
|
94
|
+
confidence: Math.min(0.9, top.similarity),
|
|
95
|
+
reason: `候选与 ${highOverlaps.length} 条现有 Recipe 高度重叠(最高相似度 ${(top.similarity * 100).toFixed(0)}%),` +
|
|
96
|
+
`建议将候选功能拆分到这些已有 Recipe 上(保留已有 Recipe 的质量数据),被修改的 Recipe 状态转为 staging。` +
|
|
97
|
+
`修改后的 Recipe 走正常可信度判断。`,
|
|
98
|
+
reorganizeTargets: highOverlaps.map((s) => ({
|
|
99
|
+
id: s.recipe.id,
|
|
100
|
+
title: s.recipe.title,
|
|
101
|
+
similarity: Math.round(s.similarity * 100) / 100,
|
|
102
|
+
})),
|
|
103
|
+
relatedRecipes: scored.slice(0, 5).map((s) => ({
|
|
104
|
+
id: s.recipe.id,
|
|
105
|
+
title: s.recipe.title,
|
|
106
|
+
similarity: Math.round(s.similarity * 100) / 100,
|
|
107
|
+
})),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// ── Step 7: 与 1 条高度重叠 → merge(融合为新 Recipe,旧 Recipe 状态回退) ──
|
|
111
|
+
if (highOverlaps.length === 1) {
|
|
112
|
+
const direction = this.#computeMergeDirection(candidate, top.recipe);
|
|
113
|
+
return {
|
|
114
|
+
action: 'merge',
|
|
115
|
+
confidence: top.similarity,
|
|
116
|
+
reason: `候选与「${top.recipe.title}」高度重叠(${(top.similarity * 100).toFixed(0)}%),` +
|
|
117
|
+
`建议将候选内容合并到该 Recipe(保留已有 Recipe 的质量数据),合并后 Recipe 状态转为 staging。` +
|
|
118
|
+
`${direction.summary}修改后走正常可信度判断。`,
|
|
119
|
+
targetRecipe: {
|
|
120
|
+
id: top.recipe.id,
|
|
121
|
+
title: top.recipe.title,
|
|
122
|
+
similarity: Math.round(top.similarity * 100) / 100,
|
|
123
|
+
},
|
|
124
|
+
mergeDirection: direction,
|
|
125
|
+
relatedRecipes: scored.slice(0, 5).map((s) => ({
|
|
126
|
+
id: s.recipe.id,
|
|
127
|
+
title: s.recipe.title,
|
|
128
|
+
similarity: Math.round(s.similarity * 100) / 100,
|
|
129
|
+
})),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// ── Step 8: 中度重叠 → 判断新建还是融合 ──
|
|
133
|
+
if (moderateOverlaps.length > 0) {
|
|
134
|
+
const direction = this.#computeMergeDirection(candidate, top.recipe);
|
|
135
|
+
if (direction.addedDimensions.length === 0) {
|
|
136
|
+
// 候选不提供任何新维度 → merge
|
|
137
|
+
return {
|
|
138
|
+
action: 'merge',
|
|
139
|
+
confidence: top.similarity,
|
|
140
|
+
reason: `候选与「${top.recipe.title}」有中度重叠(${(top.similarity * 100).toFixed(0)}%),` +
|
|
141
|
+
`且未提供新维度,建议将候选内容合并到该 Recipe(保留已有 Recipe 的质量数据),合并后 Recipe 状态转为 staging。` +
|
|
142
|
+
`修改后走正常可信度判断。`,
|
|
143
|
+
targetRecipe: {
|
|
144
|
+
id: top.recipe.id,
|
|
145
|
+
title: top.recipe.title,
|
|
146
|
+
similarity: Math.round(top.similarity * 100) / 100,
|
|
147
|
+
},
|
|
148
|
+
mergeDirection: direction,
|
|
149
|
+
relatedRecipes: scored.slice(0, 5).map((s) => ({
|
|
150
|
+
id: s.recipe.id,
|
|
151
|
+
title: s.recipe.title,
|
|
152
|
+
similarity: Math.round(s.similarity * 100) / 100,
|
|
153
|
+
})),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// 候选提供了新维度 → 可以新建,但附带上下文
|
|
157
|
+
return {
|
|
158
|
+
action: 'create',
|
|
159
|
+
confidence: 0.7,
|
|
160
|
+
reason: `候选与「${top.recipe.title}」有中度重叠(${(top.similarity * 100).toFixed(0)}%),` +
|
|
161
|
+
`但提供了新维度(${direction.addedDimensions.join('、')}),允许新建。` +
|
|
162
|
+
`请确保新 Recipe 职责边界清晰。`,
|
|
163
|
+
relatedRecipes: scored.slice(0, 5).map((s) => ({
|
|
164
|
+
id: s.recipe.id,
|
|
165
|
+
title: s.recipe.title,
|
|
166
|
+
similarity: Math.round(s.similarity * 100) / 100,
|
|
167
|
+
})),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// ── Step 9: 无显著重叠 → 正常新建 ──
|
|
171
|
+
return {
|
|
172
|
+
action: 'create',
|
|
173
|
+
confidence: 0.9,
|
|
174
|
+
reason: `候选与最相似 Recipe「${top.recipe.title}」相似度仅 ${(top.similarity * 100).toFixed(0)}%,可安全新建。`,
|
|
175
|
+
relatedRecipes: scored.slice(0, 3).map((s) => ({
|
|
176
|
+
id: s.recipe.id,
|
|
177
|
+
title: s.recipe.title,
|
|
178
|
+
similarity: Math.round(s.similarity * 100) / 100,
|
|
179
|
+
})),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 批量分析候选知识与现有知识库的关系。
|
|
184
|
+
*
|
|
185
|
+
* 除了对每个候选独立运行 analyze() 外,
|
|
186
|
+
* 还检测批次内部候选之间的重叠(防止批量提交碎片化)。
|
|
187
|
+
*
|
|
188
|
+
* @param candidates - 待提交的候选数组
|
|
189
|
+
* @returns BatchConsolidationResult — 每条分析 + 批次内重叠
|
|
190
|
+
*/
|
|
191
|
+
analyzeBatch(candidates) {
|
|
192
|
+
// 对每个候选独立分析(vs DB)
|
|
193
|
+
const items = candidates.map((c, index) => ({
|
|
194
|
+
index,
|
|
195
|
+
advice: this.analyze(c),
|
|
196
|
+
}));
|
|
197
|
+
// 检测批次内候选之间的相互重叠
|
|
198
|
+
const internalOverlaps = [];
|
|
199
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
200
|
+
for (let j = i + 1; j < candidates.length; j++) {
|
|
201
|
+
const sim = this.#computeCandidateSimilarity(candidates[i], candidates[j]);
|
|
202
|
+
if (sim >= ENHANCE_THRESHOLD) {
|
|
203
|
+
internalOverlaps.push({ indexA: i, indexB: j, similarity: Math.round(sim * 100) / 100 });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { items, internalOverlaps };
|
|
208
|
+
}
|
|
209
|
+
/* ════════════════════ 独立价值评估 ════════════════════ */
|
|
210
|
+
/**
|
|
211
|
+
* 评估候选是否具备独立成条的"实质性"(0-1)。
|
|
212
|
+
*
|
|
213
|
+
* 维度:
|
|
214
|
+
* 1. 内容充实度 (0.4) — doClause+dontClause 长度 + coreCode 存在
|
|
215
|
+
* 2. 具体性 (0.3) — 是否有具体的 trigger + whenClause(非通用)
|
|
216
|
+
* 3. 独立代码 (0.3) — coreCode 是否足够独立(非 snippet 级别)
|
|
217
|
+
*/
|
|
218
|
+
#assessSubstance(c) {
|
|
219
|
+
let contentScore = 0;
|
|
220
|
+
const doLen = (c.doClause || '').length;
|
|
221
|
+
const dontLen = (c.dontClause || '').length;
|
|
222
|
+
const clauseLen = doLen + dontLen;
|
|
223
|
+
// doClause + dontClause 内容长度评估
|
|
224
|
+
if (clauseLen >= 100) {
|
|
225
|
+
contentScore = 1.0;
|
|
226
|
+
}
|
|
227
|
+
else if (clauseLen >= 40) {
|
|
228
|
+
contentScore = 0.6;
|
|
229
|
+
}
|
|
230
|
+
else if (clauseLen > 0) {
|
|
231
|
+
contentScore = 0.3;
|
|
232
|
+
}
|
|
233
|
+
// 有 coreCode 加分
|
|
234
|
+
const codeLen = (c.coreCode || '').trim().length;
|
|
235
|
+
if (codeLen >= 50) {
|
|
236
|
+
contentScore = Math.min(1.0, contentScore + 0.2);
|
|
237
|
+
}
|
|
238
|
+
// 具体性: trigger + whenClause
|
|
239
|
+
let specificityScore = 0;
|
|
240
|
+
if (c.trigger?.startsWith('@') && c.trigger.length > 3) {
|
|
241
|
+
specificityScore += 0.5;
|
|
242
|
+
}
|
|
243
|
+
if (c.whenClause && c.whenClause.length >= 20) {
|
|
244
|
+
specificityScore += 0.5;
|
|
245
|
+
}
|
|
246
|
+
// 代码独立性
|
|
247
|
+
let codeScore = 0;
|
|
248
|
+
if (codeLen >= 100) {
|
|
249
|
+
codeScore = 1.0;
|
|
250
|
+
}
|
|
251
|
+
else if (codeLen >= 30) {
|
|
252
|
+
codeScore = 0.5;
|
|
253
|
+
}
|
|
254
|
+
else if (codeLen > 0) {
|
|
255
|
+
codeScore = 0.2;
|
|
256
|
+
}
|
|
257
|
+
const total = contentScore * 0.4 + specificityScore * 0.3 + codeScore * 0.3;
|
|
258
|
+
return Math.round(total * 100) / 100;
|
|
259
|
+
}
|
|
260
|
+
#buildInsufficientReason(c, score, topRelated) {
|
|
261
|
+
const issues = [];
|
|
262
|
+
if ((c.doClause || '').length < 40) {
|
|
263
|
+
issues.push('doClause 过短');
|
|
264
|
+
}
|
|
265
|
+
if ((c.dontClause || '').length < 20) {
|
|
266
|
+
issues.push('dontClause 过短');
|
|
267
|
+
}
|
|
268
|
+
if ((c.coreCode || '').trim().length < 30) {
|
|
269
|
+
issues.push('coreCode 不足');
|
|
270
|
+
}
|
|
271
|
+
if (!c.trigger || !c.trigger.startsWith('@')) {
|
|
272
|
+
issues.push('缺少有效 trigger');
|
|
273
|
+
}
|
|
274
|
+
if (!c.whenClause || c.whenClause.length < 20) {
|
|
275
|
+
issues.push('whenClause 过于笼统');
|
|
276
|
+
}
|
|
277
|
+
let msg = `候选实质性评分 ${(score * 100).toFixed(0)}% 不足(阈值 ${MIN_SUBSTANCE_SCORE * 100}%)。` +
|
|
278
|
+
`问题: ${issues.join('、')}。`;
|
|
279
|
+
if (topRelated.length > 0) {
|
|
280
|
+
const coverList = topRelated.map((r) => `「${r.recipe.title}」`).join('、');
|
|
281
|
+
msg +=
|
|
282
|
+
`该领域已有 Recipe 覆盖(${coverList}),` +
|
|
283
|
+
`建议与开发者讨论: 是补齐已有 Recipe 还是放弃此候选。`;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
msg += `建议补充更多具体细节后再提交,或将此内容合并到更广泛的 Recipe 中。`;
|
|
287
|
+
}
|
|
288
|
+
return msg;
|
|
289
|
+
}
|
|
290
|
+
/* ════════════════════ 相关 Recipe 加载 ════════════════════ */
|
|
291
|
+
#loadRelatedRecipes(candidate) {
|
|
292
|
+
try {
|
|
293
|
+
// 先按 category 精确匹配 + trigger 前缀匹配
|
|
294
|
+
const category = candidate.category || '';
|
|
295
|
+
const trigger = candidate.trigger || '';
|
|
296
|
+
const triggerPrefix = trigger.startsWith('@')
|
|
297
|
+
? trigger.slice(0, Math.max(3, trigger.indexOf('-', 1) > 0 ? trigger.indexOf('-', 1) : trigger.length))
|
|
298
|
+
: '';
|
|
299
|
+
let rows;
|
|
300
|
+
if (category) {
|
|
301
|
+
// 同 category 的 Recipe 是最有可能重叠的
|
|
302
|
+
rows = this.#db
|
|
303
|
+
.prepare(`SELECT id, title,
|
|
304
|
+
doClause,
|
|
305
|
+
dontClause,
|
|
306
|
+
json_extract(content, '$.coreCode') AS coreCode,
|
|
307
|
+
category, trigger, whenClause,
|
|
308
|
+
json_extract(content, '$.pattern') AS guardPattern
|
|
309
|
+
FROM knowledge_entries
|
|
310
|
+
WHERE lifecycle IN ('active', 'staging', 'evolving', 'pending')
|
|
311
|
+
AND category = ?
|
|
312
|
+
ORDER BY lifecycle DESC
|
|
313
|
+
LIMIT ?`)
|
|
314
|
+
.all(category, MAX_CANDIDATES_PER_ANALYSIS);
|
|
315
|
+
// 如果同 category 不够,再加载 trigger 相关的
|
|
316
|
+
if (rows.length < 5 && triggerPrefix.length >= 3) {
|
|
317
|
+
const extra = this.#db
|
|
318
|
+
.prepare(`SELECT id, title,
|
|
319
|
+
doClause,
|
|
320
|
+
dontClause,
|
|
321
|
+
json_extract(content, '$.coreCode') AS coreCode,
|
|
322
|
+
category, trigger, whenClause,
|
|
323
|
+
json_extract(content, '$.pattern') AS guardPattern
|
|
324
|
+
FROM knowledge_entries
|
|
325
|
+
WHERE lifecycle IN ('active', 'staging', 'evolving', 'pending')
|
|
326
|
+
AND category != ?
|
|
327
|
+
AND trigger LIKE ?
|
|
328
|
+
LIMIT ?`)
|
|
329
|
+
.all(category, `${triggerPrefix}%`, MAX_CANDIDATES_PER_ANALYSIS - rows.length);
|
|
330
|
+
const existingIds = new Set(rows.map((r) => r.id));
|
|
331
|
+
for (const e of extra) {
|
|
332
|
+
if (!existingIds.has(e.id)) {
|
|
333
|
+
rows.push(e);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
// 无 category 时按 title 关键词粗筛
|
|
340
|
+
rows = this.#db
|
|
341
|
+
.prepare(`SELECT id, title,
|
|
342
|
+
doClause,
|
|
343
|
+
dontClause,
|
|
344
|
+
json_extract(content, '$.coreCode') AS coreCode,
|
|
345
|
+
category, trigger, whenClause,
|
|
346
|
+
json_extract(content, '$.pattern') AS guardPattern
|
|
347
|
+
FROM knowledge_entries
|
|
348
|
+
WHERE lifecycle IN ('active', 'staging', 'evolving', 'pending')
|
|
349
|
+
LIMIT ?`)
|
|
350
|
+
.all(MAX_CANDIDATES_PER_ANALYSIS);
|
|
351
|
+
}
|
|
352
|
+
return rows.map((r) => ({
|
|
353
|
+
id: r.id,
|
|
354
|
+
title: r.title,
|
|
355
|
+
doClause: r.doClause ?? null,
|
|
356
|
+
dontClause: r.dontClause ?? null,
|
|
357
|
+
coreCode: r.coreCode ?? null,
|
|
358
|
+
category: r.category ?? null,
|
|
359
|
+
trigger: r.trigger ?? null,
|
|
360
|
+
whenClause: r.whenClause ?? null,
|
|
361
|
+
guardPattern: r.guardPattern ?? null,
|
|
362
|
+
}));
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
this.#logger.warn(`ConsolidationAdvisor: failed to load recipes: ${err instanceof Error ? err.message : String(err)}`);
|
|
366
|
+
return [];
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/* ════════════════════ 结构相似度计算 ════════════════════ */
|
|
370
|
+
/**
|
|
371
|
+
* 计算候选与某条 Recipe 的 4 维结构相似度。
|
|
372
|
+
* 复用 RedundancyAnalyzer 的权重配比。
|
|
373
|
+
*/
|
|
374
|
+
#computeSimilarity(candidate, recipe) {
|
|
375
|
+
const d1 = _a.#titleJaccard(candidate.title, recipe.title);
|
|
376
|
+
const d2 = _a.#clauseJaccard([candidate.doClause, candidate.dontClause], [recipe.doClause, recipe.dontClause]);
|
|
377
|
+
const d3 = _a.#codeSimilarity(candidate.coreCode ?? null, recipe.coreCode ?? null);
|
|
378
|
+
const candidatePattern = candidate.content?.pattern ?? null;
|
|
379
|
+
const d4 = candidatePattern && recipe.guardPattern && candidatePattern === recipe.guardPattern ? 1.0 : 0;
|
|
380
|
+
return WEIGHTS.title * d1 + WEIGHTS.clause * d2 + WEIGHTS.code * d3 + WEIGHTS.guard * d4;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* 计算两个候选之间的结构相似度(批次内重叠检测用)。
|
|
384
|
+
* 复用 title / clause / code 三维,跳过 guardPattern。
|
|
385
|
+
*/
|
|
386
|
+
#computeCandidateSimilarity(a, b) {
|
|
387
|
+
const d1 = _a.#titleJaccard(a.title, b.title);
|
|
388
|
+
const d2 = _a.#clauseJaccard([a.doClause, a.dontClause], [b.doClause, b.dontClause]);
|
|
389
|
+
const d3 = _a.#codeSimilarity(a.coreCode ?? null, b.coreCode ?? null);
|
|
390
|
+
// 批次内无 guardPattern,权重重分配: title 0.25 / clause 0.4 / code 0.35
|
|
391
|
+
return 0.25 * d1 + 0.4 * d2 + 0.35 * d3;
|
|
392
|
+
}
|
|
393
|
+
/* ════════════════════ 融合方向分析 ════════════════════ */
|
|
394
|
+
/**
|
|
395
|
+
* 分析候选能为已有 Recipe 补充哪些新「维度」。
|
|
396
|
+
* 如果候选不提供任何新维度 → 纯重复,应合并到已有 Recipe。
|
|
397
|
+
*/
|
|
398
|
+
#computeMergeDirection(candidate, target) {
|
|
399
|
+
const added = [];
|
|
400
|
+
// 1. 候选有 coreCode 但目标无(或很短)
|
|
401
|
+
const candidateCodeLen = (candidate.coreCode || '').trim().length;
|
|
402
|
+
const targetCodeLen = (target.coreCode || '').trim().length;
|
|
403
|
+
if (candidateCodeLen > 30 && targetCodeLen < 30) {
|
|
404
|
+
added.push('coreCode');
|
|
405
|
+
}
|
|
406
|
+
// 2. 候选有 dontClause 但目标无
|
|
407
|
+
if ((candidate.dontClause || '').length > 20 && !(target.dontClause || '').trim()) {
|
|
408
|
+
added.push('dontClause');
|
|
409
|
+
}
|
|
410
|
+
// 3. 候选有更具体的 whenClause
|
|
411
|
+
if ((candidate.whenClause || '').length > 30 && (target.whenClause || '').length < 15) {
|
|
412
|
+
added.push('whenClause');
|
|
413
|
+
}
|
|
414
|
+
// 4. 候选的 doClause 提供了 target 未涵盖的关键词
|
|
415
|
+
const candidateKeywords = _a.#extractKeyTerms(candidate.doClause || '');
|
|
416
|
+
const targetKeywords = _a.#extractKeyTerms([target.doClause, target.dontClause].filter(Boolean).join(' '));
|
|
417
|
+
const newTerms = [...candidateKeywords].filter((t) => !targetKeywords.has(t));
|
|
418
|
+
if (newTerms.length >= 3) {
|
|
419
|
+
added.push(`新关键词(${newTerms.slice(0, 3).join(',')})`);
|
|
420
|
+
}
|
|
421
|
+
let summary;
|
|
422
|
+
if (added.length > 0) {
|
|
423
|
+
summary = `候选可为已有 Recipe 补充: ${added.join('、')}。`;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
summary = `候选未提供已有 Recipe 缺失的维度,合并后内容以已有 Recipe 为主。`;
|
|
427
|
+
}
|
|
428
|
+
return { addedDimensions: added, summary };
|
|
429
|
+
}
|
|
430
|
+
/* ════════════════════ 静态工具方法 ════════════════════ */
|
|
431
|
+
static #titleJaccard(titleA, titleB) {
|
|
432
|
+
const wordsA = ContradictionDetector.extractTopicWords(titleA);
|
|
433
|
+
const wordsB = ContradictionDetector.extractTopicWords(titleB);
|
|
434
|
+
if (wordsA.size === 0 && wordsB.size === 0) {
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
let intersection = 0;
|
|
438
|
+
for (const w of wordsA) {
|
|
439
|
+
if (wordsB.has(w)) {
|
|
440
|
+
intersection++;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const union = wordsA.size + wordsB.size - intersection;
|
|
444
|
+
return union === 0 ? 0 : intersection / union;
|
|
445
|
+
}
|
|
446
|
+
static #clauseJaccard(clausesA, clausesB) {
|
|
447
|
+
const textA = clausesA.filter(Boolean).join(' ');
|
|
448
|
+
const textB = clausesB.filter(Boolean).join(' ');
|
|
449
|
+
if (!textA || !textB) {
|
|
450
|
+
return 0;
|
|
451
|
+
}
|
|
452
|
+
const wordsA = ContradictionDetector.extractTopicWords(textA);
|
|
453
|
+
const wordsB = ContradictionDetector.extractTopicWords(textB);
|
|
454
|
+
if (wordsA.size === 0 && wordsB.size === 0) {
|
|
455
|
+
return 0;
|
|
456
|
+
}
|
|
457
|
+
let intersection = 0;
|
|
458
|
+
for (const w of wordsA) {
|
|
459
|
+
if (wordsB.has(w)) {
|
|
460
|
+
intersection++;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const union = wordsA.size + wordsB.size - intersection;
|
|
464
|
+
return union === 0 ? 0 : intersection / union;
|
|
465
|
+
}
|
|
466
|
+
static #codeSimilarity(codeA, codeB) {
|
|
467
|
+
if (!codeA || !codeB) {
|
|
468
|
+
return 0;
|
|
469
|
+
}
|
|
470
|
+
const a = codeA.replace(/\s+/g, '');
|
|
471
|
+
const b = codeB.replace(/\s+/g, '');
|
|
472
|
+
if (a.length === 0 || b.length === 0) {
|
|
473
|
+
return 0;
|
|
474
|
+
}
|
|
475
|
+
// n-gram Jaccard (n=3) — 适合中等长度代码比较
|
|
476
|
+
return _a.#ngramJaccard(a, b, 3);
|
|
477
|
+
}
|
|
478
|
+
static #ngramJaccard(a, b, n) {
|
|
479
|
+
const gramsA = new Set();
|
|
480
|
+
const gramsB = new Set();
|
|
481
|
+
for (let i = 0; i <= a.length - n; i++) {
|
|
482
|
+
gramsA.add(a.slice(i, i + n));
|
|
483
|
+
}
|
|
484
|
+
for (let i = 0; i <= b.length - n; i++) {
|
|
485
|
+
gramsB.add(b.slice(i, i + n));
|
|
486
|
+
}
|
|
487
|
+
if (gramsA.size === 0 && gramsB.size === 0) {
|
|
488
|
+
return 0;
|
|
489
|
+
}
|
|
490
|
+
let intersection = 0;
|
|
491
|
+
for (const g of gramsA) {
|
|
492
|
+
if (gramsB.has(g)) {
|
|
493
|
+
intersection++;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const union = gramsA.size + gramsB.size - intersection;
|
|
497
|
+
return union === 0 ? 0 : intersection / union;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* 从文本中提取关键术语(过滤掉小词和常见停用词)
|
|
501
|
+
*/
|
|
502
|
+
static #extractKeyTerms(text) {
|
|
503
|
+
const words = ContradictionDetector.extractTopicWords(text);
|
|
504
|
+
const STOP = new Set([
|
|
505
|
+
'use',
|
|
506
|
+
'using',
|
|
507
|
+
'used',
|
|
508
|
+
'make',
|
|
509
|
+
'code',
|
|
510
|
+
'file',
|
|
511
|
+
'class',
|
|
512
|
+
'method',
|
|
513
|
+
'function',
|
|
514
|
+
'should',
|
|
515
|
+
'must',
|
|
516
|
+
'will',
|
|
517
|
+
'can',
|
|
518
|
+
'need',
|
|
519
|
+
'when',
|
|
520
|
+
'for',
|
|
521
|
+
'with',
|
|
522
|
+
'from',
|
|
523
|
+
'使用',
|
|
524
|
+
'需要',
|
|
525
|
+
'可以',
|
|
526
|
+
'应该',
|
|
527
|
+
'不要',
|
|
528
|
+
'必须',
|
|
529
|
+
'进行',
|
|
530
|
+
'方法',
|
|
531
|
+
'函数',
|
|
532
|
+
]);
|
|
533
|
+
const result = new Set();
|
|
534
|
+
for (const w of words) {
|
|
535
|
+
if (!STOP.has(w) && w.length >= 3) {
|
|
536
|
+
result.add(w);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return result;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
_a = ConsolidationAdvisor;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContradictionDetector — Recipe 级矛盾检测
|
|
3
|
+
*
|
|
4
|
+
* 从 MemoryConsolidator 提升:Memory 层只做 session 内去重,
|
|
5
|
+
* Recipe 层做跨 lifecycle 的持久化矛盾检测。
|
|
6
|
+
*
|
|
7
|
+
* 检测维度:
|
|
8
|
+
* 1. 否定模式检测(中/英双语 negation patterns)
|
|
9
|
+
* 2. 主题词重叠 ≥ 30% Jaccard
|
|
10
|
+
* 3. doClause vs dontClause 交叉引用
|
|
11
|
+
* 4. guard regex 互斥检测
|
|
12
|
+
*
|
|
13
|
+
* 结果:硬矛盾 (confidence ≥ 0.8) / 软矛盾 (0.4-0.8)
|
|
14
|
+
*/
|
|
15
|
+
import type { SignalBus } from '../../infrastructure/signal/SignalBus.js';
|
|
16
|
+
interface DatabaseLike {
|
|
17
|
+
prepare(sql: string): {
|
|
18
|
+
all(...params: unknown[]): Record<string, unknown>[];
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface ContradictionResult {
|
|
22
|
+
recipeA: string;
|
|
23
|
+
recipeB: string;
|
|
24
|
+
confidence: number;
|
|
25
|
+
type: 'hard' | 'soft';
|
|
26
|
+
evidence: string[];
|
|
27
|
+
}
|
|
28
|
+
interface RecipeEntry {
|
|
29
|
+
id: string;
|
|
30
|
+
title: string;
|
|
31
|
+
lifecycle: string;
|
|
32
|
+
doClause: string | null;
|
|
33
|
+
dontClause: string | null;
|
|
34
|
+
guardPattern: string | null;
|
|
35
|
+
description: string | null;
|
|
36
|
+
content_markdown: string | null;
|
|
37
|
+
}
|
|
38
|
+
export declare class ContradictionDetector {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(db: DatabaseLike, options?: {
|
|
41
|
+
signalBus?: SignalBus;
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* 检测所有 active/staging/evolving Recipe 之间的矛盾
|
|
45
|
+
*/
|
|
46
|
+
detectAll(): ContradictionResult[];
|
|
47
|
+
/**
|
|
48
|
+
* 检测两条 Recipe 是否矛盾
|
|
49
|
+
*/
|
|
50
|
+
detectPair(a: RecipeEntry, b: RecipeEntry): ContradictionResult | null;
|
|
51
|
+
/** 提取主题词(公开为静态方法,供 RedundancyAnalyzer 复用) */
|
|
52
|
+
static extractTopicWords(text: string): Set<string>;
|
|
53
|
+
}
|
|
54
|
+
export {};
|