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