reprompt-cli 1.9.0__tar.gz → 1.10.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-1.9.0 → reprompt_cli-1.10.0}/PKG-INFO +23 -2
  2. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/README.md +22 -1
  3. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/pyproject.toml +1 -1
  4. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/__init__.py +1 -1
  5. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/cli.py +123 -6
  6. reprompt_cli-1.10.0/src/reprompt/core/lint.py +242 -0
  7. reprompt_cli-1.10.0/src/reprompt/core/rewrite.py +221 -0
  8. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/suggestions.py +2 -0
  9. reprompt_cli-1.10.0/src/reprompt/output/rewrite_terminal.py +69 -0
  10. reprompt_cli-1.10.0/tests/test_init_cli.py +80 -0
  11. reprompt_cli-1.10.0/tests/test_lint.py +237 -0
  12. reprompt_cli-1.10.0/tests/test_rewrite.py +157 -0
  13. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_suggestions.py +4 -0
  14. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/uv.lock +1 -1
  15. reprompt_cli-1.9.0/src/reprompt/core/lint.py +0 -112
  16. reprompt_cli-1.9.0/tests/test_lint.py +0 -81
  17. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.editorconfig +0 -0
  18. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  19. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  20. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  21. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  22. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  23. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.github/dependabot.yml +0 -0
  24. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.github/workflows/ci.yml +0 -0
  25. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.github/workflows/publish.yml +0 -0
  26. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.gitignore +0 -0
  27. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.pre-commit-config.yaml +0 -0
  28. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.pre-commit-hooks.yaml +0 -0
  29. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.testmondata-shm +0 -0
  30. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/.testmondata-wal +0 -0
  31. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/CHANGELOG.md +0 -0
  32. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/CODE_OF_CONDUCT.md +0 -0
  33. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/CONTRIBUTING.md +0 -0
  34. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/LICENSE +0 -0
  35. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/SECURITY.md +0 -0
  36. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/Screenshot 2026-03-24 at 09.45.03.png +0 -0
  37. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/action.yml +0 -0
  38. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/demo.gif +0 -0
  39. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/brand-icon-128.png +0 -0
  40. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/brand-icon-16.png +0 -0
  41. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/brand-icon-256.png +0 -0
  42. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/brand-icon-32.png +0 -0
  43. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/brand-icon-48.png +0 -0
  44. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/brand-icon-512.png +0 -0
  45. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/brand-icon-96.png +0 -0
  46. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/brand-icon.svg +0 -0
  47. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-favicon-128.png +0 -0
  48. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-favicon-16.png +0 -0
  49. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-favicon-256.png +0 -0
  50. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-favicon-32.png +0 -0
  51. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-favicon-48.png +0 -0
  52. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-favicon-512.png +0 -0
  53. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-favicon-96.png +0 -0
  54. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-favicon.svg +0 -0
  55. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-icon-128.png +0 -0
  56. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-icon-16.png +0 -0
  57. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-icon-256.png +0 -0
  58. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-icon-32.png +0 -0
  59. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-icon-48.png +0 -0
  60. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-icon-512.png +0 -0
  61. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-icon-96.png +0 -0
  62. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/cli-icon.svg +0 -0
  63. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/favicon-128.png +0 -0
  64. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/favicon-16.png +0 -0
  65. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/favicon-256.png +0 -0
  66. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/favicon-32.png +0 -0
  67. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/favicon-48.png +0 -0
  68. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/favicon-512.png +0 -0
  69. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/favicon-96.png +0 -0
  70. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/favicon.svg +0 -0
  71. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/icons/generate.sh +0 -0
  72. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/superpowers/specs/2026-03-24-v14-command-consolidation-design.md +0 -0
  73. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/docs/superpowers/specs/2026-03-25-v1.5-dashboard-design.md +0 -0
  74. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/scripts/generate_demo_data.py +0 -0
  75. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/__init__.py +0 -0
  76. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/aider.py +0 -0
  77. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/base.py +0 -0
  78. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/chatgpt.py +0 -0
  79. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/claude_chat.py +0 -0
  80. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/claude_code.py +0 -0
  81. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/cline.py +0 -0
  82. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/codex.py +0 -0
  83. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/cursor.py +0 -0
  84. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/filters.py +0 -0
  85. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/gemini.py +0 -0
  86. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/adapters/openclaw.py +0 -0
  87. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/bridge/__init__.py +0 -0
  88. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/bridge/handler.py +0 -0
  89. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/bridge/host.py +0 -0
  90. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/bridge/manifest.py +0 -0
  91. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/bridge/protocol.py +0 -0
  92. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/commands/__init__.py +0 -0
  93. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/commands/telemetry.py +0 -0
  94. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/commands/wrapped.py +0 -0
  95. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/config.py +0 -0
  96. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/__init__.py +0 -0
  97. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/agent.py +0 -0
  98. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/analyzer.py +0 -0
  99. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/compress.py +0 -0
  100. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/conversation.py +0 -0
  101. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/cost.py +0 -0
  102. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/dashboard.py +0 -0
  103. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/dedup.py +0 -0
  104. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/digest.py +0 -0
  105. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/distill.py +0 -0
  106. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/effectiveness.py +0 -0
  107. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/extractors.py +0 -0
  108. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/extractors_zh.py +0 -0
  109. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/insights.py +0 -0
  110. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/lang_detect.py +0 -0
  111. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/library.py +0 -0
  112. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/merge_view.py +0 -0
  113. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/models.py +0 -0
  114. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/persona.py +0 -0
  115. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/pipeline.py +0 -0
  116. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/privacy.py +0 -0
  117. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/privacy_scan.py +0 -0
  118. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/prompt_dna.py +0 -0
  119. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/recommend.py +0 -0
  120. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/repetition.py +0 -0
  121. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/scorer.py +0 -0
  122. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/segmenter.py +0 -0
  123. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/session_meta.py +0 -0
  124. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/session_quality.py +0 -0
  125. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/session_type.py +0 -0
  126. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/style.py +0 -0
  127. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/templates.py +0 -0
  128. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/timeutil.py +0 -0
  129. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/trends.py +0 -0
  130. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/core/wrapped.py +0 -0
  131. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/demo.py +0 -0
  132. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/embeddings/__init__.py +0 -0
  133. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/embeddings/base.py +0 -0
  134. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/embeddings/local_embed.py +0 -0
  135. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/embeddings/ollama.py +0 -0
  136. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/embeddings/openai_embed.py +0 -0
  137. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/embeddings/tfidf.py +0 -0
  138. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/mcp.py +0 -0
  139. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/mcp_main.py +0 -0
  140. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/__init__.py +0 -0
  141. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/agent_terminal.py +0 -0
  142. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/chartjs.min.js +0 -0
  143. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/compress_terminal.py +0 -0
  144. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/dashboard_terminal.py +0 -0
  145. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/distill_terminal.py +0 -0
  146. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/export.py +0 -0
  147. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/html_report.py +0 -0
  148. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/json_out.py +0 -0
  149. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/markdown.py +0 -0
  150. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/repetition_terminal.py +0 -0
  151. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/sessions_terminal.py +0 -0
  152. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/terminal.py +0 -0
  153. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/wrapped_html.py +0 -0
  154. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/output/wrapped_terminal.py +0 -0
  155. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/py.typed +0 -0
  156. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/sharing/__init__.py +0 -0
  157. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/sharing/client.py +0 -0
  158. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/sharing/clipboard.py +0 -0
  159. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/storage/__init__.py +0 -0
  160. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/storage/db.py +0 -0
  161. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/telemetry/__init__.py +0 -0
  162. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/telemetry/collector.py +0 -0
  163. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/telemetry/consent.py +0 -0
  164. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/telemetry/events.py +0 -0
  165. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/telemetry/prompt.py +0 -0
  166. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/telemetry/queue.py +0 -0
  167. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/src/reprompt/telemetry/sender.py +0 -0
  168. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/__init__.py +0 -0
  169. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/conftest.py +0 -0
  170. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/fixtures/aider_chat_history.md +0 -0
  171. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/fixtures/chatgpt_conversations.json +0 -0
  172. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/fixtures/claude_chat_export.json +0 -0
  173. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/fixtures/claude_session.jsonl +0 -0
  174. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/fixtures/cline_task/api_conversation_history.json +0 -0
  175. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/fixtures/export/default_export.md +0 -0
  176. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/fixtures/export/full_export.md +0 -0
  177. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/fixtures/gemini_session.json +0 -0
  178. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/fixtures/openclaw_session.jsonl +0 -0
  179. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_adapter_aider.py +0 -0
  180. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_adapter_chatgpt.py +0 -0
  181. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_adapter_claude.py +0 -0
  182. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_adapter_claude_chat.py +0 -0
  183. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_adapter_cline.py +0 -0
  184. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_adapter_gemini.py +0 -0
  185. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_adapter_openclaw.py +0 -0
  186. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_agent.py +0 -0
  187. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_agent_cli.py +0 -0
  188. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_analyzer.py +0 -0
  189. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_bridge_cli.py +0 -0
  190. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_bridge_e2e.py +0 -0
  191. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_bridge_handler.py +0 -0
  192. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_bridge_integration.py +0 -0
  193. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_bridge_manifest.py +0 -0
  194. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_bridge_protocol.py +0 -0
  195. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_cli.py +0 -0
  196. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_cli_deprecations.py +0 -0
  197. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_cli_library_effectiveness.py +0 -0
  198. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_clipboard.py +0 -0
  199. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_codex_adapter.py +0 -0
  200. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_compare_best_worst.py +0 -0
  201. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_compress.py +0 -0
  202. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_compress_cli.py +0 -0
  203. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_compress_dna.py +0 -0
  204. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_compress_html.py +0 -0
  205. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_compress_insights.py +0 -0
  206. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_config.py +0 -0
  207. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_conversation.py +0 -0
  208. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_copy_flag.py +0 -0
  209. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_cost.py +0 -0
  210. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_coverage_boost.py +0 -0
  211. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_cursor_adapter.py +0 -0
  212. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_dashboard.py +0 -0
  213. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_db.py +0 -0
  214. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_db_digest.py +0 -0
  215. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_db_effectiveness.py +0 -0
  216. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_db_session_quality.py +0 -0
  217. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_db_trends.py +0 -0
  218. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_dedup.py +0 -0
  219. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_demo.py +0 -0
  220. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_deprecated_commands.py +0 -0
  221. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_digest.py +0 -0
  222. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_digest_cli.py +0 -0
  223. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_distill.py +0 -0
  224. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_distill_cli.py +0 -0
  225. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_distill_weights.py +0 -0
  226. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_e2e.py +0 -0
  227. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_effectiveness.py +0 -0
  228. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_embeddings_local.py +0 -0
  229. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_embeddings_ollama.py +0 -0
  230. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_embeddings_openai.py +0 -0
  231. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_empty_state.py +0 -0
  232. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_export.py +0 -0
  233. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_export_cli.py +0 -0
  234. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_export_snapshot.py +0 -0
  235. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_extractors.py +0 -0
  236. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_extractors_routing.py +0 -0
  237. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_extractors_zh.py +0 -0
  238. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_extractors_zh_e2e.py +0 -0
  239. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_html_report.py +0 -0
  240. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_import_cli.py +0 -0
  241. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_import_e2e.py +0 -0
  242. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_insights.py +0 -0
  243. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_insights_cli.py +0 -0
  244. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_insights_expanded.py +0 -0
  245. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_install_hook.py +0 -0
  246. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_lang_detect.py +0 -0
  247. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_library.py +0 -0
  248. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_lint_cli.py +0 -0
  249. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_markdown.py +0 -0
  250. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_mcp.py +0 -0
  251. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_merge_view.py +0 -0
  252. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_models.py +0 -0
  253. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_output.py +0 -0
  254. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_parse_conversation_base.py +0 -0
  255. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_parse_conversation_chatgpt.py +0 -0
  256. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_parse_conversation_claude.py +0 -0
  257. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_persona.py +0 -0
  258. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_pipeline.py +0 -0
  259. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_privacy.py +0 -0
  260. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_privacy_cli.py +0 -0
  261. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_privacy_e2e.py +0 -0
  262. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_privacy_output.py +0 -0
  263. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_privacy_scan.py +0 -0
  264. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_prompt_dna.py +0 -0
  265. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_public_api.py +0 -0
  266. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_recommend.py +0 -0
  267. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_repetition.py +0 -0
  268. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_repetition_cli.py +0 -0
  269. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_repetition_output.py +0 -0
  270. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_schema_version.py +0 -0
  271. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_score_cli.py +0 -0
  272. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_scorer.py +0 -0
  273. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_segmenter.py +0 -0
  274. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_session_quality.py +0 -0
  275. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_session_type.py +0 -0
  276. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_sessions_cli.py +0 -0
  277. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_sessions_output.py +0 -0
  278. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_share_e2e.py +0 -0
  279. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_sharing_client.py +0 -0
  280. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_source_filter.py +0 -0
  281. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_style.py +0 -0
  282. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_style_trends.py +0 -0
  283. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_telemetry_cli.py +0 -0
  284. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_telemetry_collector.py +0 -0
  285. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_telemetry_consent.py +0 -0
  286. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_telemetry_e2e.py +0 -0
  287. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_telemetry_events.py +0 -0
  288. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_telemetry_prompt.py +0 -0
  289. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_telemetry_queue.py +0 -0
  290. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_telemetry_sender.py +0 -0
  291. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_template_cli.py +0 -0
  292. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_templates.py +0 -0
  293. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_timeutil.py +0 -0
  294. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_trends.py +0 -0
  295. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_trends_cli.py +0 -0
  296. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_use_cli.py +0 -0
  297. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_wrapped.py +0 -0
  298. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_wrapped_cli.py +0 -0
  299. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_wrapped_e2e.py +0 -0
  300. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_wrapped_html.py +0 -0
  301. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_wrapped_output.py +0 -0
  302. {reprompt_cli-1.9.0 → reprompt_cli-1.10.0}/tests/test_wrapped_share.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reprompt-cli
