reprompt-cli 2.0.0__tar.gz → 2.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/PKG-INFO +1 -1
  2. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/pyproject.toml +1 -1
  3. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/__init__.py +1 -1
  4. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/cli.py +51 -0
  5. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/suggestions.py +1 -0
  6. reprompt_cli-2.0.1/src/reprompt/output/projects_terminal.py +101 -0
  7. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/storage/db.py +44 -0
  8. reprompt_cli-2.0.1/tests/test_projects.py +200 -0
  9. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_suggestions.py +1 -0
  10. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.editorconfig +0 -0
  11. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  14. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  15. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  16. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.github/dependabot.yml +0 -0
  17. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.github/workflows/ci.yml +0 -0
  18. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.github/workflows/publish.yml +0 -0
  19. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.gitignore +0 -0
  20. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.pre-commit-config.yaml +0 -0
  21. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.pre-commit-hooks.yaml +0 -0
  22. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.testmondata-shm +0 -0
  23. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/.testmondata-wal +0 -0
  24. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/CHANGELOG.md +0 -0
  25. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/CODE_OF_CONDUCT.md +0 -0
  26. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/CONTRIBUTING.md +0 -0
  27. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/LICENSE +0 -0
  28. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/README.md +0 -0
  29. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/SECURITY.md +0 -0
  30. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/Screenshot 2026-03-24 at 09.45.03.png +0 -0
  31. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/action.yml +0 -0
  32. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/demo.gif +0 -0
  33. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/brand-icon-128.png +0 -0
  34. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/brand-icon-16.png +0 -0
  35. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/brand-icon-256.png +0 -0
  36. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/brand-icon-32.png +0 -0
  37. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/brand-icon-48.png +0 -0
  38. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/brand-icon-512.png +0 -0
  39. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/brand-icon-96.png +0 -0
  40. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/brand-icon.svg +0 -0
  41. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-favicon-128.png +0 -0
  42. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-favicon-16.png +0 -0
  43. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-favicon-256.png +0 -0
  44. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-favicon-32.png +0 -0
  45. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-favicon-48.png +0 -0
  46. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-favicon-512.png +0 -0
  47. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-favicon-96.png +0 -0
  48. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-favicon.svg +0 -0
  49. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-icon-128.png +0 -0
  50. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-icon-16.png +0 -0
  51. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-icon-256.png +0 -0
  52. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-icon-32.png +0 -0
  53. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-icon-48.png +0 -0
  54. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-icon-512.png +0 -0
  55. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-icon-96.png +0 -0
  56. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/cli-icon.svg +0 -0
  57. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/favicon-128.png +0 -0
  58. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/favicon-16.png +0 -0
  59. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/favicon-256.png +0 -0
  60. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/favicon-32.png +0 -0
  61. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/favicon-48.png +0 -0
  62. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/favicon-512.png +0 -0
  63. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/favicon-96.png +0 -0
  64. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/favicon.svg +0 -0
  65. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/icons/generate.sh +0 -0
  66. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/superpowers/specs/2026-03-24-v14-command-consolidation-design.md +0 -0
  67. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/docs/superpowers/specs/2026-03-25-v1.5-dashboard-design.md +0 -0
  68. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/scripts/generate_demo_data.py +0 -0
  69. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/__init__.py +0 -0
  70. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/aider.py +0 -0
  71. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/base.py +0 -0
  72. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/chatgpt.py +0 -0
  73. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/claude_chat.py +0 -0
  74. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/claude_code.py +0 -0
  75. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/cline.py +0 -0
  76. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/codex.py +0 -0
  77. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/cursor.py +0 -0
  78. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/filters.py +0 -0
  79. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/gemini.py +0 -0
  80. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/adapters/openclaw.py +0 -0
  81. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/bridge/__init__.py +0 -0
  82. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/bridge/handler.py +0 -0
  83. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/bridge/host.py +0 -0
  84. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/bridge/manifest.py +0 -0
  85. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/bridge/protocol.py +0 -0
  86. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/commands/__init__.py +0 -0
  87. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/commands/telemetry.py +0 -0
  88. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/commands/wrapped.py +0 -0
  89. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/config.py +0 -0
  90. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/__init__.py +0 -0
  91. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/agent.py +0 -0
  92. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/analyzer.py +0 -0
  93. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/compress.py +0 -0
  94. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/conversation.py +0 -0
  95. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/cost.py +0 -0
  96. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/dashboard.py +0 -0
  97. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/dedup.py +0 -0
  98. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/digest.py +0 -0
  99. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/distill.py +0 -0
  100. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/effectiveness.py +0 -0
  101. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/extractors.py +0 -0
  102. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/extractors_zh.py +0 -0
  103. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/insights.py +0 -0
  104. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/lang_detect.py +0 -0
  105. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/library.py +0 -0
  106. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/lint.py +0 -0
  107. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/merge_view.py +0 -0
  108. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/models.py +0 -0
  109. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/persona.py +0 -0
  110. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/pipeline.py +0 -0
  111. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/privacy.py +0 -0
  112. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/privacy_scan.py +0 -0
  113. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/prompt_dna.py +0 -0
  114. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/recommend.py +0 -0
  115. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/repetition.py +0 -0
  116. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/rewrite.py +0 -0
  117. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/scorer.py +0 -0
  118. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/segmenter.py +0 -0
  119. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/session_meta.py +0 -0
  120. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/session_quality.py +0 -0
  121. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/session_type.py +0 -0
  122. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/style.py +0 -0
  123. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/templates.py +0 -0
  124. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/timeutil.py +0 -0
  125. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/trends.py +0 -0
  126. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/core/wrapped.py +0 -0
  127. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/demo.py +0 -0
  128. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/embeddings/__init__.py +0 -0
  129. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/embeddings/base.py +0 -0
  130. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/embeddings/local_embed.py +0 -0
  131. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/embeddings/ollama.py +0 -0
  132. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/embeddings/openai_embed.py +0 -0
  133. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/embeddings/tfidf.py +0 -0
  134. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/mcp.py +0 -0
  135. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/mcp_main.py +0 -0
  136. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/__init__.py +0 -0
  137. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/agent_terminal.py +0 -0
  138. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/chartjs.min.js +0 -0
  139. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/compress_terminal.py +0 -0
  140. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/dashboard_terminal.py +0 -0
  141. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/distill_terminal.py +0 -0
  142. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/export.py +0 -0
  143. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/html_report.py +0 -0
  144. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/json_out.py +0 -0
  145. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/markdown.py +0 -0
  146. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/repetition_terminal.py +0 -0
  147. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/rewrite_terminal.py +0 -0
  148. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/sessions_terminal.py +0 -0
  149. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/terminal.py +0 -0
  150. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/wrapped_html.py +0 -0
  151. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/output/wrapped_terminal.py +0 -0
  152. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/py.typed +0 -0
  153. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/sharing/__init__.py +0 -0
  154. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/sharing/client.py +0 -0
  155. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/sharing/clipboard.py +0 -0
  156. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/storage/__init__.py +0 -0
  157. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/telemetry/__init__.py +0 -0
  158. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/telemetry/collector.py +0 -0
  159. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/telemetry/consent.py +0 -0
  160. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/telemetry/events.py +0 -0
  161. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/telemetry/prompt.py +0 -0
  162. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/telemetry/queue.py +0 -0
  163. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/src/reprompt/telemetry/sender.py +0 -0
  164. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/__init__.py +0 -0
  165. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/conftest.py +0 -0
  166. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/fixtures/aider_chat_history.md +0 -0
  167. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/fixtures/chatgpt_conversations.json +0 -0
  168. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/fixtures/claude_chat_export.json +0 -0
  169. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/fixtures/claude_session.jsonl +0 -0
  170. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/fixtures/cline_task/api_conversation_history.json +0 -0
  171. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/fixtures/export/default_export.md +0 -0
  172. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/fixtures/export/full_export.md +0 -0
  173. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/fixtures/gemini_session.json +0 -0
  174. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/fixtures/openclaw_session.jsonl +0 -0
  175. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_adapter_aider.py +0 -0
  176. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_adapter_chatgpt.py +0 -0
  177. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_adapter_claude.py +0 -0
  178. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_adapter_claude_chat.py +0 -0
  179. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_adapter_cline.py +0 -0
  180. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_adapter_gemini.py +0 -0
  181. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_adapter_openclaw.py +0 -0
  182. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_agent.py +0 -0
  183. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_agent_cli.py +0 -0
  184. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_analyzer.py +0 -0
  185. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_bridge_cli.py +0 -0
  186. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_bridge_e2e.py +0 -0
  187. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_bridge_handler.py +0 -0
  188. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_bridge_integration.py +0 -0
  189. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_bridge_manifest.py +0 -0
  190. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_bridge_protocol.py +0 -0
  191. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_cli.py +0 -0
  192. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_cli_deprecations.py +0 -0
  193. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_cli_library_effectiveness.py +0 -0
  194. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_clipboard.py +0 -0
  195. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_codex_adapter.py +0 -0
  196. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_compare_best_worst.py +0 -0
  197. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_compress.py +0 -0
  198. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_compress_cli.py +0 -0
  199. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_compress_dna.py +0 -0
  200. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_compress_html.py +0 -0
  201. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_compress_insights.py +0 -0
  202. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_config.py +0 -0
  203. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_conversation.py +0 -0
  204. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_copy_flag.py +0 -0
  205. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_cost.py +0 -0
  206. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_coverage_boost.py +0 -0
  207. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_cursor_adapter.py +0 -0
  208. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_dashboard.py +0 -0
  209. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_db.py +0 -0
  210. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_db_digest.py +0 -0
  211. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_db_effectiveness.py +0 -0
  212. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_db_session_quality.py +0 -0
  213. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_db_trends.py +0 -0
  214. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_dedup.py +0 -0
  215. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_demo.py +0 -0
  216. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_deprecated_commands.py +0 -0
  217. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_digest.py +0 -0
  218. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_digest_cli.py +0 -0
  219. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_distill.py +0 -0
  220. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_distill_cli.py +0 -0
  221. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_distill_weights.py +0 -0
  222. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_e2e.py +0 -0
  223. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_effectiveness.py +0 -0
  224. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_embeddings_local.py +0 -0
  225. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_embeddings_ollama.py +0 -0
  226. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_embeddings_openai.py +0 -0
  227. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_empty_state.py +0 -0
  228. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_export.py +0 -0
  229. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_export_cli.py +0 -0
  230. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_export_snapshot.py +0 -0
  231. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_extractors.py +0 -0
  232. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_extractors_routing.py +0 -0
  233. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_extractors_zh.py +0 -0
  234. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_extractors_zh_e2e.py +0 -0
  235. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_html_report.py +0 -0
  236. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_import_cli.py +0 -0
  237. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_import_e2e.py +0 -0
  238. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_init_cli.py +0 -0
  239. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_insights.py +0 -0
  240. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_insights_cli.py +0 -0
  241. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_insights_expanded.py +0 -0
  242. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_install_hook.py +0 -0
  243. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_lang_detect.py +0 -0
  244. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_library.py +0 -0
  245. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_lint.py +0 -0
  246. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_lint_cli.py +0 -0
  247. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_markdown.py +0 -0
  248. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_mcp.py +0 -0
  249. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_merge_view.py +0 -0
  250. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_models.py +0 -0
  251. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_output.py +0 -0
  252. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_parse_conversation_base.py +0 -0
  253. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_parse_conversation_chatgpt.py +0 -0
  254. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_parse_conversation_claude.py +0 -0
  255. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_persona.py +0 -0
  256. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_pipeline.py +0 -0
  257. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_privacy.py +0 -0
  258. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_privacy_cli.py +0 -0
  259. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_privacy_e2e.py +0 -0
  260. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_privacy_output.py +0 -0
  261. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_privacy_scan.py +0 -0
  262. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_prompt_dna.py +0 -0
  263. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_public_api.py +0 -0
  264. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_recommend.py +0 -0
  265. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_repetition.py +0 -0
  266. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_repetition_cli.py +0 -0
  267. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_repetition_output.py +0 -0
  268. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_rewrite.py +0 -0
  269. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_schema_version.py +0 -0
  270. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_score_cli.py +0 -0
  271. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_scorer.py +0 -0
  272. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_segmenter.py +0 -0
  273. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_session_quality.py +0 -0
  274. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_session_type.py +0 -0
  275. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_sessions_cli.py +0 -0
  276. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_sessions_output.py +0 -0
  277. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_share_e2e.py +0 -0
  278. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_sharing_client.py +0 -0
  279. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_source_filter.py +0 -0
  280. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_style.py +0 -0
  281. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_style_trends.py +0 -0
  282. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_telemetry_cli.py +0 -0
  283. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_telemetry_collector.py +0 -0
  284. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_telemetry_consent.py +0 -0
  285. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_telemetry_e2e.py +0 -0
  286. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_telemetry_events.py +0 -0
  287. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_telemetry_prompt.py +0 -0
  288. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_telemetry_queue.py +0 -0
  289. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_telemetry_sender.py +0 -0
  290. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_template_cli.py +0 -0
  291. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_templates.py +0 -0
  292. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_timeutil.py +0 -0
  293. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_trends.py +0 -0
  294. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_trends_cli.py +0 -0
  295. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_use_cli.py +0 -0
  296. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_wrapped.py +0 -0
  297. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_wrapped_cli.py +0 -0
  298. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_wrapped_e2e.py +0 -0
  299. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_wrapped_html.py +0 -0
  300. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_wrapped_output.py +0 -0
  301. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/tests/test_wrapped_share.py +0 -0
  302. {reprompt_cli-2.0.0 → reprompt_cli-2.0.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reprompt-cli
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: Discover, analyze, and optimize your prompts from AI coding sessions
5
5
  Project-URL: Homepage, https://github.com/reprompt-dev/reprompt
6
6
  Project-URL: Repository, https://github.com/reprompt-dev/reprompt
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "reprompt-cli"
3
- version = "2.0.0"
3
+ version = "2.0.1"
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__ = "2.0.0"
3
+ __version__ = "2.0.1"
4
4
 
5
5
  __all__ = [
6
6
  "__version__",
@@ -2046,6 +2046,57 @@ debug-needs-reference = true
2046
2046
  console.print(" Edit rules, then run [bold]reprompt lint[/bold] to verify.")
2047
2047
 
2048
2048
 
2049
+ @app.command(rich_help_panel="Analyze")
2050
+ def projects(
2051
+ source: str = typer.Option(
2052
+ None, "--source", "-s", help="Filter by source (e.g. claude-code, cursor)"
2053
+ ),
2054
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
2055
+ copy: bool = typer.Option(False, "--copy", help="Copy result to clipboard"),
2056
+ ) -> None:
2057
+ """Compare prompt quality across projects.
2058
+
2059
+ Shows per-project breakdown: sessions, prompts, quality scores,
2060
+ efficiency, focus, and frustration signals.
2061
+
2062
+ Examples:
2063
+
2064
+ reprompt projects # all projects
2065
+
2066
+ reprompt projects --source claude-code # filter by source
2067
+
2068
+ reprompt projects --json # machine-readable
2069
+ """
2070
+ import json as json_mod
2071
+
2072
+ from reprompt.config import Settings
2073
+ from reprompt.output.projects_terminal import render_projects_table
2074
+ from reprompt.storage.db import PromptDB
2075
+
2076
+ settings = Settings()
2077
+ db = PromptDB(settings.db_path)
2078
+
2079
+ project_data = db.get_project_summary(source=source)
2080
+
2081
+ if json_output:
2082
+ print(json_mod.dumps(project_data, indent=2, default=str))
2083
+ else:
2084
+ output = render_projects_table(project_data)
2085
+ print(output)
2086
+
2087
+ if copy:
2088
+ if json_output:
2089
+ _copy_to_clip(json_mod.dumps(project_data, indent=2, default=str), quiet=True)
2090
+ else:
2091
+ _copy_to_clip(output)
2092
+
2093
+ from reprompt.core.suggestions import get_suggestion
2094
+
2095
+ hint = get_suggestion("projects")
2096
+ if hint and not json_output:
2097
+ console.print(f" [dim]→ Try: {hint}[/dim]\n")
2098
+
2099
+
2049
2100
  @app.command(rich_help_panel="Analyze")
2050
2101
  def sessions(
2051
2102
  last: int = typer.Option(10, "--last", help="Show N most recent sessions"),
@@ -24,6 +24,7 @@ SUGGESTIONS: dict[str, str] = {
24
24
  "template": "reprompt insights (see which patterns work best)",
25
25
  "lint": "reprompt init (generate .reprompt.toml) · reprompt rewrite (improve prompts)",
26
26
  "rewrite": "reprompt compress (reduce tokens) · reprompt score (verify improvement)",
27
+ "projects": "reprompt sessions --detail <id> (deep-dive) · reprompt insights (patterns)",
27
28
  }
28
29
 
29
30
 
@@ -0,0 +1,101 @@
1
+ """Rich terminal output for projects command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from io import StringIO
6
+ from typing import Any
7
+
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+
12
+
13
+ def _score_style(score: float | None) -> str:
14
+ if score is None:
15
+ return "dim"
16
+ if score >= 80:
17
+ return "bold green"
18
+ if score >= 60:
19
+ return "green"
20
+ if score >= 40:
21
+ return "yellow"
22
+ return "red"
23
+
24
+
25
+ def render_projects_table(projects: list[dict[str, Any]]) -> str:
26
+ """Render project quality summary as a Rich table."""
27
+ buf = StringIO()
28
+ console = Console(file=buf, width=110, record=True)
29
+
30
+ if not projects:
31
+ console.print("\n [dim]No project data. Run [bold]reprompt scan[/bold] first.[/dim]\n")
32
+ return buf.getvalue()
33
+
34
+ # Header stats
35
+ total_sessions = sum(p.get("session_count", 0) for p in projects)
36
+ total_prompts = sum(p.get("prompt_count", 0) or 0 for p in projects)
37
+ quality_scores = [p["avg_quality"] for p in projects if p.get("avg_quality") is not None]
38
+ avg_quality = round(sum(quality_scores) / len(quality_scores), 1) if quality_scores else 0
39
+
40
+ console.print(
41
+ Panel(
42
+ f" [bold]{len(projects)}[/bold] projects · "
43
+ f"[bold]{total_sessions}[/bold] sessions · "
44
+ f"[bold]{total_prompts}[/bold] prompts · "
45
+ f"avg quality [bold]{avg_quality}[/bold]/100",
46
+ title="[bold]Project Quality[/bold]",
47
+ border_style="blue",
48
+ )
49
+ )
50
+
51
+ # Table
52
+ table = Table(show_header=True, header_style="bold", padding=(0, 1), expand=True)
53
+ table.add_column("Project", style="bold", min_width=12)
54
+ table.add_column("Sessions", justify="right", width=8)
55
+ table.add_column("Prompts", justify="right", width=8)
56
+ table.add_column("Quality", justify="right", width=8)
57
+ table.add_column("Efficiency", justify="right", width=10)
58
+ table.add_column("Focus", justify="right", width=7)
59
+ table.add_column("Frustration", justify="right", width=11)
60
+ table.add_column("Source", width=14)
61
+
62
+ for p in projects:
63
+ quality = p.get("avg_quality")
64
+ efficiency = p.get("avg_efficiency")
65
+ focus = p.get("avg_focus")
66
+
67
+ abandon = p.get("abandonment_count", 0) or 0
68
+ escalate = p.get("escalation_count", 0) or 0
69
+ frustration_total = abandon + escalate
70
+ sessions = p.get("session_count", 0)
71
+ frust_pct = round(frustration_total / sessions * 100) if sessions > 0 else 0
72
+
73
+ q_str = f"[{_score_style(quality)}]{quality:.0f}[/]" if quality else "[dim]--[/dim]"
74
+ e_str = (
75
+ f"[{_score_style(efficiency)}]{efficiency:.0f}[/]" if efficiency else "[dim]--[/dim]"
76
+ )
77
+ f_str = f"[{_score_style(focus)}]{focus:.0f}[/]" if focus else "[dim]--[/dim]"
78
+
79
+ frust_style = "red" if frust_pct > 30 else "yellow" if frust_pct > 15 else "dim"
80
+ frust_str = f"[{frust_style}]{frust_pct}%[/]" if frustration_total > 0 else "[dim]--[/dim]"
81
+
82
+ sources = p.get("sources", "")
83
+ # Shorten source names
84
+ short_sources = (
85
+ sources.replace("claude-code", "claude").replace("-ext", "") if sources else ""
86
+ )
87
+
88
+ table.add_row(
89
+ p.get("project", "unknown"),
90
+ str(sessions),
91
+ str(p.get("prompt_count", 0) or 0),
92
+ q_str,
93
+ e_str,
94
+ f_str,
95
+ frust_str,
96
+ short_sources,
97
+ )
98
+
99
+ console.print(table)
100
+ console.print()
101
+ return buf.getvalue()
@@ -809,6 +809,50 @@ class PromptDB:
809
809
  finally:
810
810
  conn.close()
811
811
 
812
+ def get_project_summary(self, source: str | None = None) -> list[dict[str, Any]]:
813
+ """Return per-project quality summary aggregated from session_meta.
814
+
815
+ Returns list of dicts with: project, session_count, prompt_count,
816
+ avg_quality, avg_efficiency, avg_focus, avg_outcome, abandonment_count,
817
+ escalation_count, sources, earliest, latest.
818
+ """
819
+ conn = self._conn()
820
+ try:
821
+ base_query = """
822
+ SELECT
823
+ COALESCE(sm.project, 'unknown') as project,
824
+ COUNT(DISTINCT sm.session_id) as session_count,
825
+ SUM(sm.prompt_count) as prompt_count,
826
+ ROUND(AVG(CASE WHEN sm.quality_score IS NOT NULL
827
+ THEN sm.quality_score END), 1) as avg_quality,
828
+ ROUND(AVG(CASE WHEN sm.efficiency_score IS NOT NULL
829
+ THEN sm.efficiency_score END), 1) as avg_efficiency,
830
+ ROUND(AVG(CASE WHEN sm.focus_score IS NOT NULL
831
+ THEN sm.focus_score END), 1) as avg_focus,
832
+ ROUND(AVG(CASE WHEN sm.outcome_score IS NOT NULL
833
+ THEN sm.outcome_score END), 1) as avg_outcome,
834
+ SUM(COALESCE(sm.has_abandonment, 0)) as abandonment_count,
835
+ SUM(COALESCE(sm.has_escalation, 0)) as escalation_count,
836
+ GROUP_CONCAT(DISTINCT sm.source) as sources,
837
+ MIN(sm.start_time) as earliest,
838
+ MAX(sm.end_time) as latest
839
+ FROM session_meta sm
840
+ WHERE sm.project IS NOT NULL AND sm.project != ''
841
+ """
842
+ if source:
843
+ base_query += " AND sm.source = ?"
844
+ rows = conn.execute(
845
+ base_query + " GROUP BY sm.project ORDER BY session_count DESC",
846
+ (source,),
847
+ ).fetchall()
848
+ else:
849
+ rows = conn.execute(
850
+ base_query + " GROUP BY sm.project ORDER BY session_count DESC"
851
+ ).fetchall()
852
+ return [dict(r) for r in rows]
853
+ finally:
854
+ conn.close()
855
+
812
856
  def get_effectiveness_for_session(self, session_id: str) -> float | None:
813
857
  """Return effectiveness_score for a session, or None if not found."""
814
858
  conn = self._conn()
@@ -0,0 +1,200 @@
1
+ """Tests for reprompt projects command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from reprompt.output.projects_terminal import render_projects_table
8
+ from reprompt.storage.db import PromptDB
9
+
10
+
11
+ def _meta(db: PromptDB, session_id: str, project: str, source: str = "claude-code", **kw):
12
+ """Helper to insert session_meta with required fields."""
13
+ db.upsert_session_meta(
14
+ session_id=session_id,
15
+ source=source,
16
+ project=project,
17
+ start_time=kw.get("start_time", "2026-03-15T10:00:00Z"),
18
+ end_time=kw.get("end_time", "2026-03-15T11:00:00Z"),
19
+ duration_seconds=kw.get("duration_seconds", 3600),
20
+ prompt_count=kw.get("prompt_count", 10),
21
+ tool_call_count=kw.get("tool_call_count", 5),
22
+ error_count=kw.get("error_count", 0),
23
+ final_status=kw.get("final_status", "completed"),
24
+ avg_prompt_length=kw.get("avg_prompt_length", 50.0),
25
+ effectiveness_score=kw.get("effectiveness_score", 0.7),
26
+ )
27
+
28
+
29
+ class TestGetProjectSummary:
30
+ def test_empty_db(self, tmp_path: Path) -> None:
31
+ db = PromptDB(tmp_path / "test.db")
32
+ assert db.get_project_summary() == []
33
+
34
+ def test_single_project(self, tmp_path: Path) -> None:
35
+ db = PromptDB(tmp_path / "test.db")
36
+ _meta(db, "s1", "myproject", prompt_count=5)
37
+ result = db.get_project_summary()
38
+ assert len(result) == 1
39
+ assert result[0]["project"] == "myproject"
40
+ assert result[0]["session_count"] == 1
41
+ assert result[0]["prompt_count"] == 5
42
+
43
+ def test_multiple_projects(self, tmp_path: Path) -> None:
44
+ db = PromptDB(tmp_path / "test.db")
45
+ for i, proj in enumerate(["alpha", "beta", "gamma"]):
46
+ for j in range(i + 1):
47
+ _meta(db, f"{proj}-s{j}", proj)
48
+ result = db.get_project_summary()
49
+ assert len(result) == 3
50
+ assert result[0]["project"] == "gamma"
51
+ assert result[0]["session_count"] == 3
52
+
53
+ def test_source_filter(self, tmp_path: Path) -> None:
54
+ db = PromptDB(tmp_path / "test.db")
55
+ _meta(db, "s1", "proj1", source="claude-code")
56
+ _meta(db, "s2", "proj2", source="cursor")
57
+ result = db.get_project_summary(source="claude-code")
58
+ assert len(result) == 1
59
+ assert result[0]["project"] == "proj1"
60
+
61
+ def test_quality_scores(self, tmp_path: Path) -> None:
62
+ db = PromptDB(tmp_path / "test.db")
63
+ _meta(db, "s1", "myproject")
64
+ db.upsert_session_quality(
65
+ session_id="s1",
66
+ quality_score=75.0,
67
+ prompt_quality_score=70.0,
68
+ efficiency_score=80.0,
69
+ focus_score=85.0,
70
+ outcome_score=65.0,
71
+ has_abandonment=False,
72
+ has_escalation=False,
73
+ stall_turns=0,
74
+ session_type="feature-dev",
75
+ quality_insight="Good",
76
+ )
77
+ result = db.get_project_summary()
78
+ assert len(result) == 1
79
+ assert result[0]["avg_quality"] == 75.0
80
+ assert result[0]["avg_efficiency"] == 80.0
81
+
82
+ def test_frustration_counts(self, tmp_path: Path) -> None:
83
+ db = PromptDB(tmp_path / "test.db")
84
+ for i in range(3):
85
+ _meta(db, f"s{i}", "troubled", error_count=3)
86
+ db.upsert_session_quality(
87
+ session_id=f"s{i}",
88
+ quality_score=40.0,
89
+ prompt_quality_score=35.0,
90
+ efficiency_score=30.0,
91
+ focus_score=45.0,
92
+ outcome_score=40.0,
93
+ has_abandonment=i < 2,
94
+ has_escalation=i == 0,
95
+ stall_turns=2,
96
+ session_type="debug",
97
+ quality_insight="Frustrating",
98
+ )
99
+ result = db.get_project_summary()
100
+ assert result[0]["abandonment_count"] == 2
101
+ assert result[0]["escalation_count"] == 1
102
+
103
+ def test_excludes_empty_project(self, tmp_path: Path) -> None:
104
+ db = PromptDB(tmp_path / "test.db")
105
+ _meta(db, "s1", "")
106
+ _meta(db, "s2", "real-project")
107
+ result = db.get_project_summary()
108
+ assert len(result) == 1
109
+ assert result[0]["project"] == "real-project"
110
+
111
+
112
+ class TestRenderProjectsTable:
113
+ def test_empty_projects(self) -> None:
114
+ output = render_projects_table([])
115
+ assert "No project data" in output
116
+
117
+ def test_renders_table(self) -> None:
118
+ data = [
119
+ {
120
+ "project": "myproject",
121
+ "session_count": 10,
122
+ "prompt_count": 50,
123
+ "avg_quality": 72.0,
124
+ "avg_efficiency": 65.0,
125
+ "avg_focus": 80.0,
126
+ "avg_outcome": None,
127
+ "abandonment_count": 1,
128
+ "escalation_count": 0,
129
+ "sources": "claude-code",
130
+ "earliest": None,
131
+ "latest": None,
132
+ }
133
+ ]
134
+ output = render_projects_table(data)
135
+ assert "myproject" in output
136
+ assert "72" in output
137
+
138
+ def test_multiple_projects(self) -> None:
139
+ data = [
140
+ {
141
+ "project": "alpha",
142
+ "session_count": 5,
143
+ "prompt_count": 20,
144
+ "avg_quality": 80.0,
145
+ "avg_efficiency": 75.0,
146
+ "avg_focus": 85.0,
147
+ "avg_outcome": None,
148
+ "abandonment_count": 0,
149
+ "escalation_count": 0,
150
+ "sources": "claude-code",
151
+ "earliest": None,
152
+ "latest": None,
153
+ },
154
+ {
155
+ "project": "beta",
156
+ "session_count": 3,
157
+ "prompt_count": 15,
158
+ "avg_quality": 45.0,
159
+ "avg_efficiency": 40.0,
160
+ "avg_focus": 50.0,
161
+ "avg_outcome": None,
162
+ "abandonment_count": 2,
163
+ "escalation_count": 1,
164
+ "sources": "cursor",
165
+ "earliest": None,
166
+ "latest": None,
167
+ },
168
+ ]
169
+ output = render_projects_table(data)
170
+ assert "alpha" in output
171
+ assert "beta" in output
172
+ assert "2 projects" in output
173
+
174
+
175
+ class TestProjectsCLI:
176
+ def test_help(self) -> None:
177
+ from typer.testing import CliRunner
178
+
179
+ from reprompt.cli import app
180
+
181
+ runner = CliRunner()
182
+ result = runner.invoke(app, ["projects", "--help"])
183
+ assert result.exit_code == 0
184
+ assert "project" in result.output.lower()
185
+
186
+ def test_empty_json(self, tmp_path: Path) -> None:
187
+ import json
188
+ import os
189
+
190
+ from typer.testing import CliRunner
191
+
192
+ from reprompt.cli import app
193
+
194
+ os.environ["REPROMPT_DB_PATH"] = str(tmp_path / "test.db")
195
+ runner = CliRunner()
196
+ result = runner.invoke(app, ["projects", "--json"])
197
+ del os.environ["REPROMPT_DB_PATH"]
198
+ assert result.exit_code == 0
199
+ data = json.loads(result.output)
200
+ assert data == []
@@ -19,6 +19,7 @@ class TestGetSuggestion:
19
19
  "template",
20
20
  "lint",
21
21
  "rewrite",
22
+ "projects",
22
23
  }
23
24
  assert set(SUGGESTIONS.keys()) == expected
24
25
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes