autosnippet 3.3.5 → 3.3.7

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 (279) hide show
  1. package/dashboard/dist/assets/icons-FHns2ypa.js +1 -0
  2. package/dashboard/dist/assets/index-BRJv5Y3r.js +135 -0
  3. package/dashboard/dist/assets/index-DzoB7kxK.css +1 -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 +137 -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/AgentRuntime.d.ts +2 -2
  11. package/dist/lib/agent/AgentRuntime.js +26 -18
  12. package/dist/lib/agent/capabilities.d.ts +11 -0
  13. package/dist/lib/agent/capabilities.js +29 -5
  14. package/dist/lib/agent/context/ExplorationTracker.js +10 -1
  15. package/dist/lib/agent/context/exploration/ExplorationStrategies.d.ts +2 -0
  16. package/dist/lib/agent/context/exploration/ExplorationStrategies.js +2 -2
  17. package/dist/lib/agent/domain/ChatAgentTasks.js +4 -0
  18. package/dist/lib/agent/domain/insight-analyst.d.ts +47 -3
  19. package/dist/lib/agent/domain/insight-analyst.js +111 -11
  20. package/dist/lib/agent/domain/insight-evolver.d.ts +69 -0
  21. package/dist/lib/agent/domain/insight-evolver.js +230 -0
  22. package/dist/lib/agent/domain/insight-gate.d.ts +42 -0
  23. package/dist/lib/agent/domain/insight-gate.js +41 -0
  24. package/dist/lib/agent/domain/insight-producer.d.ts +27 -2
  25. package/dist/lib/agent/domain/insight-producer.js +60 -5
  26. package/dist/lib/agent/domain/scan-prompts.js +10 -7
  27. package/dist/lib/agent/forced-summary.js +7 -2
  28. package/dist/lib/agent/memory/ActiveContext.d.ts +2 -28
  29. package/dist/lib/agent/memory/MemoryCoordinator.d.ts +2 -2
  30. package/dist/lib/agent/memory/SessionStore.d.ts +6 -12
  31. package/dist/lib/agent/memory/SessionStore.js +9 -15
  32. package/dist/lib/agent/memory/memory-flush-contract.d.ts +49 -0
  33. package/dist/lib/agent/memory/memory-flush-contract.js +16 -0
  34. package/dist/lib/agent/memory/session-store-schema.d.ts +20 -0
  35. package/dist/lib/agent/memory/session-store-schema.js +41 -0
  36. package/dist/lib/agent/presets.d.ts +89 -1
  37. package/dist/lib/agent/presets.js +53 -5
  38. package/dist/lib/agent/tools/_shared.d.ts +7 -15
  39. package/dist/lib/agent/tools/_shared.js +20 -21
  40. package/dist/lib/agent/tools/composite.d.ts +25 -22
  41. package/dist/lib/agent/tools/composite.js +108 -109
  42. package/dist/lib/agent/tools/evolution-tools.d.ts +145 -0
  43. package/dist/lib/agent/tools/evolution-tools.js +161 -0
  44. package/dist/lib/agent/tools/index.d.ts +163 -92
  45. package/dist/lib/agent/tools/index.js +9 -1
  46. package/dist/lib/agent/tools/lifecycle.d.ts +7 -1
  47. package/dist/lib/agent/tools/lifecycle.js +59 -75
  48. package/dist/lib/cli/AiScanService.js +5 -5
  49. package/dist/lib/cli/KnowledgeSyncService.js +1 -1
  50. package/dist/lib/core/AstAnalyzer.d.ts +1 -0
  51. package/dist/lib/core/discovery/ConfigWatcher.d.ts +64 -0
  52. package/dist/lib/core/discovery/ConfigWatcher.js +336 -0
  53. package/dist/lib/core/discovery/CustomConfigDiscoverer.d.ts +30 -0
  54. package/dist/lib/core/discovery/CustomConfigDiscoverer.js +1305 -0
  55. package/dist/lib/core/discovery/DiscovererPreference.d.ts +44 -0
  56. package/dist/lib/core/discovery/DiscovererPreference.js +141 -0
  57. package/dist/lib/core/discovery/DiscovererRegistry.d.ts +10 -1
  58. package/dist/lib/core/discovery/DiscovererRegistry.js +42 -2
  59. package/dist/lib/core/discovery/ProjectDiscoverer.d.ts +19 -0
  60. package/dist/lib/core/discovery/index.d.ts +2 -0
  61. package/dist/lib/core/discovery/index.js +4 -0
  62. package/dist/lib/core/discovery/parsers/CMakeParser.d.ts +32 -0
  63. package/dist/lib/core/discovery/parsers/CMakeParser.js +148 -0
  64. package/dist/lib/core/discovery/parsers/GradleDslParser.d.ts +43 -0
  65. package/dist/lib/core/discovery/parsers/GradleDslParser.js +171 -0
  66. package/dist/lib/core/discovery/parsers/JsonConfigParser.d.ts +45 -0
  67. package/dist/lib/core/discovery/parsers/JsonConfigParser.js +122 -0
  68. package/dist/lib/core/discovery/parsers/RubyDslParser.d.ts +49 -0
  69. package/dist/lib/core/discovery/parsers/RubyDslParser.js +282 -0
  70. package/dist/lib/core/discovery/parsers/StarlarkParser.d.ts +33 -0
  71. package/dist/lib/core/discovery/parsers/StarlarkParser.js +229 -0
  72. package/dist/lib/core/discovery/parsers/YamlConfigParser.d.ts +37 -0
  73. package/dist/lib/core/discovery/parsers/YamlConfigParser.js +212 -0
  74. package/dist/lib/{service/bootstrap/DimensionCopyRegistry.d.ts → domain/dimension/DimensionCopy.d.ts} +2 -2
  75. package/dist/lib/{service/bootstrap/DimensionCopyRegistry.js → domain/dimension/DimensionCopy.js} +22 -72
  76. package/dist/lib/domain/dimension/DimensionRegistry.d.ts +54 -0
  77. package/dist/lib/domain/dimension/DimensionRegistry.js +620 -0
  78. package/dist/lib/domain/dimension/DimensionSop.d.ts +55 -0
  79. package/dist/lib/domain/dimension/DimensionSop.js +1604 -0
  80. package/dist/lib/domain/dimension/UnifiedDimension.d.ts +61 -0
  81. package/dist/lib/domain/dimension/UnifiedDimension.js +53 -0
  82. package/dist/lib/domain/dimension/index.d.ts +10 -0
  83. package/dist/lib/domain/dimension/index.js +9 -0
  84. package/dist/lib/domain/knowledge/FieldSpec.d.ts +1 -1
  85. package/dist/lib/domain/knowledge/FieldSpec.js +29 -16
  86. package/dist/lib/domain/knowledge/KnowledgeEntry.d.ts +40 -112
  87. package/dist/lib/domain/knowledge/KnowledgeEntry.js +44 -9
  88. package/dist/lib/domain/knowledge/KnowledgeRepository.d.ts +1 -0
  89. package/dist/lib/domain/knowledge/KnowledgeRepository.js +3 -0
  90. package/dist/lib/domain/knowledge/Lifecycle.js +1 -1
  91. package/dist/lib/domain/knowledge/StyleGuide.d.ts +1 -1
  92. package/dist/lib/domain/knowledge/StyleGuide.js +1 -1
  93. package/dist/lib/domain/knowledge/UnifiedValidator.js +15 -0
  94. package/dist/lib/external/ai/AiProvider.d.ts +12 -0
  95. package/dist/lib/external/ai/AiProvider.js +24 -0
  96. package/dist/lib/external/ai/AiProviderManager.d.ts +101 -0
  97. package/dist/lib/external/ai/AiProviderManager.js +193 -0
  98. package/dist/lib/external/ai/providers/ClaudeProvider.js +11 -0
  99. package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +18 -0
  100. package/dist/lib/external/ai/providers/MockProvider.d.ts +21 -3
  101. package/dist/lib/external/ai/providers/MockProvider.js +290 -14
  102. package/dist/lib/external/ai/providers/OpenAiProvider.js +16 -0
  103. package/dist/lib/external/lark/LarkTransport.d.ts +5 -1
  104. package/dist/lib/external/lark/LarkTransport.js +10 -2
  105. package/dist/lib/external/mcp/McpServer.js +4 -0
  106. package/dist/lib/external/mcp/handlers/TargetClassifier.d.ts +1 -1
  107. package/dist/lib/external/mcp/handlers/bootstrap/BootstrapSession.d.ts +8 -16
  108. package/dist/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +10 -10
  109. package/dist/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.d.ts +7 -0
  110. package/dist/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +20 -0
  111. package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.d.ts +52 -132
  112. package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +204 -17
  113. package/dist/lib/external/mcp/handlers/bootstrap/base-dimensions.d.ts +11 -75
  114. package/dist/lib/external/mcp/handlers/bootstrap/base-dimensions.js +40 -191
  115. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.d.ts +13 -78
  116. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +30 -52
  117. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.d.ts +0 -1
  118. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.d.ts +20 -0
  119. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.js +432 -0
  120. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.d.ts +99 -12
  121. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +188 -169
  122. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +7 -17
  123. package/dist/lib/external/mcp/handlers/bootstrap/refine.js +8 -0
  124. package/dist/lib/external/mcp/handlers/bootstrap/shared/async-fill-helpers.d.ts +46 -0
  125. package/dist/lib/external/mcp/handlers/bootstrap/shared/async-fill-helpers.js +58 -0
  126. package/dist/lib/external/mcp/handlers/bootstrap/shared/audit-helpers.d.ts +25 -0
  127. package/dist/lib/external/mcp/handlers/bootstrap/shared/audit-helpers.js +47 -0
  128. package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +50 -12
  129. package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +30 -10
  130. package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-text.js +1 -1
  131. package/dist/lib/external/mcp/handlers/bootstrap/shared/handler-types.d.ts +24 -0
  132. package/dist/lib/external/mcp/handlers/bootstrap/shared/handler-types.js +14 -0
  133. package/dist/lib/external/mcp/handlers/bootstrap/shared/panorama-utils.d.ts +14 -0
  134. package/dist/lib/external/mcp/handlers/bootstrap/shared/panorama-utils.js +48 -0
  135. package/dist/lib/external/mcp/handlers/bootstrap/shared/session-helpers.d.ts +21 -0
  136. package/dist/lib/external/mcp/handlers/bootstrap/shared/session-helpers.js +45 -0
  137. package/dist/lib/external/mcp/handlers/bootstrap/shared/skill-generator.d.ts +1 -1
  138. package/dist/lib/external/mcp/handlers/bootstrap/shared/target-file-map.d.ts +27 -0
  139. package/dist/lib/external/mcp/handlers/bootstrap/shared/target-file-map.js +44 -0
  140. package/dist/lib/external/mcp/handlers/bootstrap-external.d.ts +23 -10
  141. package/dist/lib/external/mcp/handlers/bootstrap-external.js +41 -51
  142. package/dist/lib/external/mcp/handlers/bootstrap-internal.d.ts +2 -0
  143. package/dist/lib/external/mcp/handlers/bootstrap-internal.js +117 -82
  144. package/dist/lib/external/mcp/handlers/consolidated.d.ts +4 -4
  145. package/dist/lib/external/mcp/handlers/consolidated.js +108 -332
  146. package/dist/lib/external/mcp/handlers/dimension-complete-external.js +71 -2
  147. package/dist/lib/external/mcp/handlers/evolve-external.d.ts +54 -0
  148. package/dist/lib/external/mcp/handlers/evolve-external.js +229 -0
  149. package/dist/lib/external/mcp/handlers/knowledge.js +30 -5
  150. package/dist/lib/external/mcp/handlers/rescan-external.d.ts +76 -0
  151. package/dist/lib/external/mcp/handlers/rescan-external.js +335 -0
  152. package/dist/lib/external/mcp/handlers/rescan-internal.d.ts +120 -0
  153. package/dist/lib/external/mcp/handlers/rescan-internal.js +359 -0
  154. package/dist/lib/external/mcp/handlers/search.d.ts +6 -5
  155. package/dist/lib/external/mcp/handlers/search.js +6 -5
  156. package/dist/lib/external/mcp/handlers/types.d.ts +2 -1
  157. package/dist/lib/external/mcp/handlers/wiki-external.js +2 -2
  158. package/dist/lib/external/mcp/tools.d.ts +8 -18
  159. package/dist/lib/external/mcp/tools.js +58 -2
  160. package/dist/lib/http/routes/ai.js +111 -30
  161. package/dist/lib/http/routes/candidates.js +11 -4
  162. package/dist/lib/http/routes/commands.js +10 -1
  163. package/dist/lib/http/routes/health.js +11 -0
  164. package/dist/lib/http/routes/knowledge.js +122 -1
  165. package/dist/lib/http/routes/modules.js +52 -3
  166. package/dist/lib/http/routes/panorama.js +16 -4
  167. package/dist/lib/http/routes/recipes.js +7 -0
  168. package/dist/lib/http/utils/routeHelpers.js +2 -1
  169. package/dist/lib/infrastructure/cache/CacheCoordinator.d.ts +41 -0
  170. package/dist/lib/infrastructure/cache/CacheCoordinator.js +105 -0
  171. package/dist/lib/infrastructure/database/migrations/006_lifecycle_transition_events.d.ts +7 -0
  172. package/dist/lib/infrastructure/database/migrations/006_lifecycle_transition_events.js +28 -0
  173. package/dist/lib/infrastructure/vector/HnswVectorAdapter.js +1 -1
  174. package/dist/lib/injection/ServiceContainer.d.ts +6 -5
  175. package/dist/lib/injection/ServiceContainer.js +64 -25
  176. package/dist/lib/injection/ServiceMap.d.ts +10 -1
  177. package/dist/lib/injection/modules/AiModule.d.ts +6 -9
  178. package/dist/lib/injection/modules/AiModule.js +82 -39
  179. package/dist/lib/injection/modules/KnowledgeModule.js +15 -1
  180. package/dist/lib/injection/modules/PanoramaModule.js +1 -1
  181. package/dist/lib/repository/knowledge/KnowledgeRepository.impl.d.ts +4 -0
  182. package/dist/lib/repository/knowledge/KnowledgeRepository.impl.js +16 -1
  183. package/dist/lib/service/bootstrap/BootstrapEventEmitter.d.ts +3 -2
  184. package/dist/lib/service/bootstrap/BootstrapEventEmitter.js +1 -1
  185. package/dist/lib/service/bootstrap/DeliveryVerifier.d.ts +51 -0
  186. package/dist/lib/service/bootstrap/DeliveryVerifier.js +163 -0
  187. package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +5 -0
  188. package/dist/lib/service/bootstrap/UiStartupTasks.js +20 -0
  189. package/dist/lib/service/bootstrap/bootstrap-event-types.d.ts +54 -0
  190. package/dist/lib/service/bootstrap/bootstrap-event-types.js +10 -0
  191. package/dist/lib/service/cleanup/CleanupService.d.ts +132 -0
  192. package/dist/lib/service/cleanup/CleanupService.js +571 -0
  193. package/dist/lib/service/delivery/AgentInstructionsGenerator.js +39 -43
  194. package/dist/lib/service/delivery/FileProtection.d.ts +20 -0
  195. package/dist/lib/service/delivery/FileProtection.js +54 -0
  196. package/dist/lib/service/delivery/SkillsSyncer.js +16 -21
  197. package/dist/lib/service/evolution/ContentPatcher.d.ts +44 -0
  198. package/dist/lib/service/evolution/ContentPatcher.js +310 -0
  199. package/dist/lib/service/evolution/ProposalExecutor.d.ts +4 -0
  200. package/dist/lib/service/evolution/ProposalExecutor.js +77 -13
  201. package/dist/lib/service/evolution/RecipeLifecycleSupervisor.d.ts +64 -0
  202. package/dist/lib/service/evolution/RecipeLifecycleSupervisor.js +458 -0
  203. package/dist/lib/service/evolution/RecipeRelevanceAuditor.d.ts +89 -0
  204. package/dist/lib/service/evolution/RecipeRelevanceAuditor.js +492 -0
  205. package/dist/lib/service/evolution/createSupersedeProposal.d.ts +44 -0
  206. package/dist/lib/service/evolution/createSupersedeProposal.js +81 -0
  207. package/dist/lib/service/guard/ComplianceReporter.d.ts +4 -0
  208. package/dist/lib/service/guard/ComplianceReporter.js +51 -0
  209. package/dist/lib/service/guard/GuardCheckEngine.js +5 -4
  210. package/dist/lib/service/knowledge/CodeEntityGraph.d.ts +6 -0
  211. package/dist/lib/service/knowledge/CodeEntityGraph.js +16 -0
  212. package/dist/lib/service/knowledge/ConfidenceRouter.js +1 -1
  213. package/dist/lib/service/knowledge/KnowledgeService.d.ts +11 -1
  214. package/dist/lib/service/knowledge/KnowledgeService.js +67 -14
  215. package/dist/lib/service/knowledge/RecipeProductionGateway.d.ts +225 -0
  216. package/dist/lib/service/knowledge/RecipeProductionGateway.js +384 -0
  217. package/dist/lib/service/module/ModuleService.js +10 -19
  218. package/dist/lib/service/panorama/CouplingAnalyzer.d.ts +10 -1
  219. package/dist/lib/service/panorama/CouplingAnalyzer.js +44 -2
  220. package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +4 -3
  221. package/dist/lib/service/panorama/DimensionAnalyzer.js +40 -151
  222. package/dist/lib/service/panorama/LayerInferrer.d.ts +16 -1
  223. package/dist/lib/service/panorama/LayerInferrer.js +118 -1
  224. package/dist/lib/service/panorama/ModuleDiscoverer.d.ts +9 -0
  225. package/dist/lib/service/panorama/ModuleDiscoverer.js +58 -2
  226. package/dist/lib/service/panorama/PanoramaAggregator.d.ts +6 -2
  227. package/dist/lib/service/panorama/PanoramaAggregator.js +84 -6
  228. package/dist/lib/service/panorama/PanoramaScanner.js +28 -0
  229. package/dist/lib/service/panorama/PanoramaService.js +10 -5
  230. package/dist/lib/service/panorama/PanoramaTypes.d.ts +38 -0
  231. package/dist/lib/service/panorama/RoleRefiner.d.ts +2 -0
  232. package/dist/lib/service/panorama/RoleRefiner.js +41 -0
  233. package/dist/lib/service/panorama/TechStackProfiler.d.ts +13 -0
  234. package/dist/lib/service/panorama/TechStackProfiler.js +191 -0
  235. package/dist/lib/service/search/BM25Scorer.d.ts +2 -2
  236. package/dist/lib/service/search/SearchEngine.d.ts +11 -10
  237. package/dist/lib/service/search/SearchEngine.js +38 -36
  238. package/dist/lib/service/search/SearchTypes.d.ts +14 -8
  239. package/dist/lib/service/search/SearchTypes.js +1 -1
  240. package/dist/lib/service/search/tokenizer.d.ts +1 -1
  241. package/dist/lib/service/search/tokenizer.js +2 -2
  242. package/dist/lib/service/skills/SignalCollector.d.ts +1 -0
  243. package/dist/lib/service/skills/SignalCollector.js +6 -5
  244. package/dist/lib/service/vector/ContextualEnricher.d.ts +1 -0
  245. package/dist/lib/service/vector/ContextualEnricher.js +4 -0
  246. package/dist/lib/shared/LanguageService.js +3 -0
  247. package/dist/lib/shared/developer-identity.d.ts +18 -0
  248. package/dist/lib/shared/developer-identity.js +62 -0
  249. package/dist/lib/shared/schemas/common.d.ts +4 -4
  250. package/dist/lib/shared/schemas/http-requests.d.ts +20 -18
  251. package/dist/lib/shared/schemas/http-requests.js +17 -6
  252. package/dist/lib/shared/schemas/mcp-tools.d.ts +32 -2
  253. package/dist/lib/shared/schemas/mcp-tools.js +38 -0
  254. package/dist/lib/types/evolution.d.ts +135 -0
  255. package/dist/lib/types/evolution.js +6 -0
  256. package/dist/lib/types/graph-shared.d.ts +25 -0
  257. package/dist/lib/types/graph-shared.js +7 -0
  258. package/dist/lib/types/knowledge-wire.d.ts +132 -0
  259. package/dist/lib/types/knowledge-wire.js +7 -0
  260. package/dist/lib/types/project-snapshot-builder.d.ts +19 -0
  261. package/dist/lib/types/project-snapshot-builder.js +189 -0
  262. package/dist/lib/types/project-snapshot.d.ts +399 -0
  263. package/dist/lib/types/project-snapshot.js +17 -0
  264. package/dist/lib/types/search-wire.d.ts +46 -0
  265. package/dist/lib/types/search-wire.js +7 -0
  266. package/dist/lib/types/snapshot-views.d.ts +58 -0
  267. package/dist/lib/types/snapshot-views.js +103 -0
  268. package/package.json +1 -1
  269. package/skills/autosnippet-recipes/SKILL.md +1 -1
  270. package/templates/instructions/agent-static.md +2 -0
  271. package/templates/instructions/conventions.md +3 -1
  272. package/templates/recipes-setup/README.md +2 -2
  273. package/dashboard/dist/assets/icons-BJ2mUBi8.js +0 -1
  274. package/dashboard/dist/assets/index-B659K9t5.js +0 -128
  275. package/dashboard/dist/assets/index-NCm40PMD.css +0 -1
  276. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.d.ts +0 -169
  277. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +0 -727
  278. package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.d.ts +0 -370
  279. package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +0 -821
