limbo-code 0.1.9__tar.gz → 0.1.11__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 (186) hide show
  1. {limbo_code-0.1.9 → limbo_code-0.1.11}/.github/workflows/test.yml +13 -1
  2. {limbo_code-0.1.9 → limbo_code-0.1.11}/AGENTS.md +26 -9
  3. limbo_code-0.1.11/Makefile +23 -0
  4. {limbo_code-0.1.9 → limbo_code-0.1.11}/PKG-INFO +34 -31
  5. {limbo_code-0.1.9 → limbo_code-0.1.11}/README.md +33 -22
  6. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/session-management.md +2 -2
  7. {limbo_code-0.1.9 → limbo_code-0.1.11}/pyproject.toml +5 -1
  8. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/agent.py +354 -26
  9. limbo_code-0.1.11/src/limbo/btw.py +72 -0
  10. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/config.py +10 -1
  11. limbo_code-0.1.11/src/limbo/goal.py +289 -0
  12. limbo_code-0.1.11/src/limbo/goal_driver.py +315 -0
  13. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/history.py +22 -5
  14. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/anthropic_client.py +23 -3
  15. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/catalog.py +18 -5
  16. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/openai_client.py +82 -4
  17. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/responses_client.py +39 -19
  18. limbo_code-0.1.11/src/limbo/llm/sse.py +49 -0
  19. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/models.py +9 -1
  20. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/prompt.py +9 -1
  21. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/sessions.py +3 -0
  22. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/tools/base.py +33 -4
  23. limbo_code-0.1.11/src/limbo/tools/bash.py +426 -0
  24. limbo_code-0.1.11/src/limbo/tools/edit.py +253 -0
  25. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/tools/find.py +28 -0
  26. limbo_code-0.1.11/src/limbo/tools/grep.py +147 -0
  27. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/tools/ls.py +24 -3
  28. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/tools/read.py +69 -7
  29. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/tools/registry.py +25 -3
  30. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/trace.py +10 -0
  31. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/app.tcss +49 -0
  32. limbo_code-0.1.11/src/limbo/ui/screens/btw.py +91 -0
  33. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/screens/main.py +340 -14
  34. limbo_code-0.1.11/src/limbo/ui/screens/verify_picker.py +59 -0
  35. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/widgets/chat.py +11 -0
  36. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/widgets/input.py +8 -7
  37. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/widgets/status_bar.py +41 -1
  38. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/widgets/tool_card.py +10 -0
  39. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_agent.py +282 -10
  40. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_anthropic_client.py +38 -0
  41. limbo_code-0.1.11/tests/test_btw.py +90 -0
  42. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_catalog.py +18 -1
  43. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_config.py +5 -5
  44. limbo_code-0.1.11/tests/test_goal.py +245 -0
  45. limbo_code-0.1.11/tests/test_goal_driver.py +357 -0
  46. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_history.py +27 -0
  47. limbo_code-0.1.11/tests/test_interrupt.py +564 -0
  48. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_llm_client.py +100 -2
  49. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_llm_scaffold.py +3 -3
  50. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_model_switch.py +10 -10
  51. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_prompt.py +18 -0
  52. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_responses_client.py +44 -0
  53. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_sessions.py +1 -1
  54. limbo_code-0.1.11/tests/test_sse.py +92 -0
  55. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_trace.py +13 -0
  56. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_base.py +5 -2
  57. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_bash.py +77 -2
  58. limbo_code-0.1.11/tests/tools/test_edit.py +289 -0
  59. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_find.py +37 -0
  60. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_grep.py +45 -37
  61. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_ls.py +19 -1
  62. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_read.py +66 -0
  63. limbo_code-0.1.11/tests/ui/__snapshots__/test_snapshots/test_snapshot_idle.raw +172 -0
  64. limbo_code-0.1.11/tests/ui/__snapshots__/test_snapshots/test_snapshot_thinking_and_running_tool.raw +174 -0
  65. limbo_code-0.1.11/tests/ui/__snapshots__/test_snapshots/test_snapshot_tool_error_and_error_message.raw +175 -0
  66. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_app_smoke.py +1 -1
  67. limbo_code-0.1.11/tests/ui/test_btw_ui.py +158 -0
  68. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_compact_ui.py +1 -1
  69. limbo_code-0.1.11/tests/ui/test_goal_ui.py +300 -0
  70. limbo_code-0.1.11/tests/ui/test_interrupt_ui.py +241 -0
  71. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_model_picker.py +16 -16
  72. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_steer_ui.py +21 -11
  73. {limbo_code-0.1.9 → limbo_code-0.1.11}/uv.lock +12 -9
  74. limbo_code-0.1.9/src/limbo/llm/sse.py +0 -31
  75. limbo_code-0.1.9/src/limbo/tools/bash.py +0 -204
  76. limbo_code-0.1.9/src/limbo/tools/edit.py +0 -74
  77. limbo_code-0.1.9/src/limbo/tools/grep.py +0 -190
  78. limbo_code-0.1.9/tests/tools/test_edit.py +0 -130
  79. limbo_code-0.1.9/tests/ui/__snapshots__/test_snapshots/test_snapshot_idle.raw +0 -172
  80. limbo_code-0.1.9/tests/ui/__snapshots__/test_snapshots/test_snapshot_thinking_and_running_tool.raw +0 -174
  81. limbo_code-0.1.9/tests/ui/__snapshots__/test_snapshots/test_snapshot_tool_error_and_error_message.raw +0 -175
  82. {limbo_code-0.1.9 → limbo_code-0.1.11}/.agents/skills/grill-with-docs/SKILL.md +0 -0
  83. {limbo_code-0.1.9 → limbo_code-0.1.11}/.agents/skills/grill-with-docs/agents/openai.yaml +0 -0
  84. {limbo_code-0.1.9 → limbo_code-0.1.11}/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +0 -0
  85. {limbo_code-0.1.9 → limbo_code-0.1.11}/.agents/skills/improve-codebase-architecture/SKILL.md +0 -0
  86. {limbo_code-0.1.9 → limbo_code-0.1.11}/.agents/skills/improve-codebase-architecture/agents/openai.yaml +0 -0
  87. {limbo_code-0.1.9 → limbo_code-0.1.11}/.agents/skills/tdd/SKILL.md +0 -0
  88. {limbo_code-0.1.9 → limbo_code-0.1.11}/.agents/skills/tdd/agents/openai.yaml +0 -0
  89. {limbo_code-0.1.9 → limbo_code-0.1.11}/.agents/skills/tdd/mocking.md +0 -0
  90. {limbo_code-0.1.9 → limbo_code-0.1.11}/.agents/skills/tdd/tests.md +0 -0
  91. {limbo_code-0.1.9 → limbo_code-0.1.11}/.github/workflows/publish.yml +0 -0
  92. {limbo_code-0.1.9 → limbo_code-0.1.11}/.gitignore +0 -0
  93. {limbo_code-0.1.9 → limbo_code-0.1.11}/CONTEXT.md +0 -0
  94. {limbo_code-0.1.9 → limbo_code-0.1.11}/design/confirm_view.html +0 -0
  95. {limbo_code-0.1.9 → limbo_code-0.1.11}/design/limbo-ui-minimal.md +0 -0
  96. {limbo_code-0.1.9 → limbo_code-0.1.11}/design/limbo-ui-redesign.md +0 -0
  97. {limbo_code-0.1.9 → limbo_code-0.1.11}/design/prototype-minimal-confirm.png +0 -0
  98. {limbo_code-0.1.9 → limbo_code-0.1.11}/design/prototype-minimal.html +0 -0
  99. {limbo_code-0.1.9 → limbo_code-0.1.11}/design/prototype-minimal.png +0 -0
  100. {limbo_code-0.1.9 → limbo_code-0.1.11}/design/prototype.html +0 -0
  101. {limbo_code-0.1.9 → limbo_code-0.1.11}/design/prototype.png +0 -0
  102. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/limbo-current-ui.png +0 -0
  103. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/limbo-new-ui.png +0 -0
  104. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-dark/1_idle.svg +0 -0
  105. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-dark/2_thinking_tool_running.svg +0 -0
  106. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-dark/3_tool_success.svg +0 -0
  107. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-dark/4_tool_error.svg +0 -0
  108. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-dark/5_llm_error.svg +0 -0
  109. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-dark/6_edit_diff.svg +0 -0
  110. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-light/1_idle.svg +0 -0
  111. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-light/2_thinking_tool_running.svg +0 -0
  112. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-light/3_tool_success.svg +0 -0
  113. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-light/4_tool_error.svg +0 -0
  114. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-light/5_llm_error.svg +0 -0
  115. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/assets/walkthrough/limbo-light/6_edit_diff.svg +0 -0
  116. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/skills.md +0 -0
  117. {limbo_code-0.1.9 → limbo_code-0.1.11}/docs/ui-redesign-proposal.md +0 -0
  118. {limbo_code-0.1.9 → limbo_code-0.1.11}/scripts/check_contrast.py +0 -0
  119. {limbo_code-0.1.9 → limbo_code-0.1.11}/scripts/gen_banner.py +0 -0
  120. {limbo_code-0.1.9 → limbo_code-0.1.11}/scripts/ui_walkthrough.py +0 -0
  121. {limbo_code-0.1.9 → limbo_code-0.1.11}/skills-lock.json +0 -0
  122. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/__init__.py +0 -0
  123. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/__main__.py +0 -0
  124. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/app.py +0 -0
  125. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/attachments.py +0 -0
  126. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/compaction.py +0 -0
  127. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/__init__.py +0 -0
  128. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/client.py +0 -0
  129. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/factory.py +0 -0
  130. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/retry.py +0 -0
  131. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/scaffold.py +0 -0
  132. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/llm/usage.py +0 -0
  133. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/model_switch.py +0 -0
  134. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/skills.py +0 -0
  135. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/steer.py +0 -0
  136. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/tools/__init__.py +0 -0
  137. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/tools/ignore.py +0 -0
  138. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/tools/mutation_queue.py +0 -0
  139. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/tools/write.py +0 -0
  140. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/__init__.py +0 -0
  141. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/app.py +0 -0
  142. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/banner.py +0 -0
  143. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/clipboard.py +0 -0
  144. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/commands.py +0 -0
  145. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/contrast.py +0 -0
  146. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/screens/__init__.py +0 -0
  147. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/screens/game2048.py +0 -0
  148. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/screens/model_picker.py +0 -0
  149. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/screens/session_picker.py +0 -0
  150. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/syntax.py +0 -0
  151. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/theme.py +0 -0
  152. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/widgets/__init__.py +0 -0
  153. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/ui/widgets/command_menu.py +0 -0
  154. {limbo_code-0.1.9 → limbo_code-0.1.11}/src/limbo/user_paths.py +0 -0
  155. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/conftest.py +0 -0
  156. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_attachments.py +0 -0
  157. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_cli.py +0 -0
  158. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_clipboard.py +0 -0
  159. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_compaction.py +0 -0
  160. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_integration.py +0 -0
  161. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_models.py +0 -0
  162. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_retry.py +0 -0
  163. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_skills.py +0 -0
  164. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_steer.py +0 -0
  165. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_usage.py +0 -0
  166. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/test_user_paths.py +0 -0
  167. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_ignore.py +0 -0
  168. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_mutation_queue.py +0 -0
  169. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_registry.py +0 -0
  170. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/tools/test_write.py +0 -0
  171. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/__snapshots__/test_snapshots/test_snapshot_session_picker.raw +0 -0
  172. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_command_menu.py +0 -0
  173. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_commands.py +0 -0
  174. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_contrast.py +0 -0
  175. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_game2048.py +0 -0
  176. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_input_attachments.py +0 -0
  177. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_input_history.py +0 -0
  178. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_input_paste.py +0 -0
  179. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_main_screen.py +0 -0
  180. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_scroll_follow.py +0 -0
  181. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_sessions_ui.py +0 -0
  182. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_skills_ui.py +0 -0
  183. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_snapshots.py +0 -0
  184. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_startup_art.py +0 -0
  185. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_theme.py +0 -0
  186. {limbo_code-0.1.9 → limbo_code-0.1.11}/tests/ui/test_widgets.py +0 -0
