autosnippet 3.3.5 → 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.
Files changed (203) hide show
  1. package/dashboard/dist/assets/icons-D1aVZYFW.js +1 -0
  2. package/dashboard/dist/assets/index-CxHOu8Hd.css +1 -0
  3. package/dashboard/dist/assets/index-DDdAOpYT.js +128 -0
  4. package/dashboard/dist/index.html +3 -3
  5. package/dist/bin/api-server.js +1 -0
  6. package/dist/bin/cli.d.ts +1 -0
  7. package/dist/bin/cli.js +136 -9
  8. package/dist/lib/agent/AgentFactory.d.ts +0 -17
  9. package/dist/lib/agent/AgentFactory.js +1 -25
  10. package/dist/lib/agent/capabilities.d.ts +11 -0
  11. package/dist/lib/agent/capabilities.js +29 -5
  12. package/dist/lib/agent/context/ExplorationTracker.js +10 -1
  13. package/dist/lib/agent/context/exploration/ExplorationStrategies.d.ts +2 -0
  14. package/dist/lib/agent/context/exploration/ExplorationStrategies.js +2 -2
  15. package/dist/lib/agent/domain/insight-analyst.d.ts +47 -3
  16. package/dist/lib/agent/domain/insight-analyst.js +111 -11
  17. package/dist/lib/agent/domain/insight-evolver.d.ts +69 -0
  18. package/dist/lib/agent/domain/insight-evolver.js +230 -0
  19. package/dist/lib/agent/domain/insight-gate.d.ts +42 -0
  20. package/dist/lib/agent/domain/insight-gate.js +41 -0
  21. package/dist/lib/agent/domain/insight-producer.d.ts +27 -2
  22. package/dist/lib/agent/domain/insight-producer.js +60 -5
  23. package/dist/lib/agent/domain/scan-prompts.js +10 -7
  24. package/dist/lib/agent/memory/ActiveContext.d.ts +2 -28
  25. package/dist/lib/agent/memory/MemoryCoordinator.d.ts +2 -2
  26. package/dist/lib/agent/memory/SessionStore.d.ts +6 -12
  27. package/dist/lib/agent/memory/SessionStore.js +9 -15
  28. package/dist/lib/agent/memory/memory-flush-contract.d.ts +49 -0
  29. package/dist/lib/agent/memory/memory-flush-contract.js +16 -0
  30. package/dist/lib/agent/memory/session-store-schema.d.ts +20 -0
  31. package/dist/lib/agent/memory/session-store-schema.js +41 -0
  32. package/dist/lib/agent/presets.d.ts +89 -1
  33. package/dist/lib/agent/presets.js +53 -5
  34. package/dist/lib/agent/tools/_shared.d.ts +7 -15
  35. package/dist/lib/agent/tools/_shared.js +20 -21
  36. package/dist/lib/agent/tools/composite.d.ts +25 -22
  37. package/dist/lib/agent/tools/composite.js +108 -109
  38. package/dist/lib/agent/tools/evolution-tools.d.ts +145 -0
  39. package/dist/lib/agent/tools/evolution-tools.js +161 -0
  40. package/dist/lib/agent/tools/index.d.ts +163 -92
  41. package/dist/lib/agent/tools/index.js +9 -1
  42. package/dist/lib/agent/tools/lifecycle.d.ts +7 -1
  43. package/dist/lib/agent/tools/lifecycle.js +59 -75
  44. package/dist/lib/cli/AiScanService.js +1 -1
  45. package/dist/lib/cli/KnowledgeSyncService.js +1 -1
  46. package/dist/lib/core/AstAnalyzer.d.ts +1 -0
  47. package/dist/lib/{service/bootstrap/DimensionCopyRegistry.d.ts → domain/dimension/DimensionCopy.d.ts} +2 -2
  48. package/dist/lib/{service/bootstrap/DimensionCopyRegistry.js → domain/dimension/DimensionCopy.js} +22 -72
  49. package/dist/lib/domain/dimension/DimensionRegistry.d.ts +54 -0
  50. package/dist/lib/domain/dimension/DimensionRegistry.js +620 -0
  51. package/dist/lib/domain/dimension/DimensionSop.d.ts +55 -0
  52. package/dist/lib/domain/dimension/DimensionSop.js +1604 -0
  53. package/dist/lib/domain/dimension/UnifiedDimension.d.ts +61 -0
  54. package/dist/lib/domain/dimension/UnifiedDimension.js +53 -0
  55. package/dist/lib/domain/dimension/index.d.ts +10 -0
  56. package/dist/lib/domain/dimension/index.js +9 -0
  57. package/dist/lib/domain/knowledge/FieldSpec.d.ts +1 -1
  58. package/dist/lib/domain/knowledge/FieldSpec.js +29 -16
  59. package/dist/lib/domain/knowledge/KnowledgeEntry.d.ts +33 -111
  60. package/dist/lib/domain/knowledge/KnowledgeEntry.js +27 -6
  61. package/dist/lib/domain/knowledge/KnowledgeRepository.d.ts +1 -0
  62. package/dist/lib/domain/knowledge/KnowledgeRepository.js +3 -0
  63. package/dist/lib/domain/knowledge/Lifecycle.js +1 -1
  64. package/dist/lib/domain/knowledge/StyleGuide.d.ts +1 -1
  65. package/dist/lib/domain/knowledge/StyleGuide.js +1 -1
  66. package/dist/lib/domain/knowledge/UnifiedValidator.js +15 -0
  67. package/dist/lib/external/mcp/McpServer.js +4 -0
  68. package/dist/lib/external/mcp/handlers/TargetClassifier.d.ts +1 -1
  69. package/dist/lib/external/mcp/handlers/bootstrap/BootstrapSession.d.ts +8 -16
  70. package/dist/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +10 -10
  71. package/dist/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.d.ts +7 -0
  72. package/dist/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +20 -0
  73. package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.d.ts +52 -132
  74. package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +204 -17
  75. package/dist/lib/external/mcp/handlers/bootstrap/base-dimensions.d.ts +11 -75
  76. package/dist/lib/external/mcp/handlers/bootstrap/base-dimensions.js +40 -191
  77. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.d.ts +13 -78
  78. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +30 -52
  79. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.d.ts +0 -1
  80. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.d.ts +99 -12
  81. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +172 -161
  82. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +7 -17
  83. package/dist/lib/external/mcp/handlers/bootstrap/shared/async-fill-helpers.d.ts +46 -0
  84. package/dist/lib/external/mcp/handlers/bootstrap/shared/async-fill-helpers.js +58 -0
  85. package/dist/lib/external/mcp/handlers/bootstrap/shared/audit-helpers.d.ts +25 -0
  86. package/dist/lib/external/mcp/handlers/bootstrap/shared/audit-helpers.js +47 -0
  87. package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +50 -12
  88. package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +30 -10
  89. package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-text.js +1 -1
  90. package/dist/lib/external/mcp/handlers/bootstrap/shared/handler-types.d.ts +24 -0
  91. package/dist/lib/external/mcp/handlers/bootstrap/shared/handler-types.js +14 -0
  92. package/dist/lib/external/mcp/handlers/bootstrap/shared/panorama-utils.d.ts +14 -0
  93. package/dist/lib/external/mcp/handlers/bootstrap/shared/panorama-utils.js +48 -0
  94. package/dist/lib/external/mcp/handlers/bootstrap/shared/session-helpers.d.ts +21 -0
  95. package/dist/lib/external/mcp/handlers/bootstrap/shared/session-helpers.js +45 -0
  96. package/dist/lib/external/mcp/handlers/bootstrap/shared/skill-generator.d.ts +1 -1
  97. package/dist/lib/external/mcp/handlers/bootstrap/shared/target-file-map.d.ts +27 -0
  98. package/dist/lib/external/mcp/handlers/bootstrap/shared/target-file-map.js +44 -0
  99. package/dist/lib/external/mcp/handlers/bootstrap-external.d.ts +14 -10
  100. package/dist/lib/external/mcp/handlers/bootstrap-external.js +39 -51
  101. package/dist/lib/external/mcp/handlers/bootstrap-internal.d.ts +2 -0
  102. package/dist/lib/external/mcp/handlers/bootstrap-internal.js +115 -82
  103. package/dist/lib/external/mcp/handlers/consolidated.d.ts +4 -4
  104. package/dist/lib/external/mcp/handlers/consolidated.js +107 -332
  105. package/dist/lib/external/mcp/handlers/dimension-complete-external.js +69 -1
  106. package/dist/lib/external/mcp/handlers/evolve-external.d.ts +54 -0
  107. package/dist/lib/external/mcp/handlers/evolve-external.js +226 -0
  108. package/dist/lib/external/mcp/handlers/knowledge.js +26 -2
  109. package/dist/lib/external/mcp/handlers/rescan-external.d.ts +76 -0
  110. package/dist/lib/external/mcp/handlers/rescan-external.js +335 -0
  111. package/dist/lib/external/mcp/handlers/rescan-internal.d.ts +120 -0
  112. package/dist/lib/external/mcp/handlers/rescan-internal.js +359 -0
  113. package/dist/lib/external/mcp/handlers/search.d.ts +6 -5
  114. package/dist/lib/external/mcp/handlers/search.js +6 -5
  115. package/dist/lib/external/mcp/handlers/types.d.ts +2 -1
  116. package/dist/lib/external/mcp/handlers/wiki-external.js +2 -2
  117. package/dist/lib/external/mcp/tools.d.ts +8 -18
  118. package/dist/lib/external/mcp/tools.js +58 -2
  119. package/dist/lib/http/routes/knowledge.js +122 -1
  120. package/dist/lib/http/routes/modules.js +25 -3
  121. package/dist/lib/http/routes/panorama.js +16 -4
  122. package/dist/lib/infrastructure/cache/CacheCoordinator.d.ts +41 -0
  123. package/dist/lib/infrastructure/cache/CacheCoordinator.js +105 -0
  124. package/dist/lib/infrastructure/database/migrations/006_lifecycle_transition_events.d.ts +7 -0
  125. package/dist/lib/infrastructure/database/migrations/006_lifecycle_transition_events.js +28 -0
  126. package/dist/lib/infrastructure/vector/HnswVectorAdapter.js +1 -1
  127. package/dist/lib/injection/ServiceContainer.js +55 -0
  128. package/dist/lib/injection/ServiceMap.d.ts +8 -1
  129. package/dist/lib/injection/modules/KnowledgeModule.js +15 -1
  130. package/dist/lib/repository/knowledge/KnowledgeRepository.impl.d.ts +4 -0
  131. package/dist/lib/repository/knowledge/KnowledgeRepository.impl.js +16 -1
  132. package/dist/lib/service/bootstrap/BootstrapEventEmitter.d.ts +3 -2
  133. package/dist/lib/service/bootstrap/BootstrapEventEmitter.js +1 -1
  134. package/dist/lib/service/bootstrap/DeliveryVerifier.d.ts +51 -0
  135. package/dist/lib/service/bootstrap/DeliveryVerifier.js +163 -0
  136. package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +5 -0
  137. package/dist/lib/service/bootstrap/UiStartupTasks.js +20 -0
  138. package/dist/lib/service/bootstrap/bootstrap-event-types.d.ts +54 -0
  139. package/dist/lib/service/bootstrap/bootstrap-event-types.js +10 -0
  140. package/dist/lib/service/cleanup/CleanupService.d.ts +85 -0
  141. package/dist/lib/service/cleanup/CleanupService.js +324 -0
  142. package/dist/lib/service/delivery/AgentInstructionsGenerator.js +39 -43
  143. package/dist/lib/service/delivery/FileProtection.d.ts +20 -0
  144. package/dist/lib/service/delivery/FileProtection.js +54 -0
  145. package/dist/lib/service/delivery/SkillsSyncer.js +16 -21
  146. package/dist/lib/service/evolution/ContentPatcher.d.ts +44 -0
  147. package/dist/lib/service/evolution/ContentPatcher.js +310 -0
  148. package/dist/lib/service/evolution/ProposalExecutor.d.ts +4 -0
  149. package/dist/lib/service/evolution/ProposalExecutor.js +77 -13
  150. package/dist/lib/service/evolution/RecipeLifecycleSupervisor.d.ts +64 -0
  151. package/dist/lib/service/evolution/RecipeLifecycleSupervisor.js +458 -0
  152. package/dist/lib/service/evolution/RecipeRelevanceAuditor.d.ts +89 -0
  153. package/dist/lib/service/evolution/RecipeRelevanceAuditor.js +492 -0
  154. package/dist/lib/service/evolution/createSupersedeProposal.d.ts +44 -0
  155. package/dist/lib/service/evolution/createSupersedeProposal.js +81 -0
  156. package/dist/lib/service/guard/ComplianceReporter.d.ts +4 -0
  157. package/dist/lib/service/guard/ComplianceReporter.js +51 -0
  158. package/dist/lib/service/guard/GuardCheckEngine.js +5 -4
  159. package/dist/lib/service/knowledge/ConfidenceRouter.js +1 -1
  160. package/dist/lib/service/knowledge/KnowledgeService.d.ts +11 -1
  161. package/dist/lib/service/knowledge/KnowledgeService.js +44 -4
  162. package/dist/lib/service/knowledge/RecipeProductionGateway.d.ts +225 -0
  163. package/dist/lib/service/knowledge/RecipeProductionGateway.js +384 -0
  164. package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +3 -2
  165. package/dist/lib/service/panorama/DimensionAnalyzer.js +15 -140
  166. package/dist/lib/service/search/BM25Scorer.d.ts +2 -2
  167. package/dist/lib/service/search/SearchEngine.d.ts +11 -10
  168. package/dist/lib/service/search/SearchEngine.js +38 -36
  169. package/dist/lib/service/search/SearchTypes.d.ts +14 -8
  170. package/dist/lib/service/search/SearchTypes.js +1 -1
  171. package/dist/lib/service/search/tokenizer.d.ts +1 -1
  172. package/dist/lib/service/search/tokenizer.js +2 -2
  173. package/dist/lib/shared/schemas/common.d.ts +4 -4
  174. package/dist/lib/shared/schemas/http-requests.d.ts +12 -1
  175. package/dist/lib/shared/schemas/http-requests.js +8 -0
  176. package/dist/lib/shared/schemas/mcp-tools.d.ts +32 -2
  177. package/dist/lib/shared/schemas/mcp-tools.js +38 -0
  178. package/dist/lib/types/evolution.d.ts +135 -0
  179. package/dist/lib/types/evolution.js +6 -0
  180. package/dist/lib/types/graph-shared.d.ts +25 -0
  181. package/dist/lib/types/graph-shared.js +7 -0
  182. package/dist/lib/types/knowledge-wire.d.ts +131 -0
  183. package/dist/lib/types/knowledge-wire.js +7 -0
  184. package/dist/lib/types/project-snapshot-builder.d.ts +19 -0
  185. package/dist/lib/types/project-snapshot-builder.js +189 -0
  186. package/dist/lib/types/project-snapshot.d.ts +399 -0
  187. package/dist/lib/types/project-snapshot.js +17 -0
  188. package/dist/lib/types/search-wire.d.ts +46 -0
  189. package/dist/lib/types/search-wire.js +7 -0
  190. package/dist/lib/types/snapshot-views.d.ts +58 -0
  191. package/dist/lib/types/snapshot-views.js +103 -0
  192. package/package.json +1 -1
  193. package/skills/autosnippet-recipes/SKILL.md +1 -1
  194. package/templates/instructions/agent-static.md +2 -0
  195. package/templates/instructions/conventions.md +3 -1
  196. package/templates/recipes-setup/README.md +2 -2
  197. package/dashboard/dist/assets/icons-BJ2mUBi8.js +0 -1
  198. package/dashboard/dist/assets/index-B659K9t5.js +0 -128
  199. package/dashboard/dist/assets/index-NCm40PMD.css +0 -1
  200. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.d.ts +0 -169
  201. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +0 -727
  202. package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.d.ts +0 -370
  203. 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
