claude-mpm 3.9.9__py3-none-any.whl → 4.0.3__py3-none-any.whl

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 (411) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +2 -2
  3. claude_mpm/__main__.py +3 -2
  4. claude_mpm/agents/__init__.py +85 -79
  5. claude_mpm/agents/agent_loader.py +464 -1003
  6. claude_mpm/agents/agent_loader_integration.py +45 -45
  7. claude_mpm/agents/agents_metadata.py +29 -30
  8. claude_mpm/agents/async_agent_loader.py +156 -138
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/base_agent_loader.py +179 -151
  11. claude_mpm/agents/frontmatter_validator.py +229 -130
  12. claude_mpm/agents/schema/agent_schema.json +1 -1
  13. claude_mpm/agents/system_agent_config.py +213 -147
  14. claude_mpm/agents/templates/__init__.py +13 -13
  15. claude_mpm/agents/templates/code_analyzer.json +2 -2
  16. claude_mpm/agents/templates/data_engineer.json +1 -1
  17. claude_mpm/agents/templates/documentation.json +23 -11
  18. claude_mpm/agents/templates/engineer.json +22 -6
  19. claude_mpm/agents/templates/memory_manager.json +155 -0
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/project_organizer.json +1 -1
  22. claude_mpm/agents/templates/qa.json +1 -1
  23. claude_mpm/agents/templates/refactoring_engineer.json +222 -0
  24. claude_mpm/agents/templates/research.json +20 -14
  25. claude_mpm/agents/templates/security.json +1 -1
  26. claude_mpm/agents/templates/ticketing.json +1 -1
  27. claude_mpm/agents/templates/version_control.json +1 -1
  28. claude_mpm/agents/templates/web_qa.json +3 -1
  29. claude_mpm/agents/templates/web_ui.json +2 -2
  30. claude_mpm/cli/__init__.py +90 -49
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +21 -18
  33. claude_mpm/cli/commands/agents.py +279 -247
  34. claude_mpm/cli/commands/aggregate.py +138 -157
  35. claude_mpm/cli/commands/cleanup.py +147 -147
  36. claude_mpm/cli/commands/config.py +93 -76
  37. claude_mpm/cli/commands/info.py +17 -16
  38. claude_mpm/cli/commands/mcp.py +143 -762
  39. claude_mpm/cli/commands/mcp_command_router.py +139 -0
  40. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  41. claude_mpm/cli/commands/mcp_install_commands.py +20 -0
  42. claude_mpm/cli/commands/mcp_server_commands.py +175 -0
  43. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  44. claude_mpm/cli/commands/memory.py +239 -203
  45. claude_mpm/cli/commands/monitor.py +203 -81
  46. claude_mpm/cli/commands/run.py +380 -429
  47. claude_mpm/cli/commands/run_config_checker.py +160 -0
  48. claude_mpm/cli/commands/socketio_monitor.py +235 -0
  49. claude_mpm/cli/commands/tickets.py +305 -197
  50. claude_mpm/cli/parser.py +24 -1150
  51. claude_mpm/cli/parsers/__init__.py +29 -0
  52. claude_mpm/cli/parsers/agents_parser.py +136 -0
  53. claude_mpm/cli/parsers/base_parser.py +331 -0
  54. claude_mpm/cli/parsers/config_parser.py +85 -0
  55. claude_mpm/cli/parsers/mcp_parser.py +152 -0
  56. claude_mpm/cli/parsers/memory_parser.py +138 -0
  57. claude_mpm/cli/parsers/monitor_parser.py +104 -0
  58. claude_mpm/cli/parsers/run_parser.py +147 -0
  59. claude_mpm/cli/parsers/tickets_parser.py +203 -0
  60. claude_mpm/cli/ticket_cli.py +7 -3
  61. claude_mpm/cli/utils.py +55 -37
  62. claude_mpm/cli_module/__init__.py +6 -6
  63. claude_mpm/cli_module/args.py +188 -140
  64. claude_mpm/cli_module/commands.py +79 -70
  65. claude_mpm/cli_module/migration_example.py +38 -60
  66. claude_mpm/config/__init__.py +32 -25
  67. claude_mpm/config/agent_config.py +151 -119
  68. claude_mpm/config/experimental_features.py +217 -0
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +36 -18
  72. claude_mpm/core/__init__.py +9 -6
  73. claude_mpm/core/agent_name_normalizer.py +68 -71
  74. claude_mpm/core/agent_registry.py +372 -521
  75. claude_mpm/core/agent_session_manager.py +74 -63
  76. claude_mpm/core/base_service.py +116 -87
  77. claude_mpm/core/cache.py +119 -153
  78. claude_mpm/core/claude_runner.py +425 -1120
  79. claude_mpm/core/config.py +263 -168
  80. claude_mpm/core/config_aliases.py +69 -61
  81. claude_mpm/core/config_constants.py +292 -0
  82. claude_mpm/core/constants.py +57 -99
  83. claude_mpm/core/container.py +211 -178
  84. claude_mpm/core/exceptions.py +233 -89
  85. claude_mpm/core/factories.py +92 -54
  86. claude_mpm/core/framework_loader.py +378 -220
  87. claude_mpm/core/hook_manager.py +198 -83
  88. claude_mpm/core/hook_performance_config.py +136 -0
  89. claude_mpm/core/injectable_service.py +61 -55
  90. claude_mpm/core/interactive_session.py +165 -155
  91. claude_mpm/core/interfaces.py +221 -195
  92. claude_mpm/core/lazy.py +96 -96
  93. claude_mpm/core/logger.py +133 -107
  94. claude_mpm/core/logging_config.py +185 -157
  95. claude_mpm/core/minimal_framework_loader.py +20 -15
  96. claude_mpm/core/mixins.py +30 -29
  97. claude_mpm/core/oneshot_session.py +215 -181
  98. claude_mpm/core/optimized_agent_loader.py +134 -138
  99. claude_mpm/core/optimized_startup.py +159 -157
  100. claude_mpm/core/pm_hook_interceptor.py +85 -72
  101. claude_mpm/core/service_registry.py +103 -101
  102. claude_mpm/core/session_manager.py +97 -87
  103. claude_mpm/core/socketio_pool.py +212 -158
  104. claude_mpm/core/tool_access_control.py +58 -51
  105. claude_mpm/core/types.py +46 -24
  106. claude_mpm/core/typing_utils.py +166 -82
  107. claude_mpm/core/unified_agent_registry.py +721 -0
  108. claude_mpm/core/unified_config.py +550 -0
  109. claude_mpm/core/unified_paths.py +549 -0
  110. claude_mpm/dashboard/index.html +1 -1
  111. claude_mpm/dashboard/open_dashboard.py +51 -17
  112. claude_mpm/dashboard/static/css/dashboard.css +27 -8
  113. claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
  114. claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
  115. claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
  116. claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
  117. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
  118. claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
  119. claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
  120. claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
  121. claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
  122. claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
  123. claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
  124. claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
  125. claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
  126. claude_mpm/dashboard/static/dist/dashboard.js +2 -0
  127. claude_mpm/dashboard/static/dist/socket-client.js +2 -0
  128. claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
  129. claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
  130. claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
  131. claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
  132. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
  133. claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
  134. claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
  135. claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
  136. claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
  137. claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
  138. claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
  139. claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
  140. claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
  141. claude_mpm/dashboard/static/js/dashboard.js +178 -453
  142. claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
  143. claude_mpm/dashboard/static/js/socket-client.js +120 -54
  144. claude_mpm/dashboard/templates/index.html +40 -50
  145. claude_mpm/experimental/cli_enhancements.py +60 -58
  146. claude_mpm/generators/__init__.py +1 -1
  147. claude_mpm/generators/agent_profile_generator.py +75 -65
  148. claude_mpm/hooks/__init__.py +1 -1
  149. claude_mpm/hooks/base_hook.py +33 -28
  150. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  151. claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
  152. claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
  153. claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
  154. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
  155. claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
  156. claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
  157. claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
  158. claude_mpm/hooks/memory_integration_hook.py +140 -100
  159. claude_mpm/hooks/tool_call_interceptor.py +89 -76
  160. claude_mpm/hooks/validation_hooks.py +57 -49
  161. claude_mpm/init.py +145 -121
  162. claude_mpm/models/__init__.py +9 -9
  163. claude_mpm/models/agent_definition.py +33 -23
  164. claude_mpm/models/agent_session.py +228 -200
  165. claude_mpm/scripts/__init__.py +1 -1
  166. claude_mpm/scripts/socketio_daemon.py +192 -75
  167. claude_mpm/scripts/socketio_server_manager.py +328 -0
  168. claude_mpm/scripts/start_activity_logging.py +25 -22
  169. claude_mpm/services/__init__.py +68 -43
  170. claude_mpm/services/agent_capabilities_service.py +271 -0
  171. claude_mpm/services/agents/__init__.py +23 -32
  172. claude_mpm/services/agents/deployment/__init__.py +3 -3
  173. claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
  174. claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
  175. claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
  176. claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
  177. claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
  178. claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
  179. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
  180. claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
  181. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
  182. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
  183. claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
  184. claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
  185. claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
  186. claude_mpm/services/agents/deployment/agent_validator.py +352 -0
  187. claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
  188. claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
  189. claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
  190. claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
  191. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  192. claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
  193. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  194. claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
  195. claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
  196. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  197. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  198. claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
  199. claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
  200. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  201. claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
  202. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  203. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  204. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  205. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  206. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
  207. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  208. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  209. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
  210. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
  211. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
  212. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
  213. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
  214. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  215. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
  216. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  217. claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
  218. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
  219. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  220. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  221. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  222. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  223. claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
  224. claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
  225. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  226. claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
  227. claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
  228. claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
  229. claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
  230. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  231. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  232. claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
  233. claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
  234. claude_mpm/services/agents/loading/__init__.py +2 -2
  235. claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
  236. claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
  237. claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
  238. claude_mpm/services/agents/management/__init__.py +2 -2
  239. claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
  240. claude_mpm/services/agents/management/agent_management_service.py +209 -156
  241. claude_mpm/services/agents/memory/__init__.py +9 -6
  242. claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
  243. claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
  244. claude_mpm/services/agents/memory/analyzer.py +430 -0
  245. claude_mpm/services/agents/memory/content_manager.py +376 -0
  246. claude_mpm/services/agents/memory/template_generator.py +468 -0
  247. claude_mpm/services/agents/registry/__init__.py +7 -10
  248. claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
  249. claude_mpm/services/agents/registry/modification_tracker.py +351 -285
  250. claude_mpm/services/async_session_logger.py +187 -153
  251. claude_mpm/services/claude_session_logger.py +87 -72
  252. claude_mpm/services/command_handler_service.py +217 -0
  253. claude_mpm/services/communication/__init__.py +3 -2
  254. claude_mpm/services/core/__init__.py +50 -97
  255. claude_mpm/services/core/base.py +60 -53
  256. claude_mpm/services/core/interfaces/__init__.py +188 -0
  257. claude_mpm/services/core/interfaces/agent.py +351 -0
  258. claude_mpm/services/core/interfaces/communication.py +343 -0
  259. claude_mpm/services/core/interfaces/infrastructure.py +413 -0
  260. claude_mpm/services/core/interfaces/service.py +434 -0
  261. claude_mpm/services/core/interfaces.py +19 -944
  262. claude_mpm/services/event_aggregator.py +208 -170
  263. claude_mpm/services/exceptions.py +387 -308
  264. claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
  265. claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
  266. claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
  267. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
  268. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
  269. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
  270. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
  271. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
  272. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  273. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  274. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  275. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  276. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
  277. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  278. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  279. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
  280. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
  281. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  282. claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
  283. claude_mpm/services/hook_service.py +106 -114
  284. claude_mpm/services/infrastructure/__init__.py +7 -5
  285. claude_mpm/services/infrastructure/context_preservation.py +571 -0
  286. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  287. claude_mpm/services/infrastructure/logging.py +83 -76
  288. claude_mpm/services/infrastructure/monitoring.py +547 -404
  289. claude_mpm/services/mcp_gateway/__init__.py +40 -23
  290. claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
  291. claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
  292. claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
  293. claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
  294. claude_mpm/services/mcp_gateway/core/__init__.py +14 -21
  295. claude_mpm/services/mcp_gateway/core/base.py +80 -67
  296. claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
  297. claude_mpm/services/mcp_gateway/core/interfaces.py +97 -93
  298. claude_mpm/services/mcp_gateway/main.py +307 -127
  299. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +100 -101
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  302. claude_mpm/services/mcp_gateway/server/__init__.py +4 -4
  303. claude_mpm/services/mcp_gateway/server/{mcp_server.py → mcp_gateway.py} +149 -153
  304. claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
  305. claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
  306. claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
  307. claude_mpm/services/mcp_gateway/tools/base_adapter.py +110 -121
  308. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
  309. claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
  310. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
  311. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
  312. claude_mpm/services/memory/__init__.py +2 -2
  313. claude_mpm/services/memory/builder.py +451 -362
  314. claude_mpm/services/memory/cache/__init__.py +2 -2
  315. claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
  316. claude_mpm/services/memory/cache/simple_cache.py +107 -93
  317. claude_mpm/services/memory/indexed_memory.py +195 -193
  318. claude_mpm/services/memory/optimizer.py +267 -234
  319. claude_mpm/services/memory/router.py +571 -263
  320. claude_mpm/services/memory_hook_service.py +237 -0
  321. claude_mpm/services/port_manager.py +223 -0
  322. claude_mpm/services/project/__init__.py +3 -3
  323. claude_mpm/services/project/analyzer.py +451 -305
  324. claude_mpm/services/project/registry.py +262 -240
  325. claude_mpm/services/recovery_manager.py +287 -231
  326. claude_mpm/services/response_tracker.py +87 -67
  327. claude_mpm/services/runner_configuration_service.py +587 -0
  328. claude_mpm/services/session_management_service.py +304 -0
  329. claude_mpm/services/socketio/__init__.py +4 -4
  330. claude_mpm/services/socketio/client_proxy.py +174 -0
  331. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  332. claude_mpm/services/socketio/handlers/base.py +44 -30
  333. claude_mpm/services/socketio/handlers/connection.py +145 -65
  334. claude_mpm/services/socketio/handlers/file.py +123 -108
  335. claude_mpm/services/socketio/handlers/git.py +607 -373
  336. claude_mpm/services/socketio/handlers/hook.py +170 -0
  337. claude_mpm/services/socketio/handlers/memory.py +4 -4
  338. claude_mpm/services/socketio/handlers/project.py +4 -4
  339. claude_mpm/services/socketio/handlers/registry.py +53 -38
  340. claude_mpm/services/socketio/server/__init__.py +18 -0
  341. claude_mpm/services/socketio/server/broadcaster.py +252 -0
  342. claude_mpm/services/socketio/server/core.py +399 -0
  343. claude_mpm/services/socketio/server/main.py +323 -0
  344. claude_mpm/services/socketio_client_manager.py +160 -133
  345. claude_mpm/services/socketio_server.py +36 -1885
  346. claude_mpm/services/subprocess_launcher_service.py +316 -0
  347. claude_mpm/services/system_instructions_service.py +258 -0
  348. claude_mpm/services/ticket_manager.py +20 -534
  349. claude_mpm/services/utility_service.py +285 -0
  350. claude_mpm/services/version_control/__init__.py +18 -21
  351. claude_mpm/services/version_control/branch_strategy.py +20 -10
  352. claude_mpm/services/version_control/conflict_resolution.py +37 -13
  353. claude_mpm/services/version_control/git_operations.py +52 -21
  354. claude_mpm/services/version_control/semantic_versioning.py +92 -53
  355. claude_mpm/services/version_control/version_parser.py +145 -125
  356. claude_mpm/services/version_service.py +270 -0
  357. claude_mpm/storage/__init__.py +9 -0
  358. claude_mpm/storage/state_storage.py +552 -0
  359. claude_mpm/ticket_wrapper.py +2 -2
  360. claude_mpm/utils/__init__.py +2 -2
  361. claude_mpm/utils/agent_dependency_loader.py +453 -243
  362. claude_mpm/utils/config_manager.py +157 -118
  363. claude_mpm/utils/console.py +1 -1
  364. claude_mpm/utils/dependency_cache.py +102 -107
  365. claude_mpm/utils/dependency_manager.py +52 -47
  366. claude_mpm/utils/dependency_strategies.py +131 -96
  367. claude_mpm/utils/environment_context.py +110 -102
  368. claude_mpm/utils/error_handler.py +75 -55
  369. claude_mpm/utils/file_utils.py +80 -67
  370. claude_mpm/utils/framework_detection.py +12 -11
  371. claude_mpm/utils/import_migration_example.py +12 -60
  372. claude_mpm/utils/imports.py +48 -45
  373. claude_mpm/utils/path_operations.py +100 -93
  374. claude_mpm/utils/robust_installer.py +172 -164
  375. claude_mpm/utils/session_logging.py +30 -23
  376. claude_mpm/utils/subprocess_utils.py +99 -61
  377. claude_mpm/validation/__init__.py +1 -1
  378. claude_mpm/validation/agent_validator.py +151 -111
  379. claude_mpm/validation/frontmatter_validator.py +92 -71
  380. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +51 -2
  381. claude_mpm-4.0.3.dist-info/RECORD +402 -0
  382. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
  383. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
  384. claude_mpm/config/memory_guardian_config.py +0 -325
  385. claude_mpm/core/config_paths.py +0 -150
  386. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  387. claude_mpm/deployment_paths.py +0 -261
  388. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  389. claude_mpm/models/state_models.py +0 -433
  390. claude_mpm/services/agent/__init__.py +0 -24
  391. claude_mpm/services/agent/deployment.py +0 -2548
  392. claude_mpm/services/agent/management.py +0 -598
  393. claude_mpm/services/agent/registry.py +0 -813
  394. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  395. claude_mpm/services/communication/socketio.py +0 -1935
  396. claude_mpm/services/communication/websocket.py +0 -479
  397. claude_mpm/services/framework_claude_md_generator.py +0 -624
  398. claude_mpm/services/health_monitor.py +0 -893
  399. claude_mpm/services/infrastructure/memory_guardian.py +0 -770
  400. claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
  401. claude_mpm/services/optimized_hook_service.py +0 -542
  402. claude_mpm/services/project_analyzer.py +0 -864
  403. claude_mpm/services/project_registry.py +0 -608
  404. claude_mpm/services/standalone_socketio_server.py +0 -1300
  405. claude_mpm/services/ticket_manager_di.py +0 -318
  406. claude_mpm/services/ticketing_service_original.py +0 -510
  407. claude_mpm/utils/paths.py +0 -395
  408. claude_mpm/utils/platform_memory.py +0 -524
  409. claude_mpm-3.9.9.dist-info/RECORD +0 -293
  410. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
  411. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