3
- Version: 1.9.0
3
+ Version: 1.10.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
@@ -96,6 +96,8 @@ $ reprompt compress "I was wondering if you could please help me refactor this c
96
96
  | `reprompt insights` | Personal patterns vs research-optimal benchmarks |
97
97
  | `reprompt style` | Prompting fingerprint with `--trends` for evolution tracking |
98
98
  | `reprompt agent` | Agent workflow analysis -- error loops, tool patterns, session efficiency |
99
+ | `reprompt sessions` | Session quality scores with frustration signal detection |
100
+ | `reprompt repetition` | Cross-session repetition detection -- spot recurring prompts |
99
101
 
100
102
  ### Optimize
101
103
 
@@ -104,7 +106,8 @@ $ reprompt compress "I was wondering if you could please help me refactor this c
104
106
  | `reprompt compress "prompt"` | 4-layer prompt compression (40-60% token savings typical) |
105
107
  | `reprompt distill` | Extract important turns from conversations with 6-signal scoring |
106
108
  | `reprompt distill --export` | Recover context when a session runs out -- paste into new session |
107
- | `reprompt lint` | Prompt quality linter with GitHub Action support |
109
+ | `reprompt lint` | Configurable prompt quality linter with CI/GitHub Action support |
110
+ | `reprompt init` | Generate `.reprompt.toml` config for your project |
108
111
 
