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
@@ -15,7 +15,7 @@
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
17
17
  import { TEMPLATES_DIR } from '../../shared/package-root.js';
18
- import { checkWriteSafety, safeWriteFile } from './FileProtection.js';
18
+ import { mergeSection } from './FileProtection.js';
19
19
  import { estimateTokens } from './TokenBudget.js';
20
20
  /**
21
21
  * Agent 指令文件 token 预算
@@ -49,6 +49,14 @@ const MCP_TOOLS_SUMMARY = [
49
49
  { name: 'autosnippet_graph', desc: 'Knowledge graph query (query/impact/path/stats)' },
50
50
  { name: 'autosnippet_skill', desc: 'Skill management (list/load/create/update/delete)' },
51
51
  { name: 'autosnippet_bootstrap', desc: 'Project cold-start & scan' },
52
+ {
53
+ name: 'autosnippet_rescan',
54
+ desc: 'Incremental rescan: preserves Recipes, cleans caches, re-analyzes project, runs relevance audit',
55
+ },
56
+ {
57
+ name: 'autosnippet_evolve',
58
+ desc: 'Batch Recipe evolution decisions (propose_evolution/confirm_deprecation/skip), used per-dimension during rescan or standalone',
59
+ },
52
60
  {
53
61
  name: 'autosnippet_panorama',
54
62
  desc: 'Project panorama (operation: overview/module/gaps/health)',
@@ -95,8 +103,8 @@ export class AgentInstructionsGenerator {
95
103
  const filesWritten = allResults.filter((r) => !r.skipped).length;
96
104
  const skippedFiles = allResults.filter((r) => r.skipped);
97
105
  if (skippedFiles.length > 0) {
98
- this.logger.info?.(`[AgentInstructions] Skipped ${skippedFiles.length} file(s) ` +
99
- `user-owned files will not be overwritten: ${skippedFiles.map((f) => f.filePath).join(', ')}`);
106
+ this.logger.info?.(`[AgentInstructions] Skipped ${skippedFiles.length} file(s): ` +
107
+ skippedFiles.map((f) => f.filePath).join(', '));
100
108
  }
101
109
  this.logger.info?.(`[AgentInstructions] Generated ${filesWritten} files in ${duration}ms — ` +
102
110
  `AGENTS.md: ${agents.tokensUsed}t, CLAUDE.md: ${claude.tokensUsed}t, ` +
@@ -159,7 +167,8 @@ export class AgentInstructionsGenerator {
159
167
  }
160
168
  // ─── AGENTS.md ─────────────────────────────────────
161
169
  _writeAgentsMd(sections) {
162
- const lines = [
170
+ // 文件头部(仅用于新建/旧版重写场景)
171
+ const header = [
163
172
  `# ${this.projectName} — Agent Instructions`,
164
173
  '',
165
174
  '> Auto-generated by [AutoSnippet](https://github.com/GxFn/AutoSnippet). Do not edit manually.',
@@ -167,31 +176,28 @@ export class AgentInstructionsGenerator {
167
176
  'This project uses **AutoSnippet** for knowledge management.',
168
177
  'Access the knowledge base through MCP tools.',
169
178
  '',
170
- ];
171
- // Coding Standards
179
+ ].join('\n');
180
+ // 动态区段内容(始终在 markers 内管理)
181
+ const sectionLines = [];
172
182
  if (sections.ruleLines.length > 0) {
173
- lines.push('## Coding Standards', '', ...sections.ruleLines, '');
183
+ sectionLines.push('## Coding Standards', '', ...sections.ruleLines, '');
174
184
  }
175
- // Architecture Patterns
176
185
  if (sections.patternRows.length > 0) {
177
- lines.push('## Architecture Patterns', '', '| Trigger | When | Do |', '|---------|------|----|', ...sections.patternRows, '');
186
+ sectionLines.push('## Architecture Patterns', '', '| Trigger | When | Do |', '|---------|------|----|', ...sections.patternRows, '');
178
187
  }
179
- // MCP Tools
180
- lines.push('## MCP Tools', '', ...sections.toolLines, '');
181
- // Skills
188
+ sectionLines.push('## MCP Tools', '', ...sections.toolLines, '');
182
189
  if (sections.skillLines.length > 0) {
183
- lines.push('## Skills', '', 'Load with `autosnippet_skill({ operation: "load", name: "<skill>" })`:', '', ...sections.skillLines, '');
190
+ sectionLines.push('## Skills', '', 'Load with `autosnippet_skill({ operation: "load", name: "<skill>" })`:', '', ...sections.skillLines, '');
184
191
  }
185
- // Constraints
186
- lines.push('## Constraints', '', '1. Do NOT modify knowledge base files directly (`AutoSnippet/recipes/`, `.autosnippet/`).', '2. Prefer Recipes as project standards; source code is supplementary.', '3. Create or update knowledge only through MCP tools.', '');
187
- const content = `${lines.join('\n')}\n`;
192
+ sectionLines.push('## Constraints', '', '1. Do NOT modify knowledge base files directly (`AutoSnippet/recipes/`, `.autosnippet/`).', '2. Prefer Recipes as project standards; source code is supplementary.', '3. Create or update knowledge only through MCP tools.', '');
193
+ const sectionContent = sectionLines.join('\n');
188
194
  const filePath = path.join(this.projectRoot, 'AGENTS.md');
189
- const result = safeWriteFile(filePath, content, { logger: this.logger });
190
- return { filePath, tokensUsed: estimateTokens(content), skipped: !result.written };
195
+ const result = mergeSection(filePath, sectionContent, { header, logger: this.logger });
196
+ return { filePath, tokensUsed: estimateTokens(sectionContent), skipped: !result.written };
191
197
  }
192
198
  // ─── CLAUDE.md ─────────────────────────────────────
193
199
  _writeClaudeMd(sections) {
194
- const lines = [
200
+ const header = [
195
201
  `# ${this.projectName} — Claude Code Instructions`,
196
202
  '',
197
203
  '> Auto-generated by AutoSnippet. Regenerated when knowledge base changes.',
@@ -199,27 +205,23 @@ export class AgentInstructionsGenerator {
199
205
  'This project uses **AutoSnippet** for knowledge management.',
200
206
  'Access the knowledge base through MCP tools.',
201
207
  '',
202
- ];
203
- // Coding Standards
208
+ ].join('\n');
209
+ const sectionLines = [];
204
210
  if (sections.ruleLines.length > 0) {
205
- lines.push('## Coding Standards', '', ...sections.ruleLines, '');
211
+ sectionLines.push('## Coding Standards', '', ...sections.ruleLines, '');
206
212
  }
207
- // Patterns
208
213
  if (sections.patternRows.length > 0) {
209
- lines.push('## Key Patterns', '', '| Trigger | When | Do |', '|---------|------|----|', ...sections.patternRows, '');
214
+ sectionLines.push('## Key Patterns', '', '| Trigger | When | Do |', '|---------|------|----|', ...sections.patternRows, '');
210
215
  }
211
- // MCP Claude Code natively supports MCP
212
- lines.push('## MCP Tools', '', ...sections.toolLines, '');
213
- // Skills
216
+ sectionLines.push('## MCP Tools', '', ...sections.toolLines, '');
214
217
  if (sections.skillLines.length > 0) {
215
- lines.push('## Skills', '', ...sections.skillLines, '');
218
+ sectionLines.push('## Skills', '', ...sections.skillLines, '');
216
219
  }
217
- // Constraints
218
- lines.push('', '## Constraints', '', '1. Do NOT modify knowledge base files directly (`AutoSnippet/recipes/`, `.autosnippet/`).', '2. Prefer Recipes as project standards; source code is supplementary.', '3. Create or update knowledge only through MCP tools.', '');
219
- const content = `${lines.join('\n')}\n`;
220
+ sectionLines.push('', '## Constraints', '', '1. Do NOT modify knowledge base files directly (`AutoSnippet/recipes/`, `.autosnippet/`).', '2. Prefer Recipes as project standards; source code is supplementary.', '3. Create or update knowledge only through MCP tools.', '');
221
+ const sectionContent = sectionLines.join('\n');
220
222
  const filePath = path.join(this.projectRoot, 'CLAUDE.md');
221
- const result = safeWriteFile(filePath, content, { logger: this.logger });
222
- return { filePath, tokensUsed: estimateTokens(content), skipped: !result.written };
223
+ const result = mergeSection(filePath, sectionContent, { header, logger: this.logger });
224
+ return { filePath, tokensUsed: estimateTokens(sectionContent), skipped: !result.written };
223
225
  }
224
226
  // ─── copilot-instructions.md ───────────────────────
225
227
  /**
@@ -228,7 +230,7 @@ export class AgentInstructionsGenerator {
228
230
  */