@@ -22,198 +22,207 @@ documentation verbatim. Creates concise learnings that fit memory constraints
22
22
  while preserving essential information.
23
23
  """
24
24
 
25
- import re
26
25
  import os
27
- from pathlib import Path
28
- from typing import Dict, List, Optional, Any, Tuple
26
+ import re
29
27
  from datetime import datetime
28
+ from pathlib import Path
29
+ from typing import Any, Dict, List, Optional, Tuple
30
30
 
31
- from claude_mpm.core.mixins import LoggerMixin
32
31
  from claude_mpm.core.config import Config
33
- from claude_mpm.utils.paths import PathResolver
32
+ from claude_mpm.core.mixins import LoggerMixin
33
+ from claude_mpm.core.unified_paths import get_path_manager
34
34
  from claude_mpm.services.memory.router import MemoryRouter
35
- from claude_mpm.services.project_analyzer import ProjectAnalyzer
35
+ from claude_mpm.services.project.analyzer import ProjectAnalyzer
36
36
 
37
37
 
38
38
  class MemoryBuilder(LoggerMixin):
39
39
  """Builds agent memories from project documentation.
40
-
40
+
41
41
  WHY: Documentation contains patterns and guidelines that agents should know
42
42
  about. Manual memory creation is time-consuming and prone to inconsistency.
