rbtr 2026.3.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 (199) hide show
  1. rbtr-2026.3.0/PKG-INFO +1175 -0
  2. rbtr-2026.3.0/README.md +1126 -0
  3. rbtr-2026.3.0/pyproject.toml +236 -0
  4. rbtr-2026.3.0/src/rbtr/__init__.py +1 -0
  5. rbtr-2026.3.0/src/rbtr/cli.py +59 -0
  6. rbtr-2026.3.0/src/rbtr/config.py +472 -0
  7. rbtr-2026.3.0/src/rbtr/creds.py +83 -0
  8. rbtr-2026.3.0/src/rbtr/engine/__init__.py +1 -0
  9. rbtr-2026.3.0/src/rbtr/engine/connect_cmd.py +303 -0
  10. rbtr-2026.3.0/src/rbtr/engine/core.py +372 -0
  11. rbtr-2026.3.0/src/rbtr/engine/draft_cmd.py +235 -0
  12. rbtr-2026.3.0/src/rbtr/engine/index_cmd.py +508 -0
  13. rbtr-2026.3.0/src/rbtr/engine/indexing.py +160 -0
  14. rbtr-2026.3.0/src/rbtr/engine/memory_cmd.py +118 -0
  15. rbtr-2026.3.0/src/rbtr/engine/model_cmd.py +178 -0
  16. rbtr-2026.3.0/src/rbtr/engine/publish.py +642 -0
  17. rbtr-2026.3.0/src/rbtr/engine/reload_cmd.py +74 -0
  18. rbtr-2026.3.0/src/rbtr/engine/review_cmd.py +345 -0
  19. rbtr-2026.3.0/src/rbtr/engine/session_cmd.py +381 -0
  20. rbtr-2026.3.0/src/rbtr/engine/setup.py +122 -0
  21. rbtr-2026.3.0/src/rbtr/engine/shell.py +81 -0
  22. rbtr-2026.3.0/src/rbtr/engine/skill_cmd.py +75 -0
  23. rbtr-2026.3.0/src/rbtr/engine/stats_cmd.py +340 -0
  24. rbtr-2026.3.0/src/rbtr/engine/types.py +72 -0
  25. rbtr-2026.3.0/src/rbtr/events.py +300 -0
  26. rbtr-2026.3.0/src/rbtr/exceptions.py +13 -0
  27. rbtr-2026.3.0/src/rbtr/git/__init__.py +41 -0
  28. rbtr-2026.3.0/src/rbtr/git/filters.py +57 -0
  29. rbtr-2026.3.0/src/rbtr/git/objects.py +588 -0
  30. rbtr-2026.3.0/src/rbtr/git/repo.py +125 -0
  31. rbtr-2026.3.0/src/rbtr/github/__init__.py +1 -0
  32. rbtr-2026.3.0/src/rbtr/github/auth.py +80 -0
  33. rbtr-2026.3.0/src/rbtr/github/client.py +648 -0
  34. rbtr-2026.3.0/src/rbtr/github/draft.py +474 -0
  35. rbtr-2026.3.0/src/rbtr/index/__init__.py +1 -0
  36. rbtr-2026.3.0/src/rbtr/index/arrow.py +125 -0
  37. rbtr-2026.3.0/src/rbtr/index/chunks.py +116 -0
  38. rbtr-2026.3.0/src/rbtr/index/edges.py +495 -0
  39. rbtr-2026.3.0/src/rbtr/index/embeddings.py +221 -0
  40. rbtr-2026.3.0/src/rbtr/index/languages.py +49 -0
  41. rbtr-2026.3.0/src/rbtr/index/models.py +162 -0
  42. rbtr-2026.3.0/src/rbtr/index/orchestrator.py +487 -0
  43. rbtr-2026.3.0/src/rbtr/index/search.py +381 -0
  44. rbtr-2026.3.0/src/rbtr/index/sql/clear_embeddings.sql +3 -0
  45. rbtr-2026.3.0/src/rbtr/index/sql/count_orphan_chunks.sql +9 -0
  46. rbtr-2026.3.0/src/rbtr/index/sql/delete_edges.sql +2 -0
  47. rbtr-2026.3.0/src/rbtr/index/sql/delete_snapshots.sql +2 -0
  48. rbtr-2026.3.0/src/rbtr/index/sql/diff_added.sql +24 -0
  49. rbtr-2026.3.0/src/rbtr/index/sql/diff_modified.sql +23 -0
  50. rbtr-2026.3.0/src/rbtr/index/sql/get_chunks.sql +25 -0
  51. rbtr-2026.3.0/src/rbtr/index/sql/get_edges.sql +10 -0
  52. rbtr-2026.3.0/src/rbtr/index/sql/has_blob.sql +4 -0
  53. rbtr-2026.3.0/src/rbtr/index/sql/inbound_degree.sql +8 -0
  54. rbtr-2026.3.0/src/rbtr/index/sql/insert_edges.sql +7 -0
  55. rbtr-2026.3.0/src/rbtr/index/sql/insert_snapshot.sql +1 -0
  56. rbtr-2026.3.0/src/rbtr/index/sql/prune_chunks.sql +8 -0
  57. rbtr-2026.3.0/src/rbtr/index/sql/prune_edges.sql +3 -0
  58. rbtr-2026.3.0/src/rbtr/index/sql/schema.sql +43 -0
  59. rbtr-2026.3.0/src/rbtr/index/sql/search_by_name.sql +23 -0
  60. rbtr-2026.3.0/src/rbtr/index/sql/search_fulltext.sql +31 -0
  61. rbtr-2026.3.0/src/rbtr/index/sql/search_similar.sql +25 -0
  62. rbtr-2026.3.0/src/rbtr/index/sql/update_embedding.sql +3 -0
  63. rbtr-2026.3.0/src/rbtr/index/sql/update_embeddings.sql +4 -0
  64. rbtr-2026.3.0/src/rbtr/index/sql/upsert_chunks.sql +28 -0
  65. rbtr-2026.3.0/src/rbtr/index/sql/upsert_snapshots.sql +6 -0
  66. rbtr-2026.3.0/src/rbtr/index/store.py +748 -0
  67. rbtr-2026.3.0/src/rbtr/index/tokenise.py +77 -0
  68. rbtr-2026.3.0/src/rbtr/index/treesitter.py +176 -0
  69. rbtr-2026.3.0/src/rbtr/llm/__init__.py +1 -0
  70. rbtr-2026.3.0/src/rbtr/llm/agent.py +123 -0
  71. rbtr-2026.3.0/src/rbtr/llm/compact.py +311 -0
  72. rbtr-2026.3.0/src/rbtr/llm/context.py +63 -0
  73. rbtr-2026.3.0/src/rbtr/llm/costs.py +86 -0
  74. rbtr-2026.3.0/src/rbtr/llm/deps.py +26 -0
  75. rbtr-2026.3.0/src/rbtr/llm/errors.py +53 -0
  76. rbtr-2026.3.0/src/rbtr/llm/memory.py +608 -0
  77. rbtr-2026.3.0/src/rbtr/llm/operational_prompts.py +46 -0
  78. rbtr-2026.3.0/src/rbtr/llm/stream.py +1049 -0
  79. rbtr-2026.3.0/src/rbtr/llm/tools/__init__.py +6 -0
  80. rbtr-2026.3.0/src/rbtr/llm/tools/common.py +234 -0
  81. rbtr-2026.3.0/src/rbtr/llm/tools/discussion.py +99 -0
  82. rbtr-2026.3.0/src/rbtr/llm/tools/draft.py +332 -0
  83. rbtr-2026.3.0/src/rbtr/llm/tools/file.py +547 -0
  84. rbtr-2026.3.0/src/rbtr/llm/tools/git.py +188 -0
  85. rbtr-2026.3.0/src/rbtr/llm/tools/index.py +329 -0
  86. rbtr-2026.3.0/src/rbtr/llm/tools/memory.py +80 -0
  87. rbtr-2026.3.0/src/rbtr/llm/tools/notes.py +70 -0
  88. rbtr-2026.3.0/src/rbtr/llm/tools/shell.py +138 -0
  89. rbtr-2026.3.0/src/rbtr/llm/types.py +16 -0
  90. rbtr-2026.3.0/src/rbtr/log.py +69 -0
  91. rbtr-2026.3.0/src/rbtr/models.py +266 -0
  92. rbtr-2026.3.0/src/rbtr/oauth.py +329 -0
  93. rbtr-2026.3.0/src/rbtr/plugins/README.md +454 -0
  94. rbtr-2026.3.0/src/rbtr/plugins/__init__.py +6 -0
  95. rbtr-2026.3.0/src/rbtr/plugins/bash.py +58 -0
  96. rbtr-2026.3.0/src/rbtr/plugins/c.py +80 -0
  97. rbtr-2026.3.0/src/rbtr/plugins/cpp.py +79 -0
  98. rbtr-2026.3.0/src/rbtr/plugins/defaults.py +119 -0
  99. rbtr-2026.3.0/src/rbtr/plugins/go.py +134 -0
  100. rbtr-2026.3.0/src/rbtr/plugins/hookspec.py +394 -0
  101. rbtr-2026.3.0/src/rbtr/plugins/java.py +107 -0
  102. rbtr-2026.3.0/src/rbtr/plugins/javascript.py +192 -0
  103. rbtr-2026.3.0/src/rbtr/plugins/manager.py +371 -0
  104. rbtr-2026.3.0/src/rbtr/plugins/python.py +143 -0
  105. rbtr-2026.3.0/src/rbtr/plugins/ruby.py +95 -0
  106. rbtr-2026.3.0/src/rbtr/plugins/rust.py +177 -0
  107. rbtr-2026.3.0/src/rbtr/prompts/.rumdl.toml +2 -0
  108. rbtr-2026.3.0/src/rbtr/prompts/__init__.py +222 -0
  109. rbtr-2026.3.0/src/rbtr/prompts/compact.md +12 -0
  110. rbtr-2026.3.0/src/rbtr/prompts/index_status.md +20 -0
  111. rbtr-2026.3.0/src/rbtr/prompts/memory_existing_facts.md +9 -0
  112. rbtr-2026.3.0/src/rbtr/prompts/memory_extract.md +59 -0
  113. rbtr-2026.3.0/src/rbtr/prompts/review.md +108 -0
  114. rbtr-2026.3.0/src/rbtr/prompts/skills.md +16 -0
  115. rbtr-2026.3.0/src/rbtr/prompts/system.md +34 -0
  116. rbtr-2026.3.0/src/rbtr/providers/__init__.py +113 -0
  117. rbtr-2026.3.0/src/rbtr/providers/claude.py +294 -0
  118. rbtr-2026.3.0/src/rbtr/providers/endpoint.py +257 -0
  119. rbtr-2026.3.0/src/rbtr/providers/fireworks.py +71 -0
  120. rbtr-2026.3.0/src/rbtr/providers/google.py +474 -0
  121. rbtr-2026.3.0/src/rbtr/providers/openai.py +71 -0
  122. rbtr-2026.3.0/src/rbtr/providers/openai_codex.py +356 -0
  123. rbtr-2026.3.0/src/rbtr/providers/openrouter.py +76 -0
  124. rbtr-2026.3.0/src/rbtr/providers/shared.py +74 -0
  125. rbtr-2026.3.0/src/rbtr/providers/types.py +49 -0
  126. rbtr-2026.3.0/src/rbtr/scripts/__init__.py +0 -0
  127. rbtr-2026.3.0/src/rbtr/scripts/bash_complete.sh +88 -0
  128. rbtr-2026.3.0/src/rbtr/sessions/__init__.py +1 -0
  129. rbtr-2026.3.0/src/rbtr/sessions/history.py +946 -0
  130. rbtr-2026.3.0/src/rbtr/sessions/incidents.py +186 -0
  131. rbtr-2026.3.0/src/rbtr/sessions/kinds.py +181 -0
  132. rbtr-2026.3.0/src/rbtr/sessions/migrations.py +77 -0
  133. rbtr-2026.3.0/src/rbtr/sessions/overhead.py +54 -0
  134. rbtr-2026.3.0/src/rbtr/sessions/scrub.py +42 -0
  135. rbtr-2026.3.0/src/rbtr/sessions/serialise.py +380 -0
  136. rbtr-2026.3.0/src/rbtr/sessions/sql/.sqlfluff +2 -0
  137. rbtr-2026.3.0/src/rbtr/sessions/sql/compact_mark_message.sql +8 -0
  138. rbtr-2026.3.0/src/rbtr/sessions/sql/complete_message.sql +12 -0
  139. rbtr-2026.3.0/src/rbtr/sessions/sql/delete_fact.sql +2 -0
  140. rbtr-2026.3.0/src/rbtr/sessions/sql/delete_message.sql +6 -0
  141. rbtr-2026.3.0/src/rbtr/sessions/sql/delete_old_facts.sql +2 -0
  142. rbtr-2026.3.0/src/rbtr/sessions/sql/delete_old_sessions.sql +7 -0
  143. rbtr-2026.3.0/src/rbtr/sessions/sql/delete_session.sql +2 -0
  144. rbtr-2026.3.0/src/rbtr/sessions/sql/fact_counts.sql +7 -0
  145. rbtr-2026.3.0/src/rbtr/sessions/sql/fact_scopes.sql +3 -0
  146. rbtr-2026.3.0/src/rbtr/sessions/sql/finalize_request_complete.sql +5 -0
  147. rbtr-2026.3.0/src/rbtr/sessions/sql/finalize_request_header.sql +5 -0
  148. rbtr-2026.3.0/src/rbtr/sessions/sql/find_fact_by_content.sql +16 -0
  149. rbtr-2026.3.0/src/rbtr/sessions/sql/find_latest_summary.sql +13 -0
  150. rbtr-2026.3.0/src/rbtr/sessions/sql/first_kept_timestamp.sql +10 -0
  151. rbtr-2026.3.0/src/rbtr/sessions/sql/get_created_at.sql +5 -0
  152. rbtr-2026.3.0/src/rbtr/sessions/sql/get_fact.sql +11 -0
  153. rbtr-2026.3.0/src/rbtr/sessions/sql/global_incident_failure_stats.sql +20 -0
  154. rbtr-2026.3.0/src/rbtr/sessions/sql/global_incident_repair_stats.sql +9 -0
  155. rbtr-2026.3.0/src/rbtr/sessions/sql/global_model_stats.sql +15 -0
  156. rbtr-2026.3.0/src/rbtr/sessions/sql/global_overhead_stats.sql +59 -0
  157. rbtr-2026.3.0/src/rbtr/sessions/sql/global_stats.sql +13 -0
  158. rbtr-2026.3.0/src/rbtr/sessions/sql/global_tool_stats.sql +23 -0
  159. rbtr-2026.3.0/src/rbtr/sessions/sql/has_messages_after.sql +12 -0
  160. rbtr-2026.3.0/src/rbtr/sessions/sql/has_repair_incident.sql +12 -0
  161. rbtr-2026.3.0/src/rbtr/sessions/sql/incident_failure_stats.sql +24 -0
  162. rbtr-2026.3.0/src/rbtr/sessions/sql/incident_repair_stats.sql +12 -0
  163. rbtr-2026.3.0/src/rbtr/sessions/sql/insert_fact.sql +4 -0
  164. rbtr-2026.3.0/src/rbtr/sessions/sql/insert_fragment.sql +13 -0
  165. rbtr-2026.3.0/src/rbtr/sessions/sql/list_sessions.sql +19 -0
  166. rbtr-2026.3.0/src/rbtr/sessions/sql/load_active_facts.sql +14 -0
  167. rbtr-2026.3.0/src/rbtr/sessions/sql/load_all_facts.sql +14 -0
  168. rbtr-2026.3.0/src/rbtr/sessions/sql/load_message_ids.sql +14 -0
  169. rbtr-2026.3.0/src/rbtr/sessions/sql/load_messages.sql +12 -0
  170. rbtr-2026.3.0/src/rbtr/sessions/sql/migrate_2026030301_to_2026030801.sql +42 -0
  171. rbtr-2026.3.0/src/rbtr/sessions/sql/migrate_2026030801_find_inverted_pairs.sql +33 -0
  172. rbtr-2026.3.0/src/rbtr/sessions/sql/migrate_2026030801_swap_created_at.sql +5 -0
  173. rbtr-2026.3.0/src/rbtr/sessions/sql/reset_compaction.sql +7 -0
  174. rbtr-2026.3.0/src/rbtr/sessions/sql/schema.sql +82 -0
  175. rbtr-2026.3.0/src/rbtr/sessions/sql/search_facts_fts.sql +17 -0
  176. rbtr-2026.3.0/src/rbtr/sessions/sql/search_history.sql +7 -0
  177. rbtr-2026.3.0/src/rbtr/sessions/sql/session_history.sql +7 -0
  178. rbtr-2026.3.0/src/rbtr/sessions/sql/session_overhead_stats.sql +61 -0
  179. rbtr-2026.3.0/src/rbtr/sessions/sql/session_started_at.sql +5 -0
  180. rbtr-2026.3.0/src/rbtr/sessions/sql/session_token_stats.sql +82 -0
  181. rbtr-2026.3.0/src/rbtr/sessions/sql/supersede_fact.sql +5 -0
  182. rbtr-2026.3.0/src/rbtr/sessions/sql/tool_stats.sql +34 -0
  183. rbtr-2026.3.0/src/rbtr/sessions/sql/update_fact_confirmed.sql +5 -0
  184. rbtr-2026.3.0/src/rbtr/sessions/sql/update_fragment.sql +5 -0
  185. rbtr-2026.3.0/src/rbtr/sessions/stats.py +391 -0
  186. rbtr-2026.3.0/src/rbtr/sessions/store.py +1041 -0
  187. rbtr-2026.3.0/src/rbtr/shell_exec.py +214 -0
  188. rbtr-2026.3.0/src/rbtr/skills/__init__.py +10 -0
  189. rbtr-2026.3.0/src/rbtr/skills/discovery.py +204 -0
  190. rbtr-2026.3.0/src/rbtr/skills/registry.py +86 -0
  191. rbtr-2026.3.0/src/rbtr/state.py +91 -0
  192. rbtr-2026.3.0/src/rbtr/styles.py +99 -0
  193. rbtr-2026.3.0/src/rbtr/tui/__init__.py +1 -0
  194. rbtr-2026.3.0/src/rbtr/tui/footer.py +176 -0
  195. rbtr-2026.3.0/src/rbtr/tui/input.py +1019 -0
  196. rbtr-2026.3.0/src/rbtr/tui/key_encoding.py +110 -0
  197. rbtr-2026.3.0/src/rbtr/tui/ui.py +1300 -0
  198. rbtr-2026.3.0/src/rbtr/usage.py +280 -0
  199. rbtr-2026.3.0/src/rbtr/workspace.py +48 -0