@@ -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 {};
@@ -0,0 +1,458 @@
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 { randomUUID } from 'node:crypto';
20
+ import { isValidTransition } from '../../domain/knowledge/Lifecycle.js';
21
+ import Logger from '../../infrastructure/logging/Logger.js';
22
+ /* ────────────────────── Constants ────────────────────── */
23
+ /** 中间态超时配置(毫秒) */
24
+ const TIMEOUT_MS = {
25
+ evolving: 7 * 24 * 60 * 60 * 1000, // 7 天
26
+ decaying: 30 * 24 * 60 * 60 * 1000, // 30 天
27
+ staging: 7 * 24 * 60 * 60 * 1000, // 7 天(安全兜底,正常由 StagingManager 处理)
28
+ pending: 30 * 24 * 60 * 60 * 1000, // 30 天
29
+ };
30
+ /** 超时后的目标状态 */
31
+ const TIMEOUT_TARGET = {
32
+ evolving: 'active', // 回退到 active(内容不变)
33
+ decaying: 'deprecated', // 长期衰退 → 废弃
34
+ pending: 'deprecated', // 30 天未审核 → 废弃
35
+ };
36
+ /** 卡死告警阈值(毫秒) */
37
+ const STUCK_THRESHOLD_MS = {
38
+ evolving: 3 * 24 * 60 * 60 * 1000, // > 3天
39
+ decaying: 15 * 24 * 60 * 60 * 1000, // > 15天
40
+ staging: 3 * 24 * 60 * 60 * 1000, // > 3天
41
+ pending: 7 * 24 * 60 * 60 * 1000, // > 7天
42
+ };
43
+ /* ────────────────────── Entry/Exit Action Types ────────────────────── */
44
+ /** 进入状态时写入 stats 的元数据键 */
45
+ const ENTRY_META_KEYS = {
46
+ staging: 'stagingEnteredAt',
47
+ evolving: 'evolvingStartedAt',
48
+ decaying: 'decayStartedAt',
49
+ active: 'activeSince',
50
+ };
51
+ /* ────────────────────── Class ────────────────────── */
52
+ export class RecipeLifecycleSupervisor {
53
+ #db;
54
+ #signalBus;
55
+ #logger = Logger.getInstance();
56
+ constructor(db, options = {}) {
57
+ this.#db = db;
58
+ this.#signalBus = options.signalBus ?? null;
59
+ }
60
+ /* ═══════════════════ Core Transition ═══════════════════ */
61
+ /**
62
+ * 执行状态转移 — 统一入口
63
+ *
64
+ * 1. 获取当前状态
65
+ * 2. Guard 检查(合法转移 + 扩展条件)
66
+ * 3. Exit Action(离开旧状态)
67
+ * 4. 更新 lifecycle
68
+ * 5. Entry Action(进入新状态)
69
+ * 6. 记录 TransitionEvent
70
+ * 7. 发射信号
71
+ */
72
+ transition(request) {
73
+ const { recipeId, targetState, trigger, evidence, proposalId, operatorId } = request;
74
+ const opId = operatorId ?? 'system';
75
+ // 1. 获取当前状态
76
+ const current = this.#getRecipeState(recipeId);
77
+ if (!current) {
78
+ return {
79
+ success: false,
80
+ fromState: 'unknown',
81
+ toState: targetState,
82
+ error: 'Recipe not found',
83
+ };
84
+ }
85
+ const fromState = current.lifecycle;
86
+ // 2. Guard 检查
87
+ if (!isValidTransition(fromState, targetState)) {
88
+ this.#logger.warn(`[Supervisor] Invalid transition: ${recipeId} ${fromState} → ${targetState} (trigger: ${trigger})`);
89
+ return {
90
+ success: false,
91
+ fromState,
92
+ toState: targetState,
93
+ error: `Invalid transition: ${fromState} → ${targetState}`,
94
+ };
95
+ }
96
+ // 3. Exit Action
97
+ this.#executeExitAction(recipeId, fromState);
98
+ // 4. 更新 lifecycle
99
+ const now = Date.now();
100
+ this.#db
101
+ .prepare(`UPDATE knowledge_entries SET lifecycle = ?, updatedAt = ? WHERE id = ?`)
102
+ .run(targetState, now, recipeId);
103
+ // 5. Entry Action
104
+ this.#executeEntryAction(recipeId, targetState, now, proposalId);
105
+ // 6. 记录 TransitionEvent
106
+ const event = this.#recordEvent({
107
+ recipeId,
108
+ fromState,
109
+ toState: targetState,
110
+ trigger,
111
+ evidence: evidence ?? null,
112
+ proposalId: proposalId ?? null,
113
+ operatorId: opId,
114
+ createdAt: now,
115
+ });
116
+ // 7. 发射信号
117
+ this.#emitSignal(recipeId, fromState, targetState, trigger);
118
+ this.#logger.info(`[Supervisor] ${recipeId}: ${fromState} → ${targetState} (trigger: ${trigger})`);
119
+ return { success: true, fromState, toState: targetState, event };
120
+ }
121
+ /* ═══════════════════ Timeout Check ═══════════════════ */
122
+ /**
123
+ * 检查中间态超时 + 自动处理
124
+ *
125
+ * 处理范围:
126
+ * - evolving > 7d → active(回退)
127
+ * - decaying > 30d → deprecated
128
+ */
129
+ checkTimeouts() {
130
+ const result = { timedOut: [], checked: 0 };
131
+ const now = Date.now();
132
+ for (const [state, timeoutMs] of Object.entries(TIMEOUT_MS)) {
133
+ if (!(state in TIMEOUT_TARGET)) {
134
+ continue;
135
+ }
136
+ const targetState = TIMEOUT_TARGET[state];
137
+ const rows = this.#db
138
+ .prepare(`SELECT id, stats FROM knowledge_entries WHERE lifecycle = ?`)
139
+ .all(state);
140
+ result.checked += rows.length;
141
+ for (const row of rows) {
142
+ const stats = safeJsonParse(row.stats, {});
143
+ const entryKey = ENTRY_META_KEYS[state];
144
+ const enteredAt = (entryKey ? stats[entryKey] : null);
145
+ // 用 updatedAt 作为 fallback
146
+ const stateAge = enteredAt ? now - enteredAt : this.#getRecipeAge(row.id, now);
147
+ if (stateAge > timeoutMs) {
148
+ const transitionResult = this.transition({
149
+ recipeId: row.id,
150
+ targetState,
151
+ trigger: 'timeout-recovery',
152
+ evidence: {
153
+ reason: `${state} timeout after ${Math.round(stateAge / (24 * 60 * 60 * 1000))}d`,
154
+ },
155
+ });
156
+ if (transitionResult.success) {
157
+ result.timedOut.push({
158
+ recipeId: row.id,
159
+ fromState: state,
160
+ toState: targetState,
161
+ age: stateAge,
162
+ });
163
+ }
164
+ }
165
+ }
166
+ }
167
+ if (result.timedOut.length > 0) {
168
+ this.#logger.info(`[Supervisor] Timeout check: ${result.timedOut.length} recipes timed out (checked: ${result.checked})`);
169
+ }
170
+ return result;
171
+ }
172
+ /* ═══════════════════ Query ═══════════════════ */
173
+ /**
174
+ * 查询 Recipe 的转移历史
175
+ */
176
+ getTransitionHistory(recipeId, limit = 50) {
177
+ try {
178
+ const rows = this.#db
179
+ .prepare(`SELECT id, recipe_id, from_state, to_state, trigger, operator_id,
180
+ evidence_json, proposal_id, created_at
181
+ FROM lifecycle_transition_events
182
+ WHERE recipe_id = ?
183
+ ORDER BY created_at DESC
184
+ LIMIT ?`)
185
+ .all(recipeId, limit);
186
+ return rows.map((row) => this.#rowToEvent(row));
187
+ }
188
+ catch {
189
+ // 表可能不存在(migration 未运行)
190
+ return [];
191
+ }
192
+ }
193
+ /**
194
+ * 获取全局状态健康摘要
195
+ */
196
+ getHealthSummary() {
197
+ const now = Date.now();
198
+ // 状态分布
199
+ const stateDistribution = this.#getStateDistribution();
200
+ // 中间态卡死检测
201
+ const intermediateStates = {
202
+ stuckEvolving: this.#getStuckInfo('evolving', STUCK_THRESHOLD_MS.evolving, now),
203
+ stuckDecaying: this.#getStuckInfo('decaying', STUCK_THRESHOLD_MS.decaying, now),
204
+ stuckStaging: this.#getStuckInfo('staging', STUCK_THRESHOLD_MS.staging, now),
205
+ stuckPending: this.#getStuckInfo('pending', STUCK_THRESHOLD_MS.pending, now),
206
+ };
207
+ // 最近转移统计
208
+ const recentTransitions = this.#getRecentTransitionStats(now);
209
+ // Proposal 指标
210
+ const proposalMetrics = this.#getProposalMetrics();
211
+ return { stateDistribution, intermediateStates, recentTransitions, proposalMetrics };
212
+ }
213
+ /* ═══════════════════ Entry/Exit Actions ═══════════════════ */
214
+ #executeEntryAction(recipeId, state, now, proposalId) {
215
+ const metaKey = ENTRY_META_KEYS[state];
216
+ if (!metaKey) {
217
+ return;
218
+ }
219
+ const statsRow = this.#db
220
+ .prepare(`SELECT stats FROM knowledge_entries WHERE id = ?`)
221
+ .get(recipeId);
222
+ const stats = safeJsonParse(statsRow?.stats, {});
223
+ stats[metaKey] = now;
224
+ if (state === 'evolving' && proposalId) {
225
+ stats.evolvingProposalId = proposalId;
226
+ }
227
+ if (state === 'active') {
228
+ // 清除中间态元数据
229
+ delete stats.evolvingStartedAt;
230
+ delete stats.evolvingProposalId;
231
+ delete stats.decayStartedAt;
232
+ }
233
+ if (state === 'deprecated') {
234
+ stats.deprecatedAt = now;
235
+ }
236
+ this.#db
237
+ .prepare(`UPDATE knowledge_entries SET stats = ? WHERE id = ?`)
238
+ .run(JSON.stringify(stats), recipeId);
239
+ }
240
+ #executeExitAction(recipeId, state) {
241
+ if (state === 'active') {
242
+ // 记录 lastActiveAt
243
+ const statsRow = this.#db
244
+ .prepare(`SELECT stats FROM knowledge_entries WHERE id = ?`)
245
+ .get(recipeId);
246
+ const stats = safeJsonParse(statsRow?.stats, {});
247
+ stats.lastActiveAt = Date.now();
248
+ this.#db
249
+ .prepare(`UPDATE knowledge_entries SET stats = ? WHERE id = ?`)
250
+ .run(JSON.stringify(stats), recipeId);
251
+ }
252
+ // staging exit: 清除 staging 元数据(由 StagingManager 自行处理)
253
+ }
254
+ /* ═══════════════════ Event Recording ═══════════════════ */
255
+ #recordEvent(params) {
256
+ const id = randomUUID();
257
+ const event = {
258
+ id,
259
+ recipeId: params.recipeId,
260
+ fromState: params.fromState,
261
+ toState: params.toState,
262
+ trigger: params.trigger,
263
+ evidence: params.evidence,
264
+ proposalId: params.proposalId,
265
+ operatorId: params.operatorId,
266
+ createdAt: params.createdAt,
267
+ };
268
+ try {
269
+ this.#db
270
+ .prepare(`INSERT INTO lifecycle_transition_events
271
+ (id, recipe_id, from_state, to_state, trigger, operator_id, evidence_json, proposal_id, created_at)
272
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
273
+ .run(id, params.recipeId, params.fromState, params.toState, params.trigger, params.operatorId, params.evidence ? JSON.stringify(params.evidence) : null, params.proposalId, params.createdAt);
274
+ }
275
+ catch {
276
+ // lifecycle_transition_events 表可能不存在(降级容忍)
277
+ this.#logger.warn(`[Supervisor] Failed to record transition event (table may not exist)`);
278
+ }
279
+ return event;
280
+ }
281
+ /* ═══════════════════ Health Queries ═══════════════════ */
282
+ #getStateDistribution() {
283
+ const dist = {
284
+ pending: 0,
285
+ staging: 0,
286
+ active: 0,
287
+ evolving: 0,
288
+ decaying: 0,
289
+ deprecated: 0,
290
+ };
291
+ try {
292
+ const rows = this.#db
293
+ .prepare(`SELECT lifecycle, COUNT(*) as cnt FROM knowledge_entries GROUP BY lifecycle`)
294
+ .all();
295
+ for (const row of rows) {
296
+ dist[row.lifecycle] = row.cnt;
297
+ }
298
+ }
299
+ catch {
300
+ // fallback
301
+ }
302
+ return dist;
303
+ }
304
+ #getStuckInfo(state, thresholdMs, now) {
305
+ try {
306
+ const rows = this.#db
307
+ .prepare(`SELECT id, stats, updatedAt FROM knowledge_entries WHERE lifecycle = ?`)
308
+ .all(state);
309
+ let count = 0;
310
+ let oldestAge = 0;
311
+ for (const row of rows) {
312
+ const stats = safeJsonParse(row.stats, {});
313
+ const metaKey = ENTRY_META_KEYS[state];
314
+ const enteredAt = (metaKey ? stats[metaKey] : null);
315
+ const age = enteredAt ? now - enteredAt : now - (row.updatedAt || now);
316
+ if (age > thresholdMs) {
317
+ count++;
318
+ if (age > oldestAge) {
319
+ oldestAge = age;
320
+ }
321
+ }
322
+ }
323
+ return { count, oldestAge };
324
+ }
325
+ catch {
326
+ return { count: 0, oldestAge: 0 };
327
+ }
328
+ }
329
+ #getRecentTransitionStats(now) {
330
+ try {
331
+ const last24hCount = this.#db
332
+ .prepare(`SELECT COUNT(*) as cnt FROM lifecycle_transition_events WHERE created_at > ?`)
333
+ .get(now - 24 * 60 * 60 * 1000)?.cnt ?? 0;
334
+ const last7dCount = this.#db
335
+ .prepare(`SELECT COUNT(*) as cnt FROM lifecycle_transition_events WHERE created_at > ?`)
336
+ .get(now - 7 * 24 * 60 * 60 * 1000)?.cnt ?? 0;
337
+ const triggerRows = this.#db
338
+ .prepare(`SELECT trigger, COUNT(*) as cnt
339
+ FROM lifecycle_transition_events
340
+ WHERE created_at > ?
341
+ GROUP BY trigger
342
+ ORDER BY cnt DESC
343
+ LIMIT 5`)
344
+ .all(now - 7 * 24 * 60 * 60 * 1000);
345
+ return {
346
+ last24h: last24hCount,
347
+ last7d: last7dCount,
348
+ topTriggers: triggerRows.map((r) => ({ trigger: r.trigger, count: r.cnt })),
349
+ };
350
+ }
351
+ catch {
352
+ return { last24h: 0, last7d: 0, topTriggers: [] };
353
+ }
354
+ }
355
+ #getProposalMetrics() {
356
+ try {
357
+ const statusCounts = this.#db
358
+ .prepare(`SELECT status, COUNT(*) as cnt FROM evolution_proposals GROUP BY status`)
359
+ .all();
360
+ const map = {};
361
+ for (const row of statusCounts) {
362
+ map[row.status] = row.cnt;
363
+ }
364
+ const pending = map.pending ?? 0;
365
+ const observing = map.observing ?? 0;
366
+ const executed = map.executed ?? 0;
367
+ const rejected = map.rejected ?? 0;
368
+ const expired = map.expired ?? 0;
369
+ const total = executed + rejected + expired;
370
+ // contentPatchRate: 有 patch 的事件 / 总 proposal-execution 事件
371
+ let contentPatchRate = 0;
372
+ try {
373
+ const patchEvents = this.#db
374
+ .prepare(`SELECT COUNT(*) as cnt FROM lifecycle_transition_events
375
+ WHERE trigger = 'content-patch-complete'`)
376
+ .get();
377
+ const execEvents = this.#db
378
+ .prepare(`SELECT COUNT(*) as cnt FROM lifecycle_transition_events
379
+ WHERE trigger = 'proposal-execution' OR trigger = 'proposal-attach'`)
380
+ .get();
381
+ const patchCount = patchEvents?.cnt ?? 0;
382
+ const execCount = execEvents?.cnt ?? 0;
383
+ contentPatchRate = execCount > 0 ? patchCount / execCount : 0;
384
+ }
385
+ catch {
386
+ // table may not exist yet
387
+ }
388
+ return {
389
+ pendingCount: pending,
390
+ observingCount: observing,
391
+ executionRate: total > 0 ? executed / total : 0,
392
+ avgObservationDays: 0, // TODO: calculate from resolved proposals
393
+ contentPatchRate,
394
+ };
395
+ }
396
+ catch {
397
+ return {
398
+ pendingCount: 0,
399
+ observingCount: 0,
400
+ executionRate: 0,
401
+ avgObservationDays: 0,
402
+ contentPatchRate: 0,
403
+ };
404
+ }
405
+ }
406
+ /* ═══════════════════ DB Helpers ═══════════════════ */
407
+ #getRecipeState(recipeId) {
408
+ const row = this.#db
409
+ .prepare(`SELECT lifecycle FROM knowledge_entries WHERE id = ?`)
410
+ .get(recipeId);
411
+ return row ?? null;
412
+ }
413
+ #getRecipeAge(recipeId, now) {
414
+ const row = this.#db
415
+ .prepare(`SELECT updatedAt FROM knowledge_entries WHERE id = ?`)
416
+ .get(recipeId);
417
+ return row ? now - row.updatedAt : 0;
418
+ }
419
+ #rowToEvent(row) {
420
+ return {
421
+ id: row.id,
422
+ recipeId: row.recipe_id,
423
+ fromState: row.from_state,
424
+ toState: row.to_state,
425
+ trigger: row.trigger,
426
+ evidence: row.evidence_json ? safeJsonParse(row.evidence_json, null) : null,
427
+ proposalId: row.proposal_id ?? null,
428
+ operatorId: row.operator_id,
429
+ createdAt: row.created_at,
430
+ };
431
+ }
432
+ /* ═══════════════════ Signal ═══════════════════ */
433
+ #emitSignal(recipeId, fromState, toState, trigger) {
434
+ if (!this.#signalBus) {
435
+ return;
436
+ }
437
+ this.#signalBus.send('lifecycle', 'RecipeLifecycleSupervisor', 0.5, {
438
+ target: recipeId,
439
+ metadata: {
440
+ fromState,
441
+ toState,
442
+ trigger,
443
+ },
444
+ });
445
+ }
446
+ }
447
+ /* ────────────────────── Util ────────────────────── */
448
+ function safeJsonParse(json, fallback) {
449
+ if (!json) {
450
+ return fallback;
451
+ }
452
+ try {
453
+ return JSON.parse(json);
454
+ }
455
+ catch {
456
+ return fallback;
457
+ }
458
+ }