43
43
  This service automates the extraction and assignment process.
44
-
44
+
45
45
  DESIGN DECISION: Uses pattern matching and content analysis to extract
46
46
  actionable insights rather than copying raw documentation. Focuses on
47
47
  creating learnings that will actually be useful to agents.
48
48
  """
49
-
49
+
50
50
  # Documentation files to process
51
51
  DOC_FILES = {
52
- 'CLAUDE.md': {
53
- 'priority': 'high',
54
- 'sections': ['development guidelines', 'key components', 'common issues'],
55
- 'agents': ['pm', 'engineer']
52
+ "CLAUDE.md": {
53
+ "priority": "high",
54
+ "sections": ["development guidelines", "key components", "common issues"],
55
+ "agents": ["pm", "engineer"],
56
+ },
57
+ "docs/STRUCTURE.md": {
58
+ "priority": "high",
59
+ "sections": ["file placement", "design patterns", "directory structure"],
60
+ "agents": ["engineer", "documentation"],
56
61
  },
57
- 'docs/STRUCTURE.md': {
58
- 'priority': 'high',
59
- 'sections': ['file placement', 'design patterns', 'directory structure'],
60
- 'agents': ['engineer', 'documentation']
62
+ "docs/QA.md": {
63
+ "priority": "high",
64
+ "sections": ["testing", "quality assurance", "validation"],
65
+ "agents": ["qa", "engineer"],
61
66
  },
62
- 'docs/QA.md': {
63
- 'priority': 'high',
64
- 'sections': ['testing', 'quality assurance', 'validation'],
65
- 'agents': ['qa', 'engineer']
67
+ "docs/DEPLOY.md": {
68
+ "priority": "medium",
69
+ "sections": ["deployment", "versioning", "release"],
70
+ "agents": ["engineer", "pm"],
66
71
  },
67
- 'docs/DEPLOY.md': {
68
- 'priority': 'medium',
69
- 'sections': ['deployment', 'versioning', 'release'],
70
- 'agents': ['engineer', 'pm']
72
+ "docs/VERSIONING.md": {
73
+ "priority": "medium",
74
+ "sections": ["version management", "semantic versioning"],
75
+ "agents": ["engineer", "pm"],
71
76
  },
72
- 'docs/VERSIONING.md': {
73
- 'priority': 'medium',
74
- 'sections': ['version management', 'semantic versioning'],
75
- 'agents': ['engineer', 'pm']
76
- }
77
77
  }
78
-
78
+
79
79
  # Patterns for extracting actionable content
80
80
  EXTRACTION_PATTERNS = {
81
- 'guidelines': [
82
- r'(?:must|should|always|never|avoid|ensure|remember to)\s+(.+?)(?:\.|$)',
83
- r'(?:important|note|warning|tip):\s*(.+?)(?:\.|$)',
84
- r'(?:do not|don\'t)\s+(.+?)(?:\.|$)'
81
+ "guidelines": [
82
+ r"(?:must|should|always|never|avoid|ensure|remember to)\s+(.+?)(?:\.|$)",
83
+ r"(?:important|note|warning|tip):\s*(.+?)(?:\.|$)",
84
+ r"(?:do not|don\'t)\s+(.+?)(?:\.|$)",
85
85
  ],
86
- 'patterns': [
87
- r'(?:pattern|approach|strategy|method):\s*(.+?)(?:\.|$)',
88
- r'(?:use|implement|follow)\s+(.+?)\s+(?:pattern|approach|for)',
89
- r'(?:follows|uses|implements)\s+(.+?)\s+(?:pattern|architecture)'
86
+ "patterns": [
87
+ r"(?:pattern|approach|strategy|method):\s*(.+?)(?:\.|$)",
88
+ r"(?:use|implement|follow)\s+(.+?)\s+(?:pattern|approach|for)",
89
+ r"(?:follows|uses|implements)\s+(.+?)\s+(?:pattern|architecture)",
90
90
  ],
91
- 'mistakes': [
92
- r'(?:common\s+)?(?:mistake|error|issue|problem):\s*(.+?)(?:\.|$)',
93
- r'(?:avoid|never|don\'t)\s+(.+?)(?:\.|$)',
94
- r'(?:pitfall|gotcha|warning):\s*(.+?)(?:\.|$)'
91
+ "mistakes": [
92
+ r"(?:common\s+)?(?:mistake|error|issue|problem):\s*(.+?)(?:\.|$)",
93
+ r"(?:avoid|never|don\'t)\s+(.+?)(?:\.|$)",
94
+ r"(?:pitfall|gotcha|warning):\s*(.+?)(?:\.|$)",
95
+ ],
96
+ "architecture": [
97
+ r"(?:architecture|structure|design):\s*(.+?)(?:\.|$)",
98
+ r"(?:component|service|module)\s+(.+?)\s+(?:provides|handles|manages)",
99
+ r"(?:uses|implements|follows)\s+(.+?)\s+(?:architecture|pattern)",
95
100
  ],
96
- 'architecture': [
97
- r'(?:architecture|structure|design):\s*(.+?)(?:\.|$)',
98
- r'(?:component|service|module)\s+(.+?)\s+(?:provides|handles|manages)',
99
- r'(?:uses|implements|follows)\s+(.+?)\s+(?:architecture|pattern)'
100
- ]
101
101
  }
102
-
103
- def __init__(self, config: Optional[Config] = None, working_directory: Optional[Path] = None):
102
+
103
+ def __init__(
104
+ self, config: Optional[Config] = None, working_directory: Optional[Path] = None
105
+ ):
104
106
  """Initialize the memory builder.