rbtr-2026.3.0/PKG-INFO ADDED
@@ -0,0 +1,1175 @@
1
+ Metadata-Version: 2.4
2
+ Name: rbtr
3
+ Version: 2026.3.0
4
+ Summary: rbtr — Interactive LLM-powered PR review workbench
5
+ License-Expression: MIT
6
+ Requires-Dist: anyio>=4.0
7
+ Requires-Dist: httpx
8
+ Requires-Dist: pydantic>=2.12.5
9
+ Requires-Dist: pydantic-ai>=1.70.0
10
+ Requires-Dist: pydantic-settings[toml]>=2.12.0
11
+ Requires-Dist: tomli-w
12
+ Requires-Dist: pygithub
13
+ Requires-Dist: pygit2
14
+ Requires-Dist: rich
15
+ Requires-Dist: prompt-toolkit>=3.0
16
+ Requires-Dist: minijinja>=2.15.1
17
+ Requires-Dist: tree-sitter>=0.24
18
+ Requires-Dist: tree-sitter-python
19
+ Requires-Dist: tree-sitter-json
20
+ Requires-Dist: tree-sitter-yaml
21
+ Requires-Dist: tree-sitter-toml
22
+ Requires-Dist: tree-sitter-bash
23
+ Requires-Dist: duckdb>=1.0
24
+ Requires-Dist: pyarrow>=17.0
25
+ Requires-Dist: llama-cpp-python
26
+ Requires-Dist: huggingface-hub
27
+ Requires-Dist: pluggy>=1.6.0
28
+ Requires-Dist: uuid-utils>=0.14.1
29
+ Requires-Dist: ruamel-yaml>=0.19.1
30
+ Requires-Dist: python-frontmatter>=1.1.0
31
+ Requires-Dist: tree-sitter-c ; extra == 'languages'
32
+ Requires-Dist: tree-sitter-c-sharp ; extra == 'languages'
33
+ Requires-Dist: tree-sitter-cpp ; extra == 'languages'
34
+ Requires-Dist: tree-sitter-css ; extra == 'languages'
35
+ Requires-Dist: tree-sitter-go ; extra == 'languages'
36
+ Requires-Dist: tree-sitter-hcl ; extra == 'languages'
37
+ Requires-Dist: tree-sitter-html ; extra == 'languages'
38
+ Requires-Dist: tree-sitter-java ; extra == 'languages'
39
+ Requires-Dist: tree-sitter-javascript ; extra == 'languages'
40
+ Requires-Dist: tree-sitter-kotlin ; extra == 'languages'
41
+ Requires-Dist: tree-sitter-ruby ; extra == 'languages'
42
+ Requires-Dist: tree-sitter-rust ; extra == 'languages'
43
+ Requires-Dist: tree-sitter-scala ; extra == 'languages'
44
+ Requires-Dist: tree-sitter-swift ; extra == 'languages'
45
+ Requires-Dist: tree-sitter-typescript ; extra == 'languages'
46
+ Requires-Python: >=3.13
47
+ Provides-Extra: languages
48
+ Description-Content-Type: text/markdown
49
+
50
+ # rbtr
51
+
52
+ An agentic code review harness in the terminal. rbtr
53
+ indexes your repository, reads the diff, understands the
54
+ structure of the code — commit messages, PR description,
55
+ existing discussion — and helps you reason through the
56
+ changes. It writes structured review comments and posts
57
+ them to GitHub.
58
+
59
+ It connects to Claude, GPT, Gemini, and any OpenAI-compatible
60
+ endpoint. Conversations are saved automatically and survive
61
+ crashes. Long sessions compact themselves so the model never
62
+ loses context.
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ # Requires Python 3.13+
68
+ uv pip install rbtr # install into the active environment
69
+ uv add rbtr # add as a project dependency
70
+ ```
71
+
72
+ Or run without installing:
73
+
74
+ ```bash
75
+ uvx rbtr # run directly from PyPI
76
+ ```
77
+
78
+ For development:
79
+
80
+ ```bash
81
+ uv tool install -e . # editable install from a local clone
82
+ ```
83
+
84
+ ## Your first review
85
+
86
+ ```bash
87
+ rbtr # launch in a git repo
88
+ ```
89
+
90
+ Connect a provider, select a PR, and ask the model to review:
91
+
92
+ ```text
93
+ you: /connect claude
94
+ Opening browser to sign in with your Claude account…
95
+ Connected to Claude. LLM is ready.
96
+
97
+ you: /review 42
98
+ Fetching PR #42…
99
+ ⟳ indexing 177 files…
100
+ ```
101
+
102
+ The model has 20 tools — it reads files, searches the index,
103
+ inspects the diff, and follows references across the codebase.
104
+ Ask it anything about the changes:
105
+
106
+ ```text
107
+ you: explain the nature of the changes, focus on error handling
108
+
109
+ claude: [reads diff, searches for retry logic, reads related files]
110
+
111
+ The PR adds retry logic to the HTTP client but has two issues:
112
+
113
+ 1. **No backoff** — `src/client.py:42` retries immediately
114
+ in a tight loop. Under load this will hammer the server.
115
+
116
+ 2. **Unused import** — `src/client.py:89` imports `sleep`
117
+ but the retry loop uses `time.sleep` directly.
118
+
119
+ I've added both as draft comments.
120
+
121
+ you: the first one is a blocker, mark it as such
122
+ ```
123
+
124
+ Inspect and post the draft:
125
+
126
+ ```text
127
+ you: /draft
128
+
129
+ ## 2 comments
130
+
131
+ ### src/client.py
132
+
133
+ ★ 1. L:42
134
+ > resp = session.get(url)
135
+ **blocker:** Retries without backoff. Under load this
136
+ hammers the server. Use exponential backoff.
137
+
138
+ ★ 2. L:89
139
+ > from time import sleep
140
+ **nit:** Unused import — `sleep` is never called.
141
+
142
+ ## Summary
143
+
144
+ Two issues in the retry logic. The missing backoff is a
145
+ blocker; the unused import is minor.
146
+
147
+ you: /draft post
148
+ Review posted.
149
+ ```
150
+
151
+ You can also start without a PR — `rbtr` opens a plain
152
+ conversation in any git repo. Use `/review` later to select
153
+ a target.
154
+
155
+ ### Snapshot review
156
+
157
+ Review code at a single point in time — no PR, no diff, no
158
+ GitHub. Useful for onboarding, architecture review, or audit.
159
+
160
+ ```bash
161
+ rbtr v2.1.0 # launch with a tag
162
+ rbtr main # launch with a branch
163
+ rbtr HEAD # launch at current commit
164
+ ```
165
+
166
+ Or from inside rbtr:
167
+
168
+ ```text
169
+ you: /review v2.1.0
170
+ Reviewing snapshot: v2.1.0
171
+ ⟳ indexing 177 files…
172
+
173
+ you: walk me through the auth module
174
+
175
+ you: /review main feature
176
+ Reviewing branch: main → feature
177
+ ```
178
+
179
+ ## Tools
180
+
181
+ The model has 20 tools for reading code, navigating the
182
+ codebase, writing review feedback, and running shell
183
+ commands. Tools appear and disappear based on session state
184
+ — the model never sees a tool it cannot use.
185
+
186
+ | Condition | Tools available |
187
+ | ----------------- | ---------------------------------------- |
188
+ | Always | `edit`, `remember`, `run_command` |
189
+ | Repo + any target | `read_file`, `list_files`, `grep` |
190
+ | Index ready | `search`, `read_symbol`, `list_symbols`, |
191
+ | | `find_references` |
192
+ | PR or branch | `diff`, `changed_files`, `commit_log`, |
193
+ | | `changed_symbols` |
194
+ | PR only | Draft tools, `get_pr_discussion` |
195
+
196
+ In snapshot mode (`/review <ref>`) the model has file, index,
197
+ workspace, and shell tools. Diff and draft tools are hidden
198
+ — there is no base to compare against.
199
+
200
+ ### Reading code
201
+
202
+ The model reads changed files, referenced modules, tests,
203
+ and config to understand context beyond the diff.
204
+
205
+ | Tool | Description |
206
+ | ------------ | ------------------------------------------ |
207
+ | `read_file` | Read file contents, paginated by lines |
208
+ | `grep` | Substring search, scoped by glob or prefix |
209
+ | `list_files` | List files, scoped by glob or prefix |
210
+
211
+ ### Understanding changes
212
+
213
+ The model starts here — what changed, how the work was
214
+ structured, and what the changes mean structurally.
215
+
216
+ | Tool | Description |
217
+ | ----------------- | ------------------------------------ |
218
+ | `diff` | Unified diff, scoped by glob or file |
219
+ | `changed_files` | Files changed in the PR |
220
+ | `commit_log` | Commits between base and head |
221
+ | `changed_symbols` | Symbols added, removed, or modified. |
222
+ | | Flags stale docs and missing tests |
223
+
224
+ ### Navigating the codebase
225
+
226
+ The code index lets the model search by name, keyword, or
227
+ concept — and follow references to check whether a change
228
+ breaks callers or misses related code.
229
+
230
+ | Tool | Description |
231
+ | ----------------- | ------------------------------------ |
232
+ | `search` | Find symbols by name, keyword, or |
233
+ | | natural-language concept |
234
+ | `read_symbol` | Read the full source of a symbol |
235
+ | `list_symbols` | Table of contents for a file |
236
+ | `find_references` | Find all symbols referencing a given |
237
+ | | symbol via the dependency graph |
238
+
239
+ ### Writing the review
240
+
241
+ The model builds the review incrementally — adding comments
242
+ on specific lines, editing them based on discussion, and
243
+ setting the overall summary.
244
+
245
+ | Tool | Description |
246
+ | ---------------------- | -------------------------------- |
247
+ | `add_draft_comment` | Inline comment on a code snippet |
248
+ | `edit_draft_comment` | Edit an existing comment |
249
+ | `remove_draft_comment` | Remove a comment |
250
+ | `set_draft_summary` | Set the top-level review body |
251
+ | `read_draft` | Read the current draft |
252
+
253
+ ### Context and memory
254
+
255
+ The model reads existing discussion to avoid duplicating
256
+ feedback, saves durable facts for future sessions, and
257
+ writes notes to the workspace.
258
+
259
+ | Tool | Description |
260
+ | ------------------- | ----------------------------------- |
261
+ | `get_pr_discussion` | Existing reviews, comments, CI |
262
+ | `remember` | Save a fact for future sessions |
263
+ | `edit` | Create or modify `.rbtr/notes/` and |
264
+ | | `.rbtr/AGENTS.md` |
265
+
266
+ ### Shell execution
267
+
268
+ The model can run shell commands when `tools.shell.enabled`
269
+ is `true` (the default).
270
+
271
+ | Tool | Description |
272
+ | ------------- | --------------------------------------- |
273
+ | `run_command` | Execute a shell command, return output |
274
+
275
+ The primary use is executing scripts bundled with skills.
276
+ The model is steered away from using it for codebase access
277
+ — files under review live in a different branch or commit,
278
+ so `read_file`, `grep`, and other bespoke tools are the
279
+ correct choice (they read from the git object store at the
280
+ right ref). The working tree is treated as read-only.
281
+
282
+ Output is streamed to the TUI via a head/tail buffer
283
+ (first 3 + last 5 lines, refreshed at ~30 fps). When the
284
+ command executes a skill script, the header shows the skill
285
+ name and source instead of raw JSON args
286
+ (e.g. `⚙ [brave-search · user] search.sh "query"`). The
287
+ full result returned to the model is truncated to
288
+ `tools.shell.max_output_lines` (default 2000).
289
+
290
+ ```toml
291
+ [tools.shell]
292
+ enabled = true # set false to disable entirely
293
+ timeout = 120 # default timeout in seconds (0 = no limit)
294
+ max_output_lines = 2000
295
+ ```
296
+
297
+ `list_files`, `grep`, and `diff` accept a `pattern` parameter
298
+ that works like a git pathspec: a plain string is a directory
299
+ prefix or file path, glob metacharacters (`*`, `?`, `[`)
300
+ activate pattern matching, and `**` matches across
301
+ directories. For example, `pattern="src/**/*.py"` scopes to
302
+ Python files under `src/`.
303
+
304
+ Tools that read code accept a `ref` parameter — `"head"`
305
+ (default), `"base"`, or a raw commit SHA — so the model
306
+ can inspect the codebase at any point in time. File tools
307
+ read from the git object store first and fall back to the
308
+ local filesystem for untracked files (`.rbtr/notes/`,
309
+ drafts).
310
+
311
+ All paginated tools show a trailer when output is truncated.
312
+ Each turn is limited to 25 tool calls (configurable via
313
+ `max_requests_per_turn`). When the limit is reached, the
314
+ model summarises its progress and asks whether to continue.
315
+
316
+ ## Commands
317
+
318
+ | Command | Description | Context |
319
+ | ------------------------- | ------------------------------------------- | ------- |
320
+ | `/help` | Show available commands | |
321
+ | `/review` | List open PRs and branches | ✓ |
322
+ | `/review <number>` | Select a PR for review | ✓ |
323
+ | `/review <ref>` | Snapshot review at a git ref | ✓ |
324
+ | `/review <base> <target>` | Diff review between two refs | ✓ |
325
+ | `/draft` | View, sync, or post the review draft | ✓ |
326
+ | `/connect <service>` | Authenticate with a service | ✓ |
327
+ | `/model` | List available models from all providers | |
328
+ | `/model <provider/id>` | Set the active model | ✓ |
329
+ | `/index` | Index status, search, diagnostics, rebuild | partial |
330
+ | `/compact` | Summarise older context to free space | ✓ |
331
+ | `/compact reset` | Undo last compaction (before new messages) | ✓ |
332
+ | `/session` | List, inspect, or delete sessions | partial |
333
+ | `/stats` | Show session token and cost statistics | ✓ |
334
+ | `/memory` | List, extract, or purge cross-session facts | partial |
335
+ | `/skill` | List or load a skill | ✓ |
336
+ | `/reload` | Show active prompt sources | |
337
+ | `/new` | Start a new conversation | |
338
+ | `/quit` | Exit (also `/q`) | |
339
+
340
+ The **Context** column shows which commands produce
341
+ [context markers](#context-markers) for the model.
342
+ `partial` means some subcommands emit markers (e.g.
343
+ `/index status` does, `/index search` does not).
344
+
345
+ ## Providers
346
+
347
+ rbtr connects to LLMs through multiple providers. Use `/connect`
348
+ to authenticate:
349
+
350
+ | Provider | Auth | Command |
351
+ | ---------- | ------------------ | -------------------------------------- |
352
+ | Claude | OAuth (Pro/Max) | `/connect claude` |
353
+ | ChatGPT | OAuth (Plus/Pro) | `/connect chatgpt` |
354
+ | Google | OAuth (free) | `/connect google` |
355
+ | OpenAI | API key | `/connect openai sk-...` |
356
+ | Fireworks | API key | `/connect fireworks fw-...` |
357
+ | OpenRouter | API key | `/connect openrouter sk-or-...` |
358
+ | Endpoint | URL + optional key | `/connect endpoint <name> <url> [key]` |
359
+
360
+ Multiple providers can be connected at the same time. Tab on
361
+ `/connect` autocompletes provider names.
362
+
363
+ ### Endpoints
364
+
365
+ Any OpenAI-compatible API can be used as a custom endpoint:
366
+
367
+ ```text
368
+ you: /connect endpoint deepinfra https://api.deepinfra.com/v1/openai di-...
369
+ you: /connect endpoint ollama http://localhost:11434/v1
370
+ you: /model deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct
371
+ ```
372
+
373
+ Endpoints are first-class providers — they appear in `/model`
374
+ listings, support tab completion, and participate in the same
375
+ dispatch pipeline as builtin providers.
376
+
377
+ ### GitHub
378
+
379
+ rbtr uses GitHub to fetch PRs and branches for review. Authenticate
380
+ with `/connect github` (device flow). This is separate from LLM
381
+ providers — it gives rbtr read access to your repositories.
382
+
383
+ ## Models
384
+
385
+ Models use `<provider>/<model-id>` format:
386
+
387
+ ```text
388
+ /model claude/claude-sonnet-4-20250514
389
+ /model chatgpt/gpt-5.2-codex
390
+ /model openai/gpt-4o
391
+ /model deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct
392
+ ```
393
+
394
+ ### Listing models
395
+
396
+ `/model` with no argument shows all models from connected providers,
397
+ marking the active one:
398
+
399
+ ```text
400
+ you: /model
401
+ claude:
402
+ claude/claude-sonnet-4-20250514 ◂
403
+ claude/claude-opus-4-20250514
404
+ chatgpt:
405
+ chatgpt/o3-pro
406
+ chatgpt/gpt-5.2-codex
407
+ deepinfra:
408
+ deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct
409
+ ```
410
+
411
+ Providers that don't expose a model listing show a hint instead:
412
+
413
+ ```text
414
+ ollama:
415
+ /model ollama/<model-id>
416
+ ```
417
+
418
+ The model list is fetched lazily on first Tab completion or
419
+ `/model` command, and refreshed on every `/connect`.
420
+
421
+ ### Switching models mid-conversation
422
+
423
+ Conversation history is preserved when you switch models.
424
+ PydanticAI's message format is provider-agnostic, so previous
425
+ messages are converted automatically. When a provider rejects
426
+ history from a different provider (mismatched tool-call
427
+ formats, thinking metadata), rbtr repairs the history in
428
+ memory — the original messages are never modified:
429
+
430
+ ```text
431
+ you: explain the retry logic in src/client.py
432
+ claude/claude-sonnet-4-20250514: The retry logic uses exponential backoff…
433
+
434
+ you: /model chatgpt/o3-pro
435
+ Model set to chatgpt/o3-pro
436
+
437
+ you: what do you think about adding jitter?
438
+ chatgpt/o3-pro: Based on the retry logic we discussed…
439
+ ```
440
+
441
+ Only `/new` clears history (explicit user action). The active model is
442
+ persisted to `config.toml` across restarts. Conversation messages are
443
+ saved automatically to the session database (see Sessions below).
444
+
445
+ ## Terminal reference
446
+
447
+ ### Shell commands
448
+
449
+ Prefix any command with `!` to run it in a shell:
450
+
451
+ ```text
452
+ you: !git log --oneline -5
453
+ you: !rg "TODO" src/
454
+ ```
455
+
456
+ Long output is truncated — press **Ctrl+O** to expand it.
457
+
458
+ ### Tab completion
459
+
460
+ | Context | Example | Completes |
461
+ | -------------- | ------------------ | ----------------------- |
462
+ | Slash commands | `/rev` → `/review` | Command names |
463
+ | Command args | `/connect c` | Providers, models, etc. |
464
+ | Shell commands | `!git ch` | Bash completion |
465
+ | File paths | `!cat ~/Doc` | Directories and files |
466
+
467
+ ### Key bindings
468
+
469
+ | Key | Action |
470
+ | ----------------------- | ---------------------------------------- |
471
+ | Enter | Submit input |
472
+ | Shift+Enter / Alt+Enter | Insert newline (multiline input) |
473
+ | Tab | Autocomplete |
474
+ | Shift+Tab | Cycle thinking effort level |
475
+ | Up/Down | Browse history or navigate multiline |
476
+ | Ctrl+C | Cancel running task (double-tap to quit) |
477
+ | Ctrl+O | Expand truncated shell output |
478
+
479
+ ### Pasting
480
+
481
+ Bracketed paste is enabled — pasted newlines insert into the
482
+ prompt instead of submitting. Large pastes collapse into an
483
+ atomic marker (`[pasted 42 lines]`) that expands on submit.
484
+
485
+ ### Context markers
486
+
487
+ After a slash command or shell command, a context marker
488
+ appears above your input — a tag like `[/review → PR #42]`
489
+ or `[! git log — exit 0]`. On submit, markers expand into a
490
+ `[Recent actions]` block prepended to your message so the
491
+ model knows what you just did. Backspace at the start of the
492
+ input dismisses the last marker. Not every command produces
493
+ a marker — only those whose outcome is useful to the model.
494
+
495
+ ## Usage display
496
+
497
+ The footer shows token usage and context after each response:
498
+
499
+ ```text
500
+ owner/repo claude/claude-sonnet-4-20250514
501
+ PR #42 · feature-branch |7| 12% of 200k ↑24.3k ↓1.2k ↯18.0k $0.045
502
+ ```
503
+
504
+ `|7|` messages, `12%` context used, `↑` input tokens,
505
+ `↓` output tokens, `↯` cache-read tokens, `$` cost.
506
+ Colours shift from green to yellow to red as context fills.
507
+ `/new` resets all counters.
508
+
509
+ ## Sessions
510
+
511
+ Every conversation is saved to a local SQLite database at
512
+ `~/.config/rbtr/sessions.db`. Messages are persisted as they
513
+ stream — if rbtr crashes, the conversation survives up to the
514
+ last received part. Requests are always persisted before their
515
+ responses, so resumed sessions load in the correct order.
516
+
517
+ ### Persistence
518
+
519
+ The message format is provider-agnostic. History is preserved
520
+ across `/model` switches — only `/new` clears it. rbtr
521
+ repairs history automatically in memory on every turn — the
522
+ original messages are never modified. Preventive repairs
523
+ (sanitising cross-provider field values, patching cancelled
524
+ tool calls) run before each API call. When a provider still
525
+ rejects the history, escalating structural repairs retry
526
+ automatically. All repairs are recorded as incidents, visible
527
+ in `/stats`.
528
+
529
+ Ctrl+C during a tool-calling turn cancels immediately. Any tool
530
+ calls without results get synthetic `(cancelled)` returns so
531
+ the conversation can continue:
532
+
533
+ ```text
534
+ ⚠ Previous turn was cancelled mid-tool-call (read_file, grep).
535
+ Those tool results are lost — the model will continue without them.
536
+ ```
537
+
538
+ See [Conversation storage](ARCHITECTURE.md#conversation-storage)
539
+ and [Cross-provider history repair](ARCHITECTURE.md#cross-provider-history-repair)
540
+ in ARCHITECTURE.md.
541
+
542
+ ### `/new` — starting fresh
543
+
544
+ `/new` clears the in-memory conversation (history, usage
545
+ counters) and generates a new session ID. The previous session's
546
+ data stays in the database — it can be listed with `/session` and
547
+ resumed with `/session resume`.
548
+
549
+ ### `/session` command
550
+
551
+ ```text
552
+ /session List recent sessions (current repo)
553
+ /session all List sessions across all repos
554
+ /session info Show current session details
555
+ /session rename <n> Rename the current session
556
+ /session resume <q> Resume a session (ID prefix or label)
557
+ /session delete <id> Delete a session by ID prefix
558
+ /session purge 7d Delete sessions older than 7 days
559
+ ```
560
+
561
+ **`/session rename`** changes the label on the current session.
562
+ Labels are set automatically when a review target is selected
563
+ (e.g. `acme/app — main → feature-x`), but you can override
564
+ them with any name.
565
+
566
+ **`/session resume`** accepts an ID prefix or a label substring
567
+ (case-insensitive). ID prefix is tried first; if no match,
568
+ the label is searched. When several sessions share a label the
569
+ most recent one is picked.
570
+
571
+ **`/session resume`** loads the target session's messages from the
572
+ database and switches the active session ID. The conversation
573
+ continues where it left off — the model sees the full history.
574
+ If the session had a review target (PR or branch), it's
575
+ automatically restored — rbtr re-runs `/review` to fetch fresh
576
+ metadata and rebuild the code index.
577
+ You can resume sessions from different repos or different models.
578
+
579
+ **`/session delete`** requires an exact ID prefix — no label
580
+ matching, to prevent accidental deletion. Removes all fragments
581
+ for the session (cascading via foreign keys). You cannot delete
582
+ the active session — use `/new` first.
583
+
584
+ ### Automatic behaviour
585
+
586
+ - **New session on startup.** Each `rbtr` invocation starts a fresh
587
+ session, labelled with the current repo and branch. When you
588
+ select a review target, the label updates to show the base and
589
+ head branches (e.g. `acme/app — main → feature-x`).
590
+ - **Streaming persistence.** Parts are saved to the database as
591
+ they arrive from the model, not batched after the turn.
592
+ - **Input history from the database.** Up/Down arrow browses input
593
+ history across all sessions, deduplicated and sorted by recency.
594
+
595
+ ### Pruning old sessions
596
+
597
+ Sessions accumulate over time. Use `/session purge` to clean up:
598
+
599
+ ```text
600
+ /session purge 7d Delete sessions older than 7 days
601
+ /session purge 2w Delete sessions older than 2 weeks
602
+ /session purge 24h Delete sessions older than 24 hours
603
+ ```
604
+
605
+ Duration suffixes: `d` (days), `w` (weeks), `h` (hours). The
606
+ active session is never deleted. To remove a specific session,
607
+ use `/session delete <id>`.
608
+
609
+ ### `/stats` command
610
+
611
+ ```text
612
+ /stats Current session statistics
613
+ /stats <id> Stats for a specific session (prefix match)
614
+ /stats all Aggregate stats across all sessions
615
+ ```
616
+
617
+ Shows token usage (input, output, cache), cost, tool call
618
+ frequency, and — when the session has incidents — failure and
619
+ repair summaries.
620
+
621
+ #### Incident reporting
622
+
623
+ When an LLM call fails and rbtr auto-recovers, or when history
624
+ is manipulated to satisfy provider constraints, the event is
625
+ recorded as an **incident** in the session database (see
626
+ ARCHITECTURE.md — History repair). `/stats` surfaces these
627
+ when they exist:
628
+
629
+ ```text
630
+ Failures (3)
631
+ history_format 2 recovered: 2
632
+ overflow 1 recovered: 1
633
+
634
+ History repairs (6)
635
+ repair_dangling 1 (cancelled_mid_tool_call)
636
+ consolidate_tool_returns 2 (cross_provider_retry)
637
+ demote_thinking 2 (cross_provider_retry)
638
+ flatten_tool_exchanges 1 (cross_provider_retry)
639
+
640
+ Recovery rate 100% 3/3
641
+ ```
642
+
643
+ **Failures** are grouped by kind (`history_format`, `overflow`,
644
+ `tool_args`, `type_error`, `effort_unsupported`) with
645
+ recovered/failed sub-counts. **History repairs** are grouped by
646
+ strategy with the reason that triggered them. The **recovery
647
+ rate** shows what percentage of failures were automatically
648
+ resolved.
649
+
650
+ Sessions with no incidents show no extra sections — the output
651
+ is identical to before.
652
+
653
+ ## Cross-session memory
654
+
655
+ rbtr learns facts from conversations and remembers them across
656
+ sessions. Facts are durable knowledge — project conventions,
657
+ architecture decisions, user preferences, recurring patterns
658
+ discovered during review.
659
+
660
+ Static project instructions belong in `AGENTS.md`. Facts are
661
+ what the agent learns on its own.
662
+
663
+ ### How facts are created
664
+
665
+ Facts are extracted automatically at two points:
666
+
667
+ - **During compaction** — the extraction agent runs
668
+ concurrently with the summary agent, analysing the messages
669
+ being compacted.
670
+ - **After posting a review** — `/draft post` triggers
671
+ extraction on the full session, since completed reviews are
672
+ the richest source of project knowledge.
673
+
674
+ You can also trigger extraction manually with
675
+ `/memory extract`, or teach the agent directly — it has a
676
+ `remember` tool that saves facts on demand during
677
+ conversation.
678
+
679
+ ### Scopes
680
+
681
+ Each fact has a scope:
682
+
683
+ - **global** — applies everywhere (e.g. "prefers British
684
+ English spelling")
685
+ - **repo** — specific to the current repository (e.g. "uses
686
+ pytest with the mocker fixture, not unittest.mock")
687
+
688
+ Repo-scoped facts are keyed by `owner/repo` and only
689
+ injected when working in that repository.
690
+
691
+ ### Injection
692
+
693
+ At session start, active facts for the current scopes are
694
+ loaded and injected into the system prompt. Two caps control
695
+ what's included:
696
+
697
+ - `max_injected_facts` (default 20) — maximum number of
698
+ facts
699
+ - `max_injected_tokens` (default 2000) — token budget for
700
+ injected facts
701
+
702
+ Facts are ordered by `last_confirmed_at` (most recently
703
+ confirmed first), so frequently re-observed facts take
704
+ priority.
705
+
706
+ ### Deduplication
707
+
708
+ The extraction agent sees all existing facts and tags each
709
+ extraction as `new`, `confirm` (re-observed), or `supersede`
710
+ (replaces an outdated fact). Matching is content-based — no
711
+ opaque IDs are exposed to the LLM. When the LLM's reference
712
+ doesn't exactly match, a clarification retry corrects the
713
+ mismatch.
714
+
715
+ ### `/memory` command
716
+
717
+ ```text
718
+ /memory List active facts by scope
719
+ /memory all Include superseded facts
720
+ /memory extract Extract facts from the current session
721
+ /memory purge 7d Delete facts not confirmed in 7 days
722
+ /memory purge 2w Delete facts older than 2 weeks
723
+ ```
724
+
725
+ Purge uses `last_confirmed_at` — facts that are regularly
726
+ re-observed survive longer. Duration suffixes: `d` (days),
727
+ `w` (weeks), `h` (hours).
728
+
729
+ ### Memory configuration
730
+
731
+ ```toml
732
+ [memory]
733
+ enabled = true # Toggle the feature
734
+ max_injected_facts = 20 # Facts in system prompt
735
+ max_injected_tokens = 2000 # Token budget for injection
736
+ max_extraction_facts = 200 # Existing facts shown to extraction agent
737
+ fact_extraction_model = "" # Override model (empty = session model)
738
+ ```
739
+
740
+ ## Skills
741
+
742
+ Skills are self-contained instruction packages — a markdown
743
+ file with optional bundled scripts — that teach the model
744
+ new capabilities. rbtr discovers skills automatically from
745
+ multiple directories and presents them in the system prompt.
746
+
747
+ rbtr scans the same skill directories as pi and Claude Code,
748
+ so existing skills work with zero configuration:
749
+
750
+ ```text
751
+ ~/.config/rbtr/skills/ # user-level rbtr skills
752
+ ~/.claude/skills/ # Claude Code skills
753
+ ~/.pi/agent/skills/ # pi skills
754
+ ~/.agents/skills/ # Agent Skills standard
755
+ .rbtr/skills/ # project-level (any ancestor to git root)
756
+ .claude/skills/ # project-level Claude Code
757
+ .pi/skills/ # project-level pi
758
+ .agents/skills/ # project-level Agent Skills
759
+ ```
760
+
761
+ Skills use the [Agent Skills standard][agent-skills] format:
762
+ a markdown file with YAML frontmatter (`name`, `description`).
763
+ The model sees a catalog of available skills in its system
764
+ prompt and reads the full skill file on demand via `read_file`.
765
+
766
+ [agent-skills]: https://agentskills.io/specification
767
+
768
+ ### `/skill` command
769
+
770
+ ```text
771
+ /skill List discovered skills
772
+ /skill brave-search Load a skill into context
773
+ /skill brave-search "query" Load with a follow-up message
774
+ ```
775
+
776
+ Tab-completes skill names. Skills marked with
777
+ `disable-model-invocation: true` are hidden from the prompt
778
+ catalog but still loadable via `/skill`.
779
+
780
+ ### Configuration
781
+
782
+ ```toml
783
+ [skills]
784
+ project_dirs = [".rbtr/skills", ".claude/skills", ".pi/skills", ".agents/skills"]
785
+ user_dirs = ["~/.config/rbtr/skills", "~/.claude/skills", "~/.pi/agent/skills", "~/.agents/skills"]
786
+ extra_dirs = []
787
+ ```
788
+
789
+ Set `project_dirs = []` or `user_dirs = []` to disable
790
+ scanning. `extra_dirs` adds directories on top.
791
+
792
+ ## Context compaction
793
+
794
+ Long conversations fill the context window. rbtr compacts
795
+ automatically — summarising older messages while keeping
796
+ recent turns intact.
797
+
798
+ Compaction splits the conversation by turn boundaries. A turn
799
+ starts at a user prompt and includes the model's responses,
800
+ tool calls, and tool results. The last `keep_turns` (default 2)
801
+ are preserved; everything older is serialised and sent to the
802
+ model for summarisation. The original messages stay in the
803
+ database for auditing.
804
+
805
+ ### Example
806
+
807
+ Before (5 turns):
808
+
809
+ ```text
810
+ turn 1: user "set up the project" → assistant "done"
811
+ turn 2: user "add authentication" → assistant [tools] → "added auth"
812
+ turn 3: user "write tests for auth" → assistant [tools] → "added 12 tests"
813
+ turn 4: user "fix the failing test" → assistant [tools] → "fixed assertion"
814
+ turn 5: user "review the PR" → assistant [reading files...]
815
+ ```
816
+
817
+ After (turns 1–3 summarised, 4–5 kept):
818
+
819
+ ```text
820
+ summary: "[Context summary] Set up project. Added auth
821
+ middleware. Wrote 12 tests for auth module."
822
+ turn 4: user "fix the failing test" → assistant "fixed assertion"
823
+ turn 5: user "review the PR" → assistant [reading files...]
824
+ ```
825
+
826
+ ### When compaction triggers
827
+
828
+ - **Post-turn** — after a response, if context usage exceeds
829
+ `auto_compact_pct` (default 85%).
830
+ - **Mid-turn** — during a tool-calling cycle, if the threshold
831
+ is exceeded. Compacts once, reloads history, resumes the turn.
832
+ - **Overflow** — when the API rejects a request with a
833
+ context-length error. Compacts and retries.
834
+ - **Manual** — `/compact`, with optional extra instructions:
835
+
836
+ ```text
837
+ you: /compact
838
+ you: /compact Focus on the authentication changes
839
+ ```
840
+
841
+ `/compact reset` undoes the latest compaction, restoring the
842
+ original messages. Only allowed before new messages are sent.
843
+
844
+ When the old messages are too large for a single summary
845
+ request, rbtr finds the largest prefix that fits, summarises
846
+ it, and pushes the rest into the kept portion.
847
+
848
+ ### Settings
849
+
850
+ ```toml
851
+ [compaction]
852
+ auto_compact_pct = 85 # trigger threshold (% of context)
853
+ keep_turns = 2 # recent turns to preserve
854
+ reserve_tokens = 16000 # reserved for the summary response
855
+ summary_max_chars = 2000 # max chars per tool result in input
856
+ ```
857
+
858
+ See [Context compaction in ARCHITECTURE.md](ARCHITECTURE.md#context-compaction)
859
+ for the split algorithm, orphan handling, and reset mechanics.
860
+
861
+ ## Configuration
862
+
863
+ User-level files live in `~/.config/rbtr/` (override with
864
+ `RBTR_USER_DIR`). A workspace overlay at `.rbtr/config.toml`
865
+ can override per-project settings — the nearest `.rbtr/`
866
+ walking from CWD to the git root wins (monorepo-friendly).
867
+
868
+ - **`config.toml`** — model, endpoints, feature settings.
869
+ - **`creds.toml`** — API keys and OAuth tokens (0600).
870
+
871
+ ```toml
872
+ # config.toml
873
+ model = "claude/claude-sonnet-4-20250514"
874
+
875
+ [endpoints.deepinfra]
876
+ base_url = "https://api.deepinfra.com/v1/openai"
877
+ ```
878
+
879
+ Config values can also be set via environment variables with
880
+ `RBTR_` prefix (e.g. `RBTR_MODEL`). OAuth tokens are managed
881
+ by `/connect` — you never need to edit `creds.toml` by hand.
882
+
883
+ ### Customising the prompt
884
+
885
+ Three levels of customisation, loaded in order:
886
+
887
+ 1. **`AGENTS.md`** (repo root) — project-specific rules.
888
+ Configure the file list with
889
+ `project_instructions = ["AGENTS.md"]`.
890
+ 2. **`~/.config/rbtr/APPEND_SYSTEM.md`** — user-wide
891
+ preferences appended to the system prompt.
892
+ 3. **`~/.config/rbtr/SYSTEM.md`** — full system prompt
893
+ replacement (Jinja template with `project_instructions`
894
+ and `append_system` variables).
895
+
896
+ Example `AGENTS.md`:
897
+
898
+ ```markdown
899
+ - Target Python 3.13+. Use modern features.
900
+ - All code must be type-annotated.
901
+ - Run `just check` after every change.
902
+ ```
903
+
904
+ ## Code index
905
+
906
+ When you start a review (`/review`), rbtr builds a structural index
907
+ of the repository in the background. The index gives the LLM tools
908
+ to search, navigate, and reason about the codebase — not just the
909
+ diff.
910
+
911
+ ### What gets indexed
912
+
913
+ rbtr extracts **chunks** (functions, classes, methods, imports,
914
+ doc sections) from every file in the repo at the base commit, then
915
+ incrementally indexes the head commit. Each chunk records its name,
916
+ kind, file path, line range, and content.
917
+
918
+ Cross-file **edges** are inferred automatically:
919
+
920
+ - **Import edges** — structural (from tree-sitter import metadata)
921
+ or text-search fallback for languages without an extractor.
922
+ - **Test edges** — `test_foo.py` → `foo.py` by naming convention
923
+ and import analysis.
924
+ - **Doc edges** — markdown/RST sections that mention function or
925
+ class names.
926
+
927
+ **Embeddings** are computed for semantic search using a local GGUF
928
+ model (bge-m3, quantized, runs on Metal/CPU — no API calls). The
929
+ structural index is usable immediately; embeddings fill in behind
930
+ it.
931
+
932
+ ### Progress indicator
933
+
934
+ The footer shows indexing progress:
935
+
936
+ ```text
937
+ ⟳ parsing 42/177 (extracting chunks)
938
+ ⟳ embedding 85/380 (computing vectors)
939
+ ● 1.2k (ready — 1,200 chunks indexed)
940
+ ```
941
+
942
+ The review proceeds immediately — you don't have to wait for
943
+ indexing to finish.
944
+
945
+ ### `/index` command
946
+
947
+ | Subcommand | Description |
948
+ | ---------------------------- | ---------------------------------------- |
949
+ | `/index` | Show index status (chunks, edges, size) |
950
+ | `/index clear` | Delete the index database |
951
+ | `/index rebuild` | Clear and re-index from scratch |
952
+ | `/index prune` | Remove orphan chunks not in any snapshot |
953
+ | `/index model` | Show current embedding model |
954
+ | `/index model <id>` | Switch embedding model and re-embed |
955
+ | `/index search <query>` | Search the index and show ranked results |
956
+ | `/index search-diag <query>` | Search with full signal breakdown table |
957
+
958
+ ### Index configuration
959
+
960
+ ```toml
961
+ [index]
962
+ enabled = true
963
+ embedding_model = "gpustack/bge-m3-GGUF/bge-m3-Q4_K_M.gguf"
964
+ include = [".rbtr/notes/*"] # force-include (override .gitignore)
965
+ extend_exclude = [".rbtr/index"] # exclude on top of .gitignore
966
+ ```
967
+
968
+ The index is persistent — subsequent `/review` runs skip
969
+ unchanged files (keyed by git blob SHA).
970
+
971
+ ### Graceful degradation
972
+
973
+ - **No grammar installed** for a language → falls back to
974
+ line-based plaintext chunking.
975
+ - **No embedding model** (missing GGUF, GPU init failure) →
976
+ structural index works, semantic search signal is skipped
977
+ (weight redistributed to name and keyword channels).
978
+ - **Slow indexing** → review starts immediately, index catches
979
+ up in a background thread.
980
+
981
+ ## Review draft
982
+
983
+ The LLM builds a structured review using draft tools
984
+ (`add_draft_comment`, `set_draft_summary`, etc.). The draft
985
+ persists to `.rbtr/drafts/<pr>.yaml` — crash-safe, human-
986
+ editable, and synced bidirectionally with GitHub.
987
+
988
+ ### Workflow
989
+
990
+ 1. `/review 42` — fetches the PR, pulls any existing
991
+ pending review from GitHub.
992
+ 2. The LLM adds comments and a summary as it reviews.
993
+ 3. `/draft` — inspect the current state.
994
+ 4. `/draft sync` — bidirectional sync with GitHub
995
+ (three-way merge, conflicts resolve to local).
996
+ 5. `/draft post` — submit the review. Optional event type:
997
+ `approve`, `request_changes` (default `COMMENT`).
998
+
999
+ ### Draft commands
1000
+
1001
+ | Subcommand | Description |
1002
+ | --------------------- | ------------------------------------- |
1003
+ | `/draft` | Show draft with sync status |
1004
+ | `/draft sync` | Bidirectional sync with GitHub |
1005
+ | `/draft post [event]` | Submit review to GitHub |
1006
+ | `/draft clear` | Delete local draft and remote pending |
1007
+
1008
+ ### Status indicators
1009
+
1010
+ | Indicator | Meaning |
1011
+ | --------- | -------------------------------------- |
1012
+ | `✓` | Synced — matches last-pushed snapshot |
1013
+ | `✎` | Modified locally since last sync |
1014
+ | `★` | New — never synced to GitHub |
1015
+ | `✗` | Deleted — will be removed on next sync |
1016
+
1017
+ ### Safety
1018
+
1019
+ - **Unsynced guard** — `/draft post` refuses if the remote
1020
+ has comments not in the local draft.
1021
+ - **Atomic posting** — all comments are submitted in one
1022
+ API call.
1023
+ - **Crash-safe** — YAML on disk, updated on every mutation.
1024
+ Mid-sync crashes recover on the next sync.
1025
+ - **GitHub suggestions** — the LLM can provide replacement
1026
+ code; it's posted as a suggestion block that the author
1027
+ can apply with one click.
1028
+
1029
+ See [Review draft and GitHub integration][arch-draft]
1030
+ in ARCHITECTURE.md for sync internals.
1031
+
1032
+ [arch-draft]: ARCHITECTURE.md#review-draft-and-github-integration
1033
+
1034
+ ## Theme
1035
+
1036
+ rbtr defaults to a dark palette with semantically tinted panel
1037
+ backgrounds. Switch to light mode or override individual
1038
+ styles in `config.toml`:
1039
+
1040
+ ```toml
1041
+ [theme]
1042
+ mode = "light" # "dark" (default) or "light"
1043
+
1044
+ [theme.light] # override fields for the active mode
1045
+ bg_succeeded = "on #E0FFE0"
1046
+ prompt = "bold magenta"
1047
+ ```
1048
+
1049
+ Text styles use ANSI names (`bold cyan`, `dim`, `yellow`, …)
1050
+ that adapt to your terminal's colour scheme. Panel backgrounds
1051
+ use hex values. Any string that Rich accepts as a
1052
+ [style definition](https://rich.readthedocs.io/en/latest/style.html)
1053
+ is valid.
1054
+
1055
+ Available fields are defined in `PaletteConfig` in
1056
+ [`config.py`](src/rbtr/config.py).
1057
+
1058
+ ## Development
1059
+
1060
+ ```bash
1061
+ uv sync # Install dependencies
1062
+ just check # Lint + typecheck + test
1063
+ just fmt # Auto-fix and format
1064
+ ```
1065
+
1066
+ ### Releasing
1067
+
1068
+ Versions use calendar versioning (`YYYY.MM.NUM`). Dev builds
1069
+ append `-devN`. Two `just` recipes handle the full flow:
1070
+
1071
+ ```bash
1072
+ just pre-release # bump to next dev version, branch, tag, push
1073
+ just release # bump to stable version, branch, tag, push
1074
+ ```
1075
+
1076
+ Each recipe:
1077
+
1078
+ 1. Bumps the version in `pyproject.toml` via `bump-my-version`.
1079
+ 2. Creates a branch (`pre-release-v*` or `release-v*`).
1080
+ 3. Commits, tags, and pushes.
1081
+
1082
+ CI picks up the branch, runs `just check`, builds the package,
1083
+ creates a GitHub release (pre-releases are marked as such), and
1084
+ publishes to PyPI.
1085
+
1086
+ Bump only (no branch/push):
1087
+
1088
+ ```bash
1089
+ just bump-pre-release # version bump only
1090
+ just bump-release # version bump only
1091
+ ```
1092
+
1093
+ ### Architecture reference
1094
+
1095
+ For details on how providers, tools, the index, language
1096
+ plugins, session persistence, history repair, and GitHub sync
1097
+ work internally, see [ARCHITECTURE.md](ARCHITECTURE.md).
1098
+
1099
+ ### Git reference handling
1100
+
1101
+ rbtr never modifies your working tree or local branches.
1102
+ All reads go through the git object store.
1103
+
1104
+ When you select a PR, rbtr fetches the PR head and the
1105
+ base branch from origin so that diffs, commit logs, and
1106
+ changed-file lists reflect the actual PR scope — not stale
1107
+ local refs. For PRs, the exact base and head SHAs come from
1108
+ the GitHub API, so a local `main` that is behind
1109
+ `origin/main` cannot pollute the results. For local branch
1110
+ reviews, branch names are used directly.
1111
+
1112
+ Commit logs use `git log base..head` semantics — only
1113
+ commits reachable from head but not from base. Review
1114
+ comment validation diffs from the merge base of the two
1115
+ refs, matching GitHub's three-dot diff.
1116
+
1117
+ ### Benchmarking
1118
+
1119
+ ```bash
1120
+ just bench # quick benchmark (current repo)
1121
+ just bench -- /path/to/repo main # custom repo
1122
+ just bench -- . main feature # with incremental update
1123
+ just bench-scalene -- /path/to/repo # line-level CPU + memory profiling
1124
+ just scalene-view # view last scalene profile
1125
+ ```
1126
+
1127
+ Detailed results and optimization notes are in `PROFILING.md`.
1128
+
1129
+ ### Search quality
1130
+
1131
+ The unified search system fuses three signals — name matching,
1132
+ BM25 keyword search, and semantic (embedding) similarity — with
1133
+ post-fusion adjustments for chunk kind and file category. Two
1134
+ scripts measure and tune search quality:
1135
+
1136
+ ```bash
1137
+ just eval-search # evaluate against curated queries
1138
+ just tune-search # grid-search fusion weights
1139
+ just tune-search -- --step 0.05 # finer resolution (400 combos)
1140
+ just bench-search # replay real queries (current dir)
1141
+ just bench-search -- /path/to/repo # replay for a specific repo
1142
+ ```
1143
+
1144
+ **`scripts/eval_search.py`** — Runs 24+ curated queries against
1145
+ the rbtr repo, measuring recall@1, recall@5, and MRR across
1146
+ three backends (name, BM25, unified). Queries are grouped by
1147
+ the technique they test (tokenisation, IDF, kind scoring, file
1148
+ category, name matching, query understanding, structural
1149
+ signals). Results are tracked in `TODO-search.md`.
1150
+
1151
+ **`scripts/tune_search.py`** — Grid-searches over fusion weight
1152
+ combinations for each query kind (identifier / concept).
1153
+ Precomputes all channel scores once, then sweeps in-memory —
1154
+ runs in ~1s. Reports the top 10 weight combos and the current
1155
+ settings for comparison.
1156
+
1157
+ **`scripts/bench_search.py`** — Mines real search queries from
1158
+ the session history database (`~/.config/rbtr/sessions.db`).
1159
+ Pass a repo path (defaults to current directory), and the
1160
+ script filters to events matching that repo by remote URL.
1161
+ Extracts search→read pairs (search followed by `read_symbol`),
1162
+ detects retry chains, classifies queries, and replays paired
1163
+ queries through the current search pipeline. Reports R@1, R@5,
1164
+ MRR, and per-query signal breakdowns for misranked results.
1165
+
1166
+ `eval_search` and `tune_search` are rbtr-specific — they
1167
+ validate the repo identity via `pyproject.toml` before running.
1168
+ `bench_search` works with any repo that has session history.
1169
+
1170
+ For search internals (fusion algorithm, scoring functions,
1171
+ tokenisation), see [ARCHITECTURE.md](ARCHITECTURE.md).
1172
+
1173
+ ## License
1174
+
1175
+ MIT