reprompt-cli 2.0.2__tar.gz → 2.1.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-2.0.2 → reprompt_cli-2.1.0}/CHANGELOG.md +15 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/PKG-INFO +2 -2
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/README.md +1 -1
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/pyproject.toml +1 -1
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/cli.py +32 -2
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/lint.py +170 -4
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/scorer.py +21 -10
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/rewrite_terminal.py +47 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/terminal.py +1 -3
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_lint.py +176 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/uv.lock +1 -1
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.editorconfig +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/dependabot.yml +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/workflows/ci.yml +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/workflows/publish.yml +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.gitignore +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.pre-commit-config.yaml +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.pre-commit-hooks.yaml +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.testmondata-shm +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.testmondata-wal +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/CODE_OF_CONDUCT.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/CONTRIBUTING.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/LICENSE +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/SECURITY.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/Screenshot 2026-03-24 at 09.45.03.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/action.yml +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/demo.gif +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-128.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-16.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-256.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-32.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-48.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-512.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-96.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon.svg +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-128.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-16.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-256.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-32.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-48.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-512.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-96.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon.svg +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-128.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-16.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-256.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-32.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-48.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-512.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-96.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon.svg +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-128.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-16.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-256.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-32.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-48.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-512.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-96.png +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon.svg +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/generate.sh +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/superpowers/specs/2026-03-24-v14-command-consolidation-design.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/superpowers/specs/2026-03-25-v1.5-dashboard-design.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/scripts/generate_demo_data.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/aider.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/base.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/chatgpt.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/claude_chat.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/claude_code.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/cline.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/codex.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/cursor.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/filters.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/gemini.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/openclaw.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/handler.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/host.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/manifest.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/protocol.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/commands/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/commands/telemetry.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/commands/wrapped.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/config.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/agent.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/analyzer.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/compress.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/conversation.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/cost.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/dashboard.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/dedup.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/digest.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/distill.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/effectiveness.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/extractors.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/extractors_zh.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/insights.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/lang_detect.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/library.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/merge_view.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/models.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/persona.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/pipeline.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/privacy.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/privacy_scan.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/prompt_dna.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/recommend.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/repetition.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/rewrite.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/segmenter.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/session_meta.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/session_quality.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/session_type.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/style.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/suggestions.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/templates.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/timeutil.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/trends.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/wrapped.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/demo.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/base.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/local_embed.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/ollama.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/openai_embed.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/tfidf.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/mcp.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/mcp_main.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/agent_terminal.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/chartjs.min.js +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/compress_terminal.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/dashboard_terminal.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/distill_terminal.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/export.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/html_report.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/json_out.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/markdown.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/projects_terminal.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/repetition_terminal.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/sessions_terminal.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/wrapped_html.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/wrapped_terminal.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/py.typed +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/sharing/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/sharing/client.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/sharing/clipboard.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/storage/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/storage/db.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/collector.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/consent.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/events.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/prompt.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/queue.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/sender.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/__init__.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/conftest.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/aider_chat_history.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/chatgpt_conversations.json +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/claude_chat_export.json +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/claude_session.jsonl +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/cline_task/api_conversation_history.json +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/export/default_export.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/export/full_export.md +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/gemini_session.json +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/openclaw_session.jsonl +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_aider.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_chatgpt.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_claude.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_claude_chat.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_cline.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_gemini.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_openclaw.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_agent.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_agent_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_analyzer.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_e2e.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_handler.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_integration.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_manifest.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_protocol.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cli_deprecations.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cli_library_effectiveness.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_clipboard.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_codex_adapter.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compare_best_worst.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress_dna.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress_html.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress_insights.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_config.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_conversation.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_copy_flag.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cost.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_coverage_boost.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cursor_adapter.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_dashboard.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db_digest.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db_effectiveness.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db_session_quality.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db_trends.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_dedup.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_demo.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_deprecated_commands.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_digest.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_digest_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_distill.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_distill_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_distill_weights.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_e2e.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_effectiveness.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_embeddings_local.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_embeddings_ollama.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_embeddings_openai.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_empty_state.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_export.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_export_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_export_snapshot.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_extractors.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_extractors_routing.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_extractors_zh.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_extractors_zh_e2e.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_html_report.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_import_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_import_e2e.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_init_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_insights.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_insights_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_insights_expanded.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_install_hook.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_lang_detect.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_library.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_lint_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_markdown.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_mcp.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_merge_view.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_models.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_output.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_parse_conversation_base.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_parse_conversation_chatgpt.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_parse_conversation_claude.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_persona.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_pipeline.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy_e2e.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy_output.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy_scan.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_projects.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_prompt_dna.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_public_api.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_recommend.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_repetition.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_repetition_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_repetition_output.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_rewrite.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_schema_version.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_score_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_scorer.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_segmenter.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_session_quality.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_session_type.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_sessions_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_sessions_output.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_share_e2e.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_sharing_client.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_source_filter.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_style.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_style_trends.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_suggestions.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_collector.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_consent.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_e2e.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_events.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_prompt.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_queue.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_sender.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_template_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_templates.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_timeutil.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_trends.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_trends_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_use_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped_cli.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped_e2e.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped_html.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped_output.py +0 -0
- {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped_share.py +0 -0
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.1.0] - 2026-04-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Model-specific lint rules** — `reprompt lint --model claude/gpt/gemini` checks prompts against model-specific best practices. 8 rules: XML tag preference (Claude), markdown structure (GPT), JSON instruction requirements (GPT), CoT anti-pattern for o-series (GPT), prompt length limits (Gemini), broad negative detection (Gemini). Based on official model documentation.
|
|
9
|
+
- **Diff preview for rewrite** — `reprompt rewrite --diff` shows a git-style unified diff between original and rewritten prompt. Color-coded: red removals, green additions, cyan range markers.
|
|
10
|
+
- **Token budget lint** — `reprompt lint --max-tokens 4096` warns when prompts exceed a token budget. Configurable via `.reprompt.toml` (`max-tokens`) or CLI flag. Uses locale-aware token estimation from cost module.
|
|
11
|
+
- Tests: 1716 → 1741
|
|
12
|
+
|
|
13
|
+
## [2.0.2] - 2026-04-01
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Scoring rebalance** — Structure weight reduced 25→15, Clarity increased 15→25. Plain-text prompts now score 55-65 instead of 35-45. Real-world conversational prompts are no longer penalized for lacking markdown structure.
|
|
17
|
+
- **Tier labels** — Scores display as EXPERT (85+), STRONG (70+), GOOD (50+), BASIC (30+), DRAFT (<30) instead of raw numbers. Applied across CLI, extension badge, popup, and HTML dashboard.
|
|
18
|
+
- **Positive UX feedback** — Score output now includes "Strengths" section showing what the prompt does well. Suggestions show expected point gain (`+N pts`). Badge color thresholds adjusted: 85/60/40/25.
|
|
19
|
+
|
|
5
20
|
## [2.0.1] - 2026-03-31
|
|
6
21
|
|
|
7
22
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reprompt-cli
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.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
|
|
@@ -228,7 +228,7 @@ Captured prompts sync locally via Native Messaging -- nothing leaves your machin
|
|
|
228
228
|
# .pre-commit-config.yaml
|
|
229
229
|
repos:
|
|
230
230
|
- repo: https://github.com/reprompt-dev/reprompt
|
|
231
|
-
rev: v2.0
|
|
231
|
+
rev: v2.1.0
|
|
232
232
|
hooks:
|
|
233
233
|
- id: reprompt-lint
|
|
234
234
|
```
|
|
@@ -797,6 +797,12 @@ def lint(
|
|
|
797
797
|
score_threshold: int = typer.Option(
|
|
798
798
|
0, "--score-threshold", help="Fail if avg prompt score < threshold (CI mode)"
|
|
799
799
|
),
|
|
800
|
+
model: str = typer.Option(
|
|
801
|
+
None, "--model", "-m", help="Target model for model-specific rules (claude/gpt/gemini)"
|
|
802
|
+
),
|
|
803
|
+
max_tokens: int = typer.Option(
|
|
804
|
+
0, "--max-tokens", help="Warn when prompts exceed token budget (0 = disabled)"
|
|
805
|
+
),
|
|
800
806
|
copy: bool = typer.Option(False, "--copy", help="Copy result to clipboard"),
|
|
801
807
|
) -> None:
|
|
802
808
|
"""Check prompt quality against lint rules.
|
|
@@ -806,6 +812,12 @@ def lint(
|
|
|
806
812
|
- short-prompt: prompts under 40 chars (warning)
|
|
807
813
|
- vague-prompt: overly vague prompts like "fix it"
|
|
808
814
|
- debug-needs-reference: debug prompts without file/function references
|
|
815
|
+
- max-tokens: prompt exceeds token budget
|
|
816
|
+
|
|
817
|
+
Model-specific rules (--model):
|
|
818
|
+
- claude: suggests XML tags for structure
|
|
819
|
+
- gpt: warns on XML tags (may echo verbatim), prefers markdown
|
|
820
|
+
- gemini: warns on very long prompts
|
|
809
821
|
|
|
810
822
|
CI mode: use --score-threshold to fail if average score is below a threshold.
|
|
811
823
|
|
|
@@ -813,6 +825,8 @@ def lint(
|
|
|
813
825
|
|
|
814
826
|
reprompt lint # lint stored prompts
|
|
815
827
|
|
|
828
|
+
reprompt lint --model claude # with Claude-specific hints
|
|
829
|
+
|
|
816
830
|
reprompt lint --score-threshold 50 # fail if avg score < 50 (CI mode)
|
|
817
831
|
|
|
818
832
|
reprompt lint --strict --json # strict mode with JSON output
|
|
@@ -832,6 +846,10 @@ def lint(
|
|
|
832
846
|
|
|
833
847
|
# CLI flags override config file
|
|
834
848
|
effective_threshold = score_threshold if score_threshold > 0 else lint_config.score_threshold
|
|
849
|
+
if model:
|
|
850
|
+
lint_config.model = model.lower()
|
|
851
|
+
if max_tokens > 0:
|
|
852
|
+
lint_config.max_tokens = max_tokens
|
|
835
853
|
|
|
836
854
|
# Collect prompts from DB (already scanned)
|
|
837
855
|
rows = db.get_all_prompts()
|
|
@@ -1087,6 +1105,7 @@ def compress(
|
|
|
1087
1105
|
def rewrite(
|
|
1088
1106
|
text: str = typer.Argument(..., help="Prompt text to improve"),
|
|
1089
1107
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
1108
|
+
diff: bool = typer.Option(False, "--diff", help="Show unified diff (red/green)"),
|
|
1090
1109
|
copy: bool = typer.Option(False, "--copy", help="Copy rewritten text to clipboard"),
|
|
1091
1110
|
) -> None:
|
|
1092
1111
|
"""Rewrite a prompt to improve its score. Rule-based, no LLM needed.
|
|
@@ -1099,9 +1118,9 @@ def rewrite(
|
|
|
1099
1118
|
|
|
1100
1119
|
reprompt rewrite "I was wondering if you could fix the authentication bug"
|
|
1101
1120
|
|
|
1102
|
-
reprompt rewrite "
|
|
1121
|
+
reprompt rewrite "fix the login" --diff
|
|
1103
1122
|
|
|
1104
|
-
reprompt rewrite "
|
|
1123
|
+
reprompt rewrite "please help me refactor this code to be better" --copy
|
|
1105
1124
|
"""
|
|
1106
1125
|
from reprompt.core.rewrite import rewrite_prompt
|
|
1107
1126
|
|
|
@@ -1120,6 +1139,10 @@ def rewrite(
|
|
|
1120
1139
|
"manual_suggestions": result.manual_suggestions,
|
|
1121
1140
|
}
|
|
1122
1141
|
typer.echo(json_mod.dumps(data, indent=2, ensure_ascii=False))
|
|
1142
|
+
elif diff:
|
|
1143
|
+
from reprompt.output.rewrite_terminal import render_rewrite_diff
|
|
1144
|
+
|
|
1145
|
+
typer.echo(render_rewrite_diff(result))
|
|
1123
1146
|
else:
|
|
1124
1147
|
from reprompt.output.rewrite_terminal import render_rewrite
|
|
1125
1148
|
|
|
@@ -2042,6 +2065,13 @@ def init(
|
|
|
2042
2065
|
# Useful for CI: reprompt lint --score-threshold reads this value
|
|
2043
2066
|
# score-threshold = 50
|
|
2044
2067
|
|
|
2068
|
+
# Target model for model-specific rules (claude, gpt, gemini)
|
|
2069
|
+
# Enables rules like "prefer XML tags" (Claude) or "avoid XML tags" (GPT)
|
|
2070
|
+
# model = "claude"
|
|
2071
|
+
|
|
2072
|
+
# Token budget — warn when prompts exceed this limit (0 = disabled)
|
|
2073
|
+
# max-tokens = 4096
|
|
2074
|
+
|
|
2045
2075
|
[lint.rules]
|
|
2046
2076
|
# min-length: error if prompt < N chars (0 = disabled)
|
|
2047
2077
|
min-length = 20
|
|
@@ -4,7 +4,7 @@ Checks prompts against configurable quality rules and returns violations.
|
|
|
4
4
|
Designed for CI integration — each rule produces a severity + message.
|
|
5
5
|
|
|
6
6
|
Configuration loaded from (highest priority wins):
|
|
7
|
-
1. CLI flags (--score-threshold, --strict)
|
|
7
|
+
1. CLI flags (--score-threshold, --strict, --model)
|
|
8
8
|
2. .reprompt.toml in CWD or parents
|
|
9
9
|
3. [tool.reprompt.lint] in pyproject.toml
|
|
10
10
|
4. Built-in defaults
|
|
@@ -12,6 +12,7 @@ Configuration loaded from (highest priority wins):
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
+
import re
|
|
15
16
|
import sys
|
|
16
17
|
from dataclasses import dataclass, field
|
|
17
18
|
from pathlib import Path
|
|
@@ -24,13 +25,16 @@ else:
|
|
|
24
25
|
except ModuleNotFoundError: # Python 3.10
|
|
25
26
|
import tomli as tomllib # type: ignore[no-redefine]
|
|
26
27
|
|
|
28
|
+
# Valid model targets
|
|
29
|
+
VALID_MODELS = {"claude", "gpt", "gemini"}
|
|
30
|
+
|
|
27
31
|
|
|
28
32
|
@dataclass
|
|
29
33
|
class LintViolation:
|
|
30
34
|
"""A single lint rule violation."""
|
|
31
35
|
|
|
32
36
|
rule: str
|
|
33
|
-
severity: str # "error" | "warning"
|
|
37
|
+
severity: str # "error" | "warning" | "hint"
|
|
34
38
|
message: str
|
|
35
39
|
prompt_text: str
|
|
36
40
|
|
|
@@ -55,6 +59,12 @@ class LintConfig:
|
|
|
55
59
|
# CI score threshold (0 = disabled, set via --score-threshold or config)
|
|
56
60
|
score_threshold: int = 0
|
|
57
61
|
|
|
62
|
+
# Token budget (0 = disabled). Warn when prompt exceeds this many tokens.
|
|
63
|
+
max_tokens: int = 0
|
|
64
|
+
|
|
65
|
+
# Target model for model-specific rules (None = universal rules only)
|
|
66
|
+
model: str | None = None
|
|
67
|
+
|
|
58
68
|
|
|
59
69
|
# --- Default config ---
|
|
60
70
|
DEFAULT_CONFIG = LintConfig()
|
|
@@ -126,6 +136,14 @@ def _build_config(lint_data: dict) -> LintConfig:
|
|
|
126
136
|
if "score-threshold" in lint_data:
|
|
127
137
|
config.score_threshold = int(lint_data["score-threshold"])
|
|
128
138
|
|
|
139
|
+
if "max-tokens" in lint_data:
|
|
140
|
+
config.max_tokens = int(lint_data["max-tokens"])
|
|
141
|
+
|
|
142
|
+
if "model" in lint_data:
|
|
143
|
+
model = str(lint_data["model"]).lower()
|
|
144
|
+
if model in VALID_MODELS:
|
|
145
|
+
config.model = model
|
|
146
|
+
|
|
129
147
|
# Rule settings
|
|
130
148
|
rules = lint_data.get("rules", {})
|
|
131
149
|
|
|
@@ -210,6 +228,150 @@ def lint_prompt(text: str, config: LintConfig | None = None) -> list[LintViolati
|
|
|
210
228
|
)
|
|
211
229
|
)
|
|
212
230
|
|
|
231
|
+
# Rule 4: Token budget
|
|
232
|
+
if config.max_tokens > 0:
|
|
233
|
+
from reprompt.core.cost import estimate_tokens
|
|
234
|
+
|
|
235
|
+
tokens = estimate_tokens(text)
|
|
236
|
+
if tokens > config.max_tokens:
|
|
237
|
+
violations.append(
|
|
238
|
+
LintViolation(
|
|
239
|
+
rule="max-tokens",
|
|
240
|
+
severity="warning",
|
|
241
|
+
message=(
|
|
242
|
+
f"Prompt is ~{tokens} tokens (budget: {config.max_tokens}) — "
|
|
243
|
+
"consider compressing with `reprompt compress`"
|
|
244
|
+
),
|
|
245
|
+
prompt_text=text,
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# ── Model-specific rules ──
|
|
250
|
+
if config.model and len(stripped) >= 30:
|
|
251
|
+
violations.extend(_check_model_rules(text, config.model))
|
|
252
|
+
|
|
253
|
+
return violations
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# ── Model-specific patterns ──
|
|
257
|
+
|
|
258
|
+
_XML_TAG_RE = re.compile(r"<(?:context|instructions|examples?|constraints?|output|task|role)\b")
|
|
259
|
+
_MD_HEADER_RE = re.compile(r"^#{1,3}\s+\w", re.MULTILINE)
|
|
260
|
+
_JSON_MODE_RE = re.compile(r"(?:respond|output|return|reply|format).*\bjson\b", re.IGNORECASE)
|
|
261
|
+
_COT_RE = re.compile(
|
|
262
|
+
r"\b(?:think step by step|let'?s think|chain of thought|step-by-step reasoning)\b",
|
|
263
|
+
re.IGNORECASE,
|
|
264
|
+
)
|
|
265
|
+
_BROAD_NEGATIVE_RE = re.compile(
|
|
266
|
+
r"\bdo not (?:infer|guess|assume|speculate|make assumptions)\b", re.IGNORECASE
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _check_model_rules(text: str, model: str) -> list[LintViolation]:
|
|
271
|
+
"""Return model-specific lint violations and hints."""
|
|
272
|
+
violations: list[LintViolation] = []
|
|
273
|
+
has_xml = bool(_XML_TAG_RE.search(text))
|
|
274
|
+
has_md_headers = bool(_MD_HEADER_RE.search(text))
|
|
275
|
+
word_count = len(text.split())
|
|
276
|
+
|
|
277
|
+
if model == "claude":
|
|
278
|
+
# Claude: XML tags are preferred for structured prompts
|
|
279
|
+
if not has_xml and word_count > 50 and not has_md_headers:
|
|
280
|
+
violations.append(
|
|
281
|
+
LintViolation(
|
|
282
|
+
rule="claude-prefer-xml",
|
|
283
|
+
severity="hint",
|
|
284
|
+
message=(
|
|
285
|
+
"Claude handles XML tags well for structured prompts — "
|
|
286
|
+
"try <context>, <instructions>, <constraints>"
|
|
287
|
+
),
|
|
288
|
+
prompt_text=text,
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
elif model == "gpt":
|
|
293
|
+
# GPT: XML tags may be echoed verbatim; prefer markdown
|
|
294
|
+
if has_xml:
|
|
295
|
+
violations.append(
|
|
296
|
+
LintViolation(
|
|
297
|
+
rule="gpt-avoid-xml",
|
|
298
|
+
severity="warning",
|
|
299
|
+
message=(
|
|
300
|
+
"GPT may echo XML tags verbatim — "
|
|
301
|
+
"use markdown headers (## Context, ## Instructions) instead"
|
|
302
|
+
),
|
|
303
|
+
prompt_text=text,
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
# GPT: markdown headers are preferred
|
|
307
|
+
if not has_md_headers and word_count > 50:
|
|
308
|
+
violations.append(
|
|
309
|
+
LintViolation(
|
|
310
|
+
rule="gpt-prefer-markdown",
|
|
311
|
+
severity="hint",
|
|
312
|
+
message=(
|
|
313
|
+
"Long prompts for GPT benefit from markdown headers — "
|
|
314
|
+
"use ## sections for clarity"
|
|
315
|
+
),
|
|
316
|
+
prompt_text=text,
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
# GPT: JSON mode needs explicit instruction
|
|
320
|
+
if _JSON_MODE_RE.search(text) is None and "json" in text.lower():
|
|
321
|
+
violations.append(
|
|
322
|
+
LintViolation(
|
|
323
|
+
rule="gpt-json-instruction",
|
|
324
|
+
severity="warning",
|
|
325
|
+
message=(
|
|
326
|
+
"GPT requires explicit JSON instruction — "
|
|
327
|
+
'add "Respond in JSON format" for reliable JSON output'
|
|
328
|
+
),
|
|
329
|
+
prompt_text=text,
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
elif model == "gemini":
|
|
334
|
+
# Gemini: very long prompts may lose focus
|
|
335
|
+
if word_count > 500:
|
|
336
|
+
violations.append(
|
|
337
|
+
LintViolation(
|
|
338
|
+
rule="gemini-prompt-length",
|
|
339
|
+
severity="warning",
|
|
340
|
+
message=(
|
|
341
|
+
f"Prompt is {word_count} words — "
|
|
342
|
+
"Gemini may lose focus on long instructions, consider splitting"
|
|
343
|
+
),
|
|
344
|
+
prompt_text=text,
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
# Gemini: broad negatives break reasoning (official Gemini 3 guide)
|
|
348
|
+
if _BROAD_NEGATIVE_RE.search(text):
|
|
349
|
+
violations.append(
|
|
350
|
+
LintViolation(
|
|
351
|
+
rule="gemini-broad-negative",
|
|
352
|
+
severity="warning",
|
|
353
|
+
message=(
|
|
354
|
+
'Broad negatives ("do not infer/guess/assume") break '
|
|
355
|
+
"Gemini's reasoning — use positive instructions instead"
|
|
356
|
+
),
|
|
357
|
+
prompt_text=text,
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Cross-model: CoT anti-pattern for reasoning models
|
|
362
|
+
if model == "gpt" and _COT_RE.search(text):
|
|
363
|
+
violations.append(
|
|
364
|
+
LintViolation(
|
|
365
|
+
rule="gpt-no-cot-reasoning",
|
|
366
|
+
severity="hint",
|
|
367
|
+
message=(
|
|
368
|
+
'"Think step by step" hurts o-series reasoning models — '
|
|
369
|
+
"give high-level goals instead of prescriptive steps"
|
|
370
|
+
),
|
|
371
|
+
prompt_text=text,
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
|
|
213
375
|
return violations
|
|
214
376
|
|
|
215
377
|
|
|
@@ -228,15 +390,19 @@ def format_lint_results(violations: list[LintViolation], total_prompts: int) ->
|
|
|
228
390
|
|
|
229
391
|
errors = [v for v in violations if v.severity == "error"]
|
|
230
392
|
warnings = [v for v in violations if v.severity == "warning"]
|
|
393
|
+
hints = [v for v in violations if v.severity == "hint"]
|
|
231
394
|
|
|
232
395
|
lines: list[str] = []
|
|
233
396
|
lines.append(f"Checked {total_prompts} prompts\n")
|
|
234
397
|
|
|
235
398
|
for v in violations:
|
|
236
|
-
prefix = "✗" if v.severity == "error" else "!"
|
|
399
|
+
prefix = "✗" if v.severity == "error" else "!" if v.severity == "warning" else "→"
|
|
237
400
|
display = v.prompt_text[:60] + "..." if len(v.prompt_text) > 60 else v.prompt_text
|
|
238
401
|
lines.append(f' {prefix} [{v.rule}] "{display}"')
|
|
239
402
|
lines.append(f" {v.message}")
|
|
240
403
|
|
|
241
|
-
|
|
404
|
+
parts = [f"{len(errors)} error(s)", f"{len(warnings)} warning(s)"]
|
|
405
|
+
if hints:
|
|
406
|
+
parts.append(f"{len(hints)} hint(s)")
|
|
407
|
+
lines.append(f"\n{', '.join(parts)}")
|
|
242
408
|
return "\n".join(lines)
|
|
@@ -244,28 +244,39 @@ def score_prompt(dna: PromptDNA) -> ScoreBreakdown:
|
|
|
244
244
|
confirmations: list[Confirmation] = []
|
|
245
245
|
if pos_score >= 0.8:
|
|
246
246
|
confirmations.append(
|
|
247
|
-
Confirmation(
|
|
248
|
-
|
|
247
|
+
Confirmation(
|
|
248
|
+
"position",
|
|
249
|
+
"Key instruction at the start — optimal placement",
|
|
250
|
+
f"{round(position)}/20",
|
|
251
|
+
)
|
|
249
252
|
)
|
|
250
253
|
if dna.has_file_references:
|
|
251
254
|
confirmations.append(
|
|
252
|
-
Confirmation(
|
|
253
|
-
|
|
255
|
+
Confirmation(
|
|
256
|
+
"context",
|
|
257
|
+
"File references detected — specificity matters",
|
|
258
|
+
f"{round(min(context, 25))}/25",
|
|
259
|
+
)
|
|
254
260
|
)
|
|
255
261
|
if dna.has_error_messages:
|
|
256
262
|
confirmations.append(
|
|
257
|
-
Confirmation(
|
|
258
|
-
|
|
263
|
+
Confirmation(
|
|
264
|
+
"context",
|
|
265
|
+
"Error context included — 3.7x more effective",
|
|
266
|
+
f"{round(min(context, 25))}/25",
|
|
267
|
+
)
|
|
259
268
|
)
|
|
260
269
|
if dna.has_constraints:
|
|
261
270
|
confirmations.append(
|
|
262
|
-
Confirmation(
|
|
263
|
-
|
|
271
|
+
Confirmation(
|
|
272
|
+
"structure", "Constraints defined — clear boundaries set", f"{round(structure)}/15"
|
|
273
|
+
)
|
|
264
274
|
)
|
|
265
275
|
if dna.opening_quality >= 0.4:
|
|
266
276
|
confirmations.append(
|
|
267
|
-
Confirmation(
|
|
268
|
-
|
|
277
|
+
Confirmation(
|
|
278
|
+
"clarity", "Strong opening — starts with clear intent", f"{round(clarity)}/25"
|
|
279
|
+
)
|
|
269
280
|
)
|
|
270
281
|
|
|
271
282
|
# ── Total ──
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import difflib
|
|
5
6
|
from io import StringIO
|
|
6
7
|
from typing import TYPE_CHECKING
|
|
7
8
|
|
|
@@ -57,6 +58,52 @@ def render_rewrite(result: RewriteResult) -> str:
|
|
|
57
58
|
return buf.getvalue()
|
|
58
59
|
|
|
59
60
|
|
|
61
|
+
def render_rewrite_diff(result: RewriteResult) -> str:
|
|
62
|
+
"""Render a unified diff between original and rewritten prompt."""
|
|
63
|
+
buf = StringIO()
|
|
64
|
+
console = Console(file=buf, width=100, record=True)
|
|
65
|
+
|
|
66
|
+
orig_lines = result.original.splitlines(keepends=True)
|
|
67
|
+
new_lines = result.rewritten.splitlines(keepends=True)
|
|
68
|
+
|
|
69
|
+
diff = difflib.unified_diff(
|
|
70
|
+
orig_lines, new_lines, fromfile="original", tofile="rewritten", lineterm=""
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
console.print()
|
|
74
|
+
has_diff = False
|
|
75
|
+
for line in diff:
|
|
76
|
+
line = line.rstrip("\n")
|
|
77
|
+
if line.startswith("---"):
|
|
78
|
+
console.print(f"[bold red]{line}[/bold red]")
|
|
79
|
+
has_diff = True
|
|
80
|
+
elif line.startswith("+++"):
|
|
81
|
+
console.print(f"[bold green]{line}[/bold green]")
|
|
82
|
+
elif line.startswith("@@"):
|
|
83
|
+
console.print(f"[cyan]{line}[/cyan]")
|
|
84
|
+
elif line.startswith("-"):
|
|
85
|
+
console.print(f"[red]{line}[/red]")
|
|
86
|
+
elif line.startswith("+"):
|
|
87
|
+
console.print(f"[green]{line}[/green]")
|
|
88
|
+
else:
|
|
89
|
+
console.print(f" {line}")
|
|
90
|
+
|
|
91
|
+
if not has_diff:
|
|
92
|
+
console.print("[dim]No changes — prompt is already optimized.[/dim]")
|
|
93
|
+
|
|
94
|
+
# Score summary
|
|
95
|
+
delta = result.score_delta
|
|
96
|
+
if delta > 0:
|
|
97
|
+
delta_str = f"[green]+{delta:.0f}[/green]"
|
|
98
|
+
elif delta < 0:
|
|
99
|
+
delta_str = f"[red]{delta:.0f}[/red]"
|
|
100
|
+
else:
|
|
101
|
+
delta_str = "[dim]±0[/dim]"
|
|
102
|
+
console.print(f"\n Score: {result.score_before:.0f} → {result.score_after:.0f} ({delta_str})")
|
|
103
|
+
console.print()
|
|
104
|
+
return buf.getvalue()
|
|
105
|
+
|
|
106
|
+
|
|
60
107
|
def _score_color(score: float) -> str:
|
|
61
108
|
if score >= 85:
|
|
62
109
|
return "bold magenta"
|
|
@@ -348,9 +348,7 @@ def render_score(breakdown: dict[str, Any]) -> str:
|
|
|
348
348
|
else "dim"
|
|
349
349
|
)
|
|
350
350
|
|
|
351
|
-
console.print(
|
|
352
|
-
f"\n[bold]Score: {total:.0f}/100[/bold] [{tier_color}]{tier}[/{tier_color}]"
|
|
353
|
-
)
|
|
351
|
+
console.print(f"\n[bold]Score: {total:.0f}/100[/bold] [{tier_color}]{tier}[/{tier_color}]")
|
|
354
352
|
cost_info = breakdown.get("estimated_cost")
|
|
355
353
|
if cost_info:
|
|
356
354
|
console.print(
|