105
-
107
+
106
108
  Args:
107
109
  config: Optional Config object
108
110
  working_directory: Optional working directory for project-specific analysis
109
111
  """
110
112
  super().__init__()
111
113
  self.config = config or Config()
112
- self.project_root = PathResolver.get_project_root()
114
+ self.project_root = get_path_manager().project_root
113
115
  # Use current working directory by default, not project root
114
116
  self.working_directory = working_directory or Path(os.getcwd())
115
117
  self.memories_dir = self.working_directory / ".claude-mpm" / "memories"
116
118
  self.router = MemoryRouter(config)
117
119
  self.project_analyzer = ProjectAnalyzer(config, self.working_directory)
118
-
120
+
119
121
  def _get_dynamic_doc_files(self) -> Dict[str, Dict[str, Any]]:
120
122
  """Get documentation files to process based on project analysis.
121
-
123
+
122
124
  WHY: Instead of hardcoded file list, dynamically discover important files
123
125
  based on actual project structure and characteristics.
124
-
126
+
125
127
  Returns:
126
128
  Dict mapping file paths to processing configuration
127
129
  """
128
130
  dynamic_files = {}
129
-
131
+
130
132
  # Start with static important files
131
133
  static_files = self.DOC_FILES.copy()
132
-
134
+
133
135
  # Get project-specific important files
134
136
  try:
135
137
  important_files = self.project_analyzer.get_important_files_for_context()
136
138
  project_characteristics = self.project_analyzer.analyze_project()
137
-
139
+
138
140
  # Add configuration files
139
141
  for config_file in project_characteristics.important_configs:
140
142
  if config_file not in static_files:
141
143
  file_ext = Path(config_file).suffix.lower()
142
-
143
- if file_ext in ['.json', '.toml', '.yaml', '.yml']:
144
+
145
+ if file_ext in [".json", ".toml", ".yaml", ".yml"]:
144
146
  dynamic_files[config_file] = {
145
- 'priority': 'medium',
146
- 'sections': ['configuration', 'setup', 'dependencies'],
147
- 'agents': ['engineer', 'pm'],
148
- 'file_type': 'config'
147
+ "priority": "medium",
148
+ "sections": ["configuration", "setup", "dependencies"],
149
+ "agents": ["engineer", "pm"],
150
+ "file_type": "config",
149
151
  }
150
-
152
+
151
153
  # Add project-specific documentation
152
154
  for doc_file in important_files:
153
155
  if doc_file not in static_files and doc_file not in dynamic_files:
154
156
  file_path = Path(doc_file)
155
-
157
+
156
158
  # Determine processing config based on file name/path
157
- if 'api' in doc_file.lower() or 'endpoint' in doc_file.lower():
159
+ if "api" in doc_file.lower() or "endpoint" in doc_file.lower():
158
160
  dynamic_files[doc_file] = {
159
- 'priority': 'high',
160
- 'sections': ['api', 'endpoints', 'integration'],
161
- 'agents': ['engineer', 'integration'],
162
- 'file_type': 'api_doc'
161
+ "priority": "high",
162
+ "sections": ["api", "endpoints", "integration"],
163
+ "agents": ["engineer", "integration"],
164
+ "file_type": "api_doc",
163
165
  }
164
- elif 'architecture' in doc_file.lower() or 'design' in doc_file.lower():
166
+ elif (
167
+ "architecture" in doc_file.lower()
168
+ or "design" in doc_file.lower()
169
+ ):
165
170
  dynamic_files[doc_file] = {
166
- 'priority': 'high',
167
- 'sections': ['architecture', 'design', 'patterns'],
168
- 'agents': ['engineer', 'architect'],
169
- 'file_type': 'architecture'
171
+ "priority": "high",
172
+ "sections": ["architecture", "design", "patterns"],
173
+ "agents": ["engineer", "architect"],
174
+ "file_type": "architecture",
170
175
  }
171
- elif 'test' in doc_file.lower():
176
+ elif "test" in doc_file.lower():
172
177
  dynamic_files[doc_file] = {
173
- 'priority': 'medium',
174
- 'sections': ['testing', 'quality'],
175
- 'agents': ['qa', 'engineer'],
176
- 'file_type': 'test_doc'
178
+ "priority": "medium",
179
+ "sections": ["testing", "quality"],
180
+ "agents": ["qa", "engineer"],
181
+ "file_type": "test_doc",
177
182
  }
178
- elif file_path.suffix.lower() == '.md':
183
+ elif file_path.suffix.lower() == ".md":
179
184
  # Generic markdown file
180
185
  dynamic_files[doc_file] = {
181
- 'priority': 'low',
182
- 'sections': ['documentation', 'guidelines'],
183
- 'agents': ['pm', 'engineer'],
184
- 'file_type': 'markdown'
186
+ "priority": "low",
187
+ "sections": ["documentation", "guidelines"],
188
+ "agents": ["pm", "engineer"],
189
+ "file_type": "markdown",
185
190
  }
186
-
191
+
187
192
  # Add key source files for pattern analysis (limited selection)
188
193
  if project_characteristics.entry_points:
189
- for entry_point in project_characteristics.entry_points[:2]: # Only first 2
194
+ for entry_point in project_characteristics.entry_points[
195
+ :2
196
+ ]: # Only first 2
190
197
  if entry_point not in dynamic_files:
191
198
  dynamic_files[entry_point] = {
192
- 'priority': 'low',
193
- 'sections': ['patterns', 'implementation'],
194
- 'agents': ['engineer'],
195
- 'file_type': 'source',
196
- 'extract_patterns_only': True # Only extract patterns, not full content
199
+ "priority": "low",
200
+ "sections": ["patterns", "implementation"],
201
+ "agents": ["engineer"],
202
+ "file_type": "source",
203
+ "extract_patterns_only": True, # Only extract patterns, not full content
197
204
  }
198
-
205
+
199
206
  except Exception as e:
200
207
  self.logger.warning(f"Error getting dynamic doc files: {e}")
201
-
208
+
202
209
  # Merge static and dynamic files
203
210
  all_files = {**static_files, **dynamic_files}
204
-
205
- self.logger.debug(f"Processing {len(all_files)} documentation files ({len(static_files)} static, {len(dynamic_files)} dynamic)")
211
+
212
+ self.logger.debug(
213
+ f"Processing {len(all_files)} documentation files ({len(static_files)} static, {len(dynamic_files)} dynamic)"
214
+ )
206
215
  return all_files
207
-
216
+
208
217
  def build_from_documentation(self, force_rebuild: bool = False) -> Dict[str, Any]:
209
218
  """Build agent memories from project documentation.
