autosnippet 3.3.4 → 3.3.6
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 +174 -83
- package/config/constitution.yaml +2 -0
- package/dashboard/dist/assets/icons-D1aVZYFW.js +1 -0
- package/dashboard/dist/assets/index-CxHOu8Hd.css +1 -0
- package/dashboard/dist/assets/index-DDdAOpYT.js +128 -0
- package/dashboard/dist/index.html +3 -3
- package/dist/bin/api-server.js +1 -0
- package/dist/bin/cli.d.ts +1 -0
- package/dist/bin/cli.js +136 -9
- package/dist/lib/agent/AgentFactory.d.ts +0 -17
- package/dist/lib/agent/AgentFactory.js +1 -25
- package/dist/lib/agent/capabilities.d.ts +11 -0
- package/dist/lib/agent/capabilities.js +29 -5
- package/dist/lib/agent/context/ExplorationTracker.js +10 -1
- package/dist/lib/agent/context/exploration/ExplorationStrategies.d.ts +2 -0
- package/dist/lib/agent/context/exploration/ExplorationStrategies.js +2 -2
- package/dist/lib/agent/domain/insight-analyst.d.ts +47 -3
- package/dist/lib/agent/domain/insight-analyst.js +111 -11
- package/dist/lib/agent/domain/insight-evolver.d.ts +69 -0
- package/dist/lib/agent/domain/insight-evolver.js +230 -0
- package/dist/lib/agent/domain/insight-gate.d.ts +42 -0
- package/dist/lib/agent/domain/insight-gate.js +41 -0
- package/dist/lib/agent/domain/insight-producer.d.ts +27 -2
- package/dist/lib/agent/domain/insight-producer.js +60 -5
- package/dist/lib/agent/domain/scan-prompts.js +10 -7
- package/dist/lib/agent/memory/ActiveContext.d.ts +2 -28
- package/dist/lib/agent/memory/MemoryCoordinator.d.ts +2 -2
- package/dist/lib/agent/memory/SessionStore.d.ts +6 -12
- package/dist/lib/agent/memory/SessionStore.js +9 -15
- package/dist/lib/agent/memory/memory-flush-contract.d.ts +49 -0
- package/dist/lib/agent/memory/memory-flush-contract.js +16 -0
- package/dist/lib/agent/memory/session-store-schema.d.ts +20 -0
- package/dist/lib/agent/memory/session-store-schema.js +41 -0
- package/dist/lib/agent/presets.d.ts +89 -1
- package/dist/lib/agent/presets.js +53 -5
- package/dist/lib/agent/tools/_shared.d.ts +7 -15
- package/dist/lib/agent/tools/_shared.js +20 -21
- package/dist/lib/agent/tools/composite.d.ts +25 -22
- package/dist/lib/agent/tools/composite.js +108 -109
- package/dist/lib/agent/tools/evolution-tools.d.ts +145 -0
- package/dist/lib/agent/tools/evolution-tools.js +161 -0
- package/dist/lib/agent/tools/index.d.ts +163 -92
- package/dist/lib/agent/tools/index.js +9 -1
- package/dist/lib/agent/tools/lifecycle.d.ts +7 -1
- package/dist/lib/agent/tools/lifecycle.js +59 -75
- package/dist/lib/cli/AiScanService.js +1 -1
- package/dist/lib/cli/KnowledgeSyncService.d.ts +5 -1
- package/dist/lib/cli/KnowledgeSyncService.js +6 -3
- package/dist/lib/core/AstAnalyzer.d.ts +1 -0
- package/dist/lib/{service/bootstrap/DimensionCopyRegistry.d.ts → domain/dimension/DimensionCopy.d.ts} +2 -2
- package/dist/lib/{service/bootstrap/DimensionCopyRegistry.js → domain/dimension/DimensionCopy.js} +22 -72
- package/dist/lib/domain/dimension/DimensionRegistry.d.ts +54 -0
- package/dist/lib/domain/dimension/DimensionRegistry.js +620 -0
- package/dist/lib/domain/dimension/DimensionSop.d.ts +55 -0
- package/dist/lib/domain/dimension/DimensionSop.js +1604 -0
- package/dist/lib/domain/dimension/UnifiedDimension.d.ts +61 -0
- package/dist/lib/domain/dimension/UnifiedDimension.js +53 -0
- package/dist/lib/domain/dimension/index.d.ts +10 -0
- package/dist/lib/domain/dimension/index.js +9 -0
- package/dist/lib/domain/knowledge/FieldSpec.d.ts +1 -1
- package/dist/lib/domain/knowledge/FieldSpec.js +29 -16
- package/dist/lib/domain/knowledge/KnowledgeEntry.d.ts +33 -111
- package/dist/lib/domain/knowledge/KnowledgeEntry.js +27 -6
- package/dist/lib/domain/knowledge/KnowledgeRepository.d.ts +1 -0
- package/dist/lib/domain/knowledge/KnowledgeRepository.js +3 -0
- package/dist/lib/domain/knowledge/Lifecycle.js +1 -1
- package/dist/lib/domain/knowledge/StyleGuide.d.ts +1 -1
- package/dist/lib/domain/knowledge/StyleGuide.js +1 -1
- package/dist/lib/domain/knowledge/UnifiedValidator.js +15 -0
- package/dist/lib/domain/knowledge/values/Stats.d.ts +1 -1
- package/dist/lib/domain/knowledge/values/Stats.js +2 -2
- package/dist/lib/external/mcp/McpServer.js +4 -0
- package/dist/lib/external/mcp/handlers/TargetClassifier.d.ts +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap/BootstrapSession.d.ts +8 -16
- package/dist/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +10 -10
- package/dist/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.d.ts +7 -0
- package/dist/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +20 -0
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.d.ts +52 -132
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +204 -17
- package/dist/lib/external/mcp/handlers/bootstrap/base-dimensions.d.ts +11 -75
- package/dist/lib/external/mcp/handlers/bootstrap/base-dimensions.js +40 -191
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.d.ts +13 -78
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +30 -52
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.d.ts +0 -1
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.d.ts +99 -12
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +172 -161
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +7 -17
- package/dist/lib/external/mcp/handlers/bootstrap/shared/async-fill-helpers.d.ts +46 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/async-fill-helpers.js +58 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/audit-helpers.d.ts +25 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/audit-helpers.js +47 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +50 -12
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +30 -10
- package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-text.js +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap/shared/handler-types.d.ts +24 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/handler-types.js +14 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/panorama-utils.d.ts +14 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/panorama-utils.js +48 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/session-helpers.d.ts +21 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/session-helpers.js +45 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/skill-generator.d.ts +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap/shared/target-file-map.d.ts +27 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/target-file-map.js +44 -0
- package/dist/lib/external/mcp/handlers/bootstrap-external.d.ts +14 -10
- package/dist/lib/external/mcp/handlers/bootstrap-external.js +39 -51
- package/dist/lib/external/mcp/handlers/bootstrap-internal.d.ts +2 -0
- package/dist/lib/external/mcp/handlers/bootstrap-internal.js +115 -82
- package/dist/lib/external/mcp/handlers/consolidated.d.ts +4 -4
- package/dist/lib/external/mcp/handlers/consolidated.js +115 -162
- package/dist/lib/external/mcp/handlers/dimension-complete-external.js +69 -1
- package/dist/lib/external/mcp/handlers/evolve-external.d.ts +54 -0
- package/dist/lib/external/mcp/handlers/evolve-external.js +226 -0
- package/dist/lib/external/mcp/handlers/knowledge.js +26 -2
- package/dist/lib/external/mcp/handlers/rescan-external.d.ts +76 -0
- package/dist/lib/external/mcp/handlers/rescan-external.js +335 -0
- package/dist/lib/external/mcp/handlers/rescan-internal.d.ts +120 -0
- package/dist/lib/external/mcp/handlers/rescan-internal.js +359 -0
- package/dist/lib/external/mcp/handlers/search.d.ts +6 -5
- package/dist/lib/external/mcp/handlers/search.js +6 -5
- package/dist/lib/external/mcp/handlers/types.d.ts +2 -1
- package/dist/lib/external/mcp/handlers/wiki-external.js +2 -2
- package/dist/lib/external/mcp/tools.d.ts +8 -18
- package/dist/lib/external/mcp/tools.js +60 -3
- package/dist/lib/http/routes/knowledge.js +122 -1
- package/dist/lib/http/routes/modules.js +25 -3
- package/dist/lib/http/routes/panorama.js +16 -4
- package/dist/lib/infrastructure/cache/CacheCoordinator.d.ts +41 -0
- package/dist/lib/infrastructure/cache/CacheCoordinator.js +105 -0
- package/dist/lib/infrastructure/database/migrations/006_lifecycle_transition_events.d.ts +7 -0
- package/dist/lib/infrastructure/database/migrations/006_lifecycle_transition_events.js +28 -0
- package/dist/lib/infrastructure/vector/HnswVectorAdapter.js +1 -1
- package/dist/lib/injection/ServiceContainer.js +55 -0
- package/dist/lib/injection/ServiceMap.d.ts +8 -1
- package/dist/lib/injection/modules/InfraModule.js +4 -1
- package/dist/lib/injection/modules/KnowledgeModule.js +38 -1
- package/dist/lib/repository/evolution/ProposalRepository.d.ts +99 -0
- package/dist/lib/repository/evolution/ProposalRepository.js +255 -0
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.d.ts +4 -0
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.js +16 -1
- package/dist/lib/service/bootstrap/BootstrapEventEmitter.d.ts +3 -2
- package/dist/lib/service/bootstrap/BootstrapEventEmitter.js +1 -1
- package/dist/lib/service/bootstrap/DeliveryVerifier.d.ts +51 -0
- package/dist/lib/service/bootstrap/DeliveryVerifier.js +163 -0
- package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +22 -4
- package/dist/lib/service/bootstrap/UiStartupTasks.js +73 -5
- package/dist/lib/service/bootstrap/bootstrap-event-types.d.ts +54 -0
- package/dist/lib/service/bootstrap/bootstrap-event-types.js +10 -0
- package/dist/lib/service/cleanup/CleanupService.d.ts +85 -0
- package/dist/lib/service/cleanup/CleanupService.js +324 -0
- package/dist/lib/service/delivery/AgentInstructionsGenerator.js +39 -43
- package/dist/lib/service/delivery/FileProtection.d.ts +20 -0
- package/dist/lib/service/delivery/FileProtection.js +54 -0
- package/dist/lib/service/delivery/SkillsSyncer.js +16 -21
- package/dist/lib/service/evolution/ContentPatcher.d.ts +44 -0
- package/dist/lib/service/evolution/ContentPatcher.js +310 -0
- package/dist/lib/service/evolution/DecayDetector.d.ts +4 -3
- package/dist/lib/service/evolution/DecayDetector.js +97 -22
- package/dist/lib/service/evolution/KnowledgeMetabolism.d.ts +4 -2
- package/dist/lib/service/evolution/KnowledgeMetabolism.js +29 -2
- package/dist/lib/service/evolution/ProposalExecutor.d.ts +66 -0
- package/dist/lib/service/evolution/ProposalExecutor.js +424 -0
- package/dist/lib/service/evolution/RecipeLifecycleSupervisor.d.ts +64 -0
- package/dist/lib/service/evolution/RecipeLifecycleSupervisor.js +458 -0
- package/dist/lib/service/evolution/RecipeRelevanceAuditor.d.ts +89 -0
- package/dist/lib/service/evolution/RecipeRelevanceAuditor.js +492 -0
- package/dist/lib/service/evolution/StagingManager.js +5 -3
- package/dist/lib/service/evolution/createSupersedeProposal.d.ts +44 -0
- package/dist/lib/service/evolution/createSupersedeProposal.js +81 -0
- package/dist/lib/service/guard/ComplianceReporter.d.ts +4 -0
- package/dist/lib/service/guard/ComplianceReporter.js +51 -0
- package/dist/lib/service/guard/GuardCheckEngine.js +5 -4
- package/dist/lib/service/guard/GuardCrossFileChecks.js +2 -0
- package/dist/lib/service/guard/ReverseGuard.d.ts +1 -1
- package/dist/lib/service/guard/ReverseGuard.js +32 -2
- package/dist/lib/service/knowledge/ConfidenceRouter.js +1 -1
- package/dist/lib/service/knowledge/KnowledgeService.d.ts +11 -1
- package/dist/lib/service/knowledge/KnowledgeService.js +44 -4
- package/dist/lib/service/knowledge/RecipeProductionGateway.d.ts +225 -0
- package/dist/lib/service/knowledge/RecipeProductionGateway.js +384 -0
- package/dist/lib/service/knowledge/SourceRefReconciler.d.ts +2 -0
- package/dist/lib/service/knowledge/SourceRefReconciler.js +48 -0
- package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +3 -2
- package/dist/lib/service/panorama/DimensionAnalyzer.js +15 -140
- package/dist/lib/service/search/BM25Scorer.d.ts +2 -2
- package/dist/lib/service/search/SearchEngine.d.ts +11 -10
- package/dist/lib/service/search/SearchEngine.js +38 -36
- package/dist/lib/service/search/SearchTypes.d.ts +14 -8
- package/dist/lib/service/search/SearchTypes.js +1 -1
- package/dist/lib/service/search/tokenizer.d.ts +1 -1
- package/dist/lib/service/search/tokenizer.js +2 -2
- package/dist/lib/shared/schemas/common.d.ts +4 -4
- package/dist/lib/shared/schemas/http-requests.d.ts +12 -1
- package/dist/lib/shared/schemas/http-requests.js +8 -0
- package/dist/lib/shared/schemas/mcp-tools.d.ts +33 -2
- package/dist/lib/shared/schemas/mcp-tools.js +42 -0
- package/dist/lib/types/evolution.d.ts +135 -0
- package/dist/lib/types/evolution.js +6 -0
- package/dist/lib/types/graph-shared.d.ts +25 -0
- package/dist/lib/types/graph-shared.js +7 -0
- package/dist/lib/types/knowledge-wire.d.ts +131 -0
- package/dist/lib/types/knowledge-wire.js +7 -0
- package/dist/lib/types/project-snapshot-builder.d.ts +19 -0
- package/dist/lib/types/project-snapshot-builder.js +189 -0
- package/dist/lib/types/project-snapshot.d.ts +399 -0
- package/dist/lib/types/project-snapshot.js +17 -0
- package/dist/lib/types/search-wire.d.ts +46 -0
- package/dist/lib/types/search-wire.js +7 -0
- package/dist/lib/types/snapshot-views.d.ts +58 -0
- package/dist/lib/types/snapshot-views.js +103 -0
- package/package.json +1 -1
- package/skills/autosnippet-recipes/SKILL.md +1 -1
- package/templates/instructions/agent-static.md +2 -0
- package/templates/instructions/conventions.md +3 -1
- package/templates/recipes-setup/README.md +2 -2
- package/dashboard/dist/assets/icons-BJ2mUBi8.js +0 -1
- package/dashboard/dist/assets/index-B659K9t5.js +0 -128
- package/dashboard/dist/assets/index-NCm40PMD.css +0 -1
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.d.ts +0 -169
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +0 -727
- package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.d.ts +0 -370
- package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +0 -821
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecipeRelevanceAuditor — 基于代码证据验证 Recipe 当前相关性
|
|
3
|
+
*
|
|
4
|
+
* Rescan 时主动触发,检查每个保留 Recipe 的代码证据是否仍然存在:
|
|
5
|
+
* 1. 触发模式匹配 (trigger 引用的文件类型/路径模式是否有匹配)
|
|
6
|
+
* 2. 代码符号存活 (content.pattern 引用的类名/函数名是否在 AST 中)
|
|
7
|
+
* 3. 依赖关系完整 (涉及模块依赖是否在依赖图中)
|
|
8
|
+
* 4. 源代码文件存活 (reasoning.sources + content.codeChanges 的文件是否存在)
|
|
9
|
+
*
|
|
10
|
+
* 评分后驱动快速衰退:
|
|
11
|
+
* - healthy (80-100): 无操作
|
|
12
|
+
* - watch (60-79): 报告警告
|
|
13
|
+
* - decay (40-59): active → decaying (7d grace)
|
|
14
|
+
* - severe (20-39): active → decaying (3d grace)
|
|
15
|
+
* - dead (0-19): active → deprecated (immediate)
|
|
16
|
+
*
|
|
17
|
+
* 支持 Category 权重豁免:架构/规范类 Recipe 侧重触发模式而非具体符号。
|
|
18
|
+
*
|
|
19
|
+
* @module service/evolution/RecipeRelevanceAuditor
|
|
20
|
+
*/
|
|
21
|
+
// ── 常量 ────────────────────────────────────────────────────
|
|
22
|
+
/** 默认证据权重
|
|
23
|
+
*
|
|
24
|
+
* 注意:不包含 sourceFileExists。DB 中 sourceFile 存储的是 Recipe md 文件路径
|
|
25
|
+
* (如 AutoSnippet/candidates/xxx.md),不是源代码路径。
|
|
26
|
+
* 真正的源代码来源在 reasoning.sources 中,由 codeFilesExist 维度检查。
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_WEIGHTS = {
|
|
29
|
+
triggerStillMatches: 0.2,
|
|
30
|
+
symbolsAlive: 0.3,
|
|
31
|
+
depsIntact: 0.15,
|
|
32
|
+
codeFilesExist: 0.35,
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* 按 category 覆盖权重 — 架构/规范类侧重触发模式和来源文件
|
|
36
|
+
*/
|
|
37
|
+
const CATEGORY_WEIGHT_OVERRIDES = {
|
|
38
|
+
architecture: {
|
|
39
|
+
symbolsAlive: 0.05,
|
|
40
|
+
depsIntact: 0.05,
|
|
41
|
+
triggerStillMatches: 0.45,
|
|
42
|
+
codeFilesExist: 0.45,
|
|
43
|
+
},
|
|
44
|
+
'coding-standards': {
|
|
45
|
+
symbolsAlive: 0.05,
|
|
46
|
+
depsIntact: 0.05,
|
|
47
|
+
triggerStillMatches: 0.45,
|
|
48
|
+
codeFilesExist: 0.45,
|
|
49
|
+
},
|
|
50
|
+
'agent-guidelines': {
|
|
51
|
+
symbolsAlive: 0.0,
|
|
52
|
+
depsIntact: 0.0,
|
|
53
|
+
triggerStillMatches: 0.5,
|
|
54
|
+
codeFilesExist: 0.5,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
/** Grace Period 常量 */
|
|
58
|
+
const GRACE_PERIOD_DECAY = 7 * 24 * 60 * 60 * 1000; // 7d
|
|
59
|
+
const GRACE_PERIOD_SEVERE = 3 * 24 * 60 * 60 * 1000; // 3d
|
|
60
|
+
// ── RecipeRelevanceAuditor ──────────────────────────────────
|
|
61
|
+
export class RecipeRelevanceAuditor {
|
|
62
|
+
#db;
|
|
63
|
+
#logger;
|
|
64
|
+
constructor(opts) {
|
|
65
|
+
const dbRaw = opts.db;
|
|
66
|
+
this.#db = typeof dbRaw?.getDb === 'function' ? dbRaw.getDb() : opts.db;
|
|
67
|
+
this.#logger = opts.logger || { info() { }, warn() { } };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 审计所有保留 Recipe 的代码证据
|
|
71
|
+
*/
|
|
72
|
+
async audit(recipes, analysisData) {
|
|
73
|
+
const summary = {
|
|
74
|
+
totalAudited: 0,
|
|
75
|
+
healthy: 0,
|
|
76
|
+
watch: 0,
|
|
77
|
+
decay: 0,
|
|
78
|
+
severe: 0,
|
|
79
|
+
dead: 0,
|
|
80
|
+
results: [],
|
|
81
|
+
proposalsCreated: 0,
|
|
82
|
+
immediateDeprecated: 0,
|
|
83
|
+
};
|
|
84
|
+
// 预处理:构建快速查找集合
|
|
85
|
+
const fileSet = new Set(analysisData.fileList.map((f) => f.toLowerCase()));
|
|
86
|
+
const entityNames = new Set(analysisData.codeEntities.map((e) => e.name.toLowerCase()));
|
|
87
|
+
const depModules = new Set();
|
|
88
|
+
for (const edge of analysisData.dependencyGraph) {
|
|
89
|
+
depModules.add(edge.from.toLowerCase());
|
|
90
|
+
depModules.add(edge.to.toLowerCase());
|
|
91
|
+
}
|
|
92
|
+
for (const recipe of recipes) {
|
|
93
|
+
const fullRecipe = this.#loadFullRecipe(recipe.id);
|
|
94
|
+
if (!fullRecipe) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const result = this.#computeRelevanceScore(fullRecipe, {
|
|
98
|
+
fileSet,
|
|
99
|
+
entityNames,
|
|
100
|
+
depModules,
|
|
101
|
+
fileList: analysisData.fileList,
|
|
102
|
+
});
|
|
103
|
+
summary.totalAudited++;
|
|
104
|
+
summary[result.verdict]++;
|
|
105
|
+
summary.results.push(result);
|
|
106
|
+
// 执行衰退状态转换
|
|
107
|
+
if (result.verdict === 'dead' || result.verdict === 'severe' || result.verdict === 'decay') {
|
|
108
|
+
const executed = this.#executeDecay(result);
|
|
109
|
+
if (result.verdict === 'dead') {
|
|
110
|
+
summary.immediateDeprecated += executed ? 1 : 0;
|
|
111
|
+
}
|
|
112
|
+
if (executed) {
|
|
113
|
+
summary.proposalsCreated++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.#logger.info('[RecipeRelevanceAuditor] Audit complete', {
|
|
118
|
+
total: summary.totalAudited,
|
|
119
|
+
healthy: summary.healthy,
|
|
120
|
+
watch: summary.watch,
|
|
121
|
+
decay: summary.decay,
|
|
122
|
+
severe: summary.severe,
|
|
123
|
+
dead: summary.dead,
|
|
124
|
+
});
|
|
125
|
+
return summary;
|
|
126
|
+
}
|
|
127
|
+
// ─── 内部方法 ─────────────────────────────────────────
|
|
128
|
+
/** 从 DB 加载完整 Recipe 数据 */
|
|
129
|
+
#loadFullRecipe(id) {
|
|
130
|
+
try {
|
|
131
|
+
const row = this.#db
|
|
132
|
+
.prepare(`SELECT id, title, trigger, category,
|
|
133
|
+
content, doClause, coreCode
|
|
134
|
+
FROM knowledge_entries WHERE id = ?`)
|
|
135
|
+
.get(id);
|
|
136
|
+
return row || null;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** 计算单个 Recipe 的 relevanceScore */
|
|
143
|
+
#computeRelevanceScore(recipe, ctx) {
|
|
144
|
+
const category = recipe.category || '';
|
|
145
|
+
const weights = {
|
|
146
|
+
...DEFAULT_WEIGHTS,
|
|
147
|
+
...(CATEGORY_WEIGHT_OVERRIDES[category] || {}),
|
|
148
|
+
};
|
|
149
|
+
const decayReasons = [];
|
|
150
|
+
// 1. trigger 模式匹配
|
|
151
|
+
const triggerStillMatches = this.#checkTriggerMatch(recipe.trigger, ctx.fileList);
|
|
152
|
+
if (!triggerStillMatches) {
|
|
153
|
+
decayReasons.push(`触发条件 "${recipe.trigger}" 无匹配文件`);
|
|
154
|
+
}
|
|
155
|
+
// 2. 代码符号存活率
|
|
156
|
+
const referencedSymbols = this.#extractReferencedSymbols(recipe);
|
|
157
|
+
let symbolsAlive = 1.0;
|
|
158
|
+
if (referencedSymbols.length > 0) {
|
|
159
|
+
const aliveCount = referencedSymbols.filter((s) => ctx.entityNames.has(s.toLowerCase())).length;
|
|
160
|
+
symbolsAlive = aliveCount / referencedSymbols.length;
|
|
161
|
+
if (symbolsAlive < 0.5) {
|
|
162
|
+
decayReasons.push(`引用符号存活 ${aliveCount}/${referencedSymbols.length} (${(symbolsAlive * 100).toFixed(0)}%)`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// 3. 依赖关系完整性
|
|
166
|
+
const referencedModules = this.#extractModuleReferences(recipe);
|
|
167
|
+
let depsIntact = true;
|
|
168
|
+
if (referencedModules.length > 0) {
|
|
169
|
+
const intactCount = referencedModules.filter((m) => ctx.depModules.has(m.toLowerCase())).length;
|
|
170
|
+
depsIntact = intactCount >= referencedModules.length * 0.5;
|
|
171
|
+
if (!depsIntact) {
|
|
172
|
+
decayReasons.push(`模块依赖 ${intactCount}/${referencedModules.length} 存活`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 4. 源代码文件存活率(来自 reasoning.sources + content.codeChanges)
|
|
176
|
+
const codeFiles = this.#extractCodeFiles(recipe);
|
|
177
|
+
let codeFilesExist = 1.0;
|
|
178
|
+
if (codeFiles.length > 0) {
|
|
179
|
+
const existCount = codeFiles.filter((f) => ctx.fileSet.has(f.toLowerCase())).length;
|
|
180
|
+
codeFilesExist = existCount / codeFiles.length;
|
|
181
|
+
if (codeFilesExist < 0.5) {
|
|
182
|
+
decayReasons.push(`codeChanges 文件存活 ${existCount}/${codeFiles.length}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// 加权计算 relevanceScore
|
|
186
|
+
const relevanceScore = Math.round((triggerStillMatches ? 1 : 0) * weights.triggerStillMatches * 100 +
|
|
187
|
+
symbolsAlive * weights.symbolsAlive * 100 +
|
|
188
|
+
(depsIntact ? 1 : 0) * weights.depsIntact * 100 +
|
|
189
|
+
codeFilesExist * weights.codeFilesExist * 100);
|
|
190
|
+
// 分级判定
|
|
191
|
+
let verdict;
|
|
192
|
+
if (relevanceScore >= 80) {
|
|
193
|
+
verdict = 'healthy';
|
|
194
|
+
}
|
|
195
|
+
else if (relevanceScore >= 60) {
|
|
196
|
+
verdict = 'watch';
|
|
197
|
+
}
|
|
198
|
+
else if (relevanceScore >= 40) {
|
|
199
|
+
verdict = 'decay';
|
|
200
|
+
}
|
|
201
|
+
else if (relevanceScore >= 20) {
|
|
202
|
+
verdict = 'severe';
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
verdict = 'dead';
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
recipeId: recipe.id,
|
|
209
|
+
title: recipe.title,
|
|
210
|
+
relevanceScore,
|
|
211
|
+
verdict,
|
|
212
|
+
evidence: {
|
|
213
|
+
triggerStillMatches,
|
|
214
|
+
symbolsAlive,
|
|
215
|
+
depsIntact,
|
|
216
|
+
codeFilesExist,
|
|
217
|
+
},
|
|
218
|
+
decayReasons,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/** 检查 trigger 模式是否仍有匹配文件 */
|
|
222
|
+
#checkTriggerMatch(trigger, fileList) {
|
|
223
|
+
if (!trigger || trigger.trim() === '') {
|
|
224
|
+
return true; // 无 trigger 视为匹配
|
|
225
|
+
}
|
|
226
|
+
const triggerLower = trigger.toLowerCase();
|
|
227
|
+
// 检查 @trigger 格式 (如 @bilidili-api-response-model)
|
|
228
|
+
// 这些是自定义 trigger,不与文件路径匹配,视为始终有效
|
|
229
|
+
if (triggerLower.startsWith('@')) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
// 检查文件扩展名匹配 (如 "When creating .swift files")
|
|
233
|
+
const extMatch = triggerLower.match(/\.(swift|ts|tsx|js|jsx|py|java|kt|rb|go|rs|vue|svelte)\b/);
|
|
234
|
+
if (extMatch) {
|
|
235
|
+
const ext = extMatch[0];
|
|
236
|
+
return fileList.some((f) => f.toLowerCase().endsWith(ext));
|
|
237
|
+
}
|
|
238
|
+
// 检查路径模式匹配 (如 "When modifying Packages/")
|
|
239
|
+
const pathPatterns = trigger.match(/(?:[\w.-]+\/)+[\w.-]*/g) || [];
|
|
240
|
+
if (pathPatterns.length > 0) {
|
|
241
|
+
return pathPatterns.some((pattern) => {
|
|
242
|
+
const p = pattern.toLowerCase();
|
|
243
|
+
return fileList.some((f) => f.toLowerCase().includes(p));
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// 无法判断时视为匹配
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
/** 从 Recipe content 中提取引用的符号 */
|
|
250
|
+
#extractReferencedSymbols(recipe) {
|
|
251
|
+
const symbols = [];
|
|
252
|
+
// 从 content JSON 中提取
|
|
253
|
+
try {
|
|
254
|
+
const content = JSON.parse(recipe.content || '{}');
|
|
255
|
+
const pattern = content.pattern || '';
|
|
256
|
+
const markdown = content.markdown || '';
|
|
257
|
+
const text = `${pattern} ${markdown} ${recipe.doClause || ''} ${recipe.coreCode || ''}`;
|
|
258
|
+
// 匹配 PascalCase 标识符 (类名/协议名)
|
|
259
|
+
const identifiers = text.match(/\b[A-Z][a-zA-Z0-9]{2,}\b/g) || [];
|
|
260
|
+
// 去掉常见英文单词
|
|
261
|
+
const COMMON_WORDS = new Set([
|
|
262
|
+
'When',
|
|
263
|
+
'Then',
|
|
264
|
+
'The',
|
|
265
|
+
'This',
|
|
266
|
+
'That',
|
|
267
|
+
'Use',
|
|
268
|
+
'Not',
|
|
269
|
+
'All',
|
|
270
|
+
'For',
|
|
271
|
+
'With',
|
|
272
|
+
'From',
|
|
273
|
+
'Each',
|
|
274
|
+
'Must',
|
|
275
|
+
'May',
|
|
276
|
+
'Can',
|
|
277
|
+
'Will',
|
|
278
|
+
'Has',
|
|
279
|
+
'Are',
|
|
280
|
+
'New',
|
|
281
|
+
'Set',
|
|
282
|
+
'Get',
|
|
283
|
+
'Add',
|
|
284
|
+
'Run',
|
|
285
|
+
'End',
|
|
286
|
+
'Try',
|
|
287
|
+
'Nil',
|
|
288
|
+
'True',
|
|
289
|
+
'False',
|
|
290
|
+
'Void',
|
|
291
|
+
'Self',
|
|
292
|
+
'Type',
|
|
293
|
+
'Error',
|
|
294
|
+
'Result',
|
|
295
|
+
'String',
|
|
296
|
+
'Int',
|
|
297
|
+
'Bool',
|
|
298
|
+
'Array',
|
|
299
|
+
'Dict',
|
|
300
|
+
'Optional',
|
|
301
|
+
'Protocol',
|
|
302
|
+
'Class',
|
|
303
|
+
'Struct',
|
|
304
|
+
'Enum',
|
|
305
|
+
'Import',
|
|
306
|
+
'Return',
|
|
307
|
+
'Override',
|
|
308
|
+
'Private',
|
|
309
|
+
'Public',
|
|
310
|
+
'Internal',
|
|
311
|
+
'Func',
|
|
312
|
+
'Var',
|
|
313
|
+
'Let',
|
|
314
|
+
'Guard',
|
|
315
|
+
'Async',
|
|
316
|
+
'Await',
|
|
317
|
+
'Throws',
|
|
318
|
+
'Release',
|
|
319
|
+
'Debug',
|
|
320
|
+
'Swift',
|
|
321
|
+
'Function',
|
|
322
|
+
'Method',
|
|
323
|
+
'Property',
|
|
324
|
+
'Value',
|
|
325
|
+
'Default',
|
|
326
|
+
'Shared',
|
|
327
|
+
'Static',
|
|
328
|
+
'Final',
|
|
329
|
+
'Weak',
|
|
330
|
+
'Lazy',
|
|
331
|
+
]);
|
|
332
|
+
for (const id of identifiers) {
|
|
333
|
+
if (!COMMON_WORDS.has(id) && id.length >= 3) {
|
|
334
|
+
symbols.push(id);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
/* invalid JSON */
|
|
340
|
+
}
|
|
341
|
+
// 去重
|
|
342
|
+
return [...new Set(symbols)];
|
|
343
|
+
}
|
|
344
|
+
/** 从 Recipe 中提取模块/依赖引用 */
|
|
345
|
+
#extractModuleReferences(recipe) {
|
|
346
|
+
const modules = [];
|
|
347
|
+
try {
|
|
348
|
+
const content = JSON.parse(recipe.content || '{}');
|
|
349
|
+
const text = `${content.markdown || ''} ${content.pattern || ''} ${recipe.doClause || ''}`;
|
|
350
|
+
// 匹配 import 语句中的模块名
|
|
351
|
+
const importMatches = text.match(/import\s+(\w+)/g) || [];
|
|
352
|
+
for (const m of importMatches) {
|
|
353
|
+
const name = m.replace(/^import\s+/, '');
|
|
354
|
+
if (name.length >= 2) {
|
|
355
|
+
modules.push(name);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
/* invalid JSON */
|
|
361
|
+
}
|
|
362
|
+
return [...new Set(modules)];
|
|
363
|
+
}
|
|
364
|
+
/** 从 Recipe 中提取 codeChanges 引用的文件路径 */
|
|
365
|
+
#extractCodeFiles(recipe) {
|
|
366
|
+
const files = [];
|
|
367
|
+
try {
|
|
368
|
+
const content = JSON.parse(recipe.content || '{}');
|
|
369
|
+
const codeChanges = content.codeChanges;
|
|
370
|
+
if (Array.isArray(codeChanges)) {
|
|
371
|
+
for (const change of codeChanges) {
|
|
372
|
+
if (change.file) {
|
|
373
|
+
files.push(change.file);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// reasoning.sources 在 content 外层,由下方独立查询处理
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
/* invalid JSON */
|
|
381
|
+
}
|
|
382
|
+
// 也从 recipe 的 sourceFile 来源引用
|
|
383
|
+
try {
|
|
384
|
+
const row = this.#db
|
|
385
|
+
.prepare(`SELECT reasoning FROM knowledge_entries WHERE id = ?`)
|
|
386
|
+
.get(recipe.id);
|
|
387
|
+
if (row?.reasoning) {
|
|
388
|
+
const reasoning = JSON.parse(row.reasoning);
|
|
389
|
+
if (Array.isArray(reasoning.sources)) {
|
|
390
|
+
for (const src of reasoning.sources) {
|
|
391
|
+
if (typeof src === 'string') {
|
|
392
|
+
files.push(src);
|
|
393
|
+
}
|
|
394
|
+
else if (src?.file) {
|
|
395
|
+
files.push(src.file);
|
|
396
|
+
}
|
|
397
|
+
else if (src?.path) {
|
|
398
|
+
files.push(src.path);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
/* invalid JSON */
|
|
406
|
+
}
|
|
407
|
+
return [...new Set(files)];
|
|
408
|
+
}
|
|
409
|
+
/** 执行衰退状态转换 */
|
|
410
|
+
#executeDecay(result) {
|
|
411
|
+
try {
|
|
412
|
+
const now = Date.now();
|
|
413
|
+
switch (result.verdict) {
|
|
414
|
+
case 'dead': {
|
|
415
|
+
// 直接 deprecated,无需观察
|
|
416
|
+
this.#db
|
|
417
|
+
.prepare(`UPDATE knowledge_entries SET lifecycle = 'deprecated', updatedAt = ? WHERE id = ?`)
|
|
418
|
+
.run(now, result.recipeId);
|
|
419
|
+
// 记录已执行的 proposal(审计追溯)
|
|
420
|
+
this.#createProposal({
|
|
421
|
+
targetRecipeId: result.recipeId,
|
|
422
|
+
type: 'deprecate',
|
|
423
|
+
source: 'rescan-relevance-audit',
|
|
424
|
+
status: 'executed',
|
|
425
|
+
description: `[Rescan Relevance Audit] Score: ${result.relevanceScore}. ${result.decayReasons.join('; ')}`,
|
|
426
|
+
evidence: { relevanceScore: result.relevanceScore, evidence: result.evidence },
|
|
427
|
+
expiresAt: now,
|
|
428
|
+
});
|
|
429
|
+
this.#logger.info(`[RecipeRelevanceAuditor] DEAD: "${result.title}" → deprecated (score: ${result.relevanceScore})`);
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
case 'severe': {
|
|
433
|
+
// 加速衰退:3 天观察窗口
|
|
434
|
+
this.#db
|
|
435
|
+
.prepare(`UPDATE knowledge_entries SET lifecycle = 'decaying', updatedAt = ? WHERE id = ?`)
|
|
436
|
+
.run(now, result.recipeId);
|
|
437
|
+
this.#createProposal({
|
|
438
|
+
targetRecipeId: result.recipeId,
|
|
439
|
+
type: 'deprecate',
|
|
440
|
+
source: 'rescan-relevance-audit',
|
|
441
|
+
status: 'observing',
|
|
442
|
+
description: `[Rescan Relevance Audit] Score: ${result.relevanceScore}. ${result.decayReasons.join('; ')}`,
|
|
443
|
+
evidence: { relevanceScore: result.relevanceScore, evidence: result.evidence },
|
|
444
|
+
expiresAt: now + GRACE_PERIOD_SEVERE,
|
|
445
|
+
});
|
|
446
|
+
this.#logger.info(`[RecipeRelevanceAuditor] SEVERE: "${result.title}" → decaying (3d grace, score: ${result.relevanceScore})`);
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
case 'decay': {
|
|
450
|
+
// 加速衰退:7 天观察窗口
|
|
451
|
+
this.#db
|
|
452
|
+
.prepare(`UPDATE knowledge_entries SET lifecycle = 'decaying', updatedAt = ? WHERE id = ?`)
|
|
453
|
+
.run(now, result.recipeId);
|
|
454
|
+
this.#createProposal({
|
|
455
|
+
targetRecipeId: result.recipeId,
|
|
456
|
+
type: 'deprecate',
|
|
457
|
+
source: 'rescan-relevance-audit',
|
|
458
|
+
status: 'observing',
|
|
459
|
+
description: `[Rescan Relevance Audit] Score: ${result.relevanceScore}. ${result.decayReasons.join('; ')}`,
|
|
460
|
+
evidence: { relevanceScore: result.relevanceScore, evidence: result.evidence },
|
|
461
|
+
expiresAt: now + GRACE_PERIOD_DECAY,
|
|
462
|
+
});
|
|
463
|
+
this.#logger.info(`[RecipeRelevanceAuditor] DECAY: "${result.title}" → decaying (7d grace, score: ${result.relevanceScore})`);
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
default:
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
catch (err) {
|
|
471
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
472
|
+
this.#logger.warn(`[RecipeRelevanceAuditor] executeDecay failed for ${result.recipeId}: ${msg}`);
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/** 创建 evolution proposal */
|
|
477
|
+
#createProposal(input) {
|
|
478
|
+
try {
|
|
479
|
+
const id = `ep-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
480
|
+
this.#db
|
|
481
|
+
.prepare(`INSERT INTO evolution_proposals
|
|
482
|
+
(id, type, target_recipe_id, related_recipe_ids, confidence, source,
|
|
483
|
+
description, evidence, status, proposed_at, expires_at)
|
|
484
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
485
|
+
.run(id, input.type, input.targetRecipeId, '[]', 0.95, input.source, input.description, JSON.stringify([input.evidence]), input.status, Date.now(), input.expiresAt);
|
|
486
|
+
}
|
|
487
|
+
catch (err) {
|
|
488
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
489
|
+
this.#logger.warn(`[RecipeRelevanceAuditor] createProposal failed: ${msg}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* 0.85-0.89 → 72h
|
|
13
13
|
*/
|
|
14
14
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
15
|
+
import { unixNow } from '../../shared/utils/common.js';
|
|
15
16
|
/* ────────────────────── Class ────────────────────── */
|
|
16
17
|
export class StagingManager {
|
|
17
18
|
#db;
|
|
@@ -54,7 +55,7 @@ export class StagingManager {
|
|
|
54
55
|
stats.stagingEnteredAt = now;
|
|
55
56
|
this.#db
|
|
56
57
|
.prepare(`UPDATE knowledge_entries SET lifecycle = 'staging', stats = ?, updatedAt = ? WHERE id = ?`)
|
|
57
|
-
.run(JSON.stringify(stats),
|
|
58
|
+
.run(JSON.stringify(stats), unixNow(), entryId);
|
|
58
59
|
// 发射信号
|
|
59
60
|
if (this.#signalBus) {
|
|
60
61
|
this.#signalBus.send('lifecycle', 'StagingManager.enter', confidence, {
|
|
@@ -140,7 +141,7 @@ export class StagingManager {
|
|
|
140
141
|
stats.lastRollbackAt = now;
|
|
141
142
|
this.#db
|
|
142
143
|
.prepare(`UPDATE knowledge_entries SET lifecycle = 'pending', stats = ?, updatedAt = ? WHERE id = ?`)
|
|
143
|
-
.run(JSON.stringify(stats),
|
|
144
|
+
.run(JSON.stringify(stats), unixNow(), entryId);
|
|
144
145
|
if (this.#signalBus) {
|
|
145
146
|
this.#signalBus.send('lifecycle', 'StagingManager.rollback', 0.8, {
|
|
146
147
|
target: entryId,
|
|
@@ -184,9 +185,10 @@ export class StagingManager {
|
|
|
184
185
|
delete stats.stagingConfidence;
|
|
185
186
|
delete stats.stagingEnteredAt;
|
|
186
187
|
stats.autoPublishedAt = now;
|
|
188
|
+
const nowS = unixNow();
|
|
187
189
|
this.#db
|
|
188
190
|
.prepare(`UPDATE knowledge_entries SET lifecycle = 'active', publishedAt = ?, stats = ?, updatedAt = ? WHERE id = ?`)
|
|
189
|
-
.run(
|
|
191
|
+
.run(nowS, JSON.stringify(stats), nowS, entry.id);
|
|
190
192
|
if (this.#signalBus) {
|
|
191
193
|
this.#signalBus.send('lifecycle', 'StagingManager.promote', 1.0, {
|
|
192
194
|
target: entry.id,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createSupersedeProposal — 统一的 supersede 提案创建逻辑
|
|
3
|
+
*
|
|
4
|
+
* 内部 Agent 路径 (lifecycle.ts / composite.ts) 和外部 MCP 路径 (consolidated.ts)
|
|
5
|
+
* 共用此函数,确保知识替代的进化架构入口唯一。
|
|
6
|
+
*
|
|
7
|
+
* 流程:
|
|
8
|
+
* 1. 从 DI 容器获取 ProposalRepository
|
|
9
|
+
* 2. 验证旧 Recipe 存在
|
|
10
|
+
* 3. 去重检查(ProposalRepository 内部)
|
|
11
|
+
* 4. 创建 type='supersede' 提案,进入 72h 观察窗口
|
|
12
|
+
*/
|
|
13
|
+
import type { ProposalSource } from '../../repository/evolution/ProposalRepository.js';
|
|
14
|
+
/** 最小 DI 容器接口 — 兼容 ServiceContainer / McpServiceContainer / ToolHandlerContext.container */
|
|
15
|
+
interface MinimalContainer {
|
|
16
|
+
get(name: string): unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface SupersedeInput {
|
|
19
|
+
/** 被替代的旧 Recipe ID */
|
|
20
|
+
oldRecipeId: string;
|
|
21
|
+
/** 新提交的 Recipe ID 列表 */
|
|
22
|
+
newRecipeIds: string[];
|
|
23
|
+
/** 来源标识:'ide-agent' | 'metabolism' | 'decay-scan' */
|
|
24
|
+
source?: ProposalSource;
|
|
25
|
+
/** 置信度,默认 0.8 */
|
|
26
|
+
confidence?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface SupersedeResult {
|
|
29
|
+
proposalId: string;
|
|
30
|
+
type: 'supersede';
|
|
31
|
+
targetRecipe: {
|
|
32
|
+
id: string;
|
|
33
|
+
};
|
|
34
|
+
status: string;
|
|
35
|
+
expiresAt: number;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 在 DI 容器中查找 ProposalRepository,验证旧 Recipe 存在后创建 supersede 提案。
|
|
40
|
+
*
|
|
41
|
+
* @returns SupersedeResult(成功)| null(ProposalRepo 不可用 / 旧 Recipe 不存在 / 去重拒绝)
|
|
42
|
+
*/
|
|
43
|
+
export declare function createSupersedeProposal(container: MinimalContainer, input: SupersedeInput): SupersedeResult | null;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createSupersedeProposal — 统一的 supersede 提案创建逻辑
|
|
3
|
+
*
|
|
4
|
+
* 内部 Agent 路径 (lifecycle.ts / composite.ts) 和外部 MCP 路径 (consolidated.ts)
|
|
5
|
+
* 共用此函数,确保知识替代的进化架构入口唯一。
|
|
6
|
+
*
|
|
7
|
+
* 流程:
|
|
8
|
+
* 1. 从 DI 容器获取 ProposalRepository
|
|
9
|
+
* 2. 验证旧 Recipe 存在
|
|
10
|
+
* 3. 去重检查(ProposalRepository 内部)
|
|
11
|
+
* 4. 创建 type='supersede' 提案,进入 72h 观察窗口
|
|
12
|
+
*/
|
|
13
|
+
/* ────────────────────── Main ────────────────────── */
|
|
14
|
+
/**
|
|
15
|
+
* 在 DI 容器中查找 ProposalRepository,验证旧 Recipe 存在后创建 supersede 提案。
|
|
16
|
+
*
|
|
17
|
+
* @returns SupersedeResult(成功)| null(ProposalRepo 不可用 / 旧 Recipe 不存在 / 去重拒绝)
|
|
18
|
+
*/
|
|
19
|
+
export function createSupersedeProposal(container, input) {
|
|
20
|
+
const { oldRecipeId, newRecipeIds, source = 'ide-agent', confidence = 0.8 } = input;
|
|
21
|
+
if (!oldRecipeId || newRecipeIds.length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
// 1. 获取 ProposalRepository
|
|
25
|
+
let proposalRepo = null;
|
|
26
|
+
try {
|
|
27
|
+
proposalRepo = container.get('proposalRepository') ?? null;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (!proposalRepo) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
// 2. 验证旧 Recipe 存在
|
|
36
|
+
if (!verifyRecipeExists(container, oldRecipeId)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
// 3. 创建 supersede 提案(ProposalRepository 内部做去重检查)
|
|
40
|
+
const proposal = proposalRepo.create({
|
|
41
|
+
type: 'supersede',
|
|
42
|
+
targetRecipeId: oldRecipeId,
|
|
43
|
+
relatedRecipeIds: newRecipeIds,
|
|
44
|
+
confidence,
|
|
45
|
+
source,
|
|
46
|
+
description: `Agent 声明新 Recipe [${newRecipeIds.join(', ')}] 替代旧 Recipe [${oldRecipeId}]。观察窗口内将对比新旧表现。`,
|
|
47
|
+
evidence: [
|
|
48
|
+
{
|
|
49
|
+
snapshotAt: Date.now(),
|
|
50
|
+
newRecipeIds,
|
|
51
|
+
declaredBy: source,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
if (!proposal) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
proposalId: proposal.id,
|
|
60
|
+
type: 'supersede',
|
|
61
|
+
targetRecipe: { id: oldRecipeId },
|
|
62
|
+
status: proposal.status,
|
|
63
|
+
expiresAt: proposal.expiresAt,
|
|
64
|
+
message: `已创建替代提案:新 Recipe 将在观察窗口到期后自动替代旧 Recipe [${oldRecipeId}]。`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/* ────────────────────── Helpers ────────────────────── */
|
|
68
|
+
function verifyRecipeExists(container, recipeId) {
|
|
69
|
+
try {
|
|
70
|
+
const db = container.get('database');
|
|
71
|
+
if (!db) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const rawDb = db.getDb();
|
|
75
|
+
const row = rawDb.prepare('SELECT id FROM knowledge_entries WHERE id = ?').get(recipeId);
|
|
76
|
+
return row !== undefined;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|