@@ -25,12 +25,24 @@ jobs:
25
25
  with:
26
26
  python-version: ${{ matrix.python-version }}
27
27
 
28
+ # grep hard-depends on ripgrep (no Python fallback since LIM-24).
29
+ - name: Install ripgrep
30
+ run: sudo apt-get install -y ripgrep
31
+
28
32
  # Install from uv.lock so CI renders snapshots with the exact same
29
33
  # textual/rich/syrupy versions the baselines were generated with.
34
+ # Dev tools (pytest/ruff/mypy) are a PEP 735 dependency group and are
35
+ # installed by default — no --extra flag.
30
36
  - name: Install dependencies
31
- run: uv sync --frozen --extra dev
37
+ run: uv sync --frozen
32
38
  env:
33
39
  UV_PYTHON: ${{ matrix.python-version }}
34
40
 
35
41
  - name: Run tests
36
42
  run: uv run python -m pytest -v
43
+
44
+ - name: Lint
45
+ run: uv run ruff check src tests
46
+
47
+ - name: Type check
48
+ run: uv run mypy src
@@ -2,15 +2,19 @@
2
2
 
3
3
  **Limbo** is a minimal terminal AI coding agent (Python 3.11+, Textual). Users converse with an LLM in a TUI to explore, read, edit, and write code via 7 tools.