210
-
219
+
211
220
  WHY: Documentation contains project-specific knowledge that agents need.
212
221
  This method extracts and assigns relevant information to appropriate agents.
213
-
222
+
214
223
  Args:
215
224
  force_rebuild: If True, rebuilds even if docs haven't changed
216
-
225
+
217
226
  Returns:
218
227
  Dict containing build results and statistics
219
228
  """
@@ -226,123 +235,140 @@ class MemoryBuilder(LoggerMixin):
226
235
  "memories_updated": 0,
227
236
  "agents_affected": set(),
228
237
  "files": {},
229
- "errors": []
238
+ "errors": [],
230
239
  }
231
-
240
+
232
241
  # Get dynamic list of files to process
233
242
  doc_files = self._get_dynamic_doc_files()
234
-
243
+
235
244
  # Process each documentation file
236
245
  for doc_path, doc_config in doc_files.items():
237
246
  file_path = self.project_root / doc_path
238
-
247
+
239
248
  if not file_path.exists():
240
249
  self.logger.debug(f"Documentation file not found: {doc_path}")
241
250
  continue
242
-
251
+
243
252
  # Check if rebuild is needed
244
253
  if not force_rebuild and not self._needs_rebuild(file_path):
245
254
  self.logger.debug(f"Skipping {doc_path} - no changes detected")
246
255
  continue
247
-
256
+
248
257
  file_result = self._process_documentation_file(file_path, doc_config)
249
258
  results["files"][doc_path] = file_result
250
-
259
+
251
260
  # Aggregate results
252
261
  if file_result.get("success"):
253
262
  results["files_processed"] += 1
254
- results["memories_created"] += file_result.get("memories_created", 0)
255
- results["memories_updated"] += file_result.get("memories_updated", 0)
256
- results["agents_affected"].update(file_result.get("agents_affected", []))
263
+ results["memories_created"] += file_result.get(
264
+ "memories_created", 0
265
+ )
266
+ results["memories_updated"] += file_result.get(
267
+ "memories_updated", 0
268
+ )
269
+ results["agents_affected"].update(
270
+ file_result.get("agents_affected", [])
271
+ )
257
272
  else:
258
- results["errors"].append(f"{doc_path}: {file_result.get('error', 'Unknown error')}")
259
-
273
+ results["errors"].append(
274
+ f"{doc_path}: {file_result.get('error', 'Unknown error')}"
275
+ )
276
+
260
277
  # Convert set to list for JSON serialization
261
278
  results["agents_affected"] = list(results["agents_affected"])
262
279
  results["total_agents_affected"] = len(results["agents_affected"])
263
-
264
- self.logger.info(f"Built memories from documentation: {results['files_processed']} files, {results['memories_created']} memories created")
280
+
281
+ self.logger.info(
282
+ f"Built memories from documentation: {results['files_processed']} files, {results['memories_created']} memories created"
283
+ )
265
284
  return results
266
-
285
+
267
286
  except Exception as e:
268
287
  self.logger.error(f"Error building memories from documentation: {e}")
269
288
  return {
270
289
  "success": False,
271
290
  "error": str(e),
272
- "timestamp": datetime.now().isoformat()
291
+ "timestamp": datetime.now().isoformat(),
273
292
  }
274
-
293
+
275
294
  def extract_from_text(self, text: str, source: str) -> List[Dict[str, Any]]:
276
295
  """Extract memory-worthy content from text.
277
-
296
+
278
297
  WHY: Provides reusable text extraction logic that can be used for
279
298
  custom documentation or other text sources beyond standard files.
280
-
299
+
281
300
  Args:
282
301
  text: Text content to analyze
283
302
  source: Source identifier for context
284
-
303
+
285
304
  Returns:
286
305
  List of extracted memory items with metadata
287
306
  """
288
307
  try:
289
308
  extracted_items = []
290
-
309
+
291
310
  # Process each extraction pattern type
292
311
  for pattern_type, patterns in self.EXTRACTION_PATTERNS.items():
293
312
  for pattern in patterns:
294
313
  matches = re.finditer(pattern, text, re.IGNORECASE | re.MULTILINE)
295
-
314
+
296
315
  for match in matches:
297
316
  content = match.group(1).strip()
298
-
317
+
299
318
  # Clean and validate content
300
319
  content = self._clean_extracted_content(content)
301
320
  if not self._is_valid_memory_content(content):
302
321
  continue
303
-
322
+
304
323
  # Route to appropriate agent
305
324
  routing_result = self.router.analyze_and_route(content)
306
-
325
+
307
326
  extracted_item = {
308
327
  "content": content,
309
328
  "type": pattern_type,
310
329
  "source": source,
311
330
  "target_agent": routing_result.get("target_agent", "pm"),
312
- "section": routing_result.get("section", "Recent Learnings"),
331
+ "section": routing_result.get(
332
+ "section", "Recent Learnings"
333
+ ),
313
334
  "confidence": routing_result.get("confidence", 0.5),
314
- "pattern_matched": pattern
335
+ "pattern_matched": pattern,
315
336
  }
316
-
337
+
317
338
  extracted_items.append(extracted_item)
318
-
339
+
319
340
  # Remove near-duplicates
320
341
  unique_items = self._deduplicate_extracted_items(extracted_items)
321
-
322
- self.logger.debug(f"Extracted {len(unique_items)} unique items from {source}")
342
+
343
+ self.logger.debug(
344
+ f"Extracted {len(unique_items)} unique items from {source}"
345
+ )
323
346
  return unique_items
324
-
347
+
325
348
  except Exception as e:
326
349
  self.logger.error(f"Error extracting content from text: {e}")
327
350
  return []
328
-
329
- def build_agent_memory_from_items(self, agent_id: str, items: List[Dict[str, Any]]) -> Dict[str, Any]:
351
+
352
+ def build_agent_memory_from_items(
353
+ self, agent_id: str, items: List[Dict[str, Any]]
354
+ ) -> Dict[str, Any]:
330
355
  """Build or update agent memory from extracted items.
331
-
356
+
332
357
  WHY: Extracted items need to be properly integrated into agent memory
333
358
  files while respecting existing content and size limits.
334
-
359
+
335
360
  Args:
336
361
  agent_id: Target agent identifier
337
362
  items: List of extracted memory items
338
-
363
+
339
364
  Returns:
340
365
  Dict containing update results
341
366
  """
342
367
  try:
343
368
  from claude_mpm.services.agents.memory import get_memory_manager
369
+
344
370
  memory_manager = get_memory_manager(self.config)
345
-
371
+
346
372
  result = {
347
373
  "success": True,
348
374
  "agent_id": agent_id,
@@ -350,299 +376,340 @@ class MemoryBuilder(LoggerMixin):
350
376
  "items_added": 0,
351
377
  "items_skipped": 0,
352
378
  "sections_updated": set(),
353
- "errors": []
379
+ "errors": [],
354
380
  }
355
-
381
+
356
382
  # Filter items for this agent
357
- agent_items = [item for item in items if item.get("target_agent") == agent_id]
358
-
383
+ agent_items = [
384
+ item for item in items if item.get("target_agent") == agent_id
385
+ ]
386
+
359
387
  for item in agent_items:
360
388
  result["items_processed"] += 1
361
-
389
+
362
390
  try:
363
391
  # Add to memory
364
392
  section = item.get("section", "Recent Learnings")
365
393
  content = item.get("content", "")
366
-
367
- success = memory_manager.update_agent_memory(agent_id, section, content)
368
-
394
+
395
+ success = memory_manager.update_agent_memory(
396
+ agent_id, section, content
397
+ )
398
+
369
399
  if success:
370
400
  result["items_added"] += 1
371
401
  result["sections_updated"].add(section)
372
402
  else:
373
403
  result["items_skipped"] += 1
374
404
  result["errors"].append(f"Failed to add: {content[:50]}...")
375
-
405
+
376
406
  except Exception as e:
377
407
  result["items_skipped"] += 1
378
408
  result["errors"].append(f"Error processing item: {str(e)}")
379
-
409
+
380
410
  # Convert set to list
381
411
  result["sections_updated"] = list(result["sections_updated"])
382
-
412
+
383
413
  return result
384
-
414
+
385
415
  except Exception as e:
386
416
  self.logger.error(f"Error building memory for {agent_id}: {e}")
387
- return {
388
- "success": False,
389
- "agent_id": agent_id,
390
- "error": str(e)
391
- }
392
-
393
- def _extract_from_config_file(self, content: str, file_path: Path, doc_config: Dict[str, Any]) -> List[Dict[str, Any]]:
417
+ return {"success": False, "agent_id": agent_id, "error": str(e)}
418
+
419
+ def _extract_from_config_file(
420
+ self, content: str, file_path: Path, doc_config: Dict[str, Any]
421
+ ) -> List[Dict[str, Any]]:
394
422
  """Extract memory-worthy information from configuration files.
