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.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +155 -0
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +90 -49
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +21 -18
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +143 -762
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +203 -81
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +305 -197
- claude_mpm/cli/parser.py +24 -1150
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +104 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +217 -0
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +36 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +120 -54
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +571 -0
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +40 -23
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +14 -21
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +97 -93
- claude_mpm/services/mcp_gateway/main.py +307 -127
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +100 -101
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +4 -4
- claude_mpm/services/mcp_gateway/server/{mcp_server.py → mcp_gateway.py} +149 -153
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +110 -121
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +223 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +145 -65
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +170 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +20 -534
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +9 -0
- claude_mpm/storage/state_storage.py +552 -0
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +51 -2
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/memory_guardian.py +0 -770
- claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.9.dist-info/RECORD +0 -293
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {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 | 
            -
             | 
| 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. | 
| 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. | 
| 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 | 
            -
                     | 
| 53 | 
            -
                         | 
| 54 | 
            -
                         | 
| 55 | 
            -
                         | 
| 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 | 
            -
                     | 
| 58 | 
            -
                         | 
| 59 | 
            -
                         | 
| 60 | 
            -
                         | 
| 62 | 
            +
                    "docs/QA.md": {
         | 
| 63 | 
            +
                        "priority": "high",
         | 
| 64 | 
            +
                        "sections": ["testing", "quality assurance", "validation"],
         | 
| 65 | 
            +
                        "agents": ["qa", "engineer"],
         | 
| 61 66 | 
             
                    },
         | 
| 62 | 
            -
                     | 
| 63 | 
            -
                         | 
| 64 | 
            -
                         | 
| 65 | 
            -
                         | 
| 67 | 
            +
                    "docs/DEPLOY.md": {
         | 
| 68 | 
            +
                        "priority": "medium",
         | 
| 69 | 
            +
                        "sections": ["deployment", "versioning", "release"],
         | 
| 70 | 
            +
                        "agents": ["engineer", "pm"],
         | 
| 66 71 | 
             
                    },
         | 
| 67 | 
            -
                     | 
| 68 | 
            -
                         | 
| 69 | 
            -
                         | 
| 70 | 
            -
                         | 
| 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 | 
            -
                     | 
| 82 | 
            -
                        r | 
| 83 | 
            -
                        r | 
| 84 | 
            -
                        r | 
| 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 | 
            -
                     | 
| 87 | 
            -
                        r | 
| 88 | 
            -
                        r | 
| 89 | 
            -
                        r | 
| 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 | 
            -
                     | 
| 92 | 
            -
                        r | 
| 93 | 
            -
                        r | 
| 94 | 
            -
                        r | 
| 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__( | 
| 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 =  | 
| 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 [ | 
| 144 | 
            +
             | 
| 145 | 
            +
                                if file_ext in [".json", ".toml", ".yaml", ".yml"]:
         | 
| 144 146 | 
             
                                    dynamic_files[config_file] = {
         | 
| 145 | 
            -
                                         | 
| 146 | 
            -
                                         | 
| 147 | 
            -
                                         | 
| 148 | 
            -
                                         | 
| 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  | 
| 159 | 
            +
                                if "api" in doc_file.lower() or "endpoint" in doc_file.lower():
         | 
| 158 160 | 
             
                                    dynamic_files[doc_file] = {
         | 
| 159 | 
            -
                                         | 
| 160 | 
            -
                                         | 
| 161 | 
            -
                                         | 
| 162 | 
            -
                                         | 
| 161 | 
            +
                                        "priority": "high",
         | 
| 162 | 
            +
                                        "sections": ["api", "endpoints", "integration"],
         | 
| 163 | 
            +
                                        "agents": ["engineer", "integration"],
         | 
| 164 | 
            +
                                        "file_type": "api_doc",
         | 
| 163 165 | 
             
                                    }
         | 
| 164 | 
            -
                                elif  | 
| 166 | 
            +
                                elif (
         | 
| 167 | 
            +
                                    "architecture" in doc_file.lower()
         | 
| 168 | 
            +
                                    or "design" in doc_file.lower()
         | 
| 169 | 
            +
                                ):
         | 
| 165 170 | 
             
                                    dynamic_files[doc_file] = {
         | 
| 166 | 
            -
                                         | 
| 167 | 
            -
                                         | 
| 168 | 
            -
                                         | 
| 169 | 
            -
                                         | 
| 171 | 
            +
                                        "priority": "high",
         | 
| 172 | 
            +
                                        "sections": ["architecture", "design", "patterns"],
         | 
| 173 | 
            +
                                        "agents": ["engineer", "architect"],
         | 
| 174 | 
            +
                                        "file_type": "architecture",
         | 
| 170 175 | 
             
                                    }
         | 
| 171 | 
            -
                                elif  | 
| 176 | 
            +
                                elif "test" in doc_file.lower():
         | 
| 172 177 | 
             
                                    dynamic_files[doc_file] = {
         | 
| 173 | 
            -
                                         | 
| 174 | 
            -
                                         | 
| 175 | 
            -
                                         | 
| 176 | 
            -
                                         | 
| 178 | 
            +
                                        "priority": "medium",
         | 
| 179 | 
            +
                                        "sections": ["testing", "quality"],
         | 
| 180 | 
            +
                                        "agents": ["qa", "engineer"],
         | 
| 181 | 
            +
                                        "file_type": "test_doc",
         | 
| 177 182 | 
             
                                    }
         | 
| 178 | 
            -
                                elif file_path.suffix.lower() ==  | 
| 183 | 
            +
                                elif file_path.suffix.lower() == ".md":
         | 
| 179 184 | 
             
                                    # Generic markdown file
         | 
| 180 185 | 
             
                                    dynamic_files[doc_file] = {
         | 
| 181 | 
            -
                                         | 
| 182 | 
            -
                                         | 
| 183 | 
            -
                                         | 
| 184 | 
            -
                                         | 
| 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[ | 
| 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 | 
            -
                                         | 
| 193 | 
            -
                                         | 
| 194 | 
            -
                                         | 
| 195 | 
            -
                                         | 
| 196 | 
            -
                                         | 
| 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( | 
| 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( | 
| 255 | 
            -
             | 
| 256 | 
            -
                                 | 
| 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( | 
| 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( | 
| 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( | 
| 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( | 
| 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( | 
| 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 = [ | 
| 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( | 
| 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 | 
            -
             | 
| 389 | 
            -
             | 
| 390 | 
            -
             | 
| 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 ==  | 
| 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 [ | 
| 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,  | 
| 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 [ | 
| 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( | 
| 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  | 
| 456 | 
            -
                        deps = config_data[ | 
| 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 | 
            -
                                 | 
| 462 | 
            -
             | 
| 463 | 
            -
             | 
| 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": " | 
| 480 | 
            -
                                    "confidence": 0. | 
| 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( | 
| 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  | 
| 491 | 
            -
                        project_info = config_data[ | 
| 492 | 
            -
                        if  | 
| 493 | 
            -
                            deps = project_info[ | 
| 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 | 
            -
                                     | 
| 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 | 
            -
             | 
| 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( | 
| 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( | 
| 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 ==  | 
| 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 [ | 
| 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( | 
| 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 | 
            -
                             | 
| 562 | 
            -
             | 
| 563 | 
            -
             | 
| 564 | 
            -
             | 
| 565 | 
            -
             | 
| 566 | 
            -
             | 
| 567 | 
            -
             | 
| 568 | 
            -
             | 
| 569 | 
            -
             | 
| 570 | 
            -
             | 
| 571 | 
            -
             | 
| 572 | 
            -
             | 
| 573 | 
            -
                             | 
| 574 | 
            -
             | 
| 575 | 
            -
             | 
| 576 | 
            -
             | 
| 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 | 
| 631 | 
            +
                    class_matches = re.findall(r"class\s+(\w+)", content)
         | 
| 581 632 | 
             
                    if class_matches:
         | 
| 582 | 
            -
                        items.append( | 
| 583 | 
            -
                             | 
| 584 | 
            -
             | 
| 585 | 
            -
             | 
| 586 | 
            -
             | 
| 587 | 
            -
             | 
| 588 | 
            -
             | 
| 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( | 
| 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  | 
| 599 | 
            -
                        items.append( | 
| 600 | 
            -
                             | 
| 601 | 
            -
             | 
| 602 | 
            -
             | 
| 603 | 
            -
             | 
| 604 | 
            -
             | 
| 605 | 
            -
             | 
| 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  | 
| 610 | 
            -
                        items.append( | 
| 611 | 
            -
                             | 
| 612 | 
            -
             | 
| 613 | 
            -
             | 
| 614 | 
            -
             | 
| 615 | 
            -
             | 
| 616 | 
            -
             | 
| 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( | 
| 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= | 
| 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( | 
| 637 | 
            -
             | 
| 638 | 
            -
                        if file_type ==  | 
| 639 | 
            -
                            extracted_items = self._extract_from_config_file( | 
| 640 | 
            -
             | 
| 641 | 
            -
                             | 
| 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( | 
| 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 | 
            -
             | 
| 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 | 
| 758 | 
            -
             | 
| 823 | 
            +
                    content = re.sub(r"[*_`#]+", "", content)
         | 
| 824 | 
            +
             | 
| 759 825 | 
             
                    # Remove extra whitespace
         | 
| 760 | 
            -
                    content = re.sub(r | 
| 761 | 
            -
             | 
| 826 | 
            +
                    content = re.sub(r"\s+", " ", content).strip()
         | 
| 827 | 
            +
             | 
| 762 828 | 
             
                    # Remove common prefixes that don't add value
         | 
| 763 | 
            -
                    content = re.sub( | 
| 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 = [ | 
| 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 = [ | 
| 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( | 
| 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( | 
| 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
         |