229
231
  _writeCopilotInstructions(_sections) {
230
232
  const body = this._loadConventionsTemplate();
231
- const content = [
233
+ const section = [
232
234
  '<!-- autosnippet:begin -->',
233
235
  '',
234
236
  '# AutoSnippet Conventions',
@@ -236,16 +238,10 @@ export class AgentInstructionsGenerator {
236
238
  body,
237
239
  '',
238
240
  '<!-- autosnippet:end -->',
239
- '',
240
241
  ].join('\n');
241
- const destDir = path.join(this.projectRoot, '.github');
242
- const filePath = path.join(destDir, 'copilot-instructions.md');
243
- const { canWrite } = checkWriteSafety(filePath);
244
- if (canWrite) {
245
- fs.mkdirSync(destDir, { recursive: true });
246
- }
247
- const result = safeWriteFile(filePath, content, { logger: this.logger });
248
- return { filePath, tokensUsed: estimateTokens(content), skipped: !result.written };
242
+ const filePath = path.join(this.projectRoot, '.github', 'copilot-instructions.md');
243
+ const result = mergeSection(filePath, section, { logger: this.logger });
244
+ return { filePath, tokensUsed: estimateTokens(section), skipped: !result.written };
249
245
  }
250
246
  // ─── 模板读取 ──────────────────────────────────────
251
247
  /**
@@ -61,3 +61,23 @@ export declare function safeCopyFile(srcPath: string, destPath: string, options?
61
61
  reason: string;
62
62
  filePath: string;
63
63
  };
64
+ export type MergeStrategy = 'create' | 'replace-section' | 'rewrite-legacy' | 'append-section';
65
+ /**
66
+ * 智能合并 AutoSnippet 管理区段到目标文件
67
+ *
68
+ * 四种场景:
69
+ * 1. 文件不存在 → 创建完整文件(header + markers)
70
+ * 2. 文件有 begin/end 标记 → 仅替换标记区段(增量更新)
71
+ * 3. 文件有旧版 AutoSnippet 签名但无标记 → 全量重写并加标记(旧版迁移)
72
+ * 4. 文件无签名无标记(用户文件)→ 追加标记区段到末尾(共存)
73
+ */
74
+ export declare function mergeSection(filePath: string, section: string, options?: {
75
+ header?: string;
76
+ logger?: {
77
+ info?: (...args: unknown[]) => void;
78
+ };
79
+ }): {
80
+ written: boolean;
81
+ strategy: MergeStrategy;
82
+ filePath: string;
83
+ };
@@ -12,6 +12,7 @@
12
12
  * - "auto-generated by autosnippet" (case-insensitive)
13
13
  */
14
14
  import fs from 'node:fs';
15
+ import path from 'node:path';
15
16
  /**
16
17
  * AutoSnippet 文件签名模式(case-insensitive)
17
18
  * 检查文件前 1024 字节即可——签名总在文件头部
@@ -96,3 +97,56 @@ export function safeCopyFile(srcPath, destPath, options = {}) {
96
97
  logger?.info?.(`[FileProtection] Skipped "${destPath}" — ${reason} (file exists and is not AutoSnippet-generated)`);
97
98
  return { written: false, reason, filePath: destPath };
98
99
  }
100
+ // ─── Section Merge ──────────────────────────────────
101
+ const SECTION_BEGIN = '<!-- autosnippet:begin -->';
102
+ const SECTION_END = '<!-- autosnippet:end -->';
103
+ const SECTION_PATTERN = /<!-- autosnippet:begin -->[\s\S]*?<!-- autosnippet:end -->/;
104
+ /**
105
+ * 智能合并 AutoSnippet 管理区段到目标文件
106
+ *
107
+ * 四种场景:
108
+ * 1. 文件不存在 → 创建完整文件(header + markers)
109
+ * 2. 文件有 begin/end 标记 → 仅替换标记区段(增量更新)
110
+ * 3. 文件有旧版 AutoSnippet 签名但无标记 → 全量重写并加标记(旧版迁移)
111
+ * 4. 文件无签名无标记(用户文件)→ 追加标记区段到末尾(共存)
112
+ */
113
+ export function mergeSection(filePath, section, options = {}) {
114
+ const { header = '', logger } = options;
115
+ const fileName = path.basename(filePath);
116
+ // 确保 section 包含 begin/end 标记
117
+ const wrappedSection = section.includes(SECTION_BEGIN)
118
+ ? section
119
+ : `${SECTION_BEGIN}\n\n${section}\n${SECTION_END}`;
120
+ // Case 1: 文件不存在 → 创建完整文件
121
+ if (!fs.existsSync(filePath)) {
122
+ const content = header ? `${header}\n${wrappedSection}\n` : `${wrappedSection}\n`;
123
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
124
+ fs.writeFileSync(filePath, content, 'utf8');
125
+ logger?.info?.(`[FileProtection] Created "${fileName}" (new file)`);
126
+ return { written: true, strategy: 'create', filePath };
127
+ }
128
+ const existing = fs.readFileSync(filePath, 'utf8');
129
+ // Case 2: 文件已有 begin/end 标记 → 仅替换标记区段
130
+ if (SECTION_PATTERN.test(existing)) {
131
+ const updated = existing.replace(SECTION_PATTERN, wrappedSection);
132
+ if (updated === existing) {
133
+ logger?.info?.(`[FileProtection] "${fileName}" — section unchanged, skipped write`);
134
+ return { written: false, strategy: 'replace-section', filePath };
135
+ }
136
+ fs.writeFileSync(filePath, updated, 'utf8');
137
+ logger?.info?.(`[FileProtection] Updated "${fileName}" (replaced marker section)`);
138
+ return { written: true, strategy: 'replace-section', filePath };
139
+ }
140
+ // Case 3: 文件有 AutoSnippet 签名但无标记 → 旧版格式,重写并加标记
141
+ if (SIGNATURE_PATTERN.test(existing.slice(0, 1024))) {
142
+ const content = header ? `${header}\n${wrappedSection}\n` : `${wrappedSection}\n`;
143
+ fs.writeFileSync(filePath, content, 'utf8');
144
+ logger?.info?.(`[FileProtection] Rewrote "${fileName}" (legacy → marker format)`);
145
+ return { written: true, strategy: 'rewrite-legacy', filePath };
146
+ }
147
+ // Case 4: 用户自有文件 → 追加标记区段到末尾
148
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
149
+ fs.writeFileSync(filePath, `${existing}${separator}${wrappedSection}\n`, 'utf8');
150
+ logger?.info?.(`[FileProtection] Appended AutoSnippet section to "${fileName}" (user-owned file preserved)`);
151
+ return { written: true, strategy: 'append-section', filePath };
152
+ }
@@ -19,36 +19,31 @@ import { SKILLS_DIR as BUILTIN_SKILLS_DIR } from '../../shared/package-root.js';
19
19
  */
20
20
  const SKILL_NAME_MAP = {
21
21
  'project-architecture': 'autosnippet-architecture',
22
- 'project-code-standard': 'autosnippet-code-standard',
23
- 'project-profile': 'autosnippet-profile',
22
+ 'project-coding-standards': 'autosnippet-coding-standards',
24
23
  'project-agent-guidelines': 'autosnippet-guidelines',
25
- 'project-event-and-data-flow': 'autosnippet-data-flow',
26
- 'project-code-pattern': 'autosnippet-code-pattern',
27
- 'project-objc-deep-scan': 'autosnippet-objc-deep-scan',
28
- 'project-category-scan': 'autosnippet-category-scan',
29
- 'project-best-practice': 'autosnippet-best-practice',
30
- // 新语言维度
31
- 'project-module-exports': 'autosnippet-module-exports',
32
- 'project-framework-conventions': 'autosnippet-framework-conventions',
24
+ 'project-data-event-flow': 'autosnippet-data-flow',
25
+ 'project-design-patterns': 'autosnippet-design-patterns',
26
+ 'project-error-resilience': 'autosnippet-error-resilience',
27
+ 'project-swift-objc-idiom': 'autosnippet-swift-objc-idiom',
28
+ // 语言维度
29
+ 'project-ts-js-module': 'autosnippet-ts-js-module',
30
+ 'project-react-patterns': 'autosnippet-react-patterns',
33
31
  'project-python-structure': 'autosnippet-python-structure',
34
- 'project-jvm-annotations': 'autosnippet-jvm-annotations',
32
+ 'project-jvm-annotation': 'autosnippet-jvm-annotation',
35
33
  };
36
34
  /** 用途描述模板(英文,Cursor 优先) */
37
35
  const SKILL_DESC_MAP = {
38
36
  'autosnippet-architecture': 'Architecture patterns, module boundaries, and dependency rules for {project}. Use when creating new modules, reviewing architecture, or understanding dependencies.',
39
- 'autosnippet-code-standard': 'Coding standards and style conventions for {project}. Use when writing new code, reviewing formatting, or enforcing naming conventions.',
40
- 'autosnippet-profile': 'Project overview and profile for {project}. Use when needing background on the project, its tech stack, or structure.',
37
+ 'autosnippet-coding-standards': 'Coding standards and style conventions for {project}. Use when writing new code, reviewing formatting, or enforcing naming conventions.',
41
38
  'autosnippet-guidelines': 'Agent interaction guidelines for {project}. Use when understanding how to work with this specific project.',
42
39
  'autosnippet-data-flow': 'Event and data flow patterns for {project}. Use when working with events, state management, or data pipelines.',
43
- 'autosnippet-code-pattern': 'Common code patterns and idioms for {project}. Use when implementing features following project conventions.',
44
- 'autosnippet-objc-deep-scan': 'Objective-C deep scan results for {project}. Use when working with Objective-C code, method swizzling, or runtime features.',
45
- 'autosnippet-category-scan': 'Category and extension analysis for {project}. Use when working with categories or finding existing utility methods.',
46
- 'autosnippet-best-practice': 'Best practices and proven patterns for {project}. Use when making design decisions or code review.',
47
- // 新语言维度
48
- 'autosnippet-module-exports': 'Module export structure, barrel exports, and public API surface for {project}. Use when working with imports/exports or module boundaries.',
49
- 'autosnippet-framework-conventions': 'Framework-specific conventions (component structure, routing, state management) for {project}. Use when following framework patterns.',
40
+ 'autosnippet-design-patterns': 'Common code patterns and idioms for {project}. Use when implementing features following project conventions.',
41
+ 'autosnippet-error-resilience': 'Error handling, resilience patterns and defensive coding for {project}. Use when making design decisions or code review.',
42
+ 'autosnippet-swift-objc-idiom': 'Swift/ObjC idioms, categories, method swizzling and interop for {project}. Use when working with Swift or Objective-C code.',
43
+ 'autosnippet-ts-js-module': 'Module export structure, barrel exports, and public API surface for {project}. Use when working with imports/exports or module boundaries.',
44
+ 'autosnippet-react-patterns': 'React component patterns, state management conventions and routing for {project}. Use when following framework patterns.',
50
45
  'autosnippet-python-structure': 'Python package structure, __init__.py exports, import patterns and type hint coverage for {project}. Use when working with Python modules.',
51
- 'autosnippet-jvm-annotations': 'Annotation patterns (DI, ORM, API, custom) and meta-programming for {project}. Use when working with Spring, Jakarta, or framework annotations.',
46
+ 'autosnippet-jvm-annotation': 'Annotation patterns (DI, ORM, API, custom) and meta-programming for {project}. Use when working with Spring, Jakarta, or framework annotations.',
52
47
  };
53
48
  export class SkillsSyncer {
54
49
  knowledgeService;
@@ -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
+ }