reprompt-cli 1.8.1__tar.gz → 1.9.0__tar.gz
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.
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/PKG-INFO +1 -1
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/pyproject.toml +1 -1
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/__init__.py +1 -1
- reprompt_cli-1.9.0/src/reprompt/bridge/handler.py +234 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/storage/db.py +17 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_bridge_e2e.py +19 -19
- reprompt_cli-1.9.0/tests/test_bridge_handler.py +354 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/uv.lock +1 -1
- reprompt_cli-1.8.1/src/reprompt/bridge/handler.py +0 -100
- reprompt_cli-1.8.1/tests/test_bridge_handler.py +0 -118
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.editorconfig +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.github/dependabot.yml +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.github/workflows/ci.yml +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.github/workflows/publish.yml +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.gitignore +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.pre-commit-config.yaml +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.pre-commit-hooks.yaml +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.testmondata-shm +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/.testmondata-wal +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/CHANGELOG.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/CODE_OF_CONDUCT.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/CONTRIBUTING.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/LICENSE +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/README.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/SECURITY.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/Screenshot 2026-03-24 at 09.45.03.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/action.yml +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/demo.gif +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/brand-icon-128.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/brand-icon-16.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/brand-icon-256.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/brand-icon-32.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/brand-icon-48.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/brand-icon-512.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/brand-icon-96.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/brand-icon.svg +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-favicon-128.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-favicon-16.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-favicon-256.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-favicon-32.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-favicon-48.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-favicon-512.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-favicon-96.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-favicon.svg +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-icon-128.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-icon-16.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-icon-256.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-icon-32.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-icon-48.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-icon-512.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-icon-96.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/cli-icon.svg +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/favicon-128.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/favicon-16.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/favicon-256.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/favicon-32.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/favicon-48.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/favicon-512.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/favicon-96.png +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/favicon.svg +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/icons/generate.sh +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/superpowers/specs/2026-03-24-v14-command-consolidation-design.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/docs/superpowers/specs/2026-03-25-v1.5-dashboard-design.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/scripts/generate_demo_data.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/aider.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/base.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/chatgpt.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/claude_chat.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/claude_code.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/cline.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/codex.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/cursor.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/filters.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/gemini.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/adapters/openclaw.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/bridge/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/bridge/host.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/bridge/manifest.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/bridge/protocol.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/commands/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/commands/telemetry.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/commands/wrapped.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/config.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/agent.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/analyzer.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/compress.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/conversation.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/cost.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/dashboard.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/dedup.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/digest.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/distill.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/effectiveness.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/extractors.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/extractors_zh.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/insights.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/lang_detect.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/library.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/lint.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/merge_view.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/models.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/persona.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/pipeline.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/privacy.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/privacy_scan.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/prompt_dna.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/recommend.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/repetition.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/scorer.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/segmenter.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/session_meta.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/session_quality.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/session_type.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/style.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/suggestions.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/templates.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/timeutil.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/trends.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/core/wrapped.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/demo.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/embeddings/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/embeddings/base.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/embeddings/local_embed.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/embeddings/ollama.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/embeddings/openai_embed.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/embeddings/tfidf.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/mcp.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/mcp_main.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/agent_terminal.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/chartjs.min.js +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/compress_terminal.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/dashboard_terminal.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/distill_terminal.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/export.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/html_report.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/json_out.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/markdown.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/repetition_terminal.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/sessions_terminal.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/terminal.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/wrapped_html.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/output/wrapped_terminal.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/py.typed +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/sharing/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/sharing/client.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/sharing/clipboard.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/storage/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/telemetry/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/telemetry/collector.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/telemetry/consent.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/telemetry/events.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/telemetry/prompt.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/telemetry/queue.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/src/reprompt/telemetry/sender.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/__init__.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/conftest.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/fixtures/aider_chat_history.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/fixtures/chatgpt_conversations.json +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/fixtures/claude_chat_export.json +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/fixtures/claude_session.jsonl +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/fixtures/cline_task/api_conversation_history.json +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/fixtures/export/default_export.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/fixtures/export/full_export.md +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/fixtures/gemini_session.json +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/fixtures/openclaw_session.jsonl +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_adapter_aider.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_adapter_chatgpt.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_adapter_claude.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_adapter_claude_chat.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_adapter_cline.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_adapter_gemini.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_adapter_openclaw.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_agent.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_agent_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_analyzer.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_bridge_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_bridge_integration.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_bridge_manifest.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_bridge_protocol.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_cli_deprecations.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_cli_library_effectiveness.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_clipboard.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_codex_adapter.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_compare_best_worst.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_compress.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_compress_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_compress_dna.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_compress_html.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_compress_insights.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_config.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_conversation.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_copy_flag.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_cost.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_coverage_boost.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_cursor_adapter.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_dashboard.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_db.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_db_digest.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_db_effectiveness.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_db_session_quality.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_db_trends.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_dedup.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_demo.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_deprecated_commands.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_digest.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_digest_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_distill.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_distill_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_distill_weights.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_e2e.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_effectiveness.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_embeddings_local.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_embeddings_ollama.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_embeddings_openai.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_empty_state.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_export.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_export_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_export_snapshot.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_extractors.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_extractors_routing.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_extractors_zh.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_extractors_zh_e2e.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_html_report.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_import_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_import_e2e.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_insights.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_insights_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_insights_expanded.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_install_hook.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_lang_detect.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_library.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_lint.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_lint_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_markdown.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_mcp.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_merge_view.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_models.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_output.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_parse_conversation_base.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_parse_conversation_chatgpt.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_parse_conversation_claude.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_persona.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_pipeline.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_privacy.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_privacy_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_privacy_e2e.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_privacy_output.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_privacy_scan.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_prompt_dna.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_public_api.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_recommend.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_repetition.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_repetition_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_repetition_output.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_schema_version.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_score_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_scorer.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_segmenter.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_session_quality.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_session_type.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_sessions_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_sessions_output.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_share_e2e.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_sharing_client.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_source_filter.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_style.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_style_trends.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_suggestions.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_telemetry_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_telemetry_collector.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_telemetry_consent.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_telemetry_e2e.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_telemetry_events.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_telemetry_prompt.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_telemetry_queue.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_telemetry_sender.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_template_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_templates.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_timeutil.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_trends.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_trends_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_use_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_wrapped.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_wrapped_cli.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_wrapped_e2e.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_wrapped_html.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_wrapped_output.py +0 -0
- {reprompt_cli-1.8.1 → reprompt_cli-1.9.0}/tests/test_wrapped_share.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reprompt-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: Discover, analyze, and optimize your prompts from AI coding sessions
|
|
5
5
|
Project-URL: Homepage, https://github.com/reprompt-dev/reprompt
|
|
6
6
|
Project-URL: Repository, https://github.com/reprompt-dev/reprompt
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Message handler for Native Messaging bridge.
|
|
2
|
+
|
|
3
|
+
Processes incoming messages from the browser extension:
|
|
4
|
+
- ping -> pong (health check)
|
|
5
|
+
- sync_prompts -> store in DB, return counts + lightweight insights
|
|
6
|
+
- get_status -> return DB stats
|
|
7
|
+
- get_insights -> return full analysis (repetition, patterns, top insight)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from reprompt import __version__
|
|
17
|
+
from reprompt.adapters.filters import should_keep_prompt
|
|
18
|
+
from reprompt.storage.db import PromptDB
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def handle_message(message: dict[str, Any], db: PromptDB) -> dict[str, Any]:
|
|
24
|
+
"""Process a single message and return a response dict."""
|
|
25
|
+
msg_type = message.get("type", "")
|
|
26
|
+
|
|
27
|
+
if msg_type == "ping":
|
|
28
|
+
return {"type": "pong", "version": __version__}
|
|
29
|
+
|
|
30
|
+
if msg_type == "sync_prompts":
|
|
31
|
+
return _handle_sync(message, db)
|
|
32
|
+
|
|
33
|
+
if msg_type == "get_status":
|
|
34
|
+
return _handle_status(db)
|
|
35
|
+
|
|
36
|
+
if msg_type == "get_insights":
|
|
37
|
+
return _handle_insights(message, db)
|
|
38
|
+
|
|
39
|
+
return {"type": "error", "message": f"Unknown message type: {msg_type}"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _handle_sync(message: dict[str, Any], db: PromptDB) -> dict[str, Any]:
|
|
43
|
+
"""Store synced prompts in DB, skipping noise and duplicates."""
|
|
44
|
+
prompts = message.get("prompts", [])
|
|
45
|
+
received = len(prompts)
|
|
46
|
+
new_stored = 0
|
|
47
|
+
duplicates = 0
|
|
48
|
+
|
|
49
|
+
for p in prompts:
|
|
50
|
+
text = p.get("text", "").strip()
|
|
51
|
+
if not should_keep_prompt(text):
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
source = p.get("source", "extension")
|
|
55
|
+
session_id = p.get("conversation_id", "")
|
|
56
|
+
project = p.get("conversation_title", "")
|
|
57
|
+
timestamp = p.get("timestamp", "")
|
|
58
|
+
|
|
59
|
+
inserted = db.insert_prompt(
|
|
60
|
+
text,
|
|
61
|
+
source=source,
|
|
62
|
+
project=project,
|
|
63
|
+
session_id=session_id,
|
|
64
|
+
timestamp=timestamp,
|
|
65
|
+
)
|
|
66
|
+
if inserted:
|
|
67
|
+
new_stored += 1
|
|
68
|
+
else:
|
|
69
|
+
duplicates += 1
|
|
70
|
+
|
|
71
|
+
# Record last sync time
|
|
72
|
+
_update_last_sync(db)
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
"type": "sync_result",
|
|
76
|
+
"received": received,
|
|
77
|
+
"new_stored": new_stored,
|
|
78
|
+
"duplicates": duplicates,
|
|
79
|
+
"insights": _compute_quick_insights(db),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _handle_status(db: PromptDB) -> dict[str, Any]:
|
|
84
|
+
"""Return current database stats."""
|
|
85
|
+
stats = db.get_stats()
|
|
86
|
+
return {
|
|
87
|
+
"type": "status",
|
|
88
|
+
"total_prompts": stats.get("total_prompts", 0),
|
|
89
|
+
"last_sync": _get_last_sync(db),
|
|
90
|
+
"version": __version__,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _handle_insights(message: dict[str, Any], db: PromptDB) -> dict[str, Any]:
|
|
95
|
+
"""Return full analysis: repetition, effectiveness patterns, insights.
|
|
96
|
+
|
|
97
|
+
Heavier computation than sync — call on-demand, not every sync.
|
|
98
|
+
"""
|
|
99
|
+
source = message.get("source")
|
|
100
|
+
result: dict[str, Any] = {"type": "insights_result"}
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
from reprompt.core.insights import (
|
|
104
|
+
compute_insights,
|
|
105
|
+
get_cross_session_repetition_insight,
|
|
106
|
+
get_effectiveness_insight,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
features = db.get_all_features(source=source)
|
|
110
|
+
if features:
|
|
111
|
+
full = compute_insights(features)
|
|
112
|
+
result["avg_score"] = full.get("avg_score", 0.0)
|
|
113
|
+
result["prompt_count"] = full.get("prompt_count", 0)
|
|
114
|
+
result["score_distribution"] = full.get("score_distribution", {})
|
|
115
|
+
result["insights"] = [
|
|
116
|
+
{"category": i["category"], "action": i["action"], "impact": i["impact"]}
|
|
117
|
+
for i in full.get("insights", [])
|
|
118
|
+
]
|
|
119
|
+
else:
|
|
120
|
+
result["avg_score"] = 0.0
|
|
121
|
+
result["prompt_count"] = 0
|
|
122
|
+
result["score_distribution"] = {}
|
|
123
|
+
result["insights"] = []
|
|
124
|
+
|
|
125
|
+
# Repetition (may be None if insufficient data)
|
|
126
|
+
rep = get_cross_session_repetition_insight(db, source=source)
|
|
127
|
+
if rep:
|
|
128
|
+
result["repetition"] = {
|
|
129
|
+
"rate": rep["repetition_rate"],
|
|
130
|
+
"top_topics": rep["top_topics"],
|
|
131
|
+
"total_recurring": rep["total_recurring_topics"],
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Effectiveness patterns (may be None)
|
|
135
|
+
eff = get_effectiveness_insight(db, source=source)
|
|
136
|
+
if eff:
|
|
137
|
+
result["effectiveness"] = {
|
|
138
|
+
"top_patterns": eff["top_patterns"],
|
|
139
|
+
"total_patterns": eff["total_patterns"],
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
except Exception:
|
|
143
|
+
logger.warning("Failed to compute full insights for extension", exc_info=True)
|
|
144
|
+
result["error"] = "Failed to compute insights"
|
|
145
|
+
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _compute_quick_insights(db: PromptDB) -> dict[str, Any]:
|
|
150
|
+
"""Lightweight stats for extension display. Pure SQL, no heavy computation."""
|
|
151
|
+
stats = db.get_stats()
|
|
152
|
+
total = stats.get("total_prompts", 0)
|
|
153
|
+
|
|
154
|
+
if total == 0:
|
|
155
|
+
return {
|
|
156
|
+
"avg_score": 0.0,
|
|
157
|
+
"total_prompts": 0,
|
|
158
|
+
"score_trend": "stable",
|
|
159
|
+
"top_insight": None,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
scores = db.get_recent_scores(limit=50)
|
|
163
|
+
|
|
164
|
+
if not scores:
|
|
165
|
+
return {
|
|
166
|
+
"avg_score": 0.0,
|
|
167
|
+
"total_prompts": total,
|
|
168
|
+
"score_trend": "stable",
|
|
169
|
+
"top_insight": None,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
avg_score = round(sum(scores) / len(scores), 1)
|
|
173
|
+
|
|
174
|
+
# Trend: compare first half (recent) vs second half (older)
|
|
175
|
+
mid = len(scores) // 2
|
|
176
|
+
if mid >= 5:
|
|
177
|
+
recent_avg = sum(scores[:mid]) / mid
|
|
178
|
+
older_avg = sum(scores[mid:]) / (len(scores) - mid)
|
|
179
|
+
diff = recent_avg - older_avg
|
|
180
|
+
if diff > 3:
|
|
181
|
+
trend = "improving"
|
|
182
|
+
elif diff < -3:
|
|
183
|
+
trend = "declining"
|
|
184
|
+
else:
|
|
185
|
+
trend = "stable"
|
|
186
|
+
else:
|
|
187
|
+
trend = "stable"
|
|
188
|
+
|
|
189
|
+
# Top insight: get highest-impact actionable tip
|
|
190
|
+
top_insight = _get_top_insight(db)
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
"avg_score": avg_score,
|
|
194
|
+
"total_prompts": total,
|
|
195
|
+
"score_trend": trend,
|
|
196
|
+
"top_insight": top_insight,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _get_top_insight(db: PromptDB) -> str | None:
|
|
201
|
+
"""Return the single most impactful insight as a string, or None."""
|
|
202
|
+
try:
|
|
203
|
+
from reprompt.core.insights import compute_insights
|
|
204
|
+
|
|
205
|
+
features = db.get_all_features()
|
|
206
|
+
if len(features) < 5:
|
|
207
|
+
return None
|
|
208
|
+
result = compute_insights(features)
|
|
209
|
+
insights = result.get("insights", [])
|
|
210
|
+
# Prioritize high-impact insights
|
|
211
|
+
for impact in ("high", "medium", "low"):
|
|
212
|
+
for i in insights:
|
|
213
|
+
if i.get("impact") == impact:
|
|
214
|
+
return i["action"]
|
|
215
|
+
except Exception:
|
|
216
|
+
logger.debug("Failed to compute top insight", exc_info=True)
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _update_last_sync(db: PromptDB) -> None:
|
|
221
|
+
"""Store last sync timestamp in the DB settings table."""
|
|
222
|
+
now_ts = str(int(datetime.now(tz=timezone.utc).timestamp()))
|
|
223
|
+
db.set_setting("last_extension_sync", now_ts)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _get_last_sync(db: PromptDB) -> str:
|
|
227
|
+
"""Get last sync timestamp. Returns empty string if never synced."""
|
|
228
|
+
val = db.get_setting("last_extension_sync")
|
|
229
|
+
if val:
|
|
230
|
+
try:
|
|
231
|
+
return datetime.fromtimestamp(int(val), tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
232
|
+
except (ValueError, OSError):
|
|
233
|
+
return ""
|
|
234
|
+
return ""
|
|
@@ -964,6 +964,23 @@ class PromptDB:
|
|
|
964
964
|
finally:
|
|
965
965
|
conn.close()
|
|
966
966
|
|
|
967
|
+
def get_recent_scores(self, limit: int = 50) -> list[float]:
|
|
968
|
+
"""Return recent prompt scores ordered by timestamp (newest first)."""
|
|
969
|
+
conn = self._conn()
|
|
970
|
+
try:
|
|
971
|
+
rows = conn.execute(
|
|
972
|
+
"""SELECT pf.overall_score
|
|
973
|
+
FROM prompt_features pf
|
|
974
|
+
JOIN prompts p ON pf.prompt_hash = p.hash
|
|
975
|
+
WHERE pf.overall_score IS NOT NULL
|
|
976
|
+
ORDER BY p.timestamp DESC
|
|
977
|
+
LIMIT ?""",
|
|
978
|
+
(limit,),
|
|
979
|
+
).fetchall()
|
|
980
|
+
return [r["overall_score"] for r in rows]
|
|
981
|
+
finally:
|
|
982
|
+
conn.close()
|
|
983
|
+
|
|
967
984
|
def get_all_features(self, source: str | None = None) -> list[dict[str, Any]]:
|
|
968
985
|
"""Return all stored feature vectors, optionally filtered by source."""
|
|
969
986
|
conn = self._conn()
|
|
@@ -129,25 +129,25 @@ class TestExtensionE2EPipeline:
|
|
|
129
129
|
# Verify ping
|
|
130
130
|
assert responses[0]["type"] == "pong"
|
|
131
131
|
|
|
132
|
-
# Verify sync results
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
132
|
+
# Verify sync results (check core fields; insights key added in v1.9)
|
|
133
|
+
r1 = responses[1]
|
|
134
|
+
assert r1["type"] == "sync_result"
|
|
135
|
+
assert r1["received"] == 2
|
|
136
|
+
assert r1["new_stored"] == 2
|
|
137
|
+
assert r1["duplicates"] == 0
|
|
138
|
+
assert "insights" in r1
|
|
139
|
+
|
|
140
|
+
r2 = responses[2]
|
|
141
|
+
assert r2["type"] == "sync_result"
|
|
142
|
+
assert r2["received"] == 1
|
|
143
|
+
assert r2["new_stored"] == 1
|
|
144
|
+
assert r2["duplicates"] == 0
|
|
145
|
+
|
|
146
|
+
r3 = responses[3]
|
|
147
|
+
assert r3["type"] == "sync_result"
|
|
148
|
+
assert r3["received"] == 1
|
|
149
|
+
assert r3["new_stored"] == 1
|
|
150
|
+
assert r3["duplicates"] == 0
|
|
151
151
|
|
|
152
152
|
# Verify status shows all 4 prompts
|
|
153
153
|
assert responses[4]["type"] == "status"
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Tests for Native Messaging message handler."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from reprompt.bridge.handler import handle_message
|
|
8
|
+
from reprompt.storage.db import PromptDB
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_handle_ping(tmp_path: Path) -> None:
|
|
12
|
+
db = PromptDB(tmp_path / "test.db")
|
|
13
|
+
response = handle_message({"type": "ping"}, db)
|
|
14
|
+
assert response["type"] == "pong"
|
|
15
|
+
assert "version" in response
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_handle_sync_prompts(tmp_path: Path) -> None:
|
|
19
|
+
db = PromptDB(tmp_path / "test.db")
|
|
20
|
+
msg = {
|
|
21
|
+
"type": "sync_prompts",
|
|
22
|
+
"prompts": [
|
|
23
|
+
{
|
|
24
|
+
"text": "Explain how async/await works in Python",
|
|
25
|
+
"source": "chatgpt-ext",
|
|
26
|
+
"timestamp": "2026-03-15T10:00:00Z",
|
|
27
|
+
"conversation_id": "conv-001",
|
|
28
|
+
"conversation_title": "Python async",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"text": "Show me a simple example of asyncio.gather",
|
|
32
|
+
"source": "chatgpt-ext",
|
|
33
|
+
"timestamp": "2026-03-15T10:01:00Z",
|
|
34
|
+
"conversation_id": "conv-001",
|
|
35
|
+
"conversation_title": "Python async",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
}
|
|
39
|
+
response = handle_message(msg, db)
|
|
40
|
+
assert response["type"] == "sync_result"
|
|
41
|
+
assert response["received"] == 2
|
|
42
|
+
assert response["new_stored"] == 2
|
|
43
|
+
assert response["duplicates"] == 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_handle_sync_dedup(tmp_path: Path) -> None:
|
|
47
|
+
"""Second sync of same prompts should report duplicates."""
|
|
48
|
+
db = PromptDB(tmp_path / "test.db")
|
|
49
|
+
msg = {
|
|
50
|
+
"type": "sync_prompts",
|
|
51
|
+
"prompts": [
|
|
52
|
+
{
|
|
53
|
+
"text": "Explain how async/await works in Python",
|
|
54
|
+
"source": "chatgpt-ext",
|
|
55
|
+
"timestamp": "2026-03-15T10:00:00Z",
|
|
56
|
+
"conversation_id": "conv-001",
|
|
57
|
+
"conversation_title": "Python async",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
handle_message(msg, db)
|
|
62
|
+
response = handle_message(msg, db)
|
|
63
|
+
assert response["new_stored"] == 0
|
|
64
|
+
assert response["duplicates"] == 1
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_handle_sync_filters_short(tmp_path: Path) -> None:
|
|
68
|
+
"""Short/noise prompts should be filtered out."""
|
|
69
|
+
db = PromptDB(tmp_path / "test.db")
|
|
70
|
+
msg = {
|
|
71
|
+
"type": "sync_prompts",
|
|
72
|
+
"prompts": [
|
|
73
|
+
{
|
|
74
|
+
"text": "ok",
|
|
75
|
+
"source": "chatgpt-ext",
|
|
76
|
+
"timestamp": "",
|
|
77
|
+
"conversation_id": "c1",
|
|
78
|
+
"conversation_title": "t",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"text": "yes",
|
|
82
|
+
"source": "chatgpt-ext",
|
|
83
|
+
"timestamp": "",
|
|
84
|
+
"conversation_id": "c1",
|
|
85
|
+
"conversation_title": "t",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
}
|
|
89
|
+
response = handle_message(msg, db)
|
|
90
|
+
assert response["received"] == 2
|
|
91
|
+
assert response["new_stored"] == 0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_handle_get_status(tmp_path: Path) -> None:
|
|
95
|
+
db = PromptDB(tmp_path / "test.db")
|
|
96
|
+
# Insert a prompt so stats are non-empty
|
|
97
|
+
db.insert_prompt(
|
|
98
|
+
"Test prompt for status check", source="chatgpt-ext", project="test", session_id="s1"
|
|
99
|
+
)
|
|
100
|
+
response = handle_message({"type": "get_status"}, db)
|
|
101
|
+
assert response["type"] == "status"
|
|
102
|
+
assert response["total_prompts"] >= 1
|
|
103
|
+
assert "version" in response
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_handle_unknown_type(tmp_path: Path) -> None:
|
|
107
|
+
db = PromptDB(tmp_path / "test.db")
|
|
108
|
+
response = handle_message({"type": "unknown_xyz"}, db)
|
|
109
|
+
assert response["type"] == "error"
|
|
110
|
+
assert "unknown" in response["message"].lower()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_handle_empty_sync(tmp_path: Path) -> None:
|
|
114
|
+
db = PromptDB(tmp_path / "test.db")
|
|
115
|
+
response = handle_message({"type": "sync_prompts", "prompts": []}, db)
|
|
116
|
+
assert response["type"] == "sync_result"
|
|
117
|
+
assert response["received"] == 0
|
|
118
|
+
assert response["new_stored"] == 0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# -- Insights in sync response --
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_sync_result_includes_insights(tmp_path: Path) -> None:
|
|
125
|
+
"""sync_result should always include an insights dict."""
|
|
126
|
+
db = PromptDB(tmp_path / "test.db")
|
|
127
|
+
msg = {
|
|
128
|
+
"type": "sync_prompts",
|
|
129
|
+
"prompts": [
|
|
130
|
+
{
|
|
131
|
+
"text": "Explain the difference between threads and processes in Python",
|
|
132
|
+
"source": "chatgpt-ext",
|
|
133
|
+
"timestamp": "2026-03-20T10:00:00Z",
|
|
134
|
+
"conversation_id": "c1",
|
|
135
|
+
"conversation_title": "",
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}
|
|
139
|
+
response = handle_message(msg, db)
|
|
140
|
+
assert "insights" in response
|
|
141
|
+
insights = response["insights"]
|
|
142
|
+
assert "avg_score" in insights
|
|
143
|
+
assert "total_prompts" in insights
|
|
144
|
+
assert "score_trend" in insights
|
|
145
|
+
assert insights["score_trend"] in ("improving", "declining", "stable")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_sync_insights_empty_db(tmp_path: Path) -> None:
|
|
149
|
+
"""Empty DB sync should return zero insights with stable trend."""
|
|
150
|
+
db = PromptDB(tmp_path / "test.db")
|
|
151
|
+
response = handle_message({"type": "sync_prompts", "prompts": []}, db)
|
|
152
|
+
insights = response["insights"]
|
|
153
|
+
assert insights["avg_score"] == 0.0
|
|
154
|
+
assert insights["total_prompts"] == 0
|
|
155
|
+
assert insights["score_trend"] == "stable"
|
|
156
|
+
assert insights["top_insight"] is None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_sync_insights_with_scored_prompts(tmp_path: Path) -> None:
|
|
160
|
+
"""Insights should reflect stored scores when features exist."""
|
|
161
|
+
db = PromptDB(tmp_path / "test.db")
|
|
162
|
+
# Insert prompts and store features to simulate scored data
|
|
163
|
+
for i in range(10):
|
|
164
|
+
text = f"Implement a Python decorator that caches function results variant {i}"
|
|
165
|
+
db.insert_prompt(text, source="chatgpt-ext", project="test", session_id="s1")
|
|
166
|
+
import hashlib
|
|
167
|
+
|
|
168
|
+
h = hashlib.sha256(text.encode()).hexdigest()
|
|
169
|
+
db.store_features(h, {"overall_score": 60.0 + i, "task_type": "implement"})
|
|
170
|
+
|
|
171
|
+
response = handle_message({"type": "sync_prompts", "prompts": []}, db)
|
|
172
|
+
insights = response["insights"]
|
|
173
|
+
assert insights["total_prompts"] == 10
|
|
174
|
+
assert insights["avg_score"] > 0
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_sync_insights_trend_improving(tmp_path: Path) -> None:
|
|
178
|
+
"""Score trend should detect improvement."""
|
|
179
|
+
db = PromptDB(tmp_path / "test.db")
|
|
180
|
+
import hashlib
|
|
181
|
+
|
|
182
|
+
# Insert 30 prompts: older ones low score, recent ones high score
|
|
183
|
+
for i in range(30):
|
|
184
|
+
ts = f"2026-03-{10 + i // 2:02d}T{10 + i % 24:02d}:00:00Z"
|
|
185
|
+
text = f"Write a comprehensive test suite for the user auth module version {i}"
|
|
186
|
+
db.insert_prompt(text, source="chatgpt-ext", project="test", session_id="s1", timestamp=ts)
|
|
187
|
+
h = hashlib.sha256(text.encode()).hexdigest()
|
|
188
|
+
# Older prompts (higher i = more recent timestamp) get higher scores
|
|
189
|
+
score = 40.0 + i * 1.5
|
|
190
|
+
db.store_features(h, {"overall_score": score, "task_type": "test"})
|
|
191
|
+
|
|
192
|
+
response = handle_message({"type": "sync_prompts", "prompts": []}, db)
|
|
193
|
+
insights = response["insights"]
|
|
194
|
+
assert insights["score_trend"] == "improving"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_sync_insights_trend_declining(tmp_path: Path) -> None:
|
|
198
|
+
"""Score trend should detect decline."""
|
|
199
|
+
db = PromptDB(tmp_path / "test.db")
|
|
200
|
+
import hashlib
|
|
201
|
+
|
|
202
|
+
for i in range(30):
|
|
203
|
+
ts = f"2026-03-{10 + i // 2:02d}T{10 + i % 24:02d}:00:00Z"
|
|
204
|
+
text = f"Debug the authentication middleware crash in production variant {i}"
|
|
205
|
+
db.insert_prompt(text, source="chatgpt-ext", project="test", session_id="s1", timestamp=ts)
|
|
206
|
+
h = hashlib.sha256(text.encode()).hexdigest()
|
|
207
|
+
# More recent = lower score
|
|
208
|
+
score = 85.0 - i * 1.5
|
|
209
|
+
db.store_features(h, {"overall_score": score, "task_type": "debug"})
|
|
210
|
+
|
|
211
|
+
response = handle_message({"type": "sync_prompts", "prompts": []}, db)
|
|
212
|
+
insights = response["insights"]
|
|
213
|
+
assert insights["score_trend"] == "declining"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# -- get_insights message type --
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_handle_get_insights_empty(tmp_path: Path) -> None:
|
|
220
|
+
"""get_insights on empty DB should return valid structure."""
|
|
221
|
+
db = PromptDB(tmp_path / "test.db")
|
|
222
|
+
response = handle_message({"type": "get_insights"}, db)
|
|
223
|
+
assert response["type"] == "insights_result"
|
|
224
|
+
assert response["avg_score"] == 0.0
|
|
225
|
+
assert response["prompt_count"] == 0
|
|
226
|
+
assert response["insights"] == []
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_handle_get_insights_with_data(tmp_path: Path) -> None:
|
|
230
|
+
"""get_insights should return analysis when data exists."""
|
|
231
|
+
db = PromptDB(tmp_path / "test.db")
|
|
232
|
+
import hashlib
|
|
233
|
+
|
|
234
|
+
for i in range(10):
|
|
235
|
+
text = f"Refactor the database connection pool to use async context managers v{i}"
|
|
236
|
+
db.insert_prompt(
|
|
237
|
+
text,
|
|
238
|
+
source="claude-ext",
|
|
239
|
+
project="test",
|
|
240
|
+
session_id="s1",
|
|
241
|
+
timestamp=f"2026-03-{15 + i}T10:00:00Z",
|
|
242
|
+
)
|
|
243
|
+
h = hashlib.sha256(text.encode()).hexdigest()
|
|
244
|
+
db.store_features(
|
|
245
|
+
h,
|
|
246
|
+
{
|
|
247
|
+
"overall_score": 55.0 + i,
|
|
248
|
+
"task_type": "refactor",
|
|
249
|
+
"key_instruction_position": 0.1,
|
|
250
|
+
"keyword_repetition_freq": 0.2,
|
|
251
|
+
"context_specificity": 0.4,
|
|
252
|
+
"has_constraints": False,
|
|
253
|
+
"ambiguity_score": 0.3,
|
|
254
|
+
"compressibility": 0.2,
|
|
255
|
+
"source": "claude-ext",
|
|
256
|
+
"token_count": 50,
|
|
257
|
+
},
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
response = handle_message({"type": "get_insights"}, db)
|
|
261
|
+
assert response["type"] == "insights_result"
|
|
262
|
+
assert response["prompt_count"] == 10
|
|
263
|
+
assert response["avg_score"] > 0
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_handle_get_insights_with_source_filter(tmp_path: Path) -> None:
|
|
267
|
+
"""get_insights should respect source filter."""
|
|
268
|
+
db = PromptDB(tmp_path / "test.db")
|
|
269
|
+
import hashlib
|
|
270
|
+
|
|
271
|
+
# ChatGPT prompts
|
|
272
|
+
for i in range(5):
|
|
273
|
+
text = f"Explain Python decorators with practical examples variant {i}"
|
|
274
|
+
db.insert_prompt(text, source="chatgpt-ext", project="t", session_id="s1")
|
|
275
|
+
h = hashlib.sha256(text.encode()).hexdigest()
|
|
276
|
+
db.store_features(
|
|
277
|
+
h,
|
|
278
|
+
{
|
|
279
|
+
"overall_score": 70.0,
|
|
280
|
+
"task_type": "explain",
|
|
281
|
+
"source": "chatgpt-ext",
|
|
282
|
+
"token_count": 30,
|
|
283
|
+
},
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Claude prompts
|
|
287
|
+
for i in range(5):
|
|
288
|
+
text = f"Create a REST API with FastAPI for user management variant {i}"
|
|
289
|
+
db.insert_prompt(text, source="claude-ext", project="t", session_id="s2")
|
|
290
|
+
h = hashlib.sha256(text.encode()).hexdigest()
|
|
291
|
+
db.store_features(
|
|
292
|
+
h,
|
|
293
|
+
{
|
|
294
|
+
"overall_score": 80.0,
|
|
295
|
+
"task_type": "implement",
|
|
296
|
+
"source": "claude-ext",
|
|
297
|
+
"token_count": 40,
|
|
298
|
+
},
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Filter to chatgpt only
|
|
302
|
+
response = handle_message({"type": "get_insights", "source": "chatgpt-ext"}, db)
|
|
303
|
+
assert response["type"] == "insights_result"
|
|
304
|
+
assert response["prompt_count"] == 5
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# -- DB: get_recent_scores --
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def test_get_recent_scores_empty(tmp_path: Path) -> None:
|
|
311
|
+
db = PromptDB(tmp_path / "test.db")
|
|
312
|
+
assert db.get_recent_scores() == []
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_get_recent_scores_ordered(tmp_path: Path) -> None:
|
|
316
|
+
"""Scores should be returned newest-first."""
|
|
317
|
+
db = PromptDB(tmp_path / "test.db")
|
|
318
|
+
import hashlib
|
|
319
|
+
|
|
320
|
+
texts = [
|
|
321
|
+
("Oldest prompt about Python typing", "2026-03-01T10:00:00Z", 50.0),
|
|
322
|
+
("Middle prompt about async patterns", "2026-03-15T10:00:00Z", 70.0),
|
|
323
|
+
("Newest prompt about testing strategies", "2026-03-30T10:00:00Z", 90.0),
|
|
324
|
+
]
|
|
325
|
+
for text, ts, score in texts:
|
|
326
|
+
db.insert_prompt(text, source="test", project="p", session_id="s", timestamp=ts)
|
|
327
|
+
h = hashlib.sha256(text.encode()).hexdigest()
|
|
328
|
+
db.store_features(h, {"overall_score": score, "task_type": "other"})
|
|
329
|
+
|
|
330
|
+
scores = db.get_recent_scores(limit=10)
|
|
331
|
+
assert len(scores) == 3
|
|
332
|
+
# Newest first
|
|
333
|
+
assert scores[0] == 90.0
|
|
334
|
+
assert scores[-1] == 50.0
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def test_get_recent_scores_respects_limit(tmp_path: Path) -> None:
|
|
338
|
+
db = PromptDB(tmp_path / "test.db")
|
|
339
|
+
import hashlib
|
|
340
|
+
|
|
341
|
+
for i in range(20):
|
|
342
|
+
text = f"Generate unit tests for the payment processing module variant {i}"
|
|
343
|
+
db.insert_prompt(
|
|
344
|
+
text,
|
|
345
|
+
source="test",
|
|
346
|
+
project="p",
|
|
347
|
+
session_id="s",
|
|
348
|
+
timestamp=f"2026-03-{i + 1:02d}T10:00:00Z",
|
|
349
|
+
)
|
|
350
|
+
h = hashlib.sha256(text.encode()).hexdigest()
|
|
351
|
+
db.store_features(h, {"overall_score": 50.0 + i, "task_type": "test"})
|
|
352
|
+
|
|
353
|
+
scores = db.get_recent_scores(limit=5)
|
|
354
|
+
assert len(scores) == 5
|