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.
Files changed (302) hide show
  1. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/CHANGELOG.md +15 -0
  2. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/PKG-INFO +2 -2
  3. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/README.md +1 -1
  4. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/pyproject.toml +1 -1
  5. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/cli.py +32 -2
  6. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/lint.py +170 -4
  7. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/scorer.py +21 -10
  8. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/rewrite_terminal.py +47 -0
  9. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/terminal.py +1 -3
  10. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_lint.py +176 -0
  11. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/uv.lock +1 -1
  12. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.editorconfig +0 -0
  13. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  15. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  16. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  17. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/dependabot.yml +0 -0
  19. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/workflows/ci.yml +0 -0
  20. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.github/workflows/publish.yml +0 -0
  21. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.gitignore +0 -0
  22. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.pre-commit-config.yaml +0 -0
  23. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.pre-commit-hooks.yaml +0 -0
  24. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.testmondata-shm +0 -0
  25. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/.testmondata-wal +0 -0
  26. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/CODE_OF_CONDUCT.md +0 -0
  27. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/CONTRIBUTING.md +0 -0
  28. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/LICENSE +0 -0
  29. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/SECURITY.md +0 -0
  30. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/Screenshot 2026-03-24 at 09.45.03.png +0 -0
  31. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/action.yml +0 -0
  32. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/demo.gif +0 -0
  33. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-128.png +0 -0
  34. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-16.png +0 -0
  35. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-256.png +0 -0
  36. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-32.png +0 -0
  37. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-48.png +0 -0
  38. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-512.png +0 -0
  39. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon-96.png +0 -0
  40. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/brand-icon.svg +0 -0
  41. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-128.png +0 -0
  42. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-16.png +0 -0
  43. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-256.png +0 -0
  44. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-32.png +0 -0
  45. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-48.png +0 -0
  46. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-512.png +0 -0
  47. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon-96.png +0 -0
  48. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-favicon.svg +0 -0
  49. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-128.png +0 -0
  50. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-16.png +0 -0
  51. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-256.png +0 -0
  52. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-32.png +0 -0
  53. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-48.png +0 -0
  54. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-512.png +0 -0
  55. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon-96.png +0 -0
  56. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/cli-icon.svg +0 -0
  57. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-128.png +0 -0
  58. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-16.png +0 -0
  59. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-256.png +0 -0
  60. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-32.png +0 -0
  61. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-48.png +0 -0
  62. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-512.png +0 -0
  63. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon-96.png +0 -0
  64. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/favicon.svg +0 -0
  65. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/icons/generate.sh +0 -0
  66. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/superpowers/specs/2026-03-24-v14-command-consolidation-design.md +0 -0
  67. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/docs/superpowers/specs/2026-03-25-v1.5-dashboard-design.md +0 -0
  68. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/scripts/generate_demo_data.py +0 -0
  69. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/__init__.py +0 -0
  70. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/__init__.py +0 -0
  71. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/aider.py +0 -0
  72. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/base.py +0 -0
  73. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/chatgpt.py +0 -0
  74. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/claude_chat.py +0 -0
  75. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/claude_code.py +0 -0
  76. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/cline.py +0 -0
  77. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/codex.py +0 -0
  78. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/cursor.py +0 -0
  79. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/filters.py +0 -0
  80. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/gemini.py +0 -0
  81. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/adapters/openclaw.py +0 -0
  82. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/__init__.py +0 -0
  83. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/handler.py +0 -0
  84. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/host.py +0 -0
  85. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/manifest.py +0 -0
  86. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/bridge/protocol.py +0 -0
  87. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/commands/__init__.py +0 -0
  88. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/commands/telemetry.py +0 -0
  89. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/commands/wrapped.py +0 -0
  90. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/config.py +0 -0
  91. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/__init__.py +0 -0
  92. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/agent.py +0 -0
  93. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/analyzer.py +0 -0
  94. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/compress.py +0 -0
  95. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/conversation.py +0 -0
  96. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/cost.py +0 -0
  97. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/dashboard.py +0 -0
  98. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/dedup.py +0 -0
  99. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/digest.py +0 -0
  100. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/distill.py +0 -0
  101. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/effectiveness.py +0 -0
  102. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/extractors.py +0 -0
  103. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/extractors_zh.py +0 -0
  104. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/insights.py +0 -0
  105. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/lang_detect.py +0 -0
  106. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/library.py +0 -0
  107. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/merge_view.py +0 -0
  108. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/models.py +0 -0
  109. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/persona.py +0 -0
  110. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/pipeline.py +0 -0
  111. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/privacy.py +0 -0
  112. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/privacy_scan.py +0 -0
  113. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/prompt_dna.py +0 -0
  114. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/recommend.py +0 -0
  115. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/repetition.py +0 -0
  116. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/rewrite.py +0 -0
  117. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/segmenter.py +0 -0
  118. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/session_meta.py +0 -0
  119. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/session_quality.py +0 -0
  120. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/session_type.py +0 -0
  121. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/style.py +0 -0
  122. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/suggestions.py +0 -0
  123. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/templates.py +0 -0
  124. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/timeutil.py +0 -0
  125. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/trends.py +0 -0
  126. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/core/wrapped.py +0 -0
  127. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/demo.py +0 -0
  128. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/__init__.py +0 -0
  129. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/base.py +0 -0
  130. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/local_embed.py +0 -0
  131. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/ollama.py +0 -0
  132. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/openai_embed.py +0 -0
  133. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/embeddings/tfidf.py +0 -0
  134. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/mcp.py +0 -0
  135. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/mcp_main.py +0 -0
  136. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/__init__.py +0 -0
  137. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/agent_terminal.py +0 -0
  138. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/chartjs.min.js +0 -0
  139. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/compress_terminal.py +0 -0
  140. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/dashboard_terminal.py +0 -0
  141. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/distill_terminal.py +0 -0
  142. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/export.py +0 -0
  143. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/html_report.py +0 -0
  144. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/json_out.py +0 -0
  145. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/markdown.py +0 -0
  146. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/projects_terminal.py +0 -0
  147. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/repetition_terminal.py +0 -0
  148. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/sessions_terminal.py +0 -0
  149. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/wrapped_html.py +0 -0
  150. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/output/wrapped_terminal.py +0 -0
  151. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/py.typed +0 -0
  152. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/sharing/__init__.py +0 -0
  153. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/sharing/client.py +0 -0
  154. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/sharing/clipboard.py +0 -0
  155. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/storage/__init__.py +0 -0
  156. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/storage/db.py +0 -0
  157. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/__init__.py +0 -0
  158. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/collector.py +0 -0
  159. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/consent.py +0 -0
  160. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/events.py +0 -0
  161. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/prompt.py +0 -0
  162. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/queue.py +0 -0
  163. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/src/reprompt/telemetry/sender.py +0 -0
  164. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/__init__.py +0 -0
  165. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/conftest.py +0 -0
  166. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/aider_chat_history.md +0 -0
  167. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/chatgpt_conversations.json +0 -0
  168. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/claude_chat_export.json +0 -0
  169. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/claude_session.jsonl +0 -0
  170. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/cline_task/api_conversation_history.json +0 -0
  171. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/export/default_export.md +0 -0
  172. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/export/full_export.md +0 -0
  173. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/gemini_session.json +0 -0
  174. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/fixtures/openclaw_session.jsonl +0 -0
  175. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_aider.py +0 -0
  176. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_chatgpt.py +0 -0
  177. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_claude.py +0 -0
  178. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_claude_chat.py +0 -0
  179. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_cline.py +0 -0
  180. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_gemini.py +0 -0
  181. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_adapter_openclaw.py +0 -0
  182. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_agent.py +0 -0
  183. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_agent_cli.py +0 -0
  184. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_analyzer.py +0 -0
  185. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_cli.py +0 -0
  186. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_e2e.py +0 -0
  187. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_handler.py +0 -0
  188. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_integration.py +0 -0
  189. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_manifest.py +0 -0
  190. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_bridge_protocol.py +0 -0
  191. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cli.py +0 -0
  192. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cli_deprecations.py +0 -0
  193. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cli_library_effectiveness.py +0 -0
  194. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_clipboard.py +0 -0
  195. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_codex_adapter.py +0 -0
  196. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compare_best_worst.py +0 -0
  197. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress.py +0 -0
  198. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress_cli.py +0 -0
  199. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress_dna.py +0 -0
  200. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress_html.py +0 -0
  201. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_compress_insights.py +0 -0
  202. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_config.py +0 -0
  203. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_conversation.py +0 -0
  204. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_copy_flag.py +0 -0
  205. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cost.py +0 -0
  206. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_coverage_boost.py +0 -0
  207. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_cursor_adapter.py +0 -0
  208. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_dashboard.py +0 -0
  209. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db.py +0 -0
  210. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db_digest.py +0 -0
  211. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db_effectiveness.py +0 -0
  212. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db_session_quality.py +0 -0
  213. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_db_trends.py +0 -0
  214. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_dedup.py +0 -0
  215. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_demo.py +0 -0
  216. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_deprecated_commands.py +0 -0
  217. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_digest.py +0 -0
  218. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_digest_cli.py +0 -0
  219. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_distill.py +0 -0
  220. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_distill_cli.py +0 -0
  221. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_distill_weights.py +0 -0
  222. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_e2e.py +0 -0
  223. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_effectiveness.py +0 -0
  224. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_embeddings_local.py +0 -0
  225. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_embeddings_ollama.py +0 -0
  226. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_embeddings_openai.py +0 -0
  227. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_empty_state.py +0 -0
  228. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_export.py +0 -0
  229. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_export_cli.py +0 -0
  230. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_export_snapshot.py +0 -0
  231. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_extractors.py +0 -0
  232. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_extractors_routing.py +0 -0
  233. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_extractors_zh.py +0 -0
  234. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_extractors_zh_e2e.py +0 -0
  235. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_html_report.py +0 -0
  236. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_import_cli.py +0 -0
  237. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_import_e2e.py +0 -0
  238. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_init_cli.py +0 -0
  239. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_insights.py +0 -0
  240. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_insights_cli.py +0 -0
  241. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_insights_expanded.py +0 -0
  242. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_install_hook.py +0 -0
  243. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_lang_detect.py +0 -0
  244. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_library.py +0 -0
  245. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_lint_cli.py +0 -0
  246. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_markdown.py +0 -0
  247. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_mcp.py +0 -0
  248. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_merge_view.py +0 -0
  249. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_models.py +0 -0
  250. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_output.py +0 -0
  251. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_parse_conversation_base.py +0 -0
  252. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_parse_conversation_chatgpt.py +0 -0
  253. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_parse_conversation_claude.py +0 -0
  254. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_persona.py +0 -0
  255. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_pipeline.py +0 -0
  256. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy.py +0 -0
  257. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy_cli.py +0 -0
  258. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy_e2e.py +0 -0
  259. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy_output.py +0 -0
  260. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_privacy_scan.py +0 -0
  261. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_projects.py +0 -0
  262. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_prompt_dna.py +0 -0
  263. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_public_api.py +0 -0
  264. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_recommend.py +0 -0
  265. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_repetition.py +0 -0
  266. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_repetition_cli.py +0 -0
  267. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_repetition_output.py +0 -0
  268. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_rewrite.py +0 -0
  269. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_schema_version.py +0 -0
  270. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_score_cli.py +0 -0
  271. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_scorer.py +0 -0
  272. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_segmenter.py +0 -0
  273. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_session_quality.py +0 -0
  274. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_session_type.py +0 -0
  275. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_sessions_cli.py +0 -0
  276. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_sessions_output.py +0 -0
  277. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_share_e2e.py +0 -0
  278. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_sharing_client.py +0 -0
  279. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_source_filter.py +0 -0
  280. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_style.py +0 -0
  281. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_style_trends.py +0 -0
  282. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_suggestions.py +0 -0
  283. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_cli.py +0 -0
  284. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_collector.py +0 -0
  285. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_consent.py +0 -0
  286. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_e2e.py +0 -0
  287. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_events.py +0 -0
  288. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_prompt.py +0 -0
  289. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_queue.py +0 -0
  290. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_telemetry_sender.py +0 -0
  291. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_template_cli.py +0 -0
  292. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_templates.py +0 -0
  293. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_timeutil.py +0 -0
  294. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_trends.py +0 -0
  295. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_trends_cli.py +0 -0
  296. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_use_cli.py +0 -0
  297. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped.py +0 -0
  298. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped_cli.py +0 -0
  299. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped_e2e.py +0 -0
  300. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped_html.py +0 -0
  301. {reprompt_cli-2.0.2 → reprompt_cli-2.1.0}/tests/test_wrapped_output.py +0 -0
  302. {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.2
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.1
231
+ rev: v2.1.0
232
232
  hooks:
233
233
  - id: reprompt-lint
234
234
  ```
@@ -183,7 +183,7 @@ Captured prompts sync locally via Native Messaging -- nothing leaves your machin
183
183
  # .pre-commit-config.yaml
184
184
  repos:
185
185
  - repo: https://github.com/reprompt-dev/reprompt
186
- rev: v2.0.1
186
+ rev: v2.1.0
187
187
  hooks:
188
188
  - id: reprompt-lint
189
189
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "reprompt-cli"
3
- version = "2.0.2"
3
+ version = "2.1.0"
4
4
  description = "Discover, analyze, and optimize your prompts from AI coding sessions"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -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 "please help me refactor this code to be better" --copy
1121
+ reprompt rewrite "fix the login" --diff
1103
1122
 
1104
- reprompt rewrite "fix the login" --json
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
- lines.append(f"\n{len(errors)} error(s), {len(warnings)} warning(s)")
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("position", "Key instruction at the start — optimal placement",
248
- f"{round(position)}/20")
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("context", "File references detected — specificity matters",
253
- f"{round(min(context, 25))}/25")
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("context", "Error context included — 3.7x more effective",
258
- f"{round(min(context, 25))}/25")
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("structure", "Constraints defined — clear boundaries set",
263
- f"{round(structure)}/15")
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("clarity", "Strong opening — starts with clear intent",
268
- f"{round(clarity)}/25")
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(