4
4
 
5
- **Tools execute immediately — no confirmation flow.** Guardrails: a workdir fence on file tools (+ session-scoped grants from user-mentioned paths), a sensitive-file blocklist on `read`, and a heuristic dangerous-command filter on `bash` (rejects matches outright; bypassable via subshells/variables — documented in the tool description).
5
+ **Tools execute immediately — no confirmation flow.** Convenience guardrails (not a security boundary): a workdir scope on file tools (+ session-scoped grants from user-mentioned paths), a sensitive-file skip list shared by `read`/`grep`/`find`, and a heuristic dangerous-command filter on `bash` (best-effort, rejects matches outright; `bash` is otherwise not covered by any guardrail).
6
6
 
7
7
  ## Quick Start
8
8
 
9
9
  ```bash
10
- pip install -e .
11
- limbo --workdir /path/to/project # config: ~/.limbo/config.toml
10
+ uv sync # installs runtime + dev tools (PEP 735 dev group, on by default)
11
+ uv run limbo --workdir /path/to/project # config: ~/.limbo/config.toml
12
12
  ```
13
13
 
14
+ Prefer `uv` over `pip`: dev tools (pytest/ruff/mypy) are a `[dependency-groups]`
15
+ group, NOT an extra — `pip install -e ".[dev]"` no longer exists. `pip install -e .`
16
+ still works for a runtime-only install.
17
+
14
18
  ## Architecture
