reprompt-cli 1.4.0__tar.gz → 1.4.1__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.4.0 → reprompt_cli-1.4.1}/PKG-INFO +1 -1
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/docs/roadmap.md +8 -6
- reprompt_cli-1.4.1/docs/superpowers/specs/2026-03-24-v141-polish-design.md +184 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/pyproject.toml +1 -1
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/__init__.py +1 -1
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/cli.py +67 -6
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/style.py +62 -1
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/terminal.py +90 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/storage/db.py +40 -0
- reprompt_cli-1.4.1/tests/test_compare_best_worst.py +233 -0
- reprompt_cli-1.4.1/tests/test_style_trends.py +256 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.editorconfig +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.github/dependabot.yml +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.github/workflows/ci.yml +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.github/workflows/publish.yml +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.gitignore +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.pre-commit-config.yaml +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/.testmondata +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/CHANGELOG.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/CLAUDE.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/CODE_OF_CONDUCT.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/CONTRIBUTING.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/LICENSE +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/README.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/SECURITY.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/action.yml +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/docs/launch-post.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/docs/superpowers/specs/2026-03-11-html-dashboard-design.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/docs/superpowers/specs/2026-03-11-merge-view-design.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/docs/superpowers/specs/2026-03-11-prompt-templates-design.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/docs/superpowers/specs/2026-03-22-prompt-compress-design.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/docs/superpowers/specs/2026-03-23-distill-design.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/docs/superpowers/specs/2026-03-23-v131-suggestions-source-design.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/docs/superpowers/specs/2026-03-24-v14-context-recovery-design.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/module.yaml +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/scripts/generate_demo_data.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/scripts/launch/hn_monitor.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/scripts/launch/reddit_helper.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/aider.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/base.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/chatgpt.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/claude_chat.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/claude_code.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/cline.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/cursor.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/filters.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/gemini.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/adapters/openclaw.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/bridge/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/bridge/handler.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/bridge/host.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/bridge/manifest.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/bridge/protocol.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/commands/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/commands/telemetry.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/commands/wrapped.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/config.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/analyzer.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/compress.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/conversation.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/dedup.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/digest.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/distill.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/effectiveness.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/extractors.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/extractors_zh.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/insights.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/lang_detect.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/library.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/lint.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/merge_view.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/models.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/persona.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/pipeline.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/privacy.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/prompt_dna.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/recommend.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/scorer.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/segmenter.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/session_meta.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/suggestions.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/templates.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/timeutil.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/trends.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/core/wrapped.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/demo.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/embeddings/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/embeddings/base.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/embeddings/local_embed.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/embeddings/ollama.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/embeddings/openai_embed.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/embeddings/tfidf.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/mcp.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/mcp_main.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/chartjs.min.js +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/compress_terminal.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/distill_terminal.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/export.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/html_report.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/json_out.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/markdown.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/wrapped_html.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/output/wrapped_terminal.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/py.typed +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/sharing/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/sharing/client.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/sharing/clipboard.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/storage/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/telemetry/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/telemetry/collector.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/telemetry/consent.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/telemetry/events.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/telemetry/prompt.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/telemetry/queue.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/src/reprompt/telemetry/sender.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/__init__.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/conftest.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/fixtures/aider_chat_history.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/fixtures/chatgpt_conversations.json +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/fixtures/claude_chat_export.json +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/fixtures/claude_session.jsonl +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/fixtures/cline_task/api_conversation_history.json +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/fixtures/export/default_export.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/fixtures/export/full_export.md +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/fixtures/gemini_session.json +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/fixtures/openclaw_session.jsonl +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_adapter_aider.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_adapter_chatgpt.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_adapter_claude.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_adapter_claude_chat.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_adapter_cline.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_adapter_gemini.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_adapter_openclaw.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_analyzer.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_bridge_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_bridge_handler.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_bridge_integration.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_bridge_manifest.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_bridge_protocol.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_cli_library_effectiveness.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_clipboard.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_compress.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_compress_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_compress_dna.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_compress_html.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_compress_insights.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_config.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_conversation.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_coverage_boost.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_cursor_adapter.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_db.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_db_digest.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_db_effectiveness.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_db_trends.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_dedup.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_demo.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_deprecated_commands.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_digest.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_digest_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_distill.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_distill_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_distill_weights.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_e2e.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_effectiveness.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_embeddings_local.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_embeddings_ollama.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_embeddings_openai.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_empty_state.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_export.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_export_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_export_snapshot.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_extractors.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_extractors_routing.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_extractors_zh.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_extractors_zh_e2e.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_html_report.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_import_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_import_e2e.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_insights.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_insights_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_insights_expanded.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_install_hook.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_lang_detect.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_library.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_lint.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_lint_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_markdown.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_mcp.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_merge_view.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_models.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_output.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_parse_conversation_base.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_parse_conversation_chatgpt.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_parse_conversation_claude.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_persona.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_pipeline.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_privacy.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_privacy_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_privacy_e2e.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_privacy_output.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_prompt_dna.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_public_api.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_recommend.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_schema_version.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_score_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_scorer.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_segmenter.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_share_e2e.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_sharing_client.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_source_filter.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_style.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_suggestions.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_telemetry_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_telemetry_collector.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_telemetry_consent.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_telemetry_e2e.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_telemetry_events.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_telemetry_prompt.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_telemetry_queue.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_telemetry_sender.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_template_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_templates.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_timeutil.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_trends.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_trends_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_use_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_wrapped.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_wrapped_cli.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_wrapped_e2e.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_wrapped_html.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_wrapped_output.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/tests/test_wrapped_share.py +0 -0
- {reprompt_cli-1.4.0 → reprompt_cli-1.4.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reprompt-cli
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# reprompt Roadmap
|
|
2
2
|
|
|
3
|
-
> Last updated: 2026-03-
|
|
3
|
+
> Last updated: 2026-03-25 · Current version: v1.4.1
|
|
4
4
|
|
|
5
5
|
## Vision
|
|
6
6
|
|
|
@@ -35,9 +35,10 @@ Claude Code · OpenClaw · Cursor IDE · Aider · Gemini CLI · Cline · ChatGPT
|
|
|
35
35
|
| v1.3.0 | Conversation distillation | `reprompt distill` — 6-signal importance scoring for conversation turns |
|
|
36
36
|
| v1.3.1 | UX polish | Actionable suggestions on 5 commands, `--source` filter on all data commands |
|
|
37
37
|
| v1.4.0 | Context recovery + consolidation | `distill --export` context document, signal transparency, command consolidation (27→23) |
|
|
38
|
+
| v1.4.1 | Compare + style polish | `compare --best-worst` auto-pick, `style --trends` period-over-period deltas |
|
|
38
39
|
|
|
39
40
|
### Quality
|
|
40
|
-
-
|
|
41
|
+
- 1316 tests, ≥90% coverage
|
|
41
42
|
- Strict mypy, ruff lint/format
|
|
42
43
|
- CI: coverage gate + pre-publish test step
|
|
43
44
|
- Stable public API (`score_prompt`, `compare_prompts`, `extract_features`)
|
|
@@ -51,12 +52,12 @@ Claude Code · OpenClaw · Cursor IDE · Aider · Gemini CLI · Cline · ChatGPT
|
|
|
51
52
|
| P1 | `distill --export` context recovery | **DONE** — community signal: resume sessions after compaction/timeout |
|
|
52
53
|
| P2 | Command consolidation: `save`/`templates`/`use` → `template [save\|list\|use]` | **DONE** — 3 commands doing 1 thing = cognitive overload |
|
|
53
54
|
| P2 | Command consolidation: `effectiveness`/`merge-view` → `insights` sub-insights | **DONE** — concepts unclear to users |
|
|
54
|
-
| P3 | `style` shows change trends |
|
|
55
|
+
| P3 | `style` shows change trends | **DONE** — `--trends` flag with period-over-period deltas |
|
|
55
56
|
| P4 | `distill --show-weights` / `--weights` signal transparency | Community request for weight visibility |
|
|
56
|
-
| P5 | `compare --best-worst` auto-pick |
|
|
57
|
-
| P5 | `--copy` as standard option on remaining commands |
|
|
57
|
+
| P5 | `compare --best-worst` auto-pick | **DONE** — auto-selects from DB scores |
|
|
58
|
+
| P5 | `--copy` as standard option on remaining commands | Decided against — no clear paste destination for analysis commands |
|
|
58
59
|
|
|
59
|
-
**Status: 27 → 23 visible commands. P1+P2 shipped
|
|
60
|
+
**Status: 27 → 23 visible commands. P1+P2+P3+P5 shipped.**
|
|
60
61
|
|
|
61
62
|
---
|
|
62
63
|
|
|
@@ -64,6 +65,7 @@ Claude Code · OpenClaw · Cursor IDE · Aider · Gemini CLI · Cline · ChatGPT
|
|
|
64
65
|
|
|
65
66
|
| Feature | Description |
|
|
66
67
|
|---------|-------------|
|
|
68
|
+
| Distill false positive reduction | Position signal breaks on small-talk openers / "thanks bye" closers; long error dumps score high on length+uniqueness but aren't decision points; "ok try again" triggers error_recovery but is noise. Community feedback from r/LLMDevs. |
|
|
67
69
|
| Sensitive content detection | Privacy narrative; PII in prompts |
|
|
68
70
|
| Agent workflow analysis | Multi-step agent session patterns |
|
|
69
71
|
| `.reprompt.yml` configurable lint | Team/Pro direction |
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# v1.4.1 Polish Design Spec
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Two UX improvements that make existing commands more useful without adding new commands.
|
|
6
|
+
|
|
7
|
+
**Version:** v1.4.1 (patch release)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Feature 1: `compare --best-worst`
|
|
12
|
+
|
|
13
|
+
### Problem
|
|
14
|
+
|
|
15
|
+
`reprompt compare` requires users to manually type two prompt strings. Most users want a quick "show me my best vs worst" without digging through the database.
|
|
16
|
+
|
|
17
|
+
### Solution
|
|
18
|
+
|
|
19
|
+
Add `--best-worst` flag that auto-selects the highest-scored and lowest-scored prompts from `prompt_features`, then runs the existing comparison logic.
|
|
20
|
+
|
|
21
|
+
### Interface
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Existing (unchanged)
|
|
25
|
+
reprompt compare "prompt A" "prompt B"
|
|
26
|
+
|
|
27
|
+
# New
|
|
28
|
+
reprompt compare --best-worst
|
|
29
|
+
reprompt compare --best-worst --json
|
|
30
|
+
reprompt compare --best-worst --source claude-code
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Implementation Details
|
|
34
|
+
|
|
35
|
+
**CLI changes (`cli.py`):**
|
|
36
|
+
- `prompt_a` and `prompt_b` become `Optional[str]` (default `None`)
|
|
37
|
+
- Add `--best-worst` flag (bool, default `False`)
|
|
38
|
+
- Add `--source` filter (str, optional) for consistency
|
|
39
|
+
- Manual mutual-exclusion guard at top of function body (Typer has no built-in mechanism):
|
|
40
|
+
```python
|
|
41
|
+
if best_worst and (prompt_a or prompt_b):
|
|
42
|
+
console.print("[red]--best-worst cannot be combined with prompt arguments[/red]")
|
|
43
|
+
raise typer.Exit(1)
|
|
44
|
+
if not best_worst and not (prompt_a and prompt_b):
|
|
45
|
+
console.print("[red]Provide two prompts or use --best-worst[/red]")
|
|
46
|
+
raise typer.Exit(1)
|
|
47
|
+
```
|
|
48
|
+
Pattern matches existing `distill` command (cli.py:963–971).
|
|
49
|
+
- When `--best-worst`: call `db.get_best_worst_prompts(source)` to get texts, then run existing scoring logic
|
|
50
|
+
|
|
51
|
+
**DB method (`storage/db.py`):**
|
|
52
|
+
```python
|
|
53
|
+
def get_best_worst_prompts(self, source: str | None = None) -> tuple[str, str] | None:
|
|
54
|
+
"""Return (best_text, worst_text) from scored prompts.
|
|
55
|
+
|
|
56
|
+
Filters to prompts with word_count >= 5 to avoid noise.
|
|
57
|
+
Returns None if fewer than 2 qualifying prompts exist.
|
|
58
|
+
"""
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Query: JOIN `prompt_features` ON `prompts.hash = prompt_features.prompt_hash`, filter `overall_score IS NOT NULL`, order by score DESC for best and ASC for worst. Filter in Python after the JOIN: fetch all scored rows with their `prompts.text`, apply `len(text.split()) >= 5`, then take max/min `overall_score` rows from survivors. This avoids JSON parsing in the hot path and is straightforward to test.
|
|
62
|
+
|
|
63
|
+
**Terminal output enhancement:**
|
|
64
|
+
When using `--best-worst`, show the prompt texts (truncated to 80 chars) above the comparison table so the user knows what's being compared.
|
|
65
|
+
|
|
66
|
+
### Edge Cases
|
|
67
|
+
|
|
68
|
+
- Fewer than 2 scored prompts: print guidance ("Run `reprompt scan` then `reprompt score` to build your score history")
|
|
69
|
+
- Best and worst are the same prompt (only 1 unique score): print message ("All prompts have similar scores")
|
|
70
|
+
- `--best-worst` combined with positional args: error with clear message
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Feature 2: `style --trends`
|
|
75
|
+
|
|
76
|
+
### Problem
|
|
77
|
+
|
|
78
|
+
`reprompt style` shows a static snapshot. Users have no way to see if their prompting style is improving over time.
|
|
79
|
+
|
|
80
|
+
### Solution
|
|
81
|
+
|
|
82
|
+
Add `--trends` flag that compares current period vs previous period style metrics, showing deltas.
|
|
83
|
+
|
|
84
|
+
### Interface
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Existing (unchanged)
|
|
88
|
+
reprompt style
|
|
89
|
+
reprompt style --json
|
|
90
|
+
|
|
91
|
+
# New
|
|
92
|
+
reprompt style --trends
|
|
93
|
+
reprompt style --trends --period 30d
|
|
94
|
+
reprompt style --trends --json
|
|
95
|
+
reprompt style --trends --source claude-code
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Implementation Details
|
|
99
|
+
|
|
100
|
+
**New function in `core/style.py`:**
|
|
101
|
+
```python
|
|
102
|
+
def compute_style_trends(
|
|
103
|
+
db: PromptDB,
|
|
104
|
+
period: str = "7d",
|
|
105
|
+
source: str | None = None,
|
|
106
|
+
) -> dict[str, Any]:
|
|
107
|
+
"""Compare style between current and previous period.
|
|
108
|
+
|
|
109
|
+
Returns dict with:
|
|
110
|
+
period, current (style dict), previous (style dict),
|
|
111
|
+
deltas: {specificity, avg_length, top_category_changed, prompt_count}
|
|
112
|
+
"""
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Reuses:
|
|
116
|
+
- `sliding_windows(period, count=2)` from `core/timeutil` — returns `list[TimeWindow]` with `.start`/`.end` as `datetime` objects
|
|
117
|
+
- `db.get_prompts_in_range(start, end, source=source)` — `source` is keyword-only; `start`/`end` must be ISO-8601 strings, so call `window.start.isoformat()` / `window.end.isoformat()` before passing
|
|
118
|
+
- `compute_style(prompts)` applied to each window's prompts
|
|
119
|
+
- `categorize_prompt()` from `core/library` to build prompt dicts
|
|
120
|
+
|
|
121
|
+
**Delta computation:**
|
|
122
|
+
- `specificity_delta`: current - previous (e.g., +0.12)
|
|
123
|
+
- `avg_length_delta`: current - previous (e.g., +15.3 chars)
|
|
124
|
+
- `prompt_count_delta`: current - previous
|
|
125
|
+
- `top_category_changed`: bool (did the top category shift?)
|
|
126
|
+
- `top_category_current` / `top_category_previous`: for display
|
|
127
|
+
|
|
128
|
+
**CLI changes (`cli.py`):**
|
|
129
|
+
- Add `--trends` flag (bool, default `False`)
|
|
130
|
+
- Add `--period` option (str, default `"7d"`)
|
|
131
|
+
- When `--trends`: call `compute_style_trends()`, render with new function
|
|
132
|
+
|
|
133
|
+
**Terminal rendering (`output/terminal.py`):**
|
|
134
|
+
New `render_style_trends(data)` function. Output format:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Style Trends (7d)
|
|
138
|
+
─────────────────────────────────
|
|
139
|
+
Specificity 0.72 → 0.84 (+12%)
|
|
140
|
+
Avg Length 45 → 52 chars (+16%)
|
|
141
|
+
Prompts 23 → 31 (+35%)
|
|
142
|
+
Top Category debugging → refactoring
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Coloring rules:
|
|
146
|
+
- `Specificity`: green if positive delta (higher = better), red if negative
|
|
147
|
+
- `Prompts`: green if positive delta (more activity = good), dim if negative
|
|
148
|
+
- `Avg Length`: always `[dim]` (neutral metric, no green/red — length is not inherently better or worse)
|
|
149
|
+
- `Top Category`: no color, just show the shift
|
|
150
|
+
|
|
151
|
+
**JSON output:** Return raw `{period, current, previous, deltas}` dict.
|
|
152
|
+
|
|
153
|
+
### Edge Cases
|
|
154
|
+
|
|
155
|
+
- No prompts in either window: "Not enough data for trends. Keep prompting and check back next week."
|
|
156
|
+
- No prompts in previous window only: show current stats with "New! No previous data to compare."
|
|
157
|
+
- `--trends` combined with existing `--json`: works (returns trends JSON instead of snapshot JSON)
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## What's NOT in v1.4.1
|
|
162
|
+
|
|
163
|
+
- `--copy` standardization: decided against (no clear paste destination for analysis commands)
|
|
164
|
+
- New commands: none
|
|
165
|
+
- Breaking changes: none
|
|
166
|
+
|
|
167
|
+
## Testing
|
|
168
|
+
|
|
169
|
+
- `compare --best-worst`: ~8 tests (DB method, CLI integration, edge cases, JSON output, source filter, mutual exclusion with args)
|
|
170
|
+
- `style --trends`: ~8 tests (compute function, CLI integration, edge cases, JSON output, source filter, period option)
|
|
171
|
+
- Total estimated: ~16 new tests
|
|
172
|
+
|
|
173
|
+
## Files Modified
|
|
174
|
+
|
|
175
|
+
| File | Change |
|
|
176
|
+
|------|--------|
|
|
177
|
+
| `src/reprompt/cli.py` | `compare` gains `--best-worst`/`--source`; `style` gains `--trends`/`--period` |
|
|
178
|
+
| `src/reprompt/storage/db.py` | Add `get_best_worst_prompts()` |
|
|
179
|
+
| `src/reprompt/core/style.py` | Add `compute_style_trends()` |
|
|
180
|
+
| `src/reprompt/output/terminal.py` | Add `render_style_trends()`, modify `render_compare()` for prompt text display |
|
|
181
|
+
| `pyproject.toml` | Version bump to 1.4.1 |
|
|
182
|
+
| `src/reprompt/__init__.py` | Version bump to 1.4.1 |
|
|
183
|
+
| `tests/test_compare_best_worst.py` | New test file |
|
|
184
|
+
| `tests/test_style_trends.py` | New test file |
|
|
@@ -709,18 +709,34 @@ def style(
|
|
|
709
709
|
source: str | None = typer.Option(
|
|
710
710
|
None, "--source", "-s", help="Filter by source (e.g. claude-code, cursor, aider)"
|
|
711
711
|
),
|
|
712
|
+
trends: bool = typer.Option(False, "--trends", help="Show style change trends"),
|
|
713
|
+
period: str = typer.Option("7d", "--period", help="Comparison period (with --trends)"),
|
|
712
714
|
) -> None:
|
|
713
715
|
"""Show your personal prompting style fingerprint."""
|
|
714
716
|
import json as json_mod
|
|
715
717
|
|
|
716
718
|
from reprompt.config import Settings
|
|
717
|
-
from reprompt.core.library import categorize_prompt
|
|
718
|
-
from reprompt.core.style import compute_style
|
|
719
|
-
from reprompt.output.terminal import render_style
|
|
720
719
|
from reprompt.storage.db import PromptDB
|
|
721
720
|
|
|
722
721
|
settings = Settings()
|
|
723
722
|
db = PromptDB(settings.db_path)
|
|
723
|
+
|
|
724
|
+
if trends:
|
|
725
|
+
from reprompt.core.style import compute_style_trends
|
|
726
|
+
from reprompt.output.terminal import render_style_trends
|
|
727
|
+
|
|
728
|
+
data = compute_style_trends(db, period=period, source=source)
|
|
729
|
+
|
|
730
|
+
if json_output:
|
|
731
|
+
print(json_mod.dumps(data, indent=2))
|
|
732
|
+
else:
|
|
733
|
+
print(render_style_trends(data), end="")
|
|
734
|
+
return
|
|
735
|
+
|
|
736
|
+
from reprompt.core.library import categorize_prompt
|
|
737
|
+
from reprompt.core.style import compute_style
|
|
738
|
+
from reprompt.output.terminal import render_style
|
|
739
|
+
|
|
724
740
|
rows = db.get_all_prompts(source=source)
|
|
725
741
|
prompts = [
|
|
726
742
|
{
|
|
@@ -1231,15 +1247,55 @@ def _load_conversation(
|
|
|
1231
1247
|
|
|
1232
1248
|
@app.command()
|
|
1233
1249
|
def compare(
|
|
1234
|
-
prompt_a: str = typer.Argument(
|
|
1235
|
-
prompt_b: str = typer.Argument(
|
|
1250
|
+
prompt_a: str | None = typer.Argument(None, help="First prompt"),
|
|
1251
|
+
prompt_b: str | None = typer.Argument(None, help="Second prompt"),
|
|
1236
1252
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
1253
|
+
best_worst: bool = typer.Option(
|
|
1254
|
+
False, "--best-worst", help="Auto-select best and worst from DB"
|
|
1255
|
+
),
|
|
1256
|
+
source: str | None = typer.Option(
|
|
1257
|
+
None, "--source", "-s", help="Filter by source (with --best-worst)"
|
|
1258
|
+
),
|
|
1237
1259
|
) -> None:
|
|
1238
1260
|
"""Compare two prompts side by side using Prompt DNA analysis."""
|
|
1261
|
+
from typing import Any
|
|
1262
|
+
|
|
1239
1263
|
from reprompt.core.extractors import extract_features
|
|
1240
1264
|
from reprompt.core.prompt_dna import PromptDNA
|
|
1241
1265
|
from reprompt.core.scorer import ScoreBreakdown, score_prompt
|
|
1242
1266
|
|
|
1267
|
+
# Mutual exclusion guard
|
|
1268
|
+
if best_worst and (prompt_a or prompt_b):
|
|
1269
|
+
console.print("[red]--best-worst cannot be combined with prompt arguments[/red]")
|
|
1270
|
+
raise typer.Exit(1)
|
|
1271
|
+
if not best_worst and not (prompt_a and prompt_b):
|
|
1272
|
+
console.print("[red]Provide two prompts or use --best-worst[/red]")
|
|
1273
|
+
raise typer.Exit(1)
|
|
1274
|
+
|
|
1275
|
+
prompt_a_text: str | None = None
|
|
1276
|
+
prompt_b_text: str | None = None
|
|
1277
|
+
|
|
1278
|
+
if best_worst:
|
|
1279
|
+
from reprompt.config import Settings
|
|
1280
|
+
from reprompt.storage.db import PromptDB
|
|
1281
|
+
|
|
1282
|
+
settings = Settings()
|
|
1283
|
+
db = PromptDB(settings.db_path)
|
|
1284
|
+
pair = db.get_best_worst_prompts(source=source)
|
|
1285
|
+
if pair is None:
|
|
1286
|
+
console.print(
|
|
1287
|
+
"Not enough scored prompts. Run [bold]reprompt scan[/bold]"
|
|
1288
|
+
" to build your score history."
|
|
1289
|
+
)
|
|
1290
|
+
raise typer.Exit(1)
|
|
1291
|
+
prompt_a = pair[0] # best
|
|
1292
|
+
prompt_b = pair[1] # worst
|
|
1293
|
+
prompt_a_text = prompt_a
|
|
1294
|
+
prompt_b_text = prompt_b
|
|
1295
|
+
|
|
1296
|
+
# Type narrowing for mypy strict (guards above guarantee non-None)
|
|
1297
|
+
assert prompt_a is not None and prompt_b is not None
|
|
1298
|
+
|
|
1243
1299
|
dna_a = extract_features(prompt_a, source="manual", session_id="compare-a")
|
|
1244
1300
|
dna_b = extract_features(prompt_b, source="manual", session_id="compare-b")
|
|
1245
1301
|
score_a = score_prompt(dna_a)
|
|
@@ -1259,11 +1315,16 @@ def compare(
|
|
|
1259
1315
|
"ambiguity_score": dna.ambiguity_score,
|
|
1260
1316
|
}
|
|
1261
1317
|
|
|
1262
|
-
result = {
|
|
1318
|
+
result: dict[str, Any] = {
|
|
1263
1319
|
"prompt_a": _build_data(dna_a, score_a),
|
|
1264
1320
|
"prompt_b": _build_data(dna_b, score_b),
|
|
1265
1321
|
}
|
|
1266
1322
|
|
|
1323
|
+
# Include prompt texts for --best-worst display
|
|
1324
|
+
if prompt_a_text:
|
|
1325
|
+
result["prompt_a_text"] = prompt_a_text
|
|
1326
|
+
result["prompt_b_text"] = prompt_b_text
|
|
1327
|
+
|
|
1267
1328
|
if json_output:
|
|
1268
1329
|
import json as json_mod
|
|
1269
1330
|
|
|
@@ -13,7 +13,10 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import re
|
|
15
15
|
from collections import Counter
|
|
16
|
-
from typing import Any
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from reprompt.storage.db import PromptDB
|
|
17
20
|
|
|
18
21
|
# Patterns that indicate specificity
|
|
19
22
|
_FILE_REF = re.compile(r"[\w/]+\.\w{1,5}") # file.py, path/to/file.ts
|
|
@@ -105,3 +108,61 @@ def compute_style(prompts: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
105
108
|
"specificity": round(specificity, 2),
|
|
106
109
|
"length_distribution": length_dist,
|
|
107
110
|
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def compute_style_trends(
|
|
114
|
+
db: PromptDB,
|
|
115
|
+
period: str = "7d",
|
|
116
|
+
source: str | None = None,
|
|
117
|
+
) -> dict[str, Any]:
|
|
118
|
+
"""Compare style between current and previous period.
|
|
119
|
+
|
|
120
|
+
Returns dict with:
|
|
121
|
+
period, current (style dict), previous (style dict),
|
|
122
|
+
deltas: {specificity, avg_length, prompt_count,
|
|
123
|
+
top_category_changed, top_category_current, top_category_previous}
|
|
124
|
+
"""
|
|
125
|
+
from reprompt.core.library import categorize_prompt
|
|
126
|
+
from reprompt.core.timeutil import sliding_windows
|
|
127
|
+
|
|
128
|
+
windows = sliding_windows(period=period, count=2)
|
|
129
|
+
prev_window = windows[0]
|
|
130
|
+
curr_window = windows[1]
|
|
131
|
+
|
|
132
|
+
def _build_prompts(window):
|
|
133
|
+
rows = db.get_prompts_in_range(
|
|
134
|
+
window.start.isoformat(),
|
|
135
|
+
window.end.isoformat(),
|
|
136
|
+
source=source,
|
|
137
|
+
)
|
|
138
|
+
return [
|
|
139
|
+
{
|
|
140
|
+
"text": r["text"],
|
|
141
|
+
"category": categorize_prompt(r["text"]),
|
|
142
|
+
"char_count": r.get("char_count", len(r["text"])),
|
|
143
|
+
}
|
|
144
|
+
for r in rows
|
|
145
|
+
if r.get("duplicate_of") is None
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
prev_prompts = _build_prompts(prev_window)
|
|
149
|
+
curr_prompts = _build_prompts(curr_window)
|
|
150
|
+
|
|
151
|
+
previous = compute_style(prev_prompts)
|
|
152
|
+
current = compute_style(curr_prompts)
|
|
153
|
+
|
|
154
|
+
deltas: dict[str, Any] = {
|
|
155
|
+
"specificity": round(current["specificity"] - previous["specificity"], 2),
|
|
156
|
+
"avg_length": round(current["avg_length"] - previous["avg_length"], 1),
|
|
157
|
+
"prompt_count": current["prompt_count"] - previous["prompt_count"],
|
|
158
|
+
"top_category_changed": current["top_category"] != previous["top_category"],
|
|
159
|
+
"top_category_current": current["top_category"],
|
|
160
|
+
"top_category_previous": previous["top_category"],
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
"period": period,
|
|
165
|
+
"current": current,
|
|
166
|
+
"previous": previous,
|
|
167
|
+
"deltas": deltas,
|
|
168
|
+
}
|
|
@@ -462,6 +462,18 @@ def render_compare(data: dict[str, Any]) -> str:
|
|
|
462
462
|
|
|
463
463
|
console.print("\n[bold]Prompt Comparison[/bold]")
|
|
464
464
|
|
|
465
|
+
# Show prompt texts if provided (from --best-worst)
|
|
466
|
+
if "prompt_a_text" in data:
|
|
467
|
+
a_text = data["prompt_a_text"]
|
|
468
|
+
b_text = data["prompt_b_text"]
|
|
469
|
+
|
|
470
|
+
def _truncate(t: str) -> str:
|
|
471
|
+
return (t[:77] + "...") if len(t) > 80 else t
|
|
472
|
+
|
|
473
|
+
console.print(f" [green]Best:[/green] {_truncate(a_text)}")
|
|
474
|
+
console.print(f" [red]Worst:[/red] {_truncate(b_text)}")
|
|
475
|
+
console.print()
|
|
476
|
+
|
|
465
477
|
table = Table()
|
|
466
478
|
table.add_column("Feature", style="dim", min_width=18)
|
|
467
479
|
table.add_column("Prompt A", justify="right")
|
|
@@ -657,6 +669,84 @@ def render_style(data: dict[str, Any]) -> str:
|
|
|
657
669
|
return buf.getvalue()
|
|
658
670
|
|
|
659
671
|
|
|
672
|
+
def render_style_trends(data: dict[str, Any]) -> str:
|
|
673
|
+
"""Render style trends comparison between two periods."""
|
|
674
|
+
buf = StringIO()
|
|
675
|
+
console = Console(file=buf, force_terminal=True, width=80)
|
|
676
|
+
|
|
677
|
+
curr = data["current"]
|
|
678
|
+
prev = data["previous"]
|
|
679
|
+
deltas = data["deltas"]
|
|
680
|
+
|
|
681
|
+
# Handle empty data
|
|
682
|
+
if curr["prompt_count"] == 0 and prev["prompt_count"] == 0:
|
|
683
|
+
console.print(
|
|
684
|
+
"\n[dim]Not enough data for trends. Keep prompting and check back next week.[/dim]"
|
|
685
|
+
)
|
|
686
|
+
return buf.getvalue()
|
|
687
|
+
|
|
688
|
+
if prev["prompt_count"] == 0:
|
|
689
|
+
console.print(f"\n[bold]Style Trends ({data['period']})[/bold]")
|
|
690
|
+
console.print("\u2500" * 40)
|
|
691
|
+
console.print(" [dim]New! No previous data to compare.[/dim]")
|
|
692
|
+
console.print(
|
|
693
|
+
f" Current: {curr['prompt_count']} prompts,"
|
|
694
|
+
f" specificity {curr['specificity']:.2f},"
|
|
695
|
+
f" avg {curr['avg_length']:.0f} chars"
|
|
696
|
+
)
|
|
697
|
+
return buf.getvalue()
|
|
698
|
+
|
|
699
|
+
console.print(f"\n[bold]Style Trends ({data['period']})[/bold]")
|
|
700
|
+
console.print("\u2500" * 40)
|
|
701
|
+
|
|
702
|
+
# Specificity (green = improvement)
|
|
703
|
+
spec_delta = deltas["specificity"]
|
|
704
|
+
spec_sign = "+" if spec_delta > 0 else ""
|
|
705
|
+
spec_pct = (
|
|
706
|
+
f" ({spec_sign}{spec_delta / prev['specificity'] * 100:.0f}%)"
|
|
707
|
+
if prev["specificity"] > 0
|
|
708
|
+
else ""
|
|
709
|
+
)
|
|
710
|
+
spec_color = "green" if spec_delta > 0 else "red" if spec_delta < 0 else "dim"
|
|
711
|
+
console.print(
|
|
712
|
+
f" Specificity {prev['specificity']:.2f}"
|
|
713
|
+
f" \u2192 {curr['specificity']:.2f}"
|
|
714
|
+
f" [{spec_color}]{spec_sign}{spec_delta:.2f}"
|
|
715
|
+
f"{spec_pct}[/{spec_color}]"
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
# Avg Length (neutral, always dim)
|
|
719
|
+
len_delta = deltas["avg_length"]
|
|
720
|
+
len_sign = "+" if len_delta > 0 else ""
|
|
721
|
+
console.print(
|
|
722
|
+
f" Avg Length {prev['avg_length']:.0f}"
|
|
723
|
+
f" \u2192 {curr['avg_length']:.0f} chars"
|
|
724
|
+
f" [dim]{len_sign}{len_delta:.0f} chars[/dim]"
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
# Prompt count (green = more activity)
|
|
728
|
+
count_delta = deltas["prompt_count"]
|
|
729
|
+
count_sign = "+" if count_delta > 0 else ""
|
|
730
|
+
count_color = "green" if count_delta > 0 else "dim"
|
|
731
|
+
console.print(
|
|
732
|
+
f" Prompts {prev['prompt_count']}"
|
|
733
|
+
f" \u2192 {curr['prompt_count']}"
|
|
734
|
+
f" [{count_color}]{count_sign}{count_delta}[/{count_color}]"
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
# Top category shift
|
|
738
|
+
if deltas["top_category_changed"]:
|
|
739
|
+
console.print(
|
|
740
|
+
f" Top Category {deltas['top_category_previous']}"
|
|
741
|
+
f" \u2192 {deltas['top_category_current']}"
|
|
742
|
+
)
|
|
743
|
+
else:
|
|
744
|
+
console.print(f" Top Category {deltas['top_category_current']} (unchanged)")
|
|
745
|
+
|
|
746
|
+
console.print()
|
|
747
|
+
return buf.getvalue()
|
|
748
|
+
|
|
749
|
+
|
|
660
750
|
def render_privacy(data: dict[str, Any]) -> str:
|
|
661
751
|
"""Render privacy exposure summary."""
|
|
662
752
|
buf = StringIO()
|
|
@@ -954,6 +954,46 @@ class PromptDB:
|
|
|
954
954
|
finally:
|
|
955
955
|
conn.close()
|
|
956
956
|
|
|
957
|
+
def get_best_worst_prompts(self, source: str | None = None) -> tuple[str, str] | None:
|
|
958
|
+
"""Return (best_text, worst_text) from scored prompts.
|
|
959
|
+
|
|
960
|
+
Filters to prompts with >= 5 words to avoid noise.
|
|
961
|
+
Returns None if fewer than 2 qualifying prompts exist.
|
|
962
|
+
"""
|
|
963
|
+
conn = self._conn()
|
|
964
|
+
try:
|
|
965
|
+
if source:
|
|
966
|
+
rows = conn.execute(
|
|
967
|
+
"""SELECT p.text, pf.overall_score
|
|
968
|
+
FROM prompt_features pf
|
|
969
|
+
JOIN prompts p ON pf.prompt_hash = p.hash
|
|
970
|
+
WHERE pf.overall_score IS NOT NULL AND p.source = ?
|
|
971
|
+
ORDER BY pf.overall_score DESC""",
|
|
972
|
+
(source,),
|
|
973
|
+
).fetchall()
|
|
974
|
+
else:
|
|
975
|
+
rows = conn.execute(
|
|
976
|
+
"""SELECT p.text, pf.overall_score
|
|
977
|
+
FROM prompt_features pf
|
|
978
|
+
JOIN prompts p ON pf.prompt_hash = p.hash
|
|
979
|
+
WHERE pf.overall_score IS NOT NULL
|
|
980
|
+
ORDER BY pf.overall_score DESC""",
|
|
981
|
+
).fetchall()
|
|
982
|
+
|
|
983
|
+
# Filter to prompts with >= 5 words
|
|
984
|
+
qualified = [
|
|
985
|
+
(row["text"], row["overall_score"]) for row in rows if len(row["text"].split()) >= 5
|
|
986
|
+
]
|
|
987
|
+
|
|
988
|
+
if len(qualified) < 2:
|
|
989
|
+
return None
|
|
990
|
+
|
|
991
|
+
best_text = qualified[0][0] # highest score (first in DESC order)
|
|
992
|
+
worst_text = qualified[-1][0] # lowest score (last in DESC order)
|
|
993
|
+
return (best_text, worst_text)
|
|
994
|
+
finally:
|
|
995
|
+
conn.close()
|
|
996
|
+
|
|
957
997
|
# -- digest_log ---------------------------------------------------------
|
|
958
998
|
|
|
959
999
|
def log_digest(
|