+ }
@@ -17,6 +17,8 @@
17
17
  */
18
18
  import type { SignalBus } from '../../infrastructure/signal/SignalBus.js';
19
19
  import type { ProposalRepository, ProposalType } from '../../repository/evolution/ProposalRepository.js';
20
+ import type { ContentPatcher } from './ContentPatcher.js';
21
+ import type { RecipeLifecycleSupervisor } from './RecipeLifecycleSupervisor.js';
20
22
  interface DatabaseLike {
21
23
  prepare(sql: string): {
22
24
  all(...params: unknown[]): Record<string, unknown>[];
@@ -51,6 +53,8 @@ export declare class ProposalExecutor {
51
53
  #private;
52
54
  constructor(db: DatabaseLike, repo: ProposalRepository, options?: {
53
55
  signalBus?: SignalBus;
56
+ contentPatcher?: ContentPatcher;
57
+ supervisor?: RecipeLifecycleSupervisor;
54
58
  });
55
59
  /**
56
60
  * 定期调用(UiStartupTasks Stage 5)
@@ -26,11 +26,15 @@ export class ProposalExecutor {
26
26
  #db;
27
27
  #repo;
28
28
  #signalBus;
29
+ #contentPatcher;
30
+ #supervisor;
29
31
  #logger = Logger.getInstance();
30
32
  constructor(db, repo, options = {}) {
31
33
  this.#db = db;
32
34
  this.#repo = repo;
33
35
  this.#signalBus = options.signalBus ?? null;
36
+ this.#contentPatcher = options.contentPatcher ?? null;
37
+ this.#supervisor = options.supervisor ?? null;
34
38
  }
35
39
  /**
36
40
  * 定期调用(UiStartupTasks Stage 5)
@@ -100,9 +104,22 @@ export class ProposalExecutor {
100
104
  const fpOk = metrics.ruleFalsePositiveRate < 0.4;
101
105
  const hasUsage = metrics.guardHits > 0 || metrics.searchHits > 0;
102
106
  if (fpOk && hasUsage) {
103
- // 通过 → Recipe 回到 staging(重新走 ConfidenceRouter
104
- this.#transitionRecipe(proposal.targetRecipeId, 'staging');
105
- this.#repo.markExecuted(proposal.id, `观察期表现合格: FP=${(metrics.ruleFalsePositiveRate * 100).toFixed(0)}%, hits=${metrics.guardHits + metrics.searchHits}`);
107
+ // 通过 → evolving ContentPatcher staging(重走 Grace Period
108
+ this.#transitionRecipe(proposal.targetRecipeId, 'evolving', 'proposal-attach', proposal.id);
109
+ const patchResult = this.#tryApplyPatch(proposal, 'agent-suggestion');
110
+ if (patchResult?.skipped || (!patchResult?.success && patchResult !== null)) {
111
+ // Patch 跳过或失败 → 回退到 active,避免无变更进入 staging 导致空进化循环
112
+ this.#transitionRecipe(proposal.targetRecipeId, 'active', 'content-patch-complete', proposal.id);
113
+ const skipInfo = patchResult?.skipReason ? `: ${patchResult.skipReason}` : '';
114
+ this.#repo.markExecuted(proposal.id, `观察期合格但 patch 未生效${skipInfo}, 回退 active`);
115
+ }
116
+ else {
117
+ this.#transitionRecipe(proposal.targetRecipeId, 'staging', 'content-patch-complete', proposal.id);
118
+ const patchInfo = patchResult?.success
119
+ ? `, patched=[${patchResult.fieldsPatched.join(',')}]`
120
+ : '';
121
+ this.#repo.markExecuted(proposal.id, `观察期表现合格: FP=${(metrics.ruleFalsePositiveRate * 100).toFixed(0)}%, hits=${metrics.guardHits + metrics.searchHits}${patchInfo}`);
122
+ }
106
123
  result.executed.push({
107
124
  id: proposal.id,
108
125
  type: proposal.type,
@@ -151,7 +168,7 @@ export class ProposalExecutor {
151
168
  const newUsage = newMetrics.guardHits + newMetrics.searchHits;
152
169
  if (newUsage >= oldUsage * 0.5 || oldUsage === 0) {
153
170
  // 新 Recipe 表现足够 → 旧 Recipe → decaying,建立 deprecated_by
154
- this.#transitionRecipe(proposal.targetRecipeId, 'decaying');
171
+ this.#transitionRecipe(proposal.targetRecipeId, 'decaying', 'proposal-execution', proposal.id);
155
172
  this.#createDeprecatedByEdge(newRecipeId, proposal.targetRecipeId);
156
173
  this.#repo.markExecuted(proposal.id, `supersede executed: new usage=${newUsage}, old usage=${oldUsage}`);
157
174
  result.executed.push({
@@ -192,12 +209,12 @@ export class ProposalExecutor {
192
209
  // 无回升 → 根据 decayScore 决定操作
193
210
  if (currentDecay <= 19) {
194
211
  // 死亡 → 直接 deprecated
195
- this.#transitionRecipe(proposal.targetRecipeId, 'deprecated');
212
+ this.#transitionRecipe(proposal.targetRecipeId, 'deprecated', 'proposal-execution', proposal.id);
196
213
  this.#repo.markExecuted(proposal.id, `deprecated (dead): decayScore=${currentDecay}`);
197
214
  }
198
215
  else if (currentDecay <= 40) {
199
216
  // 严重 → decaying
200
- this.#transitionRecipe(proposal.targetRecipeId, 'decaying');
217
+ this.#transitionRecipe(proposal.targetRecipeId, 'decaying', 'proposal-execution', proposal.id);
201
218
  this.#repo.markExecuted(proposal.id, `decaying (severe): decayScore=${currentDecay}`);
202
219
  }
203
220
  else {
@@ -221,11 +238,24 @@ export class ProposalExecutor {
221
238
  }
222
239
  /* ── correction ── */
223
240
  #executeCorrection(proposal, metrics, result) {
224
- // correction 低风险,到期直接执行(Recipe → staging 重新审核)
241
+ // correction 低风险,到期直接执行(Recipe → evolving → patch → staging 重新审核)
225
242
  const hasUsage = metrics.guardHits > 0 || metrics.searchHits > 0;
226
243
  if (hasUsage) {
227
- this.#transitionRecipe(proposal.targetRecipeId, 'staging');
228
- this.#repo.markExecuted(proposal.id, 'correction applied, recipe → staging for re-review');
244
+ this.#transitionRecipe(proposal.targetRecipeId, 'evolving', 'proposal-attach', proposal.id);
245
+ const patchResult = this.#tryApplyPatch(proposal, 'correction');
246
+ if (patchResult?.skipped || (!patchResult?.success && patchResult !== null)) {
247
+ // Patch 跳过或失败 → 回退到 active
248
+ this.#transitionRecipe(proposal.targetRecipeId, 'active', 'content-patch-complete', proposal.id);
249
+ const skipInfo = patchResult?.skipReason ? `: ${patchResult.skipReason}` : '';
250
+ this.#repo.markExecuted(proposal.id, `correction patch 未生效${skipInfo}, 回退 active`);
251
+ }
252
+ else {
253
+ this.#transitionRecipe(proposal.targetRecipeId, 'staging', 'content-patch-complete', proposal.id);
254
+ const patchInfo = patchResult?.success
255
+ ? `, patched=[${patchResult.fieldsPatched.join(',')}]`
256
+ : '';
257
+ this.#repo.markExecuted(proposal.id, `correction applied, recipe → evolving → staging for re-review${patchInfo}`);
258
+ }
229
259
  result.executed.push({
230
260
  id: proposal.id,
231
261
  type: proposal.type,
@@ -306,10 +336,28 @@ export class ProposalExecutor {
306
336
  .get(recipeId);
307
337
  return row ?? null;
308
338
  }
309
- #transitionRecipe(recipeId, newLifecycle) {
310
- this.#db
311
- .prepare(`UPDATE knowledge_entries SET lifecycle = ?, updatedAt = ? WHERE id = ?`)
312
- .run(newLifecycle, Date.now(), recipeId);
339
+ #transitionRecipe(recipeId, newLifecycle, trigger = 'proposal-execution', proposalId) {
340
+ if (this.#supervisor) {
341
+ const result = this.#supervisor.transition({
342
+ recipeId,
343
+ targetState: newLifecycle,
344
+ trigger,
345
+ proposalId,
346
+ operatorId: 'system',
347
+ });
348
+ if (!result.success) {
349
+ this.#logger.warn(`[ProposalExecutor] Supervisor rejected transition ${recipeId} → ${newLifecycle}: ${result.error}`);
350
+ // Fallback to raw DB update for backward compatibility
351
+ this.#db
352
+ .prepare(`UPDATE knowledge_entries SET lifecycle = ?, updatedAt = ? WHERE id = ?`)
353
+ .run(newLifecycle, Date.now(), recipeId);
354
+ }
355
+ }
356
+ else {
357
+ this.#db
358
+ .prepare(`UPDATE knowledge_entries SET lifecycle = ?, updatedAt = ? WHERE id = ?`)
359
+ .run(newLifecycle, Date.now(), recipeId);
360
+ }
313
361
  }
314
362
  #restoreRecipe(recipeId) {
315
363
  // 恢复到 active(evolving/decaying → active)
@@ -318,6 +366,22 @@ export class ProposalExecutor {
318
366
  this.#transitionRecipe(recipeId, 'active');
319
367
  }
320
368
  }
369
+ /**
370
+ * 尝试通过 ContentPatcher 应用 Proposal 中的 suggestedChanges
371
+ * 降级容忍:无 ContentPatcher 或 patch 失败时返回 null/skipped,不阻塞状态转移
372
+ */
373
+ #tryApplyPatch(proposal, patchSource) {
374
+ if (!this.#contentPatcher) {
375
+ return null;
376
+ }
377
+ try {
378
+ return this.#contentPatcher.applyProposal(proposal, patchSource);
379
+ }
380
+ catch (err) {
381
+ this.#logger.warn(`[ProposalExecutor] ContentPatcher failed for proposal ${proposal.id}: ${err instanceof Error ? err.message : String(err)}`);
382
+ return null;
383
+ }
384
+ }
321
385
  #createDeprecatedByEdge(newRecipeId, oldRecipeId) {
322
386
  const now = Date.now();
323
387
  try {
@@ -0,0 +1,64 @@
1
+ /**
2
+ * RecipeLifecycleSupervisor — 统一状态转移管理层
3
+ *
4
+ * 核心职责:
5
+ * 1. Guard 前置检查 — 验证转移是否合法(VALID_TRANSITIONS + 扩展条件)
6
+ * 2. Entry/Exit Actions — 进入/离开状态的固定副作用
7
+ * 3. Event 记录 — 每次转移记录为不可变 TransitionEvent
8
+ * 4. 超时监控 — 中间态超时自动处理
9
+ * 5. 健康摘要 — 全局状态分布与卡死检测
10
+ *
11
+ * 所有状态变更建议通过 Supervisor,目前作为可选增强层存在。
12
+ * 不改变现有 ProposalExecutor/StagingManager 的内部逻辑,
13
+ * 而是在它们之上提供审计、超时检查和健康监控。
14
+ *
15
+ * 触发时机:UiStartupTasks Stage 6(新增)
16
+ *
17
+ * @module service/evolution/RecipeLifecycleSupervisor
18
+ */
19
+ import type { SignalBus } from '../../infrastructure/signal/SignalBus.js';
20
+ import type { LifecycleHealthSummary, TimeoutCheckResult, TransitionEvent, TransitionRequest, TransitionResult } from '../../types/evolution.js';
21
+ interface DatabaseLike {
22
+ prepare(sql: string): {
23
+ all(...params: unknown[]): Record<string, unknown>[];
24
+ get(...params: unknown[]): Record<string, unknown> | undefined;
25
+ run(...params: unknown[]): {
26
+ changes: number;
27
+ };
28
+ };
29
+ }
30
+ export declare class RecipeLifecycleSupervisor {
31
+ #private;
32
+ constructor(db: DatabaseLike, options?: {
33
+ signalBus?: SignalBus;
34
+ });
35
+ /**
36
+ * 执行状态转移 — 统一入口
37
+ *
38
+ * 1. 获取当前状态
39
+ * 2. Guard 检查(合法转移 + 扩展条件)
40
+ * 3. Exit Action(离开旧状态)
41
+ * 4. 更新 lifecycle
42
+ * 5. Entry Action(进入新状态)
43
+ * 6. 记录 TransitionEvent
44
+ * 7. 发射信号
45
+ */
46
+ transition(request: TransitionRequest): TransitionResult;
47
+ /**
48
+ * 检查中间态超时 + 自动处理
49
+ *
50
+ * 处理范围:
51
+ * - evolving > 7d → active(回退)
52
+ * - decaying > 30d → deprecated
53
+ */
54
+ checkTimeouts(): TimeoutCheckResult;
55
+ /**
56
+ * 查询 Recipe 的转移历史
57
+ */
58
+ getTransitionHistory(recipeId: string, limit?: number): TransitionEvent[];
59
+ /**
60
+ * 获取全局状态健康摘要
61
+ */
62
+ getHealthSummary(): LifecycleHealthSummary;
63
+ }
64
+ export {};