109
112
  ### Manage
110
113
 
@@ -223,6 +226,24 @@ reprompt lint --strict # exit 1 on warnings
223
226
  reprompt lint --json # machine-readable output
224
227
  ```
225
228
 
229
+ #### Project configuration
230
+
231
+ ```bash
232
+ reprompt init # generates .reprompt.toml with all rules documented
233
+ ```
234
+
235
+ ```toml
236
+ # .reprompt.toml (or [tool.reprompt.lint] in pyproject.toml)
237
+ [lint]
238
+ score-threshold = 50 # fail if avg score < 50
239
+
240
+ [lint.rules]
241
+ min-length = 20 # error if prompt < 20 chars (0 = off)
242
+ short-prompt = 40 # warning if < 40 chars (0 = off)
243
+ vague-prompt = true # error on "fix it" etc (false = off)
244
+ debug-needs-reference = true
245
+ ```
246
+
226
247
  ## Privacy
227
248
 
228
249
  - All analysis runs locally. No prompts leave your machine.
@@ -51,6 +51,8 @@ $ reprompt compress "I was wondering if you could please help me refactor this c
51
51
  | `reprompt insights` | Personal patterns vs research-optimal benchmarks |
52
52
  | `reprompt style` | Prompting fingerprint with `--trends` for evolution tracking |
53
53
  | `reprompt agent` | Agent workflow analysis -- error loops, tool patterns, session efficiency |
54
+ | `reprompt sessions` | Session quality scores with frustration signal detection |
55
+ | `reprompt repetition` | Cross-session repetition detection -- spot recurring prompts |
54
56
 
55
57
  ### Optimize
56
58
 
@@ -59,7 +61,8 @@ $ reprompt compress "I was wondering if you could please help me refactor this c
59
61
  | `reprompt compress "prompt"` | 4-layer prompt compression (40-60% token savings typical) |
60
62
  | `reprompt distill` | Extract important turns from conversations with 6-signal scoring |
61
63
  | `reprompt distill --export` | Recover context when a session runs out -- paste into new session |
62
- | `reprompt lint` | Prompt quality linter with GitHub Action support |
64
+ | `reprompt lint` | Configurable prompt quality linter with CI/GitHub Action support |
65
+ | `reprompt init` | Generate `.reprompt.toml` config for your project |
63
66
 
64
67
  ### Manage
65
68
 
@@ -178,6 +181,24 @@ reprompt lint --strict # exit 1 on warnings
178
181
  reprompt lint --json # machine-readable output
179
182
  ```