15
19
 
16
20
  ```
@@ -25,6 +29,8 @@ src/limbo/
25
29
  ├── model_switch.py # /model domain logic: validate, swap client, persist (UI is an adapter)
26
30
  ├── prompt.py # System-prompt assembly (tools section derived from the registry)
27
31
  ├── steer.py # Mid-turn steer queue (LIM-20): queueing semantics, cancel boundary
32
+ ├── goal.py # /goal closed-loop state machine, prompt templates, verify executor (LIM-40)
33
+ ├── goal_driver.py # /goal orchestrator (frontend-agnostic, non-UI): turn → verify → next round
28
34
  ├── sessions.py # Session JSONL save/load/list/export
29
35
  ├── trace.py # Append-only JSONL run log (sessions/traces/)
30
36
  ├── skills.py # SKILL.md discovery (user + project dirs)
@@ -33,7 +39,7 @@ src/limbo/
33
39
  │ # openai_client.py, anthropic_client.py, responses_client.py, retry.py, sse.py,
34
40
  │ # usage.py (token accounting: usage normalization + prompt-size estimation),
35
41
  │ # scaffold.py (plumbing shared by dialect clients: credentials, retry, images)
36
- ├── tools/ # base.py (BaseTool + fence + truncation), registry.py (dispatch, grants),
42
+ ├── tools/ # base.py (BaseTool + path guardrail + truncation), registry.py (dispatch, grants),
37
43
  │ # mutation_queue.py (per-file locks), ignore.py (.gitignore),
38
44
  │ # read/bash/edit/write/grep/find/ls.py
39
45
  └── ui/ # app.py + app.tcss (ALL styles here; theme vars only, no bare hex),
@@ -47,7 +53,7 @@ src/limbo/
47
53
  - `Agent.run()` yields `AgentEvent`: `TextDelta`, `ThinkingDelta`, `ToolCallRequest`, `ToolResultEvent`, `ErrorEvent`, `CompactionEvent`, `UsageUpdate`, `SteerEvent`
48
54
  - Loop top: auto-compaction check → steer drain → LLM call. Turn ends on a tool-call-free response; `max_iterations` (default 50) cancels pending calls with placeholder results
49
55
  - Tool calls in one turn run **concurrently** (`[tools] parallel`); results stream in completion order, recorded in source order; same-file mutations serialized by `mutation_queue`
50
- - `finish_reason` `length`/`max_tokens` → whole batch failed without execution, model re-issues
56
+ - `finish_reason` `length`/`max_tokens` → whole batch failed without execution; the error result names the effective `max_tokens` and steers the model to write large scripts via `write` instead of re-issuing the same call. 3 consecutive length stops abort the turn with actionable guidance (trace `kind=length_stop_loop`); a text-only truncated response emits a "may be incomplete" warning
51
57
  - Mid-turn user input queues as *steer* (LIM-20): injected at loop top, or as turn-end follow-up
52
58
  - Reasoning is stored on assistant messages and replayed per dialect (Anthropic thinking signatures, Kimi `reasoning_content`, Responses encrypted items)
53
59
  - Usage counters normalize in `llm/usage.py` (`input_tokens`+`cache_read`, `prompt_tokens`, DeepSeek cache hits) and feed the compaction trigger via `PromptSizeEstimator`
@@ -61,6 +67,7 @@ Providers/models live in `llm/catalog.py`; unknown models get generic OpenAI-com
61
67
  # max_tokens, thinking_effort, max_retries, timeout, ...
62
68
  [tools] # bash_enabled = true, parallel = true
63
69
  [compaction] # enabled, reserve_tokens = 16384, keep_recent_tokens = 20000
70
+ [goal] # max_rounds = 10, verify_timeout_ms = 600000
64
71
  [safety] # dangerous_commands, sensitive_files, auto_grant_user_paths
65
72
  [ui] # theme, show_banner
66
73
  [providers.<id>] # base_url / api_key / api_key_env / headers
@@ -72,7 +79,9 @@ Skills: `~/.limbo/skills/<name>/SKILL.md` (user) and `<workdir>/.agents/skills/<
72
79
 
