claude-mpm 5.4.85__py3-none-any.whl → 5.6.76__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/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +109 -706
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/executor.py +128 -16
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +84 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +345 -40
- claude_mpm/cli/startup_display.py +76 -7
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -0
- claude_mpm/commander/api/errors.py +133 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +149 -0
- claude_mpm/commander/chat/commands.py +124 -0
- claude_mpm/commander/chat/repl.py +1957 -0
- claude_mpm/commander/config.py +51 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +603 -0
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +392 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +233 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +57 -0
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +868 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +127 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +403 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +362 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +9 -1
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +5 -0
- claude_mpm/core/claude_runner.py +152 -0
- claude_mpm/core/config.py +35 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +53 -4
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +39 -13
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +52 -12
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +9 -9
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +466 -136
- claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +291 -59
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +41 -26
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/init.py +22 -15
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +46 -19
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +111 -16
- claude_mpm/services/pm_skills_deployer.py +261 -87
- claude_mpm/services/skills/git_skill_source_manager.py +130 -10
- claude_mpm/services/skills/selective_skill_deployer.py +142 -16
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.76.dist-info/METADATA +416 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +312 -175
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- claude_mpm-5.4.85.dist-info/METADATA +0 -1023
- /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
- /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Embedding service for semantic search.
|
|
2
|
+
|
|
3
|
+
Generates vector embeddings using sentence-transformers (local) or
|
|
4
|
+
OpenAI API (cloud). Defaults to local model for zero-cost operation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from typing import List, Literal, Optional
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
EmbeddingProvider = Literal["sentence-transformers", "openai"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EmbeddingService:
|
|
17
|
+
"""Generate vector embeddings for semantic search.
|
|
18
|
+
|
|
19
|
+
Supports multiple providers:
|
|
20
|
+
- sentence-transformers: Local, free, good quality (default)
|
|
21
|
+
- openai: Cloud API, best quality, costs money
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
provider: Embedding provider to use
|
|
25
|
+
model: Model name for the provider
|
|
26
|
+
dimension: Embedding vector dimension
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> embeddings = EmbeddingService(provider="sentence-transformers")
|
|
30
|
+
>>> vector = await embeddings.embed("Fix the login bug")
|
|
31
|
+
>>> len(vector)
|
|
32
|
+
384
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
provider: EmbeddingProvider = "sentence-transformers",
|
|
38
|
+
model: Optional[str] = None,
|
|
39
|
+
):
|
|
40
|
+
"""Initialize embedding service.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
provider: Embedding provider ('sentence-transformers' or 'openai')
|
|
44
|
+
model: Model name (provider-specific default if None)
|
|
45
|
+
"""
|
|
46
|
+
self.provider = provider
|
|
47
|
+
self._encoder = None
|
|
48
|
+
self._client = None
|
|
49
|
+
|
|
50
|
+
if provider == "sentence-transformers":
|
|
51
|
+
self.model = model or "all-MiniLM-L6-v2"
|
|
52
|
+
self.dimension = 384
|
|
53
|
+
self._init_sentence_transformers()
|
|
54
|
+
elif provider == "openai":
|
|
55
|
+
self.model = model or "text-embedding-3-small"
|
|
56
|
+
self.dimension = 1536
|
|
57
|
+
self._init_openai()
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(f"Unknown provider: {provider}")
|
|
60
|
+
|
|
61
|
+
logger.info(
|
|
62
|
+
"EmbeddingService initialized (provider: %s, model: %s, dim: %d)",
|
|
63
|
+
provider,
|
|
64
|
+
self.model,
|
|
65
|
+
self.dimension,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def _init_sentence_transformers(self) -> None:
|
|
69
|
+
"""Initialize sentence-transformers encoder.
|
|
70
|
+
|
|
71
|
+
Lazy loads on first use to avoid startup delay.
|
|
72
|
+
"""
|
|
73
|
+
# Lazy import to avoid dependency if not used
|
|
74
|
+
try:
|
|
75
|
+
from sentence_transformers import SentenceTransformer
|
|
76
|
+
|
|
77
|
+
self._encoder = SentenceTransformer(self.model)
|
|
78
|
+
logger.info("Loaded sentence-transformers model: %s", self.model)
|
|
79
|
+
except ImportError:
|
|
80
|
+
logger.error(
|
|
81
|
+
"sentence-transformers not installed. "
|
|
82
|
+
"Install with: pip install sentence-transformers"
|
|
83
|
+
)
|
|
84
|
+
raise
|
|
85
|
+
|
|
86
|
+
def _init_openai(self) -> None:
|
|
87
|
+
"""Initialize OpenAI client.
|
|
88
|
+
|
|
89
|
+
Requires OPENAI_API_KEY environment variable.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
from openai import AsyncOpenAI
|
|
93
|
+
|
|
94
|
+
self._client = AsyncOpenAI()
|
|
95
|
+
logger.info("Initialized OpenAI client")
|
|
96
|
+
except ImportError:
|
|
97
|
+
logger.error("openai not installed. Install with: pip install openai")
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
async def embed(self, text: str) -> List[float]:
|
|
101
|
+
"""Generate embedding for text.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
text: Text to embed
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Embedding vector as list of floats
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> vector = await embeddings.embed("Fix the login bug")
|
|
111
|
+
>>> len(vector)
|
|
112
|
+
384
|
|
113
|
+
"""
|
|
114
|
+
if self.provider == "sentence-transformers":
|
|
115
|
+
return await self._embed_sentence_transformers(text)
|
|
116
|
+
if self.provider == "openai":
|
|
117
|
+
return await self._embed_openai(text)
|
|
118
|
+
raise ValueError(f"Unknown provider: {self.provider}")
|
|
119
|
+
|
|
120
|
+
async def embed_batch(self, texts: List[str]) -> List[List[float]]:
|
|
121
|
+
"""Generate embeddings for multiple texts.
|
|
122
|
+
|
|
123
|
+
More efficient than calling embed() in a loop.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
texts: List of texts to embed
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
List of embedding vectors
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
>>> vectors = await embeddings.embed_batch([
|
|
133
|
+
... "Fix the login bug",
|
|
134
|
+
... "Update the README"
|
|
135
|
+
... ])
|
|
136
|
+
>>> len(vectors)
|
|
137
|
+
2
|
|
138
|
+
"""
|
|
139
|
+
if self.provider == "sentence-transformers":
|
|
140
|
+
return await self._embed_batch_sentence_transformers(texts)
|
|
141
|
+
if self.provider == "openai":
|
|
142
|
+
return await self._embed_batch_openai(texts)
|
|
143
|
+
raise ValueError(f"Unknown provider: {self.provider}")
|
|
144
|
+
|
|
145
|
+
async def _embed_sentence_transformers(self, text: str) -> List[float]:
|
|
146
|
+
"""Generate embedding using sentence-transformers.
|
|
147
|
+
|
|
148
|
+
Runs in executor to avoid blocking event loop.
|
|
149
|
+
"""
|
|
150
|
+
if self._encoder is None:
|
|
151
|
+
raise RuntimeError("Encoder not initialized")
|
|
152
|
+
|
|
153
|
+
# Run encoding in executor (CPU-bound operation)
|
|
154
|
+
loop = asyncio.get_event_loop()
|
|
155
|
+
embedding = await loop.run_in_executor(
|
|
156
|
+
None, lambda: self._encoder.encode(text, convert_to_numpy=True)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return embedding.tolist()
|
|
160
|
+
|
|
161
|
+
async def _embed_batch_sentence_transformers(
|
|
162
|
+
self, texts: List[str]
|
|
163
|
+
) -> List[List[float]]:
|
|
164
|
+
"""Generate batch embeddings using sentence-transformers."""
|
|
165
|
+
if self._encoder is None:
|
|
166
|
+
raise RuntimeError("Encoder not initialized")
|
|
167
|
+
|
|
168
|
+
# Run batch encoding in executor
|
|
169
|
+
loop = asyncio.get_event_loop()
|
|
170
|
+
embeddings = await loop.run_in_executor(
|
|
171
|
+
None,
|
|
172
|
+
lambda: self._encoder.encode(
|
|
173
|
+
texts, convert_to_numpy=True, show_progress_bar=False
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return [emb.tolist() for emb in embeddings]
|
|
178
|
+
|
|
179
|
+
async def _embed_openai(self, text: str) -> List[float]:
|
|
180
|
+
"""Generate embedding using OpenAI API."""
|
|
181
|
+
if self._client is None:
|
|
182
|
+
raise RuntimeError("OpenAI client not initialized")
|
|
183
|
+
|
|
184
|
+
response = await self._client.embeddings.create(
|
|
185
|
+
model=self.model,
|
|
186
|
+
input=text,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return response.data[0].embedding
|
|
190
|
+
|
|
191
|
+
async def _embed_batch_openai(self, texts: List[str]) -> List[List[float]]:
|
|
192
|
+
"""Generate batch embeddings using OpenAI API."""
|
|
193
|
+
if self._client is None:
|
|
194
|
+
raise RuntimeError("OpenAI client not initialized")
|
|
195
|
+
|
|
196
|
+
response = await self._client.embeddings.create(
|
|
197
|
+
model=self.model,
|
|
198
|
+
input=texts,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return [item.embedding for item in response.data]
|
|
202
|
+
|
|
203
|
+
def cosine_similarity(self, vec1: List[float], vec2: List[float]) -> float:
|
|
204
|
+
"""Calculate cosine similarity between two vectors.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
vec1: First vector
|
|
208
|
+
vec2: Second vector
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Similarity score in range [-1, 1] (1 = identical, -1 = opposite)
|
|
212
|
+
|
|
213
|
+
Example:
|
|
214
|
+
>>> sim = embeddings.cosine_similarity(vec1, vec2)
|
|
215
|
+
>>> print(f"Similarity: {sim:.3f}")
|
|
216
|
+
"""
|
|
217
|
+
import math
|
|
218
|
+
|
|
219
|
+
# Dot product
|
|
220
|
+
dot_product = sum(a * b for a, b in zip(vec1, vec2))
|
|
221
|
+
|
|
222
|
+
# Magnitudes
|
|
223
|
+
mag1 = math.sqrt(sum(a * a for a in vec1))
|
|
224
|
+
mag2 = math.sqrt(sum(b * b for b in vec2))
|
|
225
|
+
|
|
226
|
+
# Avoid division by zero
|
|
227
|
+
if mag1 == 0 or mag2 == 0:
|
|
228
|
+
return 0.0
|
|
229
|
+
|
|
230
|
+
return dot_product / (mag1 * mag2)
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""Entity extraction from conversation messages.
|
|
2
|
+
|
|
3
|
+
Extracts structured entities like files, functions, errors, and commands
|
|
4
|
+
from conversation content for enhanced search and filtering.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Any, Dict, List
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EntityType(Enum):
|
|
17
|
+
"""Types of entities that can be extracted.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
FILE: File path (e.g., "src/auth.py")
|
|
21
|
+
FUNCTION: Function or method name (e.g., "login()")
|
|
22
|
+
CLASS: Class name (e.g., "UserService")
|
|
23
|
+
ERROR: Error type or message (e.g., "ValueError")
|
|
24
|
+
COMMAND: Shell command (e.g., "pytest tests/")
|
|
25
|
+
URL: Web URL (e.g., "https://example.com")
|
|
26
|
+
PACKAGE: Package or module name (e.g., "requests")
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
FILE = "file"
|
|
30
|
+
FUNCTION = "function"
|
|
31
|
+
CLASS = "class"
|
|
32
|
+
ERROR = "error"
|
|
33
|
+
COMMAND = "command"
|
|
34
|
+
URL = "url"
|
|
35
|
+
PACKAGE = "package"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Entity:
|
|
40
|
+
"""Extracted entity from conversation.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
type: Entity type
|
|
44
|
+
value: Entity value (file path, function name, etc.)
|
|
45
|
+
context: Surrounding context (optional)
|
|
46
|
+
metadata: Additional metadata
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
>>> entity = Entity(
|
|
50
|
+
... type=EntityType.FILE,
|
|
51
|
+
... value="src/auth.py",
|
|
52
|
+
... context="Fix the login bug in auth.py"
|
|
53
|
+
... )
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
type: EntityType
|
|
57
|
+
value: str
|
|
58
|
+
context: str = ""
|
|
59
|
+
metadata: Dict[str, Any] = None
|
|
60
|
+
|
|
61
|
+
def __post_init__(self) -> None:
|
|
62
|
+
"""Initialize metadata if not provided."""
|
|
63
|
+
if self.metadata is None:
|
|
64
|
+
self.metadata = {}
|
|
65
|
+
|
|
66
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
67
|
+
"""Convert to dictionary for JSON serialization."""
|
|
68
|
+
return {
|
|
69
|
+
"type": self.type.value,
|
|
70
|
+
"value": self.value,
|
|
71
|
+
"context": self.context,
|
|
72
|
+
"metadata": self.metadata,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Entity":
|
|
77
|
+
"""Create Entity from dictionary."""
|
|
78
|
+
return cls(
|
|
79
|
+
type=EntityType(data["type"]),
|
|
80
|
+
value=data["value"],
|
|
81
|
+
context=data.get("context", ""),
|
|
82
|
+
metadata=data.get("metadata", {}),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class EntityExtractor:
|
|
87
|
+
"""Extracts entities from conversation messages.
|
|
88
|
+
|
|
89
|
+
Uses regex patterns to identify files, functions, errors, commands, etc.
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
>>> extractor = EntityExtractor()
|
|
93
|
+
>>> entities = extractor.extract("Fix the login bug in src/auth.py")
|
|
94
|
+
>>> entities[0].type
|
|
95
|
+
<EntityType.FILE: 'file'>
|
|
96
|
+
>>> entities[0].value
|
|
97
|
+
'src/auth.py'
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
# File patterns (common extensions and paths)
|
|
101
|
+
FILE_PATTERNS = [
|
|
102
|
+
r"\b[\w/\-\.]+\.(?:py|js|ts|tsx|jsx|java|cpp|c|h|go|rs|rb|php|cs|swift|kt|md|txt|json|yaml|yml|toml|xml|html|css|scss|sh|bash)\b",
|
|
103
|
+
r"\b(?:src|lib|tests?|scripts?|docs?|config)/[\w/\-\.]+\b",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
# Function patterns (with parens or common prefixes)
|
|
107
|
+
FUNCTION_PATTERNS = [
|
|
108
|
+
r"\b[a-z_][a-z0-9_]*\(\)",
|
|
109
|
+
r"\bdef\s+([a-z_][a-z0-9_]*)",
|
|
110
|
+
r"\bfunction\s+([a-z_][a-z0-9_]*)",
|
|
111
|
+
r"\basync\s+(?:def|function)\s+([a-z_][a-z0-9_]*)",
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
# Class patterns (PascalCase)
|
|
115
|
+
CLASS_PATTERNS = [
|
|
116
|
+
r"\bclass\s+([A-Z][a-zA-Z0-9_]*)",
|
|
117
|
+
r"\b([A-Z][a-zA-Z0-9_]+(?:Service|Controller|Manager|Handler|Repository|Model|View))\b",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
# Error patterns
|
|
121
|
+
ERROR_PATTERNS = [
|
|
122
|
+
r"\b([A-Z][a-zA-Z]*Error)\b",
|
|
123
|
+
r"\b([A-Z][a-zA-Z]*Exception)\b",
|
|
124
|
+
r"error:\s+(.+?)(?:\n|$)",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
# Command patterns (common CLI tools)
|
|
128
|
+
COMMAND_PATTERNS = [
|
|
129
|
+
r"\b(?:npm|yarn|pnpm|pip|poetry|cargo|go|make|docker|kubectl|git)\s+[\w\-]+",
|
|
130
|
+
r"\bpytest\s+[\w/\-\.]+",
|
|
131
|
+
r"\bpython\s+[\w/\-\.]+",
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
# URL patterns
|
|
135
|
+
URL_PATTERN = r"https?://[\w\.\-/\?=&#%]+"
|
|
136
|
+
|
|
137
|
+
# Package patterns
|
|
138
|
+
PACKAGE_PATTERNS = [
|
|
139
|
+
r"\bimport\s+([\w\.]+)",
|
|
140
|
+
r"\bfrom\s+([\w\.]+)\s+import",
|
|
141
|
+
r"\brequire\(['\"]([\w\-@/]+)['\"]",
|
|
142
|
+
r"\buse\s+([\w:]+)",
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
def extract(self, text: str) -> List[Entity]:
|
|
146
|
+
"""Extract all entities from text.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
text: Text to extract entities from
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of extracted entities
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
>>> entities = extractor.extract("Fix the login bug in src/auth.py")
|
|
156
|
+
"""
|
|
157
|
+
entities: List[Entity] = []
|
|
158
|
+
|
|
159
|
+
# Extract files
|
|
160
|
+
for pattern in self.FILE_PATTERNS:
|
|
161
|
+
for match in re.finditer(pattern, text):
|
|
162
|
+
entities.append(
|
|
163
|
+
Entity(
|
|
164
|
+
type=EntityType.FILE,
|
|
165
|
+
value=match.group(0),
|
|
166
|
+
context=self._get_context(text, match.start(), match.end()),
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Extract functions
|
|
171
|
+
for pattern in self.FUNCTION_PATTERNS:
|
|
172
|
+
for match in re.finditer(pattern, text):
|
|
173
|
+
# Use group 1 if capturing group exists, else group 0
|
|
174
|
+
value = match.group(1) if match.lastindex else match.group(0)
|
|
175
|
+
entities.append(
|
|
176
|
+
Entity(
|
|
177
|
+
type=EntityType.FUNCTION,
|
|
178
|
+
value=value.rstrip("()"), # Remove parens
|
|
179
|
+
context=self._get_context(text, match.start(), match.end()),
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Extract classes
|
|
184
|
+
for pattern in self.CLASS_PATTERNS:
|
|
185
|
+
for match in re.finditer(pattern, text):
|
|
186
|
+
value = match.group(1) if match.lastindex else match.group(0)
|
|
187
|
+
entities.append(
|
|
188
|
+
Entity(
|
|
189
|
+
type=EntityType.CLASS,
|
|
190
|
+
value=value,
|
|
191
|
+
context=self._get_context(text, match.start(), match.end()),
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Extract errors
|
|
196
|
+
for pattern in self.ERROR_PATTERNS:
|
|
197
|
+
for match in re.finditer(pattern, text):
|
|
198
|
+
value = match.group(1) if match.lastindex else match.group(0)
|
|
199
|
+
entities.append(
|
|
200
|
+
Entity(
|
|
201
|
+
type=EntityType.ERROR,
|
|
202
|
+
value=value.strip(),
|
|
203
|
+
context=self._get_context(text, match.start(), match.end()),
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Extract commands
|
|
208
|
+
for pattern in self.COMMAND_PATTERNS:
|
|
209
|
+
for match in re.finditer(pattern, text):
|
|
210
|
+
entities.append(
|
|
211
|
+
Entity(
|
|
212
|
+
type=EntityType.COMMAND,
|
|
213
|
+
value=match.group(0),
|
|
214
|
+
context=self._get_context(text, match.start(), match.end()),
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Extract URLs
|
|
219
|
+
for match in re.finditer(self.URL_PATTERN, text):
|
|
220
|
+
entities.append(
|
|
221
|
+
Entity(
|
|
222
|
+
type=EntityType.URL,
|
|
223
|
+
value=match.group(0),
|
|
224
|
+
context=self._get_context(text, match.start(), match.end()),
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Extract packages
|
|
229
|
+
for pattern in self.PACKAGE_PATTERNS:
|
|
230
|
+
for match in re.finditer(pattern, text):
|
|
231
|
+
value = match.group(1) if match.lastindex else match.group(0)
|
|
232
|
+
entities.append(
|
|
233
|
+
Entity(
|
|
234
|
+
type=EntityType.PACKAGE,
|
|
235
|
+
value=value,
|
|
236
|
+
context=self._get_context(text, match.start(), match.end()),
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Deduplicate entities (same type + value)
|
|
241
|
+
seen = set()
|
|
242
|
+
unique_entities = []
|
|
243
|
+
for entity in entities:
|
|
244
|
+
key = (entity.type, entity.value)
|
|
245
|
+
if key not in seen:
|
|
246
|
+
seen.add(key)
|
|
247
|
+
unique_entities.append(entity)
|
|
248
|
+
|
|
249
|
+
logger.debug("Extracted %d unique entities from text", len(unique_entities))
|
|
250
|
+
return unique_entities
|
|
251
|
+
|
|
252
|
+
def _get_context(self, text: str, start: int, end: int, window: int = 50) -> str:
|
|
253
|
+
"""Get surrounding context for entity.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
text: Full text
|
|
257
|
+
start: Entity start position
|
|
258
|
+
end: Entity end position
|
|
259
|
+
window: Characters to include before/after
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Context string with entity highlighted
|
|
263
|
+
"""
|
|
264
|
+
context_start = max(0, start - window)
|
|
265
|
+
context_end = min(len(text), end + window)
|
|
266
|
+
|
|
267
|
+
context = text[context_start:context_end]
|
|
268
|
+
|
|
269
|
+
# Truncate at sentence boundaries if possible
|
|
270
|
+
if context_start > 0 and ". " in context[:window]:
|
|
271
|
+
context = context.split(". ", 1)[1]
|
|
272
|
+
if context_end < len(text) and ". " in context[-window:]:
|
|
273
|
+
context = context.rsplit(". ", 1)[0] + "."
|
|
274
|
+
|
|
275
|
+
return context.strip()
|
|
276
|
+
|
|
277
|
+
def filter_by_type(
|
|
278
|
+
self, entities: List[Entity], entity_type: EntityType
|
|
279
|
+
) -> List[Entity]:
|
|
280
|
+
"""Filter entities by type.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
entities: List of entities to filter
|
|
284
|
+
entity_type: Type to filter for
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Filtered list of entities
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
>>> files = extractor.filter_by_type(entities, EntityType.FILE)
|
|
291
|
+
"""
|
|
292
|
+
return [e for e in entities if e.type == entity_type]
|
|
293
|
+
|
|
294
|
+
def get_unique_values(
|
|
295
|
+
self, entities: List[Entity], entity_type: EntityType
|
|
296
|
+
) -> List[str]:
|
|
297
|
+
"""Get unique entity values for a type.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
entities: List of entities
|
|
301
|
+
entity_type: Type to extract values for
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
List of unique values
|
|
305
|
+
|
|
306
|
+
Example:
|
|
307
|
+
>>> files = extractor.get_unique_values(entities, EntityType.FILE)
|
|
308
|
+
['src/auth.py', 'tests/test_auth.py']
|
|
309
|
+
"""
|
|
310
|
+
return list({e.value for e in entities if e.type == entity_type})
|