395
-
423
+
396
424
  WHY: Configuration files contain important setup patterns, dependencies,
397
425
  and architectural decisions that agents should understand.
398
-
426
+
399
427
  Args:
400
428
  content: File content
401
429
  file_path: Path to the file
402
430
  doc_config: Processing configuration
403
-
431
+
404
432
  Returns:
405
433
  List of extracted memory items
406
434
  """
407
435
  extracted_items = []
408
436
  source = str(file_path.relative_to(self.project_root))
409
-
437
+
410
438
  try:
411
439
  file_ext = file_path.suffix.lower()
412
-
413
- if file_ext == '.json':
440
+
441
+ if file_ext == ".json":
414
442
  # Parse JSON configuration
415
443
  import json
444
+
416
445
  config_data = json.loads(content)
417
446
  items = self._extract_from_json_config(config_data, source)
418
447
  extracted_items.extend(items)
419
-
420
- elif file_ext in ['.toml']:
448
+
449
+ elif file_ext in [".toml"]:
421
450
  # Parse TOML configuration
422
451
  try:
423
452
  try:
424
453
  import tomllib
425
454
  except ImportError:
426
455
  import tomli as tomllib
427
- with open(file_path, 'rb') as f:
456
+ with open(file_path, "rb") as f:
428
457
  config_data = tomllib.load(f)
429
458
  items = self._extract_from_toml_config(config_data, source)
430
459
  extracted_items.extend(items)
431
460
  except ImportError:
432
461
  self.logger.warning(f"TOML parsing not available for {source}")
433
-
434
- elif file_ext in ['.yaml', '.yml']:
462
+
463
+ elif file_ext in [".yaml", ".yml"]:
435
464
  # For YAML, fall back to text-based extraction for now
436
465
  items = self.extract_from_text(content, source)
437
466
  extracted_items.extend(items)
438
-
467
+
439
468
  # Also extract text patterns for comments and documentation
440
469
  text_items = self.extract_from_text(content, source)
441
470
  extracted_items.extend(text_items)
442
-
471
+
443
472
  except Exception as e:
444
473
  self.logger.warning(f"Error parsing config file {source}: {e}")
445
474
  # Fall back to text extraction
446
475
  extracted_items = self.extract_from_text(content, source)
447
-
476
+
448
477
  return extracted_items
449
-
450
- def _extract_from_json_config(self, config_data: dict, source: str) -> List[Dict[str, Any]]:
478
+
479
+ def _extract_from_json_config(
480
+ self, config_data: dict, source: str
481
+ ) -> List[Dict[str, Any]]:
451
482
  """Extract patterns from JSON configuration."""
452
483
  items = []
453
-
484
+
454
485
  # Extract dependencies information
455
- if 'dependencies' in config_data:
456
- deps = config_data['dependencies']
486
+ if "dependencies" in config_data:
487
+ deps = config_data["dependencies"]
457
488
  if isinstance(deps, dict) and deps:
458
489
  dep_names = list(deps.keys())[:5] # Limit to prevent overwhelming
459
490
  deps_str = ", ".join(dep_names)
460
- items.append({
461
- "content": f"Key dependencies: {deps_str}",
462
- "type": "dependency_info",
463
- "source": source,
464
- "target_agent": "engineer",
465
- "section": "Current Technical Context",
466
- "confidence": 0.8
467
- })
468
-
469
- # Extract scripts (for package.json)
470
- if 'scripts' in config_data:
471
- scripts = config_data['scripts']
472
- if isinstance(scripts, dict):
473
- for script_name, script_cmd in list(scripts.items())[:3]: # Limit to first 3
474
- items.append({
475
- "content": f"Build script '{script_name}': {script_cmd[:50]}{'...' if len(script_cmd) > 50 else ''}",
476
- "type": "build_pattern",
491
+ items.append(
492
+ {
493
+ "content": f"Key dependencies: {deps_str}",
494
+ "type": "dependency_info",
477
495
  "source": source,
478
496
  "target_agent": "engineer",
479
- "section": "Implementation Guidelines",
480
- "confidence": 0.7
481
- })
482
-
497
+ "section": "Current Technical Context",
498
+ "confidence": 0.8,
499
+ }
500
+ )
501
+
502
+ # Extract scripts (for package.json)
503
+ if "scripts" in config_data:
504
+ scripts = config_data["scripts"]
505
+ if isinstance(scripts, dict):
506
+ for script_name, script_cmd in list(scripts.items())[
507
+ :3
508
+ ]: # Limit to first 3
509
+ items.append(
510
+ {
511
+ "content": f"Build script '{script_name}': {script_cmd[:50]}{'...' if len(script_cmd) > 50 else ''}",
512
+ "type": "build_pattern",
513
+ "source": source,
514
+ "target_agent": "engineer",
515
+ "section": "Implementation Guidelines",
516
+ "confidence": 0.7,
517
+ }
518
+ )
519
+
483
520
  return items
484
-
485
- def _extract_from_toml_config(self, config_data: dict, source: str) -> List[Dict[str, Any]]:
521
+
522
+ def _extract_from_toml_config(
523
+ self, config_data: dict, source: str
524
+ ) -> List[Dict[str, Any]]:
486
525
  """Extract patterns from TOML configuration."""
487
526
  items = []
488
-
527
+
489
528
  # Extract project metadata (for pyproject.toml)
490
- if 'project' in config_data:
491
- project_info = config_data['project']
492
- if 'dependencies' in project_info:
493
- deps = project_info['dependencies']
529
+ if "project" in config_data:
530
+ project_info = config_data["project"]
531
+ if "dependencies" in project_info:
532
+ deps = project_info["dependencies"]
494
533
  if deps:
495
- items.append({
496
- "content": f"Python dependencies: {', '.join(deps[:5])}",
534
+ items.append(
535
+ {
536
+ "content": f"Python dependencies: {', '.join(deps[:5])}",
537
+ "type": "dependency_info",
538
+ "source": source,
539
+ "target_agent": "engineer",
540
+ "section": "Current Technical Context",
541
+ "confidence": 0.8,
542
+ }
543
+ )
544
+
545
+ # Extract Rust dependencies (for Cargo.toml)
546
+ if "dependencies" in config_data:
547
+ deps = config_data["dependencies"]
548
+ if isinstance(deps, dict) and deps:
549
+ dep_names = list(deps.keys())[:5]
550
+ items.append(
551
+ {
552
+ "content": f"Rust dependencies: {', '.join(dep_names)}",
497
553
  "type": "dependency_info",
498
554
  "source": source,
499
555
  "target_agent": "engineer",
500
556
  "section": "Current Technical Context",
501
- "confidence": 0.8
502
- })
503
-
504
- # Extract Rust dependencies (for Cargo.toml)
505
- if 'dependencies' in config_data:
506
- deps = config_data['dependencies']
507
- if isinstance(deps, dict) and deps:
508
- dep_names = list(deps.keys())[:5]
509
- items.append({
510
- "content": f"Rust dependencies: {', '.join(dep_names)}",
511
- "type": "dependency_info",
512
- "source": source,
513
- "target_agent": "engineer",
514
- "section": "Current Technical Context",
515
- "confidence": 0.8
516
- })
517
-
557
+ "confidence": 0.8,
558
+ }
559
+ )
560
+
518
561
  return items
519
-
520
- def _extract_from_source_file(self, content: str, file_path: Path, doc_config: Dict[str, Any]) -> List[Dict[str, Any]]:
562
+
563
+ def _extract_from_source_file(
564
+ self, content: str, file_path: Path, doc_config: Dict[str, Any]
565
+ ) -> List[Dict[str, Any]]:
521
566
  """Extract patterns from source code files.
