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,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentPatcher — Proposal suggestedChanges 消费引擎
|
|
3
|
+
*
|
|
4
|
+
* 核心职责:
|
|
5
|
+
* 1. 从 Proposal.evidence 提取 suggestedChanges
|
|
6
|
+
* 2. 解析为结构化 Patch(JSON 或降级为纯文本)
|
|
7
|
+
* 3. 创建 Recipe 内容快照(before)
|
|
8
|
+
* 4. 应用 patch 到 Recipe 字段
|
|
9
|
+
* 5. 创建快照(after)
|
|
10
|
+
* 6. 持久化更新
|
|
11
|
+
*
|
|
12
|
+
* 安全边界:
|
|
13
|
+
* - 只修改 Patch 指定的字段,不擅自变更其他内容
|
|
14
|
+
* - suggestedChanges 缺失或格式不合规时降级跳过(不阻塞状态转移)
|
|
15
|
+
* - 所有变更在 before/after 快照中可追溯
|
|
16
|
+
*
|
|
17
|
+
* @module service/evolution/ContentPatcher
|
|
18
|
+
*/
|
|
19
|
+
import type { ContentPatchResult } from '../../types/evolution.js';
|
|
20
|
+
interface DatabaseLike {
|
|
21
|
+
prepare(sql: string): {
|
|
22
|
+
all(...params: unknown[]): Record<string, unknown>[];
|
|
23
|
+
get(...params: unknown[]): Record<string, unknown> | undefined;
|
|
24
|
+
run(...params: unknown[]): {
|
|
25
|
+
changes: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare class ContentPatcher {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(db: DatabaseLike);
|
|
32
|
+
/**
|
|
33
|
+
* 从 Proposal evidence 提取 suggestedChanges 并应用到 Recipe
|
|
34
|
+
*
|
|
35
|
+
* @returns ContentPatchResult — success: 是否成功应用了至少一个 patch
|
|
36
|
+
*/
|
|
37
|
+
applyProposal(proposal: {
|
|
38
|
+
id: string;
|
|
39
|
+
type: string;
|
|
40
|
+
targetRecipeId: string;
|
|
41
|
+
evidence: Record<string, unknown>[];
|
|
42
|
+
}, patchSource?: 'agent-suggestion' | 'correction' | 'merge'): ContentPatchResult;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentPatcher — Proposal suggestedChanges 消费引擎
|
|
3
|
+
*
|
|
4
|
+
* 核心职责:
|
|
5
|
+
* 1. 从 Proposal.evidence 提取 suggestedChanges
|
|
6
|
+
* 2. 解析为结构化 Patch(JSON 或降级为纯文本)
|
|
7
|
+
* 3. 创建 Recipe 内容快照(before)
|
|
8
|
+
* 4. 应用 patch 到 Recipe 字段
|
|
9
|
+
* 5. 创建快照(after)
|
|
10
|
+
* 6. 持久化更新
|
|
11
|
+
*
|
|
12
|
+
* 安全边界:
|
|
13
|
+
* - 只修改 Patch 指定的字段,不擅自变更其他内容
|
|
14
|
+
* - suggestedChanges 缺失或格式不合规时降级跳过(不阻塞状态转移)
|
|
15
|
+
* - 所有变更在 before/after 快照中可追溯
|
|
16
|
+
*
|
|
17
|
+
* @module service/evolution/ContentPatcher
|
|
18
|
+
*/
|
|
19
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
20
|
+
/* ────────────────────── Patchable Fields ────────────────────── */
|
|
21
|
+
/** 允许 patch 的顶层字段白名单 */
|
|
22
|
+
const PATCHABLE_FIELDS = new Set([
|
|
23
|
+
'coreCode',
|
|
24
|
+
'doClause',
|
|
25
|
+
'dontClause',
|
|
26
|
+
'whenClause',
|
|
27
|
+
'content.markdown',
|
|
28
|
+
'content.rationale',
|
|
29
|
+
'sourceRefs',
|
|
30
|
+
'headers',
|
|
31
|
+
]);
|
|
32
|
+
/* ────────────────────── Class ────────────────────── */
|
|
33
|
+
export class ContentPatcher {
|
|
34
|
+
#db;
|
|
35
|
+
#logger = Logger.getInstance();
|
|
36
|
+
constructor(db) {
|
|
37
|
+
this.#db = db;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 从 Proposal evidence 提取 suggestedChanges 并应用到 Recipe
|
|
41
|
+
*
|
|
42
|
+
* @returns ContentPatchResult — success: 是否成功应用了至少一个 patch
|
|
43
|
+
*/
|
|
44
|
+
applyProposal(proposal, patchSource = 'agent-suggestion') {
|
|
45
|
+
const recipeId = proposal.targetRecipeId;
|
|
46
|
+
// 1. 获取 Recipe 当前内容
|
|
47
|
+
const recipe = this.#getRecipe(recipeId);
|
|
48
|
+
if (!recipe) {
|
|
49
|
+
return this.#skipResult(recipeId, patchSource, 'Recipe not found');
|
|
50
|
+
}
|
|
51
|
+
// 2. 提取 suggestedChanges
|
|
52
|
+
const rawChanges = this.#extractSuggestedChanges(proposal.evidence);
|
|
53
|
+
if (!rawChanges) {
|
|
54
|
+
return this.#skipResult(recipeId, patchSource, 'No suggestedChanges in proposal evidence');
|
|
55
|
+
}
|
|
56
|
+
// 3. 解析为结构化 Patch
|
|
57
|
+
const patch = this.#parsePatch(rawChanges);
|
|
58
|
+
if (!patch || patch.changes.length === 0) {
|
|
59
|
+
return this.#skipResult(recipeId, patchSource, 'suggestedChanges could not be parsed or empty');
|
|
60
|
+
}
|
|
61
|
+
// 4. 创建 before 快照
|
|
62
|
+
const beforeSnapshot = this.#createSnapshot(recipe);
|
|
63
|
+
// 5. 应用 patch
|
|
64
|
+
const fieldsPatched = this.#applyPatch(recipe, patch.changes);
|
|
65
|
+
if (fieldsPatched.length === 0) {
|
|
66
|
+
return this.#skipResult(recipeId, patchSource, 'No valid fields to patch');
|
|
67
|
+
}
|
|
68
|
+
// 6. 持久化
|
|
69
|
+
this.#persistRecipe(recipe);
|
|
70
|
+
// 7. 创建 after 快照
|
|
71
|
+
const afterSnapshot = this.#createSnapshot(recipe);
|
|
72
|
+
this.#logger.info(`[ContentPatcher] Applied ${fieldsPatched.length} patches to recipe ${recipeId}: ${fieldsPatched.join(', ')}`);
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
recipeId,
|
|
76
|
+
fieldsPatched,
|
|
77
|
+
beforeSnapshot,
|
|
78
|
+
afterSnapshot,
|
|
79
|
+
patchSource,
|
|
80
|
+
skipped: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/* ═══════════════════ Extract ═══════════════════ */
|
|
84
|
+
#extractSuggestedChanges(evidence) {
|
|
85
|
+
for (const ev of evidence) {
|
|
86
|
+
const cast = ev;
|
|
87
|
+
if (cast.suggestedChanges &&
|
|
88
|
+
typeof cast.suggestedChanges === 'string' &&
|
|
89
|
+
cast.suggestedChanges.trim().length > 0) {
|
|
90
|
+
return cast.suggestedChanges;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
/* ═══════════════════ Parse ═══════════════════ */
|
|
96
|
+
/**
|
|
97
|
+
* 解析 suggestedChanges — 优先 JSON,降级为纯文本全量替换
|
|
98
|
+
*/
|
|
99
|
+
#parsePatch(raw) {
|
|
100
|
+
// 尝试 JSON 解析
|
|
101
|
+
const trimmed = raw.trim();
|
|
102
|
+
if (trimmed.startsWith('{')) {
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(trimmed);
|
|
105
|
+
if (parsed.changes && Array.isArray(parsed.changes)) {
|
|
106
|
+
// JSON 有效但 changes 为空 → 视为无 patch
|
|
107
|
+
if (parsed.changes.length === 0) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
patchVersion: parsed.patchVersion ?? 1,
|
|
112
|
+
changes: parsed.changes,
|
|
113
|
+
reasoning: parsed.reasoning ?? '',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// JSON 解析失败,降级到纯文本
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// 降级:纯文本视为 content.markdown 全量替换(适用于旧格式 Agent 输出)
|
|
122
|
+
if (trimmed.length >= 20) {
|
|
123
|
+
return {
|
|
124
|
+
patchVersion: 1,
|
|
125
|
+
changes: [
|
|
126
|
+
{
|
|
127
|
+
field: 'content.markdown',
|
|
128
|
+
action: 'replace',
|
|
129
|
+
newValue: trimmed,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
reasoning: 'Fallback: raw text treated as content.markdown replacement',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/* ═══════════════════ Apply ═══════════════════ */
|
|
138
|
+
#applyPatch(recipe, changes) {
|
|
139
|
+
const patched = [];
|
|
140
|
+
for (const change of changes) {
|
|
141
|
+
if (!PATCHABLE_FIELDS.has(change.field)) {
|
|
142
|
+
this.#logger.warn(`[ContentPatcher] Skipping non-patchable field: ${change.field}`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const success = this.#applyOneChange(recipe, change);
|
|
146
|
+
if (success) {
|
|
147
|
+
patched.push(change.field);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return patched;
|
|
151
|
+
}
|
|
152
|
+
#applyOneChange(recipe, change) {
|
|
153
|
+
const { field, action } = change;
|
|
154
|
+
// content.* 嵌套字段
|
|
155
|
+
if (field.startsWith('content.')) {
|
|
156
|
+
return this.#applyContentChange(recipe, change);
|
|
157
|
+
}
|
|
158
|
+
// 顶层字段
|
|
159
|
+
if (field === 'sourceRefs' || field === 'headers') {
|
|
160
|
+
return this.#applyArrayChange(recipe, field, change);
|
|
161
|
+
}
|
|
162
|
+
// 简单字符串字段
|
|
163
|
+
const key = field;
|
|
164
|
+
if (action === 'replace' && change.newValue !== undefined) {
|
|
165
|
+
recipe[key] = change.newValue;
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (action === 'append' && change.newValue !== undefined) {
|
|
169
|
+
recipe[key] = `${recipe[key]}\n${change.newValue}`;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
#applyContentChange(recipe, change) {
|
|
175
|
+
const contentObj = safeJsonParse(recipe.content, {});
|
|
176
|
+
const subField = change.field.split('.')[1]; // 'markdown' | 'rationale'
|
|
177
|
+
if (!subField) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (change.action === 'replace' && change.newValue !== undefined) {
|
|
181
|
+
contentObj[subField] = change.newValue;
|
|
182
|
+
recipe.content = JSON.stringify(contentObj);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
if (change.action === 'replace-section' && change.section && change.newContent) {
|
|
186
|
+
const current = contentObj[subField] ?? '';
|
|
187
|
+
const updated = this.#replaceSection(current, change.section, change.newContent);
|
|
188
|
+
if (updated !== current) {
|
|
189
|
+
contentObj[subField] = updated;
|
|
190
|
+
recipe.content = JSON.stringify(contentObj);
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
if (change.action === 'append' && change.newValue !== undefined) {
|
|
196
|
+
const current = contentObj[subField] ?? '';
|
|
197
|
+
contentObj[subField] = `${current}\n${change.newValue}`;
|
|
198
|
+
recipe.content = JSON.stringify(contentObj);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
#applyArrayChange(recipe, field, change) {
|
|
204
|
+
if (change.action === 'replace' && change.newValue !== undefined) {
|
|
205
|
+
try {
|
|
206
|
+
const newArr = JSON.parse(change.newValue);
|
|
207
|
+
if (Array.isArray(newArr)) {
|
|
208
|
+
recipe[field] = JSON.stringify(newArr);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// invalid JSON array
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 替换 Markdown 中指定 section(基于标题行匹配)
|
|
221
|
+
*/
|
|
222
|
+
#replaceSection(markdown, sectionTitle, newContent) {
|
|
223
|
+
const lines = markdown.split('\n');
|
|
224
|
+
const titleLine = lines.findIndex((line) => line.trim() === sectionTitle.trim());
|
|
225
|
+
if (titleLine === -1) {
|
|
226
|
+
// Section 不存在 → 追加
|
|
227
|
+
return `${markdown}\n\n${newContent}`;
|
|
228
|
+
}
|
|
229
|
+
// 找到 section 结尾(下一个同级或更高级标题)
|
|
230
|
+
const headingLevel = (sectionTitle.match(/^#+/) ?? [''])[0].length;
|
|
231
|
+
let endLine = lines.length;
|
|
232
|
+
for (let i = titleLine + 1; i < lines.length; i++) {
|
|
233
|
+
const match = lines[i].match(/^(#+)\s/);
|
|
234
|
+
if (match && match[1].length <= headingLevel) {
|
|
235
|
+
endLine = i;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const before = lines.slice(0, titleLine);
|
|
240
|
+
const after = lines.slice(endLine);
|
|
241
|
+
return [...before, newContent, ...after].join('\n');
|
|
242
|
+
}
|
|
243
|
+
/* ═══════════════════ Snapshot ═══════════════════ */
|
|
244
|
+
#createSnapshot(recipe) {
|
|
245
|
+
const contentObj = safeJsonParse(recipe.content, {});
|
|
246
|
+
return {
|
|
247
|
+
coreCode: recipe.coreCode,
|
|
248
|
+
doClause: recipe.doClause,
|
|
249
|
+
dontClause: recipe.dontClause,
|
|
250
|
+
whenClause: recipe.whenClause,
|
|
251
|
+
content: {
|
|
252
|
+
markdown: contentObj.markdown ?? undefined,
|
|
253
|
+
rationale: contentObj.rationale ?? undefined,
|
|
254
|
+
},
|
|
255
|
+
sourceRefs: safeJsonParse(recipe.sourceRefs, []),
|
|
256
|
+
headers: safeJsonParse(recipe.headers, []),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/* ═══════════════════ DB ═══════════════════ */
|
|
260
|
+
#getRecipe(recipeId) {
|
|
261
|
+
const row = this.#db
|
|
262
|
+
.prepare(`SELECT id, title, coreCode, doClause, dontClause, whenClause, content, sourceRefs, headers
|
|
263
|
+
FROM knowledge_entries WHERE id = ?`)
|
|
264
|
+
.get(recipeId);
|
|
265
|
+
return row ?? null;
|
|
266
|
+
}
|
|
267
|
+
#persistRecipe(recipe) {
|
|
268
|
+
this.#db
|
|
269
|
+
.prepare(`UPDATE knowledge_entries
|
|
270
|
+
SET coreCode = ?, doClause = ?, dontClause = ?, whenClause = ?,
|
|
271
|
+
content = ?, sourceRefs = ?, headers = ?, updatedAt = ?
|
|
272
|
+
WHERE id = ?`)
|
|
273
|
+
.run(recipe.coreCode, recipe.doClause, recipe.dontClause, recipe.whenClause, recipe.content, recipe.sourceRefs, recipe.headers, Date.now(), recipe.id);
|
|
274
|
+
}
|
|
275
|
+
/* ═══════════════════ Helpers ═══════════════════ */
|
|
276
|
+
#skipResult(recipeId, patchSource, reason) {
|
|
277
|
+
this.#logger.info(`[ContentPatcher] Skipped for ${recipeId}: ${reason}`);
|
|
278
|
+
const emptySnapshot = {
|
|
279
|
+
coreCode: '',
|
|
280
|
+
doClause: '',
|
|
281
|
+
dontClause: '',
|
|
282
|
+
whenClause: '',
|
|
283
|
+
content: {},
|
|
284
|
+
sourceRefs: [],
|
|
285
|
+
headers: [],
|
|
286
|
+
};
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
recipeId,
|
|
290
|
+
fieldsPatched: [],
|
|
291
|
+
beforeSnapshot: emptySnapshot,
|
|
292
|
+
afterSnapshot: emptySnapshot,
|
|
293
|
+
patchSource,
|
|
294
|
+
skipped: true,
|
|
295
|
+
skipReason: reason,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/* ────────────────────── Util ────────────────────── */
|
|
300
|
+
function safeJsonParse(json, fallback) {
|
|
301
|
+
if (!json) {
|
|
302
|
+
return fallback;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
return JSON.parse(json);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return fallback;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DecayDetector — 知识衰退检测 + 评分
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 6 种衰退检测策略(任一满足即触发 decaying 转换):
|
|
5
5
|
* 1. daysSinceLastHit > 90 — 90 天无使用
|
|
6
6
|
* 2. ruleFalsePositiveRate > 0.4 && triggers > 10 — 规则已不准
|
|
7
7
|
* 3. ReverseGuard: coreCode 引用的 API 符号已删除
|
|
8
|
+
* 3b. SourceRefReconciler: 来源文件路径失效(recipe_source_refs.status = stale)
|
|
8
9
|
* 4. 同域新 Recipe 发布且 deprecated_by 关系指向它
|
|
9
10
|
* 5. ContradictionDetector: 与更新的 Recipe 硬矛盾
|
|
10
11
|
*
|
|
@@ -29,7 +30,7 @@ export interface DecaySignal {
|
|
|
29
30
|
strategy: DecayStrategy;
|
|
30
31
|
detail: string;
|
|
31
32
|
}
|
|
32
|
-
export type DecayStrategy = 'no_recent_usage' | 'high_false_positive' | 'symbol_drift' | 'superseded' | 'contradiction';
|
|
33
|
+
export type DecayStrategy = 'no_recent_usage' | 'high_false_positive' | 'symbol_drift' | 'source_ref_stale' | 'superseded' | 'contradiction';
|
|
33
34
|
export interface DecayScoreResult {
|
|
34
35
|
recipeId: string;
|
|
35
36
|
title: string;
|
|
@@ -52,7 +53,7 @@ interface RecipeForDecay {
|
|
|
52
53
|
stats: string | null;
|
|
53
54
|
quality_grade: string | null;
|
|
54
55
|
quality_score: number | null;
|
|
55
|
-
created_at:
|
|
56
|
+
created_at: number | null;
|
|
56
57
|
}
|
|
57
58
|
export declare class DecayDetector {
|
|
58
59
|
#private;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DecayDetector — 知识衰退检测 + 评分
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 6 种衰退检测策略(任一满足即触发 decaying 转换):
|
|
5
5
|
* 1. daysSinceLastHit > 90 — 90 天无使用
|
|
6
6
|
* 2. ruleFalsePositiveRate > 0.4 && triggers > 10 — 规则已不准
|
|
7
7
|
* 3. ReverseGuard: coreCode 引用的 API 符号已删除
|
|
8
|
+
* 3b. SourceRefReconciler: 来源文件路径失效(recipe_source_refs.status = stale)
|
|
8
9
|
* 4. 同域新 Recipe 发布且 deprecated_by 关系指向它
|
|
9
10
|
* 5. ContradictionDetector: 与更新的 Recipe 硬矛盾
|
|
10
11
|
*
|
|
@@ -17,7 +18,17 @@
|
|
|
17
18
|
* 20-39: 严重 → Grace Period 缩短到 15d
|
|
18
19
|
* 0-19: 死亡 → 跳过确认直接 deprecated
|
|
19
20
|
*/
|
|
21
|
+
var _a;
|
|
20
22
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
23
|
+
/* ────────────────────── Helpers ────────────────────── */
|
|
24
|
+
/**
|
|
25
|
+
* Normalize a timestamp to **milliseconds**.
|
|
26
|
+
* If the value looks like Unix seconds (< 1e12 ≈ year 2001 in ms), multiply by 1000.
|
|
27
|
+
* Otherwise assume it's already in ms and return as-is.
|
|
28
|
+
*/
|
|
29
|
+
function toMs(ts) {
|
|
30
|
+
return ts < 1e12 ? ts * 1000 : ts;
|
|
31
|
+
}
|
|
21
32
|
/* ────────────────────── Constants ────────────────────── */
|
|
22
33
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
23
34
|
const GRACE_PERIOD_STANDARD = 30 * DAY_MS;
|
|
@@ -77,13 +88,13 @@ export class DecayDetector {
|
|
|
77
88
|
* 评估单条 Recipe 的衰退状态
|
|
78
89
|
*/
|
|
79
90
|
evaluate(recipe) {
|
|
80
|
-
const stats =
|
|
91
|
+
const stats = _a.#parseStats(recipe.stats);
|
|
81
92
|
const signals = [];
|
|
82
93
|
const now = Date.now();
|
|
83
94
|
// 策略 1: 90 天无使用
|
|
84
95
|
const lastHitAt = stats.lastHitAt ?? null;
|
|
85
96
|
if (lastHitAt) {
|
|
86
|
-
const daysSince = (now - lastHitAt) / DAY_MS;
|
|
97
|
+
const daysSince = (now - toMs(lastHitAt)) / DAY_MS;
|
|
87
98
|
if (daysSince > DECAY_THRESHOLDS.NO_USAGE_DAYS) {
|
|
88
99
|
signals.push({
|
|
89
100
|
recipeId: recipe.id,
|
|
@@ -93,8 +104,8 @@ export class DecayDetector {
|
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
106
|
else {
|
|
96
|
-
// 无 lastHitAt
|
|
97
|
-
const createdAt =
|
|
107
|
+
// 无 lastHitAt,检查创建时间(DB 可能存为秒或毫秒)
|
|
108
|
+
const createdAt = toMs(recipe.created_at ?? now);
|
|
98
109
|
const daysSinceCreation = (now - createdAt) / DAY_MS;
|
|
99
110
|
if (daysSinceCreation > DECAY_THRESHOLDS.NO_USAGE_DAYS) {
|
|
100
111
|
signals.push({
|
|
@@ -123,6 +134,15 @@ export class DecayDetector {
|
|
|
123
134
|
detail: 'ReverseGuard detected symbol drift in coreCode',
|
|
124
135
|
});
|
|
125
136
|
}
|
|
137
|
+
// 策略 3b: 来源引用失效(由 SourceRefReconciler 填充 recipe_source_refs)
|
|
138
|
+
const staleRefCount = this.#getStaleSourceRefCount(recipe.id);
|
|
139
|
+
if (staleRefCount > 0) {
|
|
140
|
+
signals.push({
|
|
141
|
+
recipeId: recipe.id,
|
|
142
|
+
strategy: 'source_ref_stale',
|
|
143
|
+
detail: `${staleRefCount} source reference(s) no longer exist on disk`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
126
146
|
// 策略 4: 被取代(有 deprecated_by 关系指向更新版本)
|
|
127
147
|
if (this.#isSuperseded(recipe.id)) {
|
|
128
148
|
signals.push({
|
|
@@ -131,13 +151,14 @@ export class DecayDetector {
|
|
|
131
151
|
detail: 'Newer version exists via deprecated_by relation',
|
|
132
152
|
});
|
|
133
153
|
}
|
|
134
|
-
// 计算 decayScore
|
|
135
|
-
const
|
|
154
|
+
// 计算 decayScore(staleRatio 影响 quality 维度)
|
|
155
|
+
const staleRatio = this.#getSourceRefStaleRatio(recipe.id);
|
|
156
|
+
const dimensions = this.#computeScoreDimensions(stats, recipe, { staleRatio });
|
|
136
157
|
const decayScore = Math.round(dimensions.freshness * SCORE_WEIGHTS.freshness * 100 +
|
|
137
158
|
dimensions.usage * SCORE_WEIGHTS.usage * 100 +
|
|
138
159
|
dimensions.quality * SCORE_WEIGHTS.quality * 100 +
|
|
139
160
|
dimensions.authority * SCORE_WEIGHTS.authority * 100);
|
|
140
|
-
const level =
|
|
161
|
+
const level = _a.#scoreToLevel(decayScore);
|
|
141
162
|
const suggestedGracePeriod = level === 'dead' ? 0 : level === 'severe' ? GRACE_PERIOD_SEVERE : GRACE_PERIOD_STANDARD;
|
|
142
163
|
return {
|
|
143
164
|
recipeId: recipe.id,
|
|
@@ -153,19 +174,22 @@ export class DecayDetector {
|
|
|
153
174
|
#loadActiveRecipes() {
|
|
154
175
|
try {
|
|
155
176
|
const rows = this.#db
|
|
156
|
-
.prepare(`SELECT id, title, lifecycle, stats,
|
|
177
|
+
.prepare(`SELECT id, title, lifecycle, stats, quality, createdAt
|
|
157
178
|
FROM knowledge_entries
|
|
158
179
|
WHERE lifecycle = 'active'`)
|
|
159
180
|
.all();
|
|
160
|
-
return rows.map((r) =>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
181
|
+
return rows.map((r) => {
|
|
182
|
+
const qualityObj = _a.#parseQuality(r.quality);
|
|
183
|
+
return {
|
|
184
|
+
id: r.id,
|
|
185
|
+
title: r.title,
|
|
186
|
+
lifecycle: r.lifecycle,
|
|
187
|
+
stats: r.stats ?? null,
|
|
188
|
+
quality_grade: qualityObj.grade,
|
|
189
|
+
quality_score: qualityObj.score,
|
|
190
|
+
created_at: r.createdAt !== undefined ? Number(r.createdAt) : null,
|
|
191
|
+
};
|
|
192
|
+
});
|
|
169
193
|
}
|
|
170
194
|
catch {
|
|
171
195
|
return [];
|
|
@@ -182,17 +206,35 @@ export class DecayDetector {
|
|
|
182
206
|
return {};
|
|
183
207
|
}
|
|
184
208
|
}
|
|
185
|
-
#
|
|
209
|
+
static #parseQuality(qualityJson) {
|
|
210
|
+
if (!qualityJson) {
|
|
211
|
+
return { grade: null, score: null };
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const obj = JSON.parse(qualityJson);
|
|
215
|
+
return {
|
|
216
|
+
grade: typeof obj.grade === 'string' ? obj.grade : null,
|
|
217
|
+
score: typeof obj.overall === 'number' ? obj.overall : null,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return { grade: null, score: null };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
#computeScoreDimensions(stats, recipe, context = {}) {
|
|
186
225
|
const now = Date.now();
|
|
187
226
|
// freshness: days since last hit → 0-1 (0 = 365+ days, 1 = today)
|
|
188
227
|
const lastHit = stats.lastHitAt ?? 0;
|
|
189
|
-
const daysSinceHit = lastHit > 0 ? (now - lastHit) / DAY_MS : 365;
|
|
228
|
+
const daysSinceHit = lastHit > 0 ? (now - toMs(lastHit)) / DAY_MS : 365;
|
|
190
229
|
const freshness = Math.max(0, 1 - daysSinceHit / 365);
|
|
191
230
|
// usage: hitsLast90d 归一化 (0 = 0 hits, 1 = 50+ hits)
|
|
192
231
|
const hitsLast90d = stats.hitsLast90d ?? 0;
|
|
193
232
|
const usage = Math.min(1, hitsLast90d / 50);
|
|
194
|
-
// quality: qualityScore
|
|
195
|
-
|
|
233
|
+
// quality: qualityScore × sourceRef 健康度
|
|
234
|
+
// staleRatio 对 quality 打折,最多压低 30%(全部 stale → ×0.7)
|
|
235
|
+
const baseQuality = recipe.quality_score ?? 0.5;
|
|
236
|
+
const staleRatio = context.staleRatio ?? 0;
|
|
237
|
+
const quality = baseQuality * (1 - staleRatio * 0.3);
|
|
196
238
|
// authority: from stats.authority 归一化 (0-100 → 0-1)
|
|
197
239
|
const authorityRaw = stats.authority ?? 50;
|
|
198
240
|
const authority = Math.min(1, authorityRaw / 100);
|
|
@@ -213,6 +255,38 @@ export class DecayDetector {
|
|
|
213
255
|
return false;
|
|
214
256
|
}
|
|
215
257
|
}
|
|
258
|
+
#getStaleSourceRefCount(recipeId) {
|
|
259
|
+
try {
|
|
260
|
+
const row = this.#db
|
|
261
|
+
.prepare(`SELECT COUNT(*) AS cnt FROM recipe_source_refs
|
|
262
|
+
WHERE recipe_id = ? AND status = 'stale'`)
|
|
263
|
+
.get(recipeId);
|
|
264
|
+
return row?.cnt ?? 0;
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// recipe_source_refs 表可能不存在
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** stale / total 比率(0-1),无 ref 时返回 0(无惩罚) */
|
|
272
|
+
#getSourceRefStaleRatio(recipeId) {
|
|
273
|
+
try {
|
|
274
|
+
const row = this.#db
|
|
275
|
+
.prepare(`SELECT
|
|
276
|
+
SUM(CASE WHEN status = 'stale' THEN 1 ELSE 0 END) AS stale,
|
|
277
|
+
COUNT(*) AS total
|
|
278
|
+
FROM recipe_source_refs
|
|
279
|
+
WHERE recipe_id = ?`)
|
|
280
|
+
.get(recipeId);
|
|
281
|
+
if (!row || row.total === 0) {
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
return row.stale / row.total;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
216
290
|
#isSuperseded(recipeId) {
|
|
217
291
|
try {
|
|
218
292
|
const row = this.#db
|
|
@@ -242,3 +316,4 @@ export class DecayDetector {
|
|
|
242
316
|
return 'dead';
|
|
243
317
|
}
|
|
244
318
|
}
|
|
319
|
+
_a = DecayDetector;
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { ReportStore } from '../../infrastructure/report/ReportStore.js';
|
|
14
14
|
import type { SignalBus } from '../../infrastructure/signal/SignalBus.js';
|
|
15
|
+
import type { ProposalRepository } from '../../repository/evolution/ProposalRepository.js';
|
|
15
16
|
import type { ContradictionDetector, ContradictionResult } from './ContradictionDetector.js';
|
|
16
17
|
import type { DecayDetector, DecayScoreResult } from './DecayDetector.js';
|
|
17
18
|
import type { RedundancyAnalyzer, RedundancyResult } from './RedundancyAnalyzer.js';
|
|
18
|
-
export type ProposalType = 'merge' | 'enhance' | '
|
|
19
|
+
export type ProposalType = 'merge' | 'enhance' | 'deprecate' | 'contradiction' | 'correction';
|
|
19
20
|
export interface EvolutionProposal {
|
|
20
21
|
/** 进化提案类型 */
|
|
21
22
|
type: ProposalType;
|
|
@@ -33,7 +34,7 @@ export interface EvolutionProposal {
|
|
|
33
34
|
evidence: string[];
|
|
34
35
|
/** 创建时间 */
|
|
35
36
|
proposedAt: number;
|
|
36
|
-
/** 过期时间
|
|
37
|
+
/** 过期时间 */
|
|
37
38
|
expiresAt: number;
|
|
38
39
|
}
|
|
39
40
|
export interface MetabolismReport {
|
|
@@ -62,6 +63,7 @@ export declare class KnowledgeMetabolism {
|
|
|
62
63
|
decayDetector: DecayDetector;
|
|
63
64
|
signalBus?: SignalBus;
|
|
64
65
|
reportStore?: ReportStore;
|
|
66
|
+
proposalRepository?: ProposalRepository;
|
|
65
67
|
});
|
|
66
68
|
/**
|
|
67
69
|
* 执行完整治理周期
|
|
@@ -20,6 +20,7 @@ export class KnowledgeMetabolism {
|
|
|
20
20
|
#decayDetector;
|
|
21
21
|
#signalBus;
|
|
22
22
|
#reportStore;
|
|
23
|
+
#proposalRepo;
|
|
23
24
|
#logger = Logger.getInstance();
|
|
24
25
|
#pendingTriggers = [];
|
|
25
26
|
#debounceTimer = null;
|
|
@@ -29,6 +30,7 @@ export class KnowledgeMetabolism {
|
|
|
29
30
|
this.#decayDetector = options.decayDetector;
|
|
30
31
|
this.#signalBus = options.signalBus ?? null;
|
|
31
32
|
this.#reportStore = options.reportStore ?? null;
|
|
33
|
+
this.#proposalRepo = options.proposalRepository ?? null;
|
|
32
34
|
// Phase 2: 订阅告警型信号,触发代谢周期
|
|
33
35
|
if (this.#signalBus) {
|
|
34
36
|
this.#signalBus.subscribe('decay|quality|anomaly', (signal) => {
|
|
@@ -66,7 +68,31 @@ export class KnowledgeMetabolism {
|
|
|
66
68
|
...this.#proposalsFromRedundancies(redundancies),
|
|
67
69
|
...this.#proposalsFromDecay(decayResults),
|
|
68
70
|
];
|
|
69
|
-
// 5.
|
|
71
|
+
// 5. 持久化提案到 evolution_proposals 表
|
|
72
|
+
let persistedCount = 0;
|
|
73
|
+
if (this.#proposalRepo && proposals.length > 0) {
|
|
74
|
+
for (const p of proposals) {
|
|
75
|
+
const sourceMap = {
|
|
76
|
+
contradiction: 'metabolism',
|
|
77
|
+
redundancy: 'metabolism',
|
|
78
|
+
decay: 'decay-scan',
|
|
79
|
+
enhancement: 'metabolism',
|
|
80
|
+
};
|
|
81
|
+
const record = this.#proposalRepo.create({
|
|
82
|
+
type: p.type,
|
|
83
|
+
targetRecipeId: p.targetRecipeId,
|
|
84
|
+
relatedRecipeIds: p.relatedRecipeIds,
|
|
85
|
+
confidence: p.confidence,
|
|
86
|
+
source: sourceMap[p.source] ?? 'metabolism',
|
|
87
|
+
description: p.description,
|
|
88
|
+
evidence: p.evidence.map((e) => ({ detail: e })),
|
|
89
|
+
});
|
|
90
|
+
if (record) {
|
|
91
|
+
persistedCount++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// 6. 写入治理报告(降级:同时写 ReportStore)
|
|
70
96
|
if (this.#reportStore && proposals.length > 0) {
|
|
71
97
|
void this.#reportStore.write({
|
|
72
98
|
category: 'governance',
|
|
@@ -74,6 +100,7 @@ export class KnowledgeMetabolism {
|
|
|
74
100
|
producer: 'KnowledgeMetabolism',
|
|
75
101
|
data: {
|
|
76
102
|
proposalCount: proposals.length,
|
|
103
|
+
persistedCount,
|
|
77
104
|
contradictionCount: contradictions.length,
|
|
78
105
|
redundancyCount: redundancies.length,
|
|
79
106
|
decayingCount: decayResults.filter((d) => d.level !== 'healthy' && d.level !== 'watch')
|
|
@@ -121,7 +148,7 @@ export class KnowledgeMetabolism {
|
|
|
121
148
|
#proposalsFromContradictions(results) {
|
|
122
149
|
const now = Date.now();
|
|
123
150
|
return results.map((r) => ({
|
|
124
|
-
type: r.type === 'hard' ? '
|
|
151
|
+
type: r.type === 'hard' ? 'contradiction' : 'correction',
|
|
125
152
|
targetRecipeId: r.recipeA,
|
|
126
153
|
relatedRecipeIds: [r.recipeB],
|
|
127
154
|
confidence: r.confidence,
|