180
183
 
184
+ #### Project configuration
185
+
186
+ ```bash
187
+ reprompt init # generates .reprompt.toml with all rules documented
188
+ ```
189
+
190
+ ```toml
191
+ # .reprompt.toml (or [tool.reprompt.lint] in pyproject.toml)
192
+ [lint]
193
+ score-threshold = 50 # fail if avg score < 50
194
+
195
+ [lint.rules]
196
+ min-length = 20 # error if prompt < 20 chars (0 = off)
197
+ short-prompt = 40 # warning if < 40 chars (0 = off)
198
+ vague-prompt = true # error on "fix it" etc (false = off)
199
+ debug-needs-reference = true
200
+ ```
201
+
181
202
  ## Privacy
182
203
 
183
204
  - All analysis runs locally. No prompts leave your machine.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "reprompt-cli"
3
- version = "1.9.0"
3
+ version = "1.10.0"
4
4
  description = "Discover, analyze, and optimize your prompts from AI coding sessions"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -1,6 +1,6 @@
1
1
  """reprompt — Discover, analyze, and evolve your best prompts from AI coding sessions."""
2
2
 
3
- __version__ = "1.9.0"
3
+ __version__ = "1.10.0"
4
4
 
5
5
  __all__ = [
6
6
  "__version__",
@@ -820,13 +820,19 @@ def lint(
820
820
  import json as json_mod
821
821
 
822
822
  from reprompt.config import Settings
823
- from reprompt.core.lint import format_lint_results, lint_prompts
823
+ from reprompt.core.lint import format_lint_results, lint_prompts, load_lint_config
824
824
  from reprompt.core.pipeline import get_adapters
825
825
  from reprompt.storage.db import PromptDB
826
826
 
827
827
  settings = Settings()
828
828
  db = PromptDB(settings.db_path)
829
829
 
830
+ # Load lint config from .reprompt.toml / pyproject.toml
831
+ lint_config = load_lint_config()
832
+
833
+ # CLI flags override config file
834
+ effective_threshold = score_threshold if score_threshold > 0 else lint_config.score_threshold
835
+
830
836
  # Collect prompts from DB (already scanned)
831
837
  rows = db.get_all_prompts()
832
838
  texts = [r["text"] for r in rows]
@@ -858,11 +864,11 @@ def lint(
858
864
  console.print("No prompts found. Run [bold]reprompt scan[/bold] first.")
859
865
  raise typer.Exit(0)
860
866
 
861
- violations = lint_prompts(texts)
867
+ violations = lint_prompts(texts, config=lint_config)
862
868
 
863
869
  # Score threshold mode (CI integration)
864
870
  score_data = None
865
- if score_threshold > 0:
871
+ if effective_threshold > 0:
866
872
  from reprompt.core.extractors import extract_features
867
873
  from reprompt.core.scorer import score_prompt
868
874
 
@@ -875,8 +881,8 @@ def lint(
875
881
  "avg_score": round(avg_score, 1),
876
882
  "min_score": min(scores) if scores else 0,
877
883
  "max_score": max(scores) if scores else 0,
878
- "threshold": score_threshold,
879
- "pass": avg_score >= score_threshold,
884
+ "threshold": effective_threshold,
885
+ "pass": avg_score >= effective_threshold,
880
886
  }
881
887
 
882
888
  if json_output:
@@ -905,7 +911,7 @@ def lint(
905
911
  console.print(
906
912
  f"\n Score: avg {score_data['avg_score']}/100"
907
913
  f" (min {score_data['min_score']}, max {score_data['max_score']})"
908
- f" — threshold {score_threshold} → {status}"
914
+ f" — threshold {effective_threshold} → {status}"
909
915
  )
910
916
 
911
917
  if copy:
@@ -1059,6 +1065,58 @@ def compress(
1059
1065
  _copy_to_clip(result.compressed, quiet=json_output)
1060
1066
 
1061
1067
 
1068
+ @app.command(rich_help_panel="Optimize")
1069
+ def rewrite(
1070
+ text: str = typer.Argument(..., help="Prompt text to improve"),
1071
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
1072
+ copy: bool = typer.Option(False, "--copy", help="Copy rewritten text to clipboard"),
1073
+ ) -> None:
1074
+ """Rewrite a prompt to improve its score. Rule-based, no LLM needed.
1075
+
1076
+ Applies 4 layers: filler removal, instruction front-loading,
1077
+ key requirement echo, and hedging cleanup. Also suggests manual
1078
+ improvements you can make.
1079
+
1080
+ Examples:
1081
+
1082
+ reprompt rewrite "I was wondering if you could fix the authentication bug"
1083
+
1084
+ reprompt rewrite "please help me refactor this code to be better" --copy
1085
+
1086
+ reprompt rewrite "fix the login" --json
1087
+ """
1088
+ from reprompt.core.rewrite import rewrite_prompt
1089
+
1090
+ result = rewrite_prompt(text)
1091
+
1092
+ if json_output:
1093
+ import json as json_mod
1094
+
1095
+ data = {
1096
+ "original": result.original,
1097
+ "rewritten": result.rewritten,
1098
+ "score_before": result.score_before,
1099
+ "score_after": result.score_after,
1100
+ "score_delta": result.score_delta,
1101
+ "changes": result.changes,
1102
+ "manual_suggestions": result.manual_suggestions,
1103
+ }
1104
+ typer.echo(json_mod.dumps(data, indent=2, ensure_ascii=False))
1105
+ else:
1106
+ from reprompt.output.rewrite_terminal import render_rewrite
1107
+
1108
+ typer.echo(render_rewrite(result))
1109
+
1110
+ if copy:
1111
+ _copy_to_clip(result.rewritten, quiet=json_output)
1112
+
1113
+ from reprompt.core.suggestions import get_suggestion
1114
+
1115
+ hint = get_suggestion("rewrite")
1116
+ if hint and not json_output:
1117
+ console.print(f" [dim]→ Try: {hint}[/dim]\n")
1118
+
1119
+
1062
1120
  @app.command(rich_help_panel="Optimize")
1063
1121
  def distill(
1064
1122
  session_id: str = typer.Argument(None, help="Session ID to distill"),
@@ -1929,6 +1987,65 @@ def extension_status() -> None:
1929
1987
  console.print(" Last sync: never")
1930
1988
 
1931
1989
 
1990
+ @app.command(rich_help_panel="Setup")
1991
+ def init(
1992
+ force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing config"),
1993
+ ) -> None:
1994
+ """Generate a .reprompt.toml config file in the current directory.
1995
+
1996
+ Creates a starter config with all lint rules documented and commented.
1997
+ Use this to customize reprompt for your project or CI pipeline.
1998
+
1999
+ Examples:
2000
+
2001
+ reprompt init # create .reprompt.toml
2002
+
2003
+ reprompt init --force # overwrite existing config
2004
+ """
2005
+ config_path = Path.cwd() / ".reprompt.toml"
2006
+
2007
+ if config_path.exists() and not force:
2008
+ console.print(
2009
+ f"[yellow]{config_path.name} already exists.[/yellow]"
2010
+ " Use [bold]--force[/bold] to overwrite."
2011
+ )
2012
+ raise typer.Exit(1)
2013
+
2014
+ config_content = """\
2015
+ # reprompt configuration
2016
+ # Docs: https://github.com/reprompt-dev/reprompt
2017
+ #
2018
+ # This file configures `reprompt lint` rules and CI thresholds.
2019
+ # Place in your project root — reprompt walks up from CWD to find it.
2020
+ # Alternatively, add [tool.reprompt.lint] to pyproject.toml.
2021
+
2022
+ [lint]
2023
+ # Fail `reprompt lint` if average prompt score < threshold (0 = disabled)
2024
+ # Useful for CI: reprompt lint --score-threshold reads this value
2025
+ # score-threshold = 50
2026
+
2027
+ [lint.rules]
2028
+ # min-length: error if prompt < N chars (0 = disabled)
2029
+ min-length = 20
2030
+
2031
+ # short-prompt: warning if prompt < N chars (0 = disabled)
2032
+ short-prompt = 40
2033
+
2034
+ # vague-prompt: error on vague prompts like "fix it" (false = disabled)
2035
+ vague-prompt = true
2036
+
2037
+ # debug-needs-reference: warning if debug prompt lacks file reference (false = disabled)
2038
+ debug-needs-reference = true
2039
+
2040
+ # file-extensions: extensions recognized as file references
2041
+ # file-extensions = [".py", ".ts", ".js", ".go", ".rs", ".java", ".rb", ".cpp", ".c"]
2042
+ """
2043
+
2044
+ config_path.write_text(config_content)
2045
+ console.print(f"[green]Created[/green] {config_path.name}")
2046
+ console.print(" Edit rules, then run [bold]reprompt lint[/bold] to verify.")
2047
+
2048
+
1932
2049
  @app.command(rich_help_panel="Analyze")
1933
2050
  def sessions(
1934
2051
  last: int = typer.Option(10, "--last", help="Show N most recent sessions"),
@@ -0,0 +1,242 @@
1
+ """Prompt quality linting rules.
2
+
3
+ Checks prompts against configurable quality rules and returns violations.
4
+ Designed for CI integration — each rule produces a severity + message.
5
+
6
+ Configuration loaded from (highest priority wins):
7
+ 1. CLI flags (--score-threshold, --strict)
8
+ 2. .reprompt.toml in CWD or parents
9
+ 3. [tool.reprompt.lint] in pyproject.toml
10
+ 4. Built-in defaults
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import sys
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+
19
+ if sys.version_info >= (3, 11):
20
+ import tomllib
21
+ else:
22
+ try:
23
+ import tomllib
24
+ except ModuleNotFoundError: # Python 3.10
25
+ import tomli as tomllib # type: ignore[no-redefine]
26
+
27
+
28
+ @dataclass
29
+ class LintViolation:
30
+ """A single lint rule violation."""
31
+
32
+ rule: str
33
+ severity: str # "error" | "warning"
34
+ message: str
35
+ prompt_text: str
36
+
37
+
38
+ @dataclass
39
+ class LintConfig:
40
+ """Configuration for lint rules. Loaded from .reprompt.toml or pyproject.toml."""
41
+
42
+ # Rule thresholds (0 = disabled)
43
+ min_length: int = 20
44
+ short_prompt: int = 40
45
+
46
+ # Boolean rules (False = disabled)
47
+ vague_prompt: bool = True
48
+ debug_needs_reference: bool = True
49
+
50
+ # File extensions for debug-needs-reference
51
+ file_extensions: list[str] = field(
52
+ default_factory=lambda: [".py", ".ts", ".js", ".go", ".rs", ".java", ".rb", ".cpp", ".c"]
53
+ )
54
+
55
+ # CI score threshold (0 = disabled, set via --score-threshold or config)
56
+ score_threshold: int = 0
57
+
58
+
59
+ # --- Default config ---
60
+ DEFAULT_CONFIG = LintConfig()
61
+
62
+ VAGUE_STARTERS = frozenset(
63
+ {"fix it", "fix this", "do it", "help me", "change it", "update it", "make it work"}
64
+ )
65
+
66
+
67
+ def load_lint_config(start_dir: Path | None = None) -> LintConfig:
68
+ """Load lint config from .reprompt.toml or pyproject.toml, walking up from start_dir.
69
+
70
+ Returns DEFAULT_CONFIG if no config file found.
71
+ """
72
+ if start_dir is None:
73
+ start_dir = Path.cwd()
74
+
75
+ # Walk up to find config files
76
+ current = start_dir.resolve()
77
+ for _ in range(20): # safety limit
78
+ # Check .reprompt.toml first (project-specific)
79
+ reprompt_toml = current / ".reprompt.toml"
80
+ if reprompt_toml.is_file():
81
+ return _parse_reprompt_toml(reprompt_toml)
82
+
83
+ # Check pyproject.toml
84
+ pyproject = current / "pyproject.toml"
85
+ if pyproject.is_file():
86
+ config = _parse_pyproject_toml(pyproject)
87
+ if config is not None:
88
+ return config
89
+
90
+ parent = current.parent
91
+ if parent == current:
92
+ break
93
+ current = parent
94
+
95
+ return LintConfig()
96
+
97
+
98
+ def _parse_reprompt_toml(path: Path) -> LintConfig:
99
+ """Parse a .reprompt.toml file."""
100
+ try:
101
+ with open(path, "rb") as f:
102
+ data = tomllib.load(f)
103
+ return _build_config(data.get("lint", {}))
104
+ except Exception:
105
+ return LintConfig()
106
+
107
+
108
+ def _parse_pyproject_toml(path: Path) -> LintConfig | None:
109
+ """Parse [tool.reprompt.lint] from pyproject.toml. Returns None if section missing."""
110
+ try:
111
+ with open(path, "rb") as f:
112
+ data = tomllib.load(f)
113
+ lint_section = data.get("tool", {}).get("reprompt", {}).get("lint")
114
+ if lint_section is None:
115
+ return None
116
+ return _build_config(lint_section)
117
+ except Exception:
118
+ return None
119
+
120
+
121
+ def _build_config(lint_data: dict) -> LintConfig:
122
+ """Build LintConfig from a parsed TOML dict."""
123
+ config = LintConfig()
124
+
125
+ # Top-level lint settings
126
+ if "score-threshold" in lint_data:
127
+ config.score_threshold = int(lint_data["score-threshold"])
128
+
129
+ # Rule settings
130
+ rules = lint_data.get("rules", {})
131
+
132
+ # min-length: int threshold or false
133
+ if "min-length" in rules:
134
+ val = rules["min-length"]
135
+ config.min_length = int(val) if val else 0
136
+
137
+ # short-prompt: int threshold or false
138
+ if "short-prompt" in rules:
139
+ val = rules["short-prompt"]
140
+ config.short_prompt = int(val) if val else 0
141
+
142
+ # vague-prompt: bool
143
+ if "vague-prompt" in rules:
144
+ config.vague_prompt = bool(rules["vague-prompt"])
145
+
146
+ # debug-needs-reference: bool
147
+ if "debug-needs-reference" in rules:
148
+ config.debug_needs_reference = bool(rules["debug-needs-reference"])
149
+
150
+ # file-extensions: list
151
+ if "file-extensions" in rules:
152
+ config.file_extensions = list(rules["file-extensions"])
153
+
154
+ return config
155
+
156
+
157
+ def lint_prompt(text: str, config: LintConfig | None = None) -> list[LintViolation]:
158
+ """Lint a single prompt against quality rules."""
159
+ if config is None:
160
+ config = DEFAULT_CONFIG
161
+
162
+ violations: list[LintViolation] = []
163
+ stripped = text.strip().lower()
164
+
165
+ # Rule 1: Too short
166
+ if config.min_length > 0 and len(stripped) < config.min_length:
167
+ violations.append(
168
+ LintViolation(
169
+ rule="min-length",
170
+ severity="error",
171
+ message=f"Prompt is too short ({len(stripped)} chars, minimum {config.min_length})",
172
+ prompt_text=text,
173
+ )
174
+ )
175
+ elif config.short_prompt > 0 and len(stripped) < config.short_prompt:
176
+ violations.append(
177
+ LintViolation(
178
+ rule="short-prompt",
179
+ severity="warning",
180
+ message=(f"Prompt is short ({len(stripped)} chars) — consider adding context"),
181
+ prompt_text=text,
182
+ )
183
+ )
184
+
185
+ # Rule 2: Vague starter
186
+ if config.vague_prompt and stripped in VAGUE_STARTERS:
187
+ violations.append(
188
+ LintViolation(
189
+ rule="vague-prompt",
190
+ severity="error",
191
+ message="Prompt is too vague — specify what to fix/change and where",
192
+ prompt_text=text,
193
+ )
194
+ )
195
+
196
+ # Rule 3: No file/function reference in debug prompts
197
+ if config.debug_needs_reference:
198
+ debug_words = {"fix", "debug", "bug", "error", "broken", "failing", "crash"}
199
+ has_debug_intent = any(w in stripped for w in debug_words)
200
+ indicators = list(config.file_extensions) + ["()", "line ", "file "]
201
+ has_reference = any(indicator in text for indicator in indicators)
202
+ min_len = config.min_length if config.min_length > 0 else 20
203
+ if has_debug_intent and not has_reference and len(stripped) >= min_len:
204
+ violations.append(
205
+ LintViolation(
206
+ rule="debug-needs-reference",
207
+ severity="warning",
208
+ message="Debug prompt lacks file/function reference — add specifics",
209
+ prompt_text=text,
210
+ )
211
+ )
212
+
213
+ return violations
214
+
215
+
216
+ def lint_prompts(texts: list[str], config: LintConfig | None = None) -> list[LintViolation]:
217
+ """Lint multiple prompts and return all violations."""
218
+ violations: list[LintViolation] = []
219
+ for text in texts:
220
+ violations.extend(lint_prompt(text, config))
221
+ return violations
222
+
223
+
224
+ def format_lint_results(violations: list[LintViolation], total_prompts: int) -> str:
225
+ """Format lint results for terminal/CI output."""
226
+ if not violations:
227
+ return f"✓ {total_prompts} prompts checked, no issues found"
228
+
229
+ errors = [v for v in violations if v.severity == "error"]
230
+ warnings = [v for v in violations if v.severity == "warning"]
231
+
232
+ lines: list[str] = []
233
+ lines.append(f"Checked {total_prompts} prompts\n")
234
+
235
+ for v in violations:
236
+ prefix = "✗" if v.severity == "error" else "!"
237
+ display = v.prompt_text[:60] + "..." if len(v.prompt_text) > 60 else v.prompt_text
238
+ lines.append(f' {prefix} [{v.rule}] "{display}"')
239
+ lines.append(f" {v.message}")
240
+
241
+ lines.append(f"\n{len(errors)} error(s), {len(warnings)} warning(s)")
242
+ return "\n".join(lines)