522
-
567
+
523
568
  WHY: Source files contain implementation patterns and architectural
524
569
  decisions that agents should be aware of, but we only extract high-level
525
570
  patterns rather than detailed code analysis.
526
-
571
+
527
572
  Args:
528
573
  content: File content
529
574
  file_path: Path to the file
530
575
  doc_config: Processing configuration
531
-
576
+
532
577
  Returns:
533
578
  List of extracted memory items
534
579
  """
535
580
  extracted_items = []
536
581
  source = str(file_path.relative_to(self.project_root))
537
-
582
+
538
583
  # Only extract patterns if specified
539
- if not doc_config.get('extract_patterns_only', False):
584
+ if not doc_config.get("extract_patterns_only", False):
540
585
  return []
541
-
586
+
542
587
  file_ext = file_path.suffix.lower()
543
-
588
+
544
589
  # Language-specific pattern extraction
545
- if file_ext == '.py':
590
+ if file_ext == ".py":
546
591
  items = self._extract_python_patterns(content, source)
547
592
  extracted_items.extend(items)
548
- elif file_ext in ['.js', '.ts']:
593
+ elif file_ext in [".js", ".ts"]:
549
594
  items = self._extract_javascript_patterns(content, source)
550
595
  extracted_items.extend(items)
551
-
596
+
552
597
  return extracted_items[:3] # Limit to prevent overwhelming
553
-
554
- def _extract_python_patterns(self, content: str, source: str) -> List[Dict[str, Any]]:
598
+
599
+ def _extract_python_patterns(
600
+ self, content: str, source: str
601
+ ) -> List[Dict[str, Any]]:
555
602
  """Extract high-level patterns from Python source."""
556
603
  items = []
557
-
604
+
558
605
  # Check for common patterns
559
606
  if 'if __name__ == "__main__"' in content:
560
- items.append({
561
- "content": "Uses if __name__ == '__main__' pattern for script execution",
562
- "type": "pattern",
563
- "source": source,
564
- "target_agent": "engineer",
565
- "section": "Coding Patterns Learned",
566
- "confidence": 0.8
567
- })
568
-
569
- if 'from pathlib import Path' in content:
570
- items.append({
571
- "content": "Uses pathlib.Path for file operations (recommended pattern)",
572
- "type": "pattern",
573
- "source": source,
574
- "target_agent": "engineer",
575
- "section": "Coding Patterns Learned",
576
- "confidence": 0.7
577
- })
578
-
607
+ items.append(
608
+ {
609
+ "content": "Uses if __name__ == '__main__' pattern for script execution",
610
+ "type": "pattern",
611
+ "source": source,
612
+ "target_agent": "engineer",
613
+ "section": "Coding Patterns Learned",
614
+ "confidence": 0.8,
615
+ }
616
+ )
617
+
618
+ if "from pathlib import Path" in content:
619
+ items.append(
620
+ {
621
+ "content": "Uses pathlib.Path for file operations (recommended pattern)",
622
+ "type": "pattern",
623
+ "source": source,
624
+ "target_agent": "engineer",
625
+ "section": "Coding Patterns Learned",
626
+ "confidence": 0.7,
627
+ }
628
+ )
629
+
579
630
  # Check for class definitions
580
- class_matches = re.findall(r'class\s+(\w+)', content)
631
+ class_matches = re.findall(r"class\s+(\w+)", content)
581
632
  if class_matches:
582
- items.append({
583
- "content": f"Defines classes: {', '.join(class_matches[:3])}",
584
- "type": "architecture",
585
- "source": source,
586
- "target_agent": "engineer",
587
- "section": "Project Architecture",
588
- "confidence": 0.6
589
- })
590
-
633
+ items.append(
634
+ {
635
+ "content": f"Defines classes: {', '.join(class_matches[:3])}",
636
+ "type": "architecture",
637
+ "source": source,
638
+ "target_agent": "engineer",
639
+ "section": "Project Architecture",
640
+ "confidence": 0.6,
641
+ }
642
+ )
643
+
591
644
  return items
592
-
593
- def _extract_javascript_patterns(self, content: str, source: str) -> List[Dict[str, Any]]:
645
+
646
+ def _extract_javascript_patterns(
647
+ self, content: str, source: str
648
+ ) -> List[Dict[str, Any]]:
594
649
  """Extract high-level patterns from JavaScript/TypeScript source."""
595
650
  items = []
596
-
651
+
597
652
  # Check for async patterns
598
- if 'async function' in content or 'async ' in content:
599
- items.append({
600
- "content": "Uses async/await patterns for asynchronous operations",
601
- "type": "pattern",
602
- "source": source,
603
- "target_agent": "engineer",
604
- "section": "Coding Patterns Learned",
605
- "confidence": 0.8
606
- })
607
-
653
+ if "async function" in content or "async " in content:
654
+ items.append(
655
+ {
656
+ "content": "Uses async/await patterns for asynchronous operations",
657
+ "type": "pattern",
658
+ "source": source,
659
+ "target_agent": "engineer",
660
+ "section": "Coding Patterns Learned",
661
+ "confidence": 0.8,
662
+ }
663
+ )
664
+
608
665
  # Check for module patterns
609
- if 'export ' in content:
610
- items.append({
611
- "content": "Uses ES6 module export patterns",
612
- "type": "pattern",
613
- "source": source,
614
- "target_agent": "engineer",
615
- "section": "Coding Patterns Learned",
616
- "confidence": 0.7
617
- })
618
-
666
+ if "export " in content:
667
+ items.append(
668
+ {
669
+ "content": "Uses ES6 module export patterns",
670
+ "type": "pattern",
671
+ "source": source,
672
+ "target_agent": "engineer",
673
+ "section": "Coding Patterns Learned",
674
+ "confidence": 0.7,
675
+ }
676
+ )
677
+
619
678
  return items
620
-
621
- def _process_documentation_file(self, file_path: Path, doc_config: Dict[str, Any]) -> Dict[str, Any]:
679
+
680
+ def _process_documentation_file(
681
+ self, file_path: Path, doc_config: Dict[str, Any]
682
+ ) -> Dict[str, Any]:
622
683
  """Process a single documentation file with enhanced file type support.
623
-
684
+
624
685
  Args:
625
686
  file_path: Path to documentation file
626
687
  doc_config: Configuration for this file type
627
-
688
+
628
689
  Returns:
629
690
  Processing results