73
80
  ## UI
74
81
 
75
- Single column: status bar (state/elapsed/tokens/model/workdir/queued) → scrolling chat flow (user `❯`, streaming Markdown, tool cards, errors) → input box (Enter submits, Shift+Enter newline, paste markers, `ctrl+v` image attach) → hint line. Slash commands: `/sessions /new /export /compact /model /help /2048` + skill commands. After palette changes run `python scripts/check_contrast.py` (≥ 4.5:1).
82
+ Single column: status bar (state/elapsed/tokens/model/workdir/queued + rainbow 🎯GOAL badge while a goal loop runs) → scrolling chat flow (user `❯`, streaming Markdown, tool cards, errors) → input box (Enter submits, Shift+Enter newline, paste markers, `ctrl+v` image attach) → hint line. Slash commands: `/sessions /new /export /compact /model /help /2048 /goal` + skill commands. After palette changes run `python scripts/check_contrast.py` (≥ 4.5:1).
83
+
84
+ `/goal <text>` starts closed-loop mode (LIM-40): the first round is a proposal round — the model explores the repo and proposes acceptance command(s) in a `<verify_proposal>` block; the user confirms in a picker (accept / edit / skip), then `GoalDriver` (non-UI, shared by any frontend) runs each round as a complete `Agent.run()` turn and executes the verify command; exit 0 ends the loop, otherwise the failure output is fed back verbatim into the next round until `max_rounds`, when a no-tool wrap-up turn summarizes and any user message resumes with a fresh budget. Goal state persists on `SessionMeta.goal`. Esc during verify cancels the subprocess.
76
85
 
77
86
  ## Key Decisions
78
87
 
@@ -83,9 +92,17 @@ Single column: status bar (state/elapsed/tokens/model/workdir/queued) → scroll
83
92
  ## Testing
84
93
 
85
94
  ```bash
86
- pytest tests/ -v # pytest-asyncio, respx HTTP mocks, pytest-textual-snapshot
87
- ruff check src tests && mypy src # lint + type check
88
- pytest --snapshot-update # after intentional visual changes; review the SVG diff
95
+ make check # single entrypoint: sync + tests + lint + type check
96
+ ```
97
+
98
+ Or individually — always `python -m pytest`, never bare `uv run pytest` (which
99
+ silently falls through to a global pytest on PATH when the venv lacks pytest):
100
+
101
+ ```bash
102
+ uv sync # first time / after pulling; dev group is default
103
+ uv run python -m pytest tests/ -v # pytest-asyncio, respx HTTP mocks, pytest-textual-snapshot
104
+ uv run ruff check src tests && uv run mypy src # lint + type check
105
+ uv run python -m pytest --snapshot-update # after intentional visual changes; review the SVG diff
89
106
  ```
