reprompt-cli 2.3.0__tar.gz → 2.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-2.3.0 → reprompt_cli-2.4.1}/PKG-INFO +1 -1
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/pyproject.toml +1 -1
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/cli.py +32 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/extractors.py +4 -6
- reprompt_cli-2.4.1/src/reprompt/core/patterns.py +186 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/rewrite.py +78 -1
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/suggestions.py +1 -0
- reprompt_cli-2.4.1/src/reprompt/output/patterns_terminal.py +76 -0
- reprompt_cli-2.4.1/tests/test_patterns.py +166 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_rewrite.py +49 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_suggestions.py +1 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/uv.lock +1 -1
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.editorconfig +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.github/dependabot.yml +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.github/workflows/ci.yml +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.github/workflows/publish.yml +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.gitignore +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.pre-commit-config.yaml +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.pre-commit-hooks.yaml +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.testmondata-shm +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/.testmondata-wal +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/CHANGELOG.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/CODE_OF_CONDUCT.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/CONTRIBUTING.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/LICENSE +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/README.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/SECURITY.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/Screenshot 2026-03-24 at 09.45.03.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/action.yml +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/demo.gif +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/brand-icon-128.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/brand-icon-16.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/brand-icon-256.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/brand-icon-32.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/brand-icon-48.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/brand-icon-512.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/brand-icon-96.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/brand-icon.svg +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-favicon-128.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-favicon-16.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-favicon-256.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-favicon-32.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-favicon-48.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-favicon-512.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-favicon-96.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-favicon.svg +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-icon-128.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-icon-16.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-icon-256.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-icon-32.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-icon-48.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-icon-512.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-icon-96.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/cli-icon.svg +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/favicon-128.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/favicon-16.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/favicon-256.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/favicon-32.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/favicon-48.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/favicon-512.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/favicon-96.png +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/favicon.svg +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/icons/generate.sh +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/screenshots/build.svg +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/screenshots/check-bad.svg +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/screenshots/check-good.svg +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/screenshots/rewrite.svg +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/superpowers/specs/2026-03-24-v14-command-consolidation-design.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/docs/superpowers/specs/2026-03-25-v1.5-dashboard-design.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/scripts/gen_screenshots.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/scripts/generate_demo_data.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/aider.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/base.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/chatgpt.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/claude_chat.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/claude_code.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/cline.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/codex.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/cursor.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/filters.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/gemini.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/adapters/openclaw.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/bridge/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/bridge/handler.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/bridge/host.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/bridge/manifest.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/bridge/protocol.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/commands/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/commands/telemetry.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/commands/wrapped.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/config.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/agent.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/analyzer.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/build.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/check.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/compress.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/conversation.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/cost.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/dashboard.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/dedup.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/digest.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/distill.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/effectiveness.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/explain.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/extractors_zh.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/insights.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/lang_detect.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/library.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/lint.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/merge_view.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/models.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/persona.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/pipeline.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/privacy.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/privacy_scan.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/prompt_dna.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/recommend.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/repetition.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/scorer.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/segmenter.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/session_meta.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/session_quality.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/session_type.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/style.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/templates.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/timeutil.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/trends.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/core/wrapped.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/demo.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/embeddings/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/embeddings/base.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/embeddings/local_embed.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/embeddings/ollama.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/embeddings/openai_embed.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/embeddings/tfidf.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/mcp.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/mcp_main.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/agent_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/build_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/chartjs.min.js +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/check_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/compress_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/dashboard_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/distill_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/explain_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/export.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/html_report.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/json_out.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/markdown.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/projects_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/repetition_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/rewrite_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/sessions_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/wrapped_html.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/output/wrapped_terminal.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/py.typed +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/sharing/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/sharing/client.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/sharing/clipboard.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/storage/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/storage/db.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/telemetry/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/telemetry/collector.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/telemetry/consent.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/telemetry/events.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/telemetry/prompt.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/telemetry/queue.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/src/reprompt/telemetry/sender.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/__init__.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/conftest.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/fixtures/aider_chat_history.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/fixtures/chatgpt_conversations.json +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/fixtures/claude_chat_export.json +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/fixtures/claude_session.jsonl +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/fixtures/cline_task/api_conversation_history.json +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/fixtures/export/default_export.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/fixtures/export/full_export.md +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/fixtures/gemini_session.json +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/fixtures/openclaw_session.jsonl +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_adapter_aider.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_adapter_chatgpt.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_adapter_claude.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_adapter_claude_chat.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_adapter_cline.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_adapter_gemini.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_adapter_openclaw.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_agent.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_agent_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_analyzer.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_bridge_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_bridge_e2e.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_bridge_handler.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_bridge_integration.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_bridge_manifest.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_bridge_protocol.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_build.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_build_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_build_output.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_check.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_check_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_cli_deprecations.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_cli_library_effectiveness.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_clipboard.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_codex_adapter.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_compare_best_worst.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_compress.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_compress_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_compress_dna.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_compress_html.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_compress_insights.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_config.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_conversation.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_copy_flag.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_cost.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_coverage_boost.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_cursor_adapter.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_dashboard.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_db.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_db_digest.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_db_effectiveness.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_db_session_quality.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_db_trends.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_dedup.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_demo.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_deprecated_commands.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_digest.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_digest_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_distill.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_distill_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_distill_weights.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_e2e.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_effectiveness.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_embeddings_local.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_embeddings_ollama.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_embeddings_openai.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_empty_state.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_explain.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_explain_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_export.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_export_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_export_snapshot.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_extractors.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_extractors_routing.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_extractors_zh.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_extractors_zh_e2e.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_file_input.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_html_report.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_import_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_import_e2e.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_init_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_insights.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_insights_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_insights_expanded.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_install_hook.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_lang_detect.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_library.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_lint.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_lint_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_markdown.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_mcp.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_merge_view.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_models.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_output.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_parse_conversation_base.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_parse_conversation_chatgpt.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_parse_conversation_claude.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_persona.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_pipeline.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_privacy.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_privacy_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_privacy_e2e.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_privacy_output.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_privacy_scan.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_projects.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_prompt_dna.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_public_api.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_recommend.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_repetition.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_repetition_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_repetition_output.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_schema_version.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_score_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_scorer.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_segmenter.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_session_quality.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_session_type.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_sessions_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_sessions_output.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_share_e2e.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_sharing_client.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_source_filter.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_style.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_style_trends.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_telemetry_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_telemetry_collector.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_telemetry_consent.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_telemetry_e2e.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_telemetry_events.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_telemetry_prompt.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_telemetry_queue.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_telemetry_sender.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_template_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_templates.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_timeutil.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_trends.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_trends_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_use_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_wrapped.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_wrapped_cli.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_wrapped_e2e.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_wrapped_html.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_wrapped_output.py +0 -0
- {reprompt_cli-2.3.0 → reprompt_cli-2.4.1}/tests/test_wrapped_share.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reprompt-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.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
|
|
@@ -2400,6 +2400,38 @@ def sessions(
|
|
|
2400
2400
|
_copy_to_clip(copy_text, quiet=json_output)
|
|
2401
2401
|
|
|
2402
2402
|
|
|
2403
|
+
@app.command(rich_help_panel="Analyze")
|
|
2404
|
+
def patterns(
|
|
2405
|
+
last: int = typer.Option(500, "--last", help="Analyze N most recent prompts"),
|
|
2406
|
+
source: str = typer.Option(
|
|
2407
|
+
None, "--source", "-s", help="Filter by source (e.g. claude-code, cursor)"
|
|
2408
|
+
),
|
|
2409
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
2410
|
+
copy: bool = typer.Option(False, "--copy", help="Copy result to clipboard"),
|
|
2411
|
+
) -> None:
|
|
2412
|
+
"""Discover your personal prompt weaknesses and recurring gaps."""
|
|
2413
|
+
import json as json_mod
|
|
2414
|
+
from dataclasses import asdict
|
|
2415
|
+
|
|
2416
|
+
from reprompt.config import Settings
|
|
2417
|
+
from reprompt.core.patterns import analyze_patterns
|
|
2418
|
+
from reprompt.output.patterns_terminal import render_patterns
|
|
2419
|
+
from reprompt.storage.db import PromptDB
|
|
2420
|
+
|
|
2421
|
+
settings = Settings()
|
|
2422
|
+
db = PromptDB(settings.db_path)
|
|
2423
|
+
report = analyze_patterns(db, source=source, limit=last)
|
|
2424
|
+
|
|
2425
|
+
if json_output:
|
|
2426
|
+
typer.echo(json_mod.dumps(asdict(report), indent=2, default=str))
|
|
2427
|
+
else:
|
|
2428
|
+
typer.echo(render_patterns(report), nl=False)
|
|
2429
|
+
|
|
2430
|
+
if copy:
|
|
2431
|
+
copy_text = json_mod.dumps(asdict(report), indent=2, default=str)
|
|
2432
|
+
_copy_to_clip(copy_text, quiet=json_output)
|
|
2433
|
+
|
|
2434
|
+
|
|
2403
2435
|
@app.command(rich_help_panel="Analyze")
|
|
2404
2436
|
def repetition(
|
|
2405
2437
|
last: int = typer.Option(500, "--last", help="Analyze N most recent unique prompts"),
|
|
@@ -362,13 +362,11 @@ def _find_instruction_position(segments: list[PromptSegment]) -> float:
|
|
|
362
362
|
# No instruction found -- default to start (assume the whole thing is instruction)
|
|
363
363
|
return 0.0
|
|
364
364
|
|
|
365
|
-
#
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
# Multi-segment: use the midpoint of the first instruction segment
|
|
365
|
+
# Use the start position of the first instruction segment.
|
|
366
|
+
# Not the midpoint — the instruction *starts* at start_pos,
|
|
367
|
+
# and that's what matters for the Lost in the Middle effect.
|
|
370
368
|
seg = instruction_segments[0]
|
|
371
|
-
return
|
|
369
|
+
return seg.start_pos
|
|
372
370
|
|
|
373
371
|
|
|
374
372
|
def _classify_distribution(segments: list[PromptSegment]) -> str:
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Personal prompt patterns — analyze recurring weaknesses in prompt history.
|
|
2
|
+
|
|
3
|
+
Reads PromptDNA features from the database and identifies systematic gaps
|
|
4
|
+
the user keeps making. Example: "63% of your debug prompts lack error messages."
|
|
5
|
+
|
|
6
|
+
This is NOT template-based. It uses the user's OWN history to find patterns,
|
|
7
|
+
making suggestions personalized rather than generic.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class TaskPattern:
|
|
19
|
+
"""Analysis of prompts for a single task type."""
|
|
20
|
+
|
|
21
|
+
task_type: str
|
|
22
|
+
count: int
|
|
23
|
+
avg_score: float
|
|
24
|
+
# Feature gap rates (0.0 = always present, 1.0 = always missing)
|
|
25
|
+
gaps: list[FeatureGap] = field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class FeatureGap:
|
|
30
|
+
"""A frequently missing feature for a task type."""
|
|
31
|
+
|
|
32
|
+
feature: str
|
|
33
|
+
label: str # human-readable
|
|
34
|
+
missing_rate: float # 0.0-1.0
|
|
35
|
+
impact: str # "high", "medium", "low"
|
|
36
|
+
suggestion: str # actionable advice
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class PatternsReport:
|
|
41
|
+
"""Complete personal prompt patterns analysis."""
|
|
42
|
+
|
|
43
|
+
total_analyzed: int
|
|
44
|
+
task_distribution: dict[str, int] # task_type → count
|
|
45
|
+
patterns: list[TaskPattern]
|
|
46
|
+
top_gaps: list[FeatureGap] # cross-task most common gaps
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Feature checks: (field_name, human_label, applicable_task_types, impact)
|
|
50
|
+
_FEATURE_CHECKS: list[tuple[str, str, list[str], str]] = [
|
|
51
|
+
("has_error_messages", "error messages", ["debug"], "high"),
|
|
52
|
+
("has_file_references", "file references", ["debug", "implement", "refactor", "test"], "high"),
|
|
53
|
+
("has_code_blocks", "code blocks", ["debug", "implement"], "medium"),
|
|
54
|
+
("has_constraints", "constraints", ["implement", "refactor", "review"], "high"),
|
|
55
|
+
("has_io_spec", "I/O specifications", ["implement", "test"], "medium"),
|
|
56
|
+
("has_edge_cases", "edge cases", ["implement", "test"], "medium"),
|
|
57
|
+
("has_examples", "examples", ["implement", "test"], "medium"),
|
|
58
|
+
("has_output_format", "output format", ["implement", "review", "summarize"], "low"),
|
|
59
|
+
("has_role_definition", "role definition", ["review", "creative"], "low"),
|
|
60
|
+
("has_step_by_step", "step-by-step structure", ["implement", "refactor"], "low"),
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
_SUGGESTIONS: dict[str, str] = {
|
|
64
|
+
"has_error_messages": 'Include the error message: "Error: <paste stack trace>"',
|
|
65
|
+
"has_file_references": 'Reference files: "in src/auth/middleware.ts"',
|
|
66
|
+
"has_code_blocks": "Paste the relevant code in a ``` block",
|
|
67
|
+
"has_constraints": 'Add constraints: "Do not modify existing tests"',
|
|
68
|
+
"has_io_spec": 'Specify input/output: "Takes a user ID, returns a JWT token"',
|
|
69
|
+
"has_edge_cases": 'Mention edge cases: "Handle empty input and null values"',
|
|
70
|
+
"has_examples": "Add an example of expected input/output",
|
|
71
|
+
"has_output_format": 'Specify format: "Return as JSON with fields..."',
|
|
72
|
+
"has_role_definition": 'Set a role: "You are a security-focused code reviewer"',
|
|
73
|
+
"has_step_by_step": 'Request steps: "Break this into incremental steps"',
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def analyze_patterns(
|
|
78
|
+
db: Any,
|
|
79
|
+
*,
|
|
80
|
+
source: str | None = None,
|
|
81
|
+
limit: int = 500,
|
|
82
|
+
) -> PatternsReport:
|
|
83
|
+
"""Analyze personal prompt patterns from stored PromptDNA features."""
|
|
84
|
+
conn = db._conn()
|
|
85
|
+
try:
|
|
86
|
+
if source:
|
|
87
|
+
rows = conn.execute(
|
|
88
|
+
"SELECT pf.features_json, pf.overall_score, pf.task_type "
|
|
89
|
+
"FROM prompt_features pf "
|
|
90
|
+
"JOIN prompts p ON pf.prompt_hash = p.hash "
|
|
91
|
+
"WHERE p.source = ? "
|
|
92
|
+
"ORDER BY pf.rowid DESC LIMIT ?",
|
|
93
|
+
(source, limit),
|
|
94
|
+
).fetchall()
|
|
95
|
+
else:
|
|
96
|
+
rows = conn.execute(
|
|
97
|
+
"SELECT features_json, overall_score, task_type "
|
|
98
|
+
"FROM prompt_features "
|
|
99
|
+
"ORDER BY rowid DESC LIMIT ?",
|
|
100
|
+
(limit,),
|
|
101
|
+
).fetchall()
|
|
102
|
+
finally:
|
|
103
|
+
conn.close()
|
|
104
|
+
|
|
105
|
+
if not rows:
|
|
106
|
+
return PatternsReport(
|
|
107
|
+
total_analyzed=0,
|
|
108
|
+
task_distribution={},
|
|
109
|
+
patterns=[],
|
|
110
|
+
top_gaps=[],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Parse features and group by task type
|
|
114
|
+
by_task: dict[str, list[dict[str, Any]]] = {}
|
|
115
|
+
task_scores: dict[str, list[float]] = {}
|
|
116
|
+
|
|
117
|
+
for row in rows:
|
|
118
|
+
features = json.loads(row["features_json"])
|
|
119
|
+
task = row["task_type"] or "other"
|
|
120
|
+
by_task.setdefault(task, []).append(features)
|
|
121
|
+
task_scores.setdefault(task, []).append(float(row["overall_score"] or 0))
|
|
122
|
+
|
|
123
|
+
task_distribution = {t: len(prompts) for t, prompts in by_task.items()}
|
|
124
|
+
|
|
125
|
+
# Analyze each task type
|
|
126
|
+
patterns: list[TaskPattern] = []
|
|
127
|
+
all_gaps: list[FeatureGap] = []
|
|
128
|
+
|
|
129
|
+
for task_type, feature_list in by_task.items():
|
|
130
|
+
count = len(feature_list)
|
|
131
|
+
if count < 3: # need enough data for meaningful patterns
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
avg_score = sum(task_scores[task_type]) / count
|
|
135
|
+
gaps: list[FeatureGap] = []
|
|
136
|
+
|
|
137
|
+
for field_name, label, applicable_tasks, impact in _FEATURE_CHECKS:
|
|
138
|
+
if task_type not in applicable_tasks:
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
# Count how often this feature is missing
|
|
142
|
+
missing = sum(1 for f in feature_list if not f.get(field_name, False))
|
|
143
|
+
missing_rate = missing / count
|
|
144
|
+
|
|
145
|
+
# Only report if missing >40% of the time
|
|
146
|
+
if missing_rate > 0.4:
|
|
147
|
+
gap = FeatureGap(
|
|
148
|
+
feature=field_name,
|
|
149
|
+
label=label,
|
|
150
|
+
missing_rate=round(missing_rate, 2),
|
|
151
|
+
impact=impact,
|
|
152
|
+
suggestion=_SUGGESTIONS.get(field_name, f"Add {label}"),
|
|
153
|
+
)
|
|
154
|
+
gaps.append(gap)
|
|
155
|
+
all_gaps.append(gap)
|
|
156
|
+
|
|
157
|
+
# Sort by missing rate descending
|
|
158
|
+
gaps.sort(key=lambda g: g.missing_rate, reverse=True)
|
|
159
|
+
|
|
160
|
+
patterns.append(
|
|
161
|
+
TaskPattern(
|
|
162
|
+
task_type=task_type,
|
|
163
|
+
count=count,
|
|
164
|
+
avg_score=round(avg_score, 1),
|
|
165
|
+
gaps=gaps[:5], # top 5 gaps per task
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Sort patterns by count (most common task types first)
|
|
170
|
+
patterns.sort(key=lambda p: p.count, reverse=True)
|
|
171
|
+
|
|
172
|
+
# Deduplicate and sort top_gaps across all tasks
|
|
173
|
+
seen: set[str] = set()
|
|
174
|
+
top_gaps: list[FeatureGap] = []
|
|
175
|
+
all_gaps.sort(key=lambda g: g.missing_rate, reverse=True)
|
|
176
|
+
for gap in all_gaps:
|
|
177
|
+
if gap.feature not in seen:
|
|
178
|
+
seen.add(gap.feature)
|
|
179
|
+
top_gaps.append(gap)
|
|
180
|
+
|
|
181
|
+
return PatternsReport(
|
|
182
|
+
total_analyzed=len(rows),
|
|
183
|
+
task_distribution=task_distribution,
|
|
184
|
+
patterns=patterns,
|
|
185
|
+
top_gaps=top_gaps[:5],
|
|
186
|
+
)
|
|
@@ -7,7 +7,8 @@ Layers:
|
|
|
7
7
|
1. Compress — remove filler (reuse compress engine)
|
|
8
8
|
2. Restructure — front-load instructions (move imperative to start)
|
|
9
9
|
3. Reinforce — echo key requirement at end for long prompts
|
|
10
|
-
4.
|
|
10
|
+
4. Clarity — remove hedging language
|
|
11
|
+
5. Task-specific — append structural scaffolding based on detected task type
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
14
|
from __future__ import annotations
|
|
@@ -71,6 +72,12 @@ def rewrite_prompt(text: str) -> RewriteResult:
|
|
|
71
72
|
result = cleaned
|
|
72
73
|
changes.append("Removed hedging language")
|
|
73
74
|
|
|
75
|
+
# Layer 5: Task-specific scaffolding
|
|
76
|
+
scaffolded = _apply_task_scaffold(result, dna)
|
|
77
|
+
if scaffolded != result:
|
|
78
|
+
result = scaffolded
|
|
79
|
+
changes.append(f"Added {dna.task_type} prompt structure")
|
|
80
|
+
|
|
74
81
|
# Score rewritten
|
|
75
82
|
dna_after = extract_features(result, source="rewrite", session_id="")
|
|
76
83
|
score_after = score_prompt(dna_after)
|
|
@@ -196,6 +203,76 @@ def _remove_hedging(text: str) -> str:
|
|
|
196
203
|
return result
|
|
197
204
|
|
|
198
205
|
|
|
206
|
+
def _apply_task_scaffold(text: str, dna: object) -> str:
|
|
207
|
+
"""Append task-specific structural cues based on detected task type.
|
|
208
|
+
|
|
209
|
+
Only fires when the prompt is short (<30 words) AND missing critical
|
|
210
|
+
context for the detected task type. Adds fill-in-the-blank lines so
|
|
211
|
+
the user knows what to add — not generic advice, but structured slots.
|
|
212
|
+
|
|
213
|
+
Slot design informed by:
|
|
214
|
+
- fabric patterns (danielmiessler/fabric, 40k stars): IDENTITY/STEPS/OUTPUT
|
|
215
|
+
- steipete/agent-rules (5.6k stars): bug-fix, analyze-issue, pr-review
|
|
216
|
+
- awesome-cursorrules (38k stars): framework-specific conventions
|
|
217
|
+
"""
|
|
218
|
+
task = getattr(dna, "task_type", "other")
|
|
219
|
+
word_count = getattr(dna, "word_count", 0)
|
|
220
|
+
|
|
221
|
+
# Only scaffold short prompts — long prompts already have context
|
|
222
|
+
if word_count > 30:
|
|
223
|
+
return text
|
|
224
|
+
|
|
225
|
+
missing: list[str] = []
|
|
226
|
+
|
|
227
|
+
if task == "debug":
|
|
228
|
+
# steipete/agent-rules: bug-fix requires reproduce + expected vs actual
|
|
229
|
+
if not getattr(dna, "has_error_messages", False):
|
|
230
|
+
missing.append("Error: <paste the error message or stack trace>")
|
|
231
|
+
if not getattr(dna, "has_file_references", False):
|
|
232
|
+
missing.append("File: <which file and function>")
|
|
233
|
+
# Expected vs actual is the most diagnostic slot (from bug-fix.mdc)
|
|
234
|
+
missing.append("Expected: <what should happen vs what actually happens>")
|
|
235
|
+
|
|
236
|
+
elif task == "implement":
|
|
237
|
+
if not getattr(dna, "has_io_spec", False):
|
|
238
|
+
missing.append("Input/Output: <what it takes and returns>")
|
|
239
|
+
if not getattr(dna, "has_constraints", False):
|
|
240
|
+
missing.append("Constraints: <what NOT to change>")
|
|
241
|
+
if not getattr(dna, "has_edge_cases", False):
|
|
242
|
+
missing.append("Edge cases: <empty input, null, zero, etc.>")
|
|
243
|
+
|
|
244
|
+
elif task == "refactor":
|
|
245
|
+
if not getattr(dna, "has_file_references", False):
|
|
246
|
+
missing.append("Scope: <which files/modules to touch>")
|
|
247
|
+
if not getattr(dna, "has_constraints", False):
|
|
248
|
+
missing.append("Preserve: <what must NOT change (API, tests, etc.)>")
|
|
249
|
+
# fabric/awesome-cursorrules: refactors benefit from target pattern
|
|
250
|
+
missing.append("Goal: <readability, performance, or pattern to apply>")
|
|
251
|
+
|
|
252
|
+
elif task == "test":
|
|
253
|
+
if not getattr(dna, "has_file_references", False):
|
|
254
|
+
missing.append("Target: <function or module to test>")
|
|
255
|
+
if not getattr(dna, "has_edge_cases", False):
|
|
256
|
+
missing.append("Edge cases: <empty, null, boundary values>")
|
|
257
|
+
if not getattr(dna, "has_io_spec", False):
|
|
258
|
+
missing.append("Expected: <what the correct behavior should be>")
|
|
259
|
+
|
|
260
|
+
elif task == "review":
|
|
261
|
+
# fabric review_code: 6 named axes instead of generic "focus"
|
|
262
|
+
if not getattr(dna, "has_constraints", False):
|
|
263
|
+
missing.append(
|
|
264
|
+
"Review axes: <correctness, security, performance, readability, error handling>"
|
|
265
|
+
)
|
|
266
|
+
if not getattr(dna, "has_file_references", False):
|
|
267
|
+
missing.append("Scope: <which files or PR to review>")
|
|
268
|
+
|
|
269
|
+
if not missing:
|
|
270
|
+
return text
|
|
271
|
+
|
|
272
|
+
scaffold = "\n".join(missing)
|
|
273
|
+
return f"{text.rstrip()}\n\n{scaffold}"
|
|
274
|
+
|
|
275
|
+
|
|
199
276
|
def _generate_manual_suggestions(dna: object) -> list[str]:
|
|
200
277
|
"""Generate suggestions for improvements that require human input."""
|
|
201
278
|
suggestions: list[str] = []
|
|
@@ -28,6 +28,7 @@ SUGGESTIONS: dict[str, str] = {
|
|
|
28
28
|
"explain": "reprompt rewrite (auto-improve) · reprompt build (construct from parts)",
|
|
29
29
|
"rewrite": "reprompt compress (reduce tokens) · reprompt score (verify improvement)",
|
|
30
30
|
"projects": "reprompt sessions --detail <id> (deep-dive) · reprompt insights (patterns)",
|
|
31
|
+
"patterns": "reprompt rewrite (auto-improve gaps) · reprompt insights (full analysis)",
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Rich terminal rendering for personal prompt patterns."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from io import StringIO
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from reprompt.core.patterns import PatternsReport
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def render_patterns(report: PatternsReport) -> str:
|
|
14
|
+
"""Render a PatternsReport as formatted terminal output."""
|
|
15
|
+
console = Console(record=True, width=100, file=StringIO())
|
|
16
|
+
|
|
17
|
+
if report.total_analyzed == 0:
|
|
18
|
+
console.print(
|
|
19
|
+
"\n [dim]No scored prompts found. Run [bold]reprompt scan[/bold] first.[/dim]\n"
|
|
20
|
+
)
|
|
21
|
+
return console.export_text()
|
|
22
|
+
|
|
23
|
+
console.print()
|
|
24
|
+
|
|
25
|
+
# Header
|
|
26
|
+
analyzed = report.total_analyzed
|
|
27
|
+
console.print(
|
|
28
|
+
f" [bold]Personal Prompt Patterns[/bold] [dim]({analyzed} prompts analyzed)[/dim]"
|
|
29
|
+
)
|
|
30
|
+
console.print()
|
|
31
|
+
|
|
32
|
+
# Task distribution bar
|
|
33
|
+
total = report.total_analyzed
|
|
34
|
+
for task, count in sorted(report.task_distribution.items(), key=lambda x: x[1], reverse=True):
|
|
35
|
+
pct = count / total * 100
|
|
36
|
+
bar_len = int(pct / 2)
|
|
37
|
+
bar = "█" * bar_len
|
|
38
|
+
console.print(f" [dim]{task:<12}[/dim] [cyan]{bar}[/cyan] {count} ({pct:.0f}%)")
|
|
39
|
+
|
|
40
|
+
console.print()
|
|
41
|
+
|
|
42
|
+
# Per-task patterns
|
|
43
|
+
for pattern in report.patterns:
|
|
44
|
+
if not pattern.gaps:
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
tt = pattern.task_type
|
|
48
|
+
cnt = pattern.count
|
|
49
|
+
avg = pattern.avg_score
|
|
50
|
+
console.print(f" [bold]{tt}[/bold] prompts [dim]({cnt} total, avg score {avg})[/dim]")
|
|
51
|
+
|
|
52
|
+
for gap in pattern.gaps:
|
|
53
|
+
pct = int(gap.missing_rate * 100)
|
|
54
|
+
color = "red" if gap.impact == "high" else "yellow" if gap.impact == "medium" else "dim"
|
|
55
|
+
console.print(
|
|
56
|
+
f" [{color}]{pct}% missing {gap.label}[/{color}] [dim]→ {gap.suggestion}[/dim]"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
console.print()
|
|
60
|
+
|
|
61
|
+
# Top gaps summary
|
|
62
|
+
if report.top_gaps:
|
|
63
|
+
console.print(" [bold]Your most common gaps[/bold]")
|
|
64
|
+
table = Table(show_header=True, header_style="dim", box=None, padding=(0, 2))
|
|
65
|
+
table.add_column("Gap", style="bold")
|
|
66
|
+
table.add_column("Missing", justify="right")
|
|
67
|
+
table.add_column("Fix", style="dim")
|
|
68
|
+
|
|
69
|
+
for gap in report.top_gaps:
|
|
70
|
+
pct = f"{int(gap.missing_rate * 100)}%"
|
|
71
|
+
table.add_row(gap.label, pct, gap.suggestion)
|
|
72
|
+
|
|
73
|
+
console.print(table)
|
|
74
|
+
console.print()
|
|
75
|
+
|
|
76
|
+
return console.export_text()
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Tests for personal prompt patterns analysis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from reprompt.core.patterns import analyze_patterns
|
|
11
|
+
from reprompt.storage.db import PromptDB
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def db(tmp_path: Path) -> PromptDB:
|
|
16
|
+
return PromptDB(tmp_path / "test.db")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _store_feature(db: PromptDB, text: str, task_type: str, **features: bool | float) -> None:
|
|
20
|
+
"""Store a prompt + features for pattern analysis."""
|
|
21
|
+
import hashlib
|
|
22
|
+
|
|
23
|
+
h = hashlib.sha256(text.encode()).hexdigest()
|
|
24
|
+
|
|
25
|
+
conn = db._conn()
|
|
26
|
+
try:
|
|
27
|
+
conn.execute(
|
|
28
|
+
"INSERT OR IGNORE INTO prompts (hash, text, source, session_id) VALUES (?, ?, ?, ?)",
|
|
29
|
+
(h, text, "test", "s1"),
|
|
30
|
+
)
|
|
31
|
+
feature_dict = {
|
|
32
|
+
"has_error_messages": False,
|
|
33
|
+
"has_file_references": False,
|
|
34
|
+
"has_code_blocks": False,
|
|
35
|
+
"has_constraints": False,
|
|
36
|
+
"has_io_spec": False,
|
|
37
|
+
"has_edge_cases": False,
|
|
38
|
+
"has_examples": False,
|
|
39
|
+
"has_output_format": False,
|
|
40
|
+
"has_role_definition": False,
|
|
41
|
+
"has_step_by_step": False,
|
|
42
|
+
**features,
|
|
43
|
+
}
|
|
44
|
+
conn.execute(
|
|
45
|
+
"INSERT OR REPLACE INTO prompt_features "
|
|
46
|
+
"(prompt_hash, features_json, overall_score, task_type, computed_at) "
|
|
47
|
+
"VALUES (?, ?, ?, ?, datetime('now'))",
|
|
48
|
+
(h, json.dumps(feature_dict), features.get("overall_score", 40.0), task_type),
|
|
49
|
+
)
|
|
50
|
+
conn.commit()
|
|
51
|
+
finally:
|
|
52
|
+
conn.close()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestAnalyzePatterns:
|
|
56
|
+
def test_empty_db(self, db: PromptDB) -> None:
|
|
57
|
+
report = analyze_patterns(db)
|
|
58
|
+
assert report.total_analyzed == 0
|
|
59
|
+
assert report.patterns == []
|
|
60
|
+
assert report.top_gaps == []
|
|
61
|
+
|
|
62
|
+
def test_single_task_type(self, db: PromptDB) -> None:
|
|
63
|
+
for i in range(5):
|
|
64
|
+
_store_feature(db, f"fix bug {i}", "debug")
|
|
65
|
+
report = analyze_patterns(db)
|
|
66
|
+
assert report.total_analyzed == 5
|
|
67
|
+
assert "debug" in report.task_distribution
|
|
68
|
+
|
|
69
|
+
def test_detects_missing_error_messages(self, db: PromptDB) -> None:
|
|
70
|
+
# 4/5 debug prompts missing error messages
|
|
71
|
+
for i in range(4):
|
|
72
|
+
_store_feature(db, f"fix bug {i}", "debug", has_error_messages=False)
|
|
73
|
+
_store_feature(db, "fix bug with error", "debug", has_error_messages=True)
|
|
74
|
+
|
|
75
|
+
report = analyze_patterns(db)
|
|
76
|
+
debug_pattern = next((p for p in report.patterns if p.task_type == "debug"), None)
|
|
77
|
+
assert debug_pattern is not None
|
|
78
|
+
error_gap = next((g for g in debug_pattern.gaps if g.feature == "has_error_messages"), None)
|
|
79
|
+
assert error_gap is not None
|
|
80
|
+
assert error_gap.missing_rate == 0.8 # 4/5
|
|
81
|
+
|
|
82
|
+
def test_no_gap_when_feature_present(self, db: PromptDB) -> None:
|
|
83
|
+
# All debug prompts have error messages
|
|
84
|
+
for i in range(5):
|
|
85
|
+
_store_feature(db, f"fix bug {i}", "debug", has_error_messages=True)
|
|
86
|
+
|
|
87
|
+
report = analyze_patterns(db)
|
|
88
|
+
debug_pattern = next((p for p in report.patterns if p.task_type == "debug"), None)
|
|
89
|
+
if debug_pattern:
|
|
90
|
+
error_gap = next(
|
|
91
|
+
(g for g in debug_pattern.gaps if g.feature == "has_error_messages"), None
|
|
92
|
+
)
|
|
93
|
+
assert error_gap is None # no gap reported
|
|
94
|
+
|
|
95
|
+
def test_multiple_task_types(self, db: PromptDB) -> None:
|
|
96
|
+
for i in range(5):
|
|
97
|
+
_store_feature(db, f"fix bug {i}", "debug")
|
|
98
|
+
for i in range(3):
|
|
99
|
+
_store_feature(db, f"implement feature {i}", "implement")
|
|
100
|
+
|
|
101
|
+
report = analyze_patterns(db)
|
|
102
|
+
assert len(report.task_distribution) >= 2
|
|
103
|
+
|
|
104
|
+
def test_top_gaps_deduplication(self, db: PromptDB) -> None:
|
|
105
|
+
# file_references missing in both debug and implement
|
|
106
|
+
for i in range(5):
|
|
107
|
+
_store_feature(db, f"fix bug {i}", "debug", has_file_references=False)
|
|
108
|
+
for i in range(5):
|
|
109
|
+
_store_feature(db, f"implement feat {i}", "implement", has_file_references=False)
|
|
110
|
+
|
|
111
|
+
report = analyze_patterns(db)
|
|
112
|
+
file_gaps = [g for g in report.top_gaps if g.feature == "has_file_references"]
|
|
113
|
+
assert len(file_gaps) == 1 # deduplicated
|
|
114
|
+
|
|
115
|
+
def test_skips_small_task_groups(self, db: PromptDB) -> None:
|
|
116
|
+
# Only 2 prompts — not enough for meaningful patterns
|
|
117
|
+
_store_feature(db, "review code 1", "review")
|
|
118
|
+
_store_feature(db, "review code 2", "review")
|
|
119
|
+
|
|
120
|
+
report = analyze_patterns(db)
|
|
121
|
+
review_pattern = next((p for p in report.patterns if p.task_type == "review"), None)
|
|
122
|
+
assert review_pattern is None # skipped, need >= 3
|
|
123
|
+
|
|
124
|
+
def test_json_serializable(self, db: PromptDB) -> None:
|
|
125
|
+
for i in range(5):
|
|
126
|
+
_store_feature(db, f"fix bug {i}", "debug")
|
|
127
|
+
|
|
128
|
+
from dataclasses import asdict
|
|
129
|
+
|
|
130
|
+
report = analyze_patterns(db)
|
|
131
|
+
data = asdict(report)
|
|
132
|
+
json.dumps(data) # should not raise
|
|
133
|
+
|
|
134
|
+
def test_gap_has_suggestion(self, db: PromptDB) -> None:
|
|
135
|
+
for i in range(5):
|
|
136
|
+
_store_feature(db, f"fix bug {i}", "debug", has_error_messages=False)
|
|
137
|
+
|
|
138
|
+
report = analyze_patterns(db)
|
|
139
|
+
debug_pattern = next((p for p in report.patterns if p.task_type == "debug"), None)
|
|
140
|
+
assert debug_pattern is not None
|
|
141
|
+
for gap in debug_pattern.gaps:
|
|
142
|
+
assert gap.suggestion # non-empty
|
|
143
|
+
assert gap.impact in ("high", "medium", "low")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TestPatternsCLI:
|
|
147
|
+
def test_patterns_command_exists(self) -> None:
|
|
148
|
+
from typer.testing import CliRunner
|
|
149
|
+
|
|
150
|
+
from reprompt.cli import app
|
|
151
|
+
|
|
152
|
+
runner = CliRunner()
|
|
153
|
+
result = runner.invoke(app, ["patterns", "--help"])
|
|
154
|
+
assert result.exit_code == 0
|
|
155
|
+
assert "personal" in result.output.lower() or "weakness" in result.output.lower()
|
|
156
|
+
|
|
157
|
+
def test_patterns_json_output(self) -> None:
|
|
158
|
+
from typer.testing import CliRunner
|
|
159
|
+
|
|
160
|
+
from reprompt.cli import app
|
|
161
|
+
|
|
162
|
+
runner = CliRunner()
|
|
163
|
+
result = runner.invoke(app, ["patterns", "--json"])
|
|
164
|
+
assert result.exit_code == 0
|
|
165
|
+
data = json.loads(result.output)
|
|
166
|
+
assert "total_analyzed" in data
|
|
@@ -110,6 +110,55 @@ class TestRewritePrompt:
|
|
|
110
110
|
result = rewrite_prompt("x")
|
|
111
111
|
assert isinstance(result, RewriteResult)
|
|
112
112
|
|
|
113
|
+
# -- Layer 5: Task-specific scaffold tests --
|
|
114
|
+
|
|
115
|
+
def test_debug_scaffold_adds_error_slot(self):
|
|
116
|
+
result = rewrite_prompt("fix the auth bug")
|
|
117
|
+
# Short debug prompt should get error/file/code scaffold
|
|
118
|
+
has_scaffold = any("debug" in c.lower() or "structure" in c.lower() for c in result.changes)
|
|
119
|
+
suggestions_text = " ".join(result.manual_suggestions).lower()
|
|
120
|
+
has_error_slot = "Error:" in result.rewritten or "error" in suggestions_text
|
|
121
|
+
assert has_scaffold or has_error_slot
|
|
122
|
+
|
|
123
|
+
def test_implement_scaffold_adds_io_spec(self):
|
|
124
|
+
result = rewrite_prompt("implement user login")
|
|
125
|
+
# Should suggest I/O spec or constraints
|
|
126
|
+
all_text = result.rewritten + " ".join(result.manual_suggestions)
|
|
127
|
+
has_io = "Input/Output:" in all_text or "input/output" in all_text.lower()
|
|
128
|
+
has_constraints = "Constraints:" in all_text or "constraint" in all_text.lower()
|
|
129
|
+
assert has_io or has_constraints
|
|
130
|
+
|
|
131
|
+
def test_refactor_scaffold_adds_scope(self):
|
|
132
|
+
result = rewrite_prompt("refactor the auth module")
|
|
133
|
+
all_text = result.rewritten + " ".join(result.manual_suggestions)
|
|
134
|
+
has_scope = "Scope:" in all_text or "file" in all_text.lower()
|
|
135
|
+
assert has_scope
|
|
136
|
+
|
|
137
|
+
def test_test_scaffold_adds_target(self):
|
|
138
|
+
result = rewrite_prompt("write tests for login")
|
|
139
|
+
all_text = result.rewritten + " ".join(result.manual_suggestions)
|
|
140
|
+
lower_text = all_text.lower()
|
|
141
|
+
has_target = "Target:" in all_text or "function" in lower_text or "edge" in lower_text
|
|
142
|
+
assert has_target
|
|
143
|
+
|
|
144
|
+
def test_long_prompt_no_scaffold(self):
|
|
145
|
+
# Long prompts (>30 words) should NOT get scaffold
|
|
146
|
+
long_prompt = (
|
|
147
|
+
"Fix the authentication bug in src/auth/middleware.ts where the JWT token "
|
|
148
|
+
"expiration check fails for tokens issued before the timezone migration. "
|
|
149
|
+
"The error is TypeError: Cannot read property 'exp' of undefined at line 42. "
|
|
150
|
+
"Do not modify the refresh token logic."
|
|
151
|
+
)
|
|
152
|
+
result = rewrite_prompt(long_prompt)
|
|
153
|
+
has_scaffold = any("structure" in c.lower() for c in result.changes)
|
|
154
|
+
assert not has_scaffold
|
|
155
|
+
|
|
156
|
+
def test_scaffold_not_duplicated(self):
|
|
157
|
+
# If prompt already has error message, don't add Error: slot
|
|
158
|
+
result = rewrite_prompt("fix the TypeError in auth.ts")
|
|
159
|
+
# Should not scaffold Error: because error message is present
|
|
160
|
+
assert result.rewritten.count("Error:") <= 1
|
|
161
|
+
|
|
113
162
|
|
|
114
163
|
class TestRewriteCLI:
|
|
115
164
|
def test_rewrite_command_exists(self):
|