630
691
  """
631
692
  try:
632
693
  # Read file content
633
- content = file_path.read_text(encoding='utf-8', errors='ignore')
634
-
694
+ content = file_path.read_text(encoding="utf-8", errors="ignore")
695
+
635
696
  # Handle different file types
636
- file_type = doc_config.get('file_type', 'markdown')
637
-
638
- if file_type == 'config':
639
- extracted_items = self._extract_from_config_file(content, file_path, doc_config)
640
- elif file_type == 'source':
641
- extracted_items = self._extract_from_source_file(content, file_path, doc_config)
697
+ file_type = doc_config.get("file_type", "markdown")
698
+
699
+ if file_type == "config":
700
+ extracted_items = self._extract_from_config_file(
701
+ content, file_path, doc_config
702
+ )
703
+ elif file_type == "source":
704
+ extracted_items = self._extract_from_source_file(
705
+ content, file_path, doc_config
706
+ )
642
707
  else:
643
708
  # Default markdown/text processing
644
- extracted_items = self.extract_from_text(content, str(file_path.relative_to(self.project_root)))
645
-
709
+ extracted_items = self.extract_from_text(
710
+ content, str(file_path.relative_to(self.project_root))
711
+ )
712
+
646
713
  result = {
647
714
  "success": True,
648
715
  "file_path": str(file_path),
@@ -651,9 +718,9 @@ class MemoryBuilder(LoggerMixin):
651
718
  "memories_created": 0,
652
719
  "memories_updated": 0,
653
720
  "agents_affected": [],
654
- "agent_results": {}
721
+ "agent_results": {},
655
722
  }
656
-
723
+
657
724
  # Group items by target agent
658
725
  agent_items = {}
659
726
  for item in extracted_items:
@@ -661,165 +728,187 @@ class MemoryBuilder(LoggerMixin):
661
728
  if agent not in agent_items:
662
729
  agent_items[agent] = []
663
730
  agent_items[agent].append(item)
664
-
731
+
665
732
  # Update each agent's memory
666
733
  for agent_id, items in agent_items.items():
667
734
  agent_result = self.build_agent_memory_from_items(agent_id, items)
668
735
  result["agent_results"][agent_id] = agent_result
669
-
736
+
670
737
  if agent_result.get("success"):
671
738
  result["agents_affected"].append(agent_id)
672
739
  result["memories_created"] += agent_result.get("items_added", 0)
673
-
740
+
674
741
  # Update last processed timestamp
675
742
  self._update_last_processed(file_path)
676
-
743
+
677
744
  return result
678
-
745
+
679
746
  except Exception as e:
680
747
  self.logger.error(f"Error processing documentation file {file_path}: {e}")
681
- return {
682
- "success": False,
683
- "file_path": str(file_path),
684
- "error": str(e)
685
- }
686
-
748
+ return {"success": False, "file_path": str(file_path), "error": str(e)}
749
+
687
750
  def _needs_rebuild(self, file_path: Path) -> bool:
688
751
  """Check if documentation file needs to be processed.
689
-
752
+
690
753
  Args:
691
754
  file_path: Path to documentation file
692
-
755
+
693
756
  Returns:
694
757
  True if file needs processing
695
758
  """
696
759
  # Check if file was modified since last processing
697
760
  try:
698
761
  last_processed_file = self.memories_dir / ".last_processed.json"
699
-
762
+
700
763
  if not last_processed_file.exists():
701
764
  return True
702
-
765
+
703
766
  import json
767
+
704
768
  last_processed = json.loads(last_processed_file.read_text())
705
-
769
+
706
770
  file_key = str(file_path.relative_to(self.project_root))
707
771
  if file_key not in last_processed:
708
772
  return True
709
-
773
+
710
774
  last_processed_time = datetime.fromisoformat(last_processed[file_key])
711
775
  file_modified_time = datetime.fromtimestamp(file_path.stat().st_mtime)
712
-
776
+
713
777
  return file_modified_time > last_processed_time
714
-
778
+
715
779
  except Exception as e:
716
780
  self.logger.debug(f"Error checking rebuild status for {file_path}: {e}")
717
781
  return True # Default to rebuilding if we can't determine
718
-
782
+
719
783
  def _update_last_processed(self, file_path: Path):
720
784
  """Update last processed timestamp for file.
721
-
785
+
722
786
  Args:
723
787
  file_path: Path to documentation file
724
788
  """
725
789
  try:
726
790
  self.memories_dir.mkdir(parents=True, exist_ok=True)
727
791
  last_processed_file = self.memories_dir / ".last_processed.json"
728
-
792
+
729
793
  # Load existing data
730
794
  if last_processed_file.exists():
731
795
  import json
796
+
732
797
  last_processed = json.loads(last_processed_file.read_text())
733
798
  else:
734
799
  last_processed = {}
735
-
800
+
736
801
  # Update timestamp
737
802
  file_key = str(file_path.relative_to(self.project_root))
738
803
  last_processed[file_key] = datetime.now().isoformat()
739
-
804
+
740
805
  # Save back
741
806
  import json
807
+
742
808
  last_processed_file.write_text(json.dumps(last_processed, indent=2))
743
-
809
+
744
810
  except Exception as e:
745
811
  self.logger.warning(f"Error updating last processed timestamp: {e}")
746
-
812
+
747
813
  def _clean_extracted_content(self, content: str) -> str:
748
814
  """Clean and normalize extracted content.
749
-
815
+
750
816
  Args:
751
817
  content: Raw extracted content
752
-
818
+
753
819
  Returns:
754
820
  Cleaned content string
755
821
  """
756
822
  # Remove markdown formatting
757
- content = re.sub(r'[*_`#]+', '', content)
758
-
823
+ content = re.sub(r"[*_`#]+", "", content)
824
+
759
825
  # Remove extra whitespace
760
- content = re.sub(r'\s+', ' ', content).strip()
761
-
826
+ content = re.sub(r"\s+", " ", content).strip()
827
+
762
828
  # Remove common prefixes that don't add value
763
- content = re.sub(r'^(?:note:|tip:|important:|warning:)\s*', '', content, flags=re.IGNORECASE)
764
-
829
+ content = re.sub(
830
+ r"^(?:note:|tip:|important:|warning:)\s*", "", content, flags=re.IGNORECASE
831
+ )
832
+
765
833
  # Truncate to memory limit (with ellipsis if needed)
766
834
  if len(content) > 95: # Leave room for ellipsis
767
835
  content = content[:95] + "..."
768
-
836
+
769
837
  return content
770
-
838
+
771
839
  def _is_valid_memory_content(self, content: str) -> bool:
772
840
  """Validate if content is suitable for memory storage.
773
-
841
+
774
842
  Args:
775
843
  content: Content to validate
776
-
844
+
777
845
  Returns:
778
846
  True if content is valid for memory
779
847
  """
780
848
  # Must have minimum length
781
849
  if len(content) < 10:
782
850
  return False
783
-
851
+
784
852
  # Must contain actionable information
785
- actionable_words = ['use', 'avoid', 'ensure', 'follow', 'implement', 'check', 'must', 'should', 'never', 'always']
853
+ actionable_words = [
854
+ "use",
855
+ "avoid",
856
+ "ensure",
857
+ "follow",
858
+ "implement",
859
+ "check",
860
+ "must",
861
+ "should",
862
+ "never",
863
+ "always",
864
+ ]
786
865
  if not any(word in content.lower() for word in actionable_words):
787
866
  return False
788
-
867
+
789
868
  # Avoid overly generic content
790
- generic_phrases = ['this is', 'this document', 'see above', 'as mentioned', 'for more info']
869
+ generic_phrases = [
870
+ "this is",
871
+ "this document",
872
+ "see above",
873
+ "as mentioned",
874
+ "for more info",
875
+ ]
791
876
  if any(phrase in content.lower() for phrase in generic_phrases):
792
877
  return False
793
-
878
+
794
879
  return True
795
-
796
- def _deduplicate_extracted_items(self, items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
880
+
881
+ def _deduplicate_extracted_items(
882
+ self, items: List[Dict[str, Any]]
883
+ ) -> List[Dict[str, Any]]:
797
884
  """Remove near-duplicate extracted items.
798
-
885
+
799
886
  Args:
800
887
  items: List of extracted items
801
-
888
+
802
889
  Returns:
803
890
  Deduplicated list
804
891
  """
805
892
  from difflib import SequenceMatcher
806
-
893
+
807
894
  unique_items = []
808
-
895
+
809
896
  for item in items:
810
897
  content = item.get("content", "")
811
898
  is_duplicate = False
812
-
899
+
813
900
  # Check against existing unique items
814
901
  for unique_item in unique_items:
815
902
  unique_content = unique_item.get("content", "")
816
- similarity = SequenceMatcher(None, content.lower(), unique_content.lower()).ratio()
817
-
903
+ similarity = SequenceMatcher(
904
+ None, content.lower(), unique_content.lower()
905
+ ).ratio()
906
+
818
907
  if similarity > 0.8: # 80% similarity threshold
819
908
  is_duplicate = True
820
909
  break
821
-
910
+
822
911
  if not is_duplicate:
823
912
  unique_items.append(item)
824
-
825
- return unique_items
913
+
914
+ return unique_items