90
107
 
91
108
  ## Adding Things
@@ -0,0 +1,23 @@
1
+ # Single entrypoint for dev checks — humans and agents should only need
2
+ # `make check`. Dev tools (pytest/ruff/mypy) are a PEP 735 dependency group,
3
+ # so plain `uv sync` installs them.
4
+ #
5
+ # Note: tests run via `uv run python -m pytest`, NOT `uv run pytest` — the
6
+ # latter silently falls through to a global pytest on PATH when the venv
7
+ # lacks pytest, which can run the suite against the wrong source tree.
8
+
9
+ .PHONY: sync test lint typecheck check
10
+
11
+ sync:
12
+ uv sync
13
+
14
+ test:
15
+ uv run python -m pytest tests/ -v
16
+
17
+ lint:
18
+ uv run ruff check src tests
19
+
20
+ typecheck:
21
+ uv run mypy src
22
+
23
+ check: sync test lint typecheck
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: limbo-code
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: A minimal terminal AI coding agent
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: httpx>=0.27
@@ -10,14 +10,6 @@ Requires-Dist: pygments>=2.17
10
10
  Requires-Dist: textual>=8.0
11
11
  Requires-Dist: toml>=0.10
12
12
  Requires-Dist: tomlkit>=0.13
13
- Provides-Extra: dev
14
- Requires-Dist: mypy>=1.10; extra == 'dev'
15
- Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
16
- Requires-Dist: pytest-textual-snapshot>=1.0; extra == 'dev'
17
- Requires-Dist: pytest>=8.0; extra == 'dev'
18
- Requires-Dist: respx>=0.21; extra == 'dev'
19
- Requires-Dist: ruff>=0.4; extra == 'dev'
20
- Requires-Dist: types-pygments>=2.17; extra == 'dev'
21
13
  Description-Content-Type: text/markdown
22
14
 
23
15
  # Limbo
@@ -29,7 +21,7 @@ A minimal terminal AI coding agent.
29
21
  Requires Python 3.11 or later.
30
22
 
31
23
  ```bash
32
- pip install -e .
24
+ pip install limbo-code
33
25
  ```
34
26
 
35
27
  ## Configure
@@ -39,7 +31,7 @@ Create `~/.limbo/config.toml`:
39
31
  ```toml
40
32
  [llm]
41
33
  api_key = "your-api-key"
42
- model = "deepseek-chat"
34
+ model = "deepseek-v4-pro"
43
35
  base_url = "https://api.deepseek.com/v1"
44
36
  ```
45
37
 
@@ -226,23 +218,24 @@ flight.
226
218
  ## Safety
227
219
 
228
220
  Limbo executes every tool call immediately, without asking for confirmation.
229
- File tools (`read`, `edit`, `write`, `grep`, `find`, `ls`) are bounded to the
230
- current working directory and reject paths that escape it, including via
231
- symlinks. The boundary check resolves the path before each operation, so a
232
- symlink swapped between the check and the operation (a time-of-check-to-time-of-use
233
- race) could escape the workdir. **This is a known limitation for the MVP.**
221
+
222
+ File tools (`read`, `edit`, `write`, `grep`, `find`, `ls`) are scoped to the
223
+ current working directory plus any session grants (paths the user mentions in
224
+ a message), and `read`/`grep`/`find` skip a small blocklist of sensitive files
225
+ (`.env`, SSH keys) to avoid leaking secrets into the model context by accident.
226
+ **These are convenience guardrails against accidents, not a security boundary.**
227
+ The path check resolves the path before each operation, so a symlink swapped
228
+ between the check and the operation (a time-of-check-to-time-of-use race) could
229
+ escape it.
234
230
 
235
231
  Bash is an exception: it is started in the working directory but is **not**
236
- sandboxed. Commands can `cd ..`, use absolute paths, and read or write outside
237
- the workdir. In addition, commands that match dangerous patterns such as `rm`
238
- or `git reset --hard` are **rejected outright**.
239
- The pattern list is configurable but cannot be disabled from the UI. Bash
240
- commands are filtered with a simple heuristic, but that filter can be bypassed
241
- by subshells (`bash -c 'rm -rf /'`), command substitution (`$(rm -rf /)`),
242
- variable indirection, options before the command name
243
- (`git -C /foo reset --hard`), variable assignments before the command name
244
- (`VAR=1 rm -rf /`), and similar shell constructs. Only run Limbo with
245
- trusted commands and in repositories you can afford to modify or lose.
232
+ covered by any of these guardrails. Commands can `cd ..`, use absolute paths,
233
+ and read or write outside the workdir. Commands that match dangerous patterns
234
+ such as `rm` or `git reset --hard` are **rejected outright** by a best-effort
235
+ heuristic filter — ordinary shell constructs can bypass it, so it only
236
+ protects against accidents. The pattern list is configurable but cannot be
237
+ disabled from the UI. Only run Limbo with trusted commands and in
238
+ repositories you can afford to modify or lose.
246
239
 
247
240
  If you need to work with untrusted projects, disable the bash tool entirely:
248
241
 
@@ -253,15 +246,25 @@ bash_enabled = false
253
246
 
254
247
  ## Development
255
248
 
256
- Run tests:
249
+ Set up the environment (dev tools are a PEP 735 dependency group, so plain
250
+ `uv sync` installs pytest/ruff/mypy — no `--extra` flag):
251
+
252
+ ```bash
253
+ uv sync
254
+ ```
255
+
256
+ Run everything (sync + tests + lint + type check) with one command:
257
257
 
258
258
  ```bash
259
- pytest tests/ -v
259
+ make check
260
260
  ```
261
261
 
262
- Run linting and type checks:
262
+ Or run the pieces individually. Always use `python -m pytest`: `uv run pytest`
263
+ silently falls through to a global pytest on PATH when the venv lacks pytest,
264
+ which can run the suite against the wrong source tree.
263
265
 
264
266
  ```bash
265
- ruff check src tests
266
- mypy src
267
+ uv run python -m pytest tests/ -v # tests
268
+ uv run ruff check src tests # lint
269
+ uv run mypy src # type check
267
270
  ```
@@ -7,7 +7,7 @@ A minimal terminal AI coding agent.
7
7
  Requires Python 3.11 or later.
8
8
 
9
9
  ```bash
10
- pip install -e .
10
+ pip install limbo-code
11
11
  ```
12
12
 
13
13
  ## Configure
@@ -17,7 +17,7 @@ Create `~/.limbo/config.toml`:
17
17
  ```toml
18
18
  [llm]
19
19
  api_key = "your-api-key"
20
- model = "deepseek-chat"
20
+ model = "deepseek-v4-pro"
21
21
  base_url = "https://api.deepseek.com/v1"
22
22
  ```
23
23
 
@@ -204,23 +204,24 @@ flight.
204
204
  ## Safety
205
205
 
206
206
  Limbo executes every tool call immediately, without asking for confirmation.
207
- File tools (`read`, `edit`, `write`, `grep`, `find`, `ls`) are bounded to the
208
- current working directory and reject paths that escape it, including via
209
- symlinks. The boundary check resolves the path before each operation, so a
210
- symlink swapped between the check and the operation (a time-of-check-to-time-of-use
211
- race) could escape the workdir. **This is a known limitation for the MVP.**
207
+
208
+ File tools (`read`, `edit`, `write`, `grep`, `find`, `ls`) are scoped to the
209
+ current working directory plus any session grants (paths the user mentions in
210
+ a message), and `read`/`grep`/`find` skip a small blocklist of sensitive files
211
+ (`.env`, SSH keys) to avoid leaking secrets into the model context by accident.
212
+ **These are convenience guardrails against accidents, not a security boundary.**
213
+ The path check resolves the path before each operation, so a symlink swapped
214
+ between the check and the operation (a time-of-check-to-time-of-use race) could
215
+ escape it.
212
216
 
213
217
  Bash is an exception: it is started in the working directory but is **not**
214
- sandboxed. Commands can `cd ..`, use absolute paths, and read or write outside
215
- the workdir. In addition, commands that match dangerous patterns such as `rm`
216
- or `git reset --hard` are **rejected outright**.
217
- The pattern list is configurable but cannot be disabled from the UI. Bash
218
- commands are filtered with a simple heuristic, but that filter can be bypassed
219
- by subshells (`bash -c 'rm -rf /'`), command substitution (`$(rm -rf /)`),
220
- variable indirection, options before the command name
221
- (`git -C /foo reset --hard`), variable assignments before the command name
222
- (`VAR=1 rm -rf /`), and similar shell constructs. Only run Limbo with
223
- trusted commands and in repositories you can afford to modify or lose.
218
+ covered by any of these guardrails. Commands can `cd ..`, use absolute paths,
219
+ and read or write outside the workdir. Commands that match dangerous patterns
220
+ such as `rm` or `git reset --hard` are **rejected outright** by a best-effort
221
+ heuristic filter — ordinary shell constructs can bypass it, so it only
222
+ protects against accidents. The pattern list is configurable but cannot be
223
+ disabled from the UI. Only run Limbo with trusted commands and in
224
+ repositories you can afford to modify or lose.
224
225
 
225
226
  If you need to work with untrusted projects, disable the bash tool entirely:
226
227
 
@@ -231,15 +232,25 @@ bash_enabled = false
231
232
 
232
233
  ## Development
233
234
 
234
- Run tests:
235
+ Set up the environment (dev tools are a PEP 735 dependency group, so plain
236
+ `uv sync` installs pytest/ruff/mypy — no `--extra` flag):
237
+
238
+ ```bash
239
+ uv sync
240
+ ```
241
+
242
+ Run everything (sync + tests + lint + type check) with one command:
235
243
 
236
244
  ```bash
237
- pytest tests/ -v
245
+ make check
238
246
  ```
239
247
 
240
- Run linting and type checks:
248
+ Or run the pieces individually. Always use `python -m pytest`: `uv run pytest`
249
+ silently falls through to a global pytest on PATH when the venv lacks pytest,
250
+ which can run the suite against the wrong source tree.
241
251
 
242
252
  ```bash
243
- ruff check src tests
244
- mypy src
253
+ uv run python -m pytest tests/ -v # tests
254
+ uv run ruff check src tests # lint
255
+ uv run mypy src # type check
245
256
  ```
@@ -26,7 +26,7 @@
26
26
  JSONL,首行为 meta,后续为消息行。**向后兼容**:无 meta 行的旧文件可正常加载。
27
27
 
28
28
  ```jsonl
29
- {"type":"meta","id":"20260720-103000-123456-42","workdir":"/path/to/proj","model":"deepseek-chat","title":"修复 grep 工具 bug","created_at":"...","updated_at":"..."}
29
+ {"type":"meta","id":"20260720-103000-123456-42","workdir":"/path/to/proj","model":"deepseek-v4-pro","title":"修复 grep 工具 bug","created_at":"...","updated_at":"..."}
30
30
  {"role":"system","content":"..."}
31
31
  {"role":"user","content":"..."}
32
32
  {"role":"assistant","content":"...","tool_calls":[...]}
@@ -107,7 +107,7 @@ append-only,崩溃/打断最多丢失写入中的一条)。`/export` 默认
107
107
  | `llm_response` | `duration`、`ttft`、`finish_reason`、`usage`(原始用量,含 provider 缓存字段)、`cached_tokens`(归一化缓存命中)、content/reasoning 长度、tool_calls 摘要 |
108
108
  | `llm_error` | `exception_type`、`error`、`traceback` |
109
109
  | `tool_call` / `tool_result` | `id`、`name`、`arguments`、`success`、`output`/`error`、耗时;崩溃时带 `exception_type` + `traceback` |
110
- | `error` | `kind`(如 max_iterations)、`message` |
110
+ | `error` | `kind`(如 max_iterations / length_stop / length_stop_loop)、`message` |
111
111
  | `turn_end` | `iterations`、`duration`、`status`(completed) |
112
112
  | `session_save_error` | `error` |
113
113
 
@@ -16,7 +16,11 @@ dependencies = [
16
16
  "pygments>=2.17",
17
17
  ]
18
18
 
19
- [project.optional-dependencies]
19
+ [dependency-groups]
20
+ # Dev tools live in a PEP 735 dependency group (not an extra) so `uv sync`
21
+ # installs them by default — no `--extra`/`--group` flag needed. Always run
22
+ # tests via `uv run python -m pytest` so a missing venv fails loudly instead
23
+ # of silently falling through to a global pytest on PATH.
20
24
  dev = [
21
25
  "pytest>=8.0",
22
26
  "pytest-asyncio>=0.23",