cli-agent-runner 0.2.1__tar.gz → 0.2.2__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 (273) hide show
  1. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/.github/workflows/ci.yml +4 -0
  2. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/.gitignore +5 -0
  3. cli_agent_runner-0.2.2/.vulture-whitelist.py +244 -0
  4. cli_agent_runner-0.2.2/BACKLOG.md +90 -0
  5. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/CHANGELOG.md +23 -0
  6. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/CONTRIBUTING.md +26 -0
  7. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/PKG-INFO +7 -7
  8. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/README.md +6 -5
  9. cli_agent_runner-0.2.2/README.zh.md +37 -0
  10. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/SECURITY.md +17 -0
  11. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/_docgen.py +45 -19
  12. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/_throttle.py +13 -4
  13. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/_version.py +2 -2
  14. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/agent_runtime.py +0 -19
  15. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/api.py +11 -17
  16. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/builtin_plugins/default_dirty_handler.py +20 -12
  17. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/events_cmd.py +4 -2
  18. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/install_cmd.py +1 -3
  19. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/serve_cmd.py +4 -25
  20. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/service_cmd.py +1 -7
  21. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/upgrade_cmd.py +1 -1
  22. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/config.py +62 -28
  23. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/context_store.py +0 -23
  24. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/defenses.py +21 -11
  25. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/events.py +0 -1
  26. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/hooks.py +10 -9
  27. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/http_progress.py +1 -1
  28. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/lifecycle.py +1 -1
  29. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/monitor.py +28 -18
  30. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/prompt_loader.py +0 -7
  31. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/round_view.py +3 -2
  32. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/runner.py +12 -8
  33. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/vcs_state.py +162 -108
  34. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/build.sh +13 -1
  35. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/README.md +12 -5
  36. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/architecture.md +16 -14
  37. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/commands.md +9 -9
  38. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/configuration.md +43 -20
  39. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/events.md +1 -1
  40. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.23.md +7 -0
  41. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.26.md +6 -0
  42. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.27.md +7 -0
  43. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.32.md +11 -0
  44. cli_agent_runner-0.2.2/docs/migrations/0.2.2.md +242 -0
  45. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/plugins.md +111 -142
  46. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/quickstart.md +3 -4
  47. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/recipes/aider.md +5 -3
  48. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/runbook.md +3 -7
  49. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/thesis.md +1 -1
  50. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/pyproject.toml +22 -8
  51. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/contract/test_public_api_surface.py +25 -12
  52. cli_agent_runner-0.2.2/tests/generate_vulture_whitelist.py +82 -0
  53. cli_agent_runner-0.2.2/tests/integration/test_fresh_eyes_signal.py +135 -0
  54. cli_agent_runner-0.2.2/tests/integration/test_plugin_owned_paths.py +279 -0
  55. cli_agent_runner-0.2.2/tests/invariants/_event_scan.py +94 -0
  56. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_architecture.py +1 -1
  57. cli_agent_runner-0.2.2/tests/invariants/test_auto_stop_policy_ssot.py +39 -0
  58. cli_agent_runner-0.2.2/tests/invariants/test_catalogs.py +82 -0
  59. cli_agent_runner-0.2.2/tests/invariants/test_config_error_consistency.py +73 -0
  60. cli_agent_runner-0.2.2/tests/invariants/test_config_value_set_ssot.py +64 -0
  61. cli_agent_runner-0.2.2/tests/invariants/test_doc_builtin_plugins.py +42 -0
  62. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_doc_claims_match_ssot.py +34 -5
  63. cli_agent_runner-0.2.2/tests/invariants/test_doc_cli_claims.py +122 -0
  64. cli_agent_runner-0.2.2/tests/invariants/test_doc_operator_surface.py +83 -0
  65. cli_agent_runner-0.2.2/tests/invariants/test_doc_transient_error_claims.py +58 -0
  66. cli_agent_runner-0.2.2/tests/invariants/test_docs_index_complete.py +34 -0
  67. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_event_kind_registry.py +16 -24
  68. cli_agent_runner-0.2.2/tests/invariants/test_event_kinds_ssot.py +151 -0
  69. cli_agent_runner-0.2.2/tests/invariants/test_hook_contract_docs.py +77 -0
  70. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_module_boundaries.py +39 -15
  71. cli_agent_runner-0.2.2/tests/invariants/test_plugins_config_stable.py +38 -0
  72. cli_agent_runner-0.2.2/tests/invariants/test_readme_zh_is_pointer.py +54 -0
  73. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_round_result_stable.py +1 -0
  74. cli_agent_runner-0.2.2/tests/invariants/test_set_diff_for_auto_tool_classification.py +97 -0
  75. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_stash_uses_sha_not_index.py +3 -12
  76. cli_agent_runner-0.2.2/tests/invariants/test_vulture_whitelist_generated.py +19 -0
  77. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_agent_runtime.py +1 -13
  78. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_api_service.py +0 -15
  79. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_cli_service_peek_monitor.py +0 -10
  80. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_config.py +102 -0
  81. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_context_store.py +0 -36
  82. cli_agent_runner-0.2.2/tests/unit/test_default_dirty_handler.py +177 -0
  83. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_defenses.py +59 -3
  84. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_docgen.py +66 -1
  85. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_events.py +0 -1
  86. cli_agent_runner-0.2.2/tests/unit/test_events_cmd_tail.py +73 -0
  87. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_prompt_loader.py +0 -12
  88. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_runner.py +61 -4
  89. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_serve_cmd_bounded.py +2 -3
  90. cli_agent_runner-0.2.2/tests/unit/test_throttle_plugin_classification.py +42 -0
  91. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_vcs_state.py +29 -38
  92. cli_agent_runner-0.2.1/.vulture-whitelist.py +0 -6
  93. cli_agent_runner-0.2.1/README.zh.md +0 -157
  94. cli_agent_runner-0.2.1/agent_runner/detector_helpers.py +0 -115
  95. cli_agent_runner-0.2.1/deploy/example-agent-runner.toml +0 -24
  96. cli_agent_runner-0.2.1/deploy/launchd.plist.tmpl +0 -39
  97. cli_agent_runner-0.2.1/deploy/run-loop.sh +0 -27
  98. cli_agent_runner-0.2.1/deploy/systemd.service.tmpl +0 -15
  99. cli_agent_runner-0.2.1/docs/marketing/README.md +0 -34
  100. cli_agent_runner-0.2.1/docs/marketing/promo-cn.html +0 -287
  101. cli_agent_runner-0.2.1/tests/integration/test_fresh_eyes_signal.py +0 -69
  102. cli_agent_runner-0.2.1/tests/integration/test_plugin_owned_paths.py +0 -88
  103. cli_agent_runner-0.2.1/tests/invariants/test_catalogs.py +0 -228
  104. cli_agent_runner-0.2.1/tests/invariants/test_event_kinds_ssot.py +0 -41
  105. cli_agent_runner-0.2.1/tests/unit/test_default_dirty_handler.py +0 -77
  106. cli_agent_runner-0.2.1/tests/unit/test_detector_helpers.py +0 -141
  107. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/.codecov.yml +0 -0
  108. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  109. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  110. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  111. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  112. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/.github/workflows/release.yml +0 -0
  113. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/CODE_OF_CONDUCT.md +0 -0
  114. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/LICENSE +0 -0
  115. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/__init__.py +0 -0
  116. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/_emit.py +0 -0
  117. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/_redact.py +0 -0
  118. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/_registry.py +0 -0
  119. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/_substrate.py +0 -0
  120. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/api_types.py +0 -0
  121. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/builtin_plugins/__init__.py +0 -0
  122. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/builtin_plugins/_constants.py +0 -0
  123. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  124. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/builtin_plugins/codewhale.py +0 -0
  125. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/builtin_plugins/gemini.py +0 -0
  126. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/__init__.py +0 -0
  127. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/__main__.py +0 -0
  128. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/common.py +0 -0
  129. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/init_cmd.py +0 -0
  130. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/monitor_cmd.py +0 -0
  131. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/peek_cmd.py +0 -0
  132. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/cli/round_cmd.py +0 -0
  133. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/metrics.py +0 -0
  134. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/presets/__init__.py +0 -0
  135. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/presets/aider.toml +0 -0
  136. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/presets/claude.toml +0 -0
  137. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/presets/codewhale.toml +0 -0
  138. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/presets/gemini.toml +0 -0
  139. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/round_log.py +0 -0
  140. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/scaffold.py +0 -0
  141. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/service_unit.py +0 -0
  142. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/agent_runner/startup_check.py +0 -0
  143. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/long-running-agents.md +0 -0
  144. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.16.md +0 -0
  145. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.17.md +0 -0
  146. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.19.md +0 -0
  147. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.20.md +0 -0
  148. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.21.md +0 -0
  149. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.22.md +0 -0
  150. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.24.md +0 -0
  151. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.25.md +0 -0
  152. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.28.md +0 -0
  153. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.29.md +0 -0
  154. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.30.md +0 -0
  155. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.31.md +0 -0
  156. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.33.md +0 -0
  157. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.34.md +0 -0
  158. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.35.md +0 -0
  159. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.36.md +0 -0
  160. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.37.md +0 -0
  161. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.38.md +0 -0
  162. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.39.md +0 -0
  163. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.40.md +0 -0
  164. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.1.42.md +0 -0
  165. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.2.0.md +0 -0
  166. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/migrations/0.2.1.md +0 -0
  167. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/docs/recipes/codewhale.md +0 -0
  168. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/__init__.py +0 -0
  169. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/_test_helpers.py +0 -0
  170. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/conftest.py +0 -0
  171. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/contract/__init__.py +0 -0
  172. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/e2e/__init__.py +0 -0
  173. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/e2e/conftest.py +0 -0
  174. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  175. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/e2e/test_e2e_install_systemd.py +0 -0
  176. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  177. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  178. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  179. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  180. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  181. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/__init__.py +0 -0
  182. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_bounded_run.py +0 -0
  183. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_context_enricher_namespacing.py +0 -0
  184. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_dirty_handler_seam.py +0 -0
  185. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_grace_kill_emission.py +0 -0
  186. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_install_dry_run.py +0 -0
  187. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_monitor_seeded.py +0 -0
  188. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_plugin_detector_loaded.py +0 -0
  189. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_plugin_real_flow.py +0 -0
  190. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  191. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  192. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_scaffold_presets.py +0 -0
  193. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_serve_loop.py +0 -0
  194. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_substrate_fingerprint.py +0 -0
  195. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/integration/test_transient_error_backoff.py +0 -0
  196. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/__init__.py +0 -0
  197. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_atomic_write_enforced.py +0 -0
  198. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_classification_ssot.py +0 -0
  199. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_docs_generated.py +0 -0
  200. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_entry_points_resolve.py +0 -0
  201. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_events_doc_contract.py +0 -0
  202. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_layer_2_loop_size.py +0 -0
  203. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_module_sizes.py +0 -0
  204. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_no_ai_signatures.py +0 -0
  205. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  206. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_peek_schema_version.py +0 -0
  207. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  208. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/invariants/test_upstream_schema_canary.py +0 -0
  209. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/literate/__init__.py +0 -0
  210. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/literate/parser.py +0 -0
  211. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/literate/test_parser.py +0 -0
  212. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/literate/test_quickstart.py +0 -0
  213. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/__init__.py +0 -0
  214. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_agent_runtime_grace.py +0 -0
  215. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_agent_runtime_progress.py +0 -0
  216. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  217. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_api_assemble_prompt.py +0 -0
  218. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_api_events_stream.py +0 -0
  219. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_api_install.py +0 -0
  220. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_api_observation.py +0 -0
  221. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_api_read_round_num.py +0 -0
  222. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_api_resolve_phase.py +0 -0
  223. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_api_types.py +0 -0
  224. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_auto_stop_gating.py +0 -0
  225. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_catalogs.py +0 -0
  226. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_claude_error_detector.py +0 -0
  227. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_cli.py +0 -0
  228. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_cli_common.py +0 -0
  229. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_cli_init_install.py +0 -0
  230. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_cli_monitor_http.py +0 -0
  231. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_cli_upgrade.py +0 -0
  232. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_codewhale_plugin.py +0 -0
  233. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_config_fresh_eyes.py +0 -0
  234. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_config_max_rounds.py +0 -0
  235. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_config_prompt_delivery.py +0 -0
  236. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_config_stop_file.py +0 -0
  237. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  238. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_config_transient_error_action.py +0 -0
  239. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_detector_protocol.py +0 -0
  240. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_dirty_handlers.py +0 -0
  241. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_events_cmd.py +0 -0
  242. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_exit_cause.py +0 -0
  243. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  244. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_gemini_plugin.py +0 -0
  245. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_hook_failure_isolation.py +0 -0
  246. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_hooks.py +0 -0
  247. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_http_progress.py +0 -0
  248. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_init_entry_points.py +0 -0
  249. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_lifecycle.py +0 -0
  250. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_metrics.py +0 -0
  251. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_monitor_assembly.py +0 -0
  252. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  253. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  254. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  255. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_monitor_detectors.py +0 -0
  256. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_monitor_remote.py +0 -0
  257. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_network_blip_signal_guard.py +0 -0
  258. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_peek_argparse.py +0 -0
  259. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_peek_select.py +0 -0
  260. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_presets.py +0 -0
  261. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_redact.py +0 -0
  262. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_round_log_helpers.py +0 -0
  263. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_round_view.py +0 -0
  264. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_runner_throttle.py +0 -0
  265. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_scaffold.py +0 -0
  266. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_serve_config_broken.py +0 -0
  267. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_serve_crash_loop.py +0 -0
  268. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_serve_round_log.py +0 -0
  269. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_serve_sentinel.py +0 -0
  270. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_serve_startup_hooks.py +0 -0
  271. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_service_unit.py +0 -0
  272. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_startup_check.py +0 -0
  273. {cli_agent_runner-0.2.1 → cli_agent_runner-0.2.2}/tests/unit/test_substrate.py +0 -0
@@ -87,6 +87,10 @@ jobs:
87
87
  run: |
88
88
  ruff check .
89
89
  ruff format --check .
90
+ - name: vulture (dead-code scan)
91
+ # Config in [tool.vulture]; exits nonzero on any finding. No pipe — a
92
+ # `| tail` would swallow the exit code and let dead code through.
93
+ run: vulture
90
94
  - name: tests with coverage
91
95
  run: pytest -q --ignore=tests/e2e --ignore=tests/literate --cov --cov-report=xml --cov-report=term
92
96
  - name: docs CI gate
@@ -30,3 +30,8 @@ htmlcov/
30
30
 
31
31
  # Personal /team-onboarding artifact — not project content
32
32
  ONBOARDING.md
33
+
34
+ # Local tooling scratch directories — never project content, never published.
35
+ # Defense in depth only: releases build from a fresh CI checkout, and every
36
+ # sdist 0.1.1–0.2.1 was checked clean. This only guards a local `python -m build`.
37
+ .superpowers/
@@ -0,0 +1,244 @@
1
+ # GENERATED by tests/generate_vulture_whitelist.py — DO NOT EDIT BY HAND.
2
+ # Regenerate: ./build.sh vulture-whitelist
3
+ #
4
+ # vulture (min_confidence=60) reports @dataclass fields as "unused variable":
5
+ # they cross a JSON / CLI / plugin boundary static analysis cannot see. Each
6
+ # bare name below marks one field "used". SSOT = the @dataclass definitions in
7
+ # agent_runner/; a new field is covered by regenerating, never by editing here.
8
+
9
+ # agent_runner.agent_runtime.RunResult
10
+ exit_code
11
+ duration_s
12
+ timed_out
13
+ pid
14
+ killed_for_grace
15
+ grace_kill_children
16
+
17
+ # agent_runner.api_types.Alert
18
+ severity
19
+ detector
20
+ message
21
+ context
22
+ ts
23
+ auto_action
24
+
25
+ # agent_runner.api_types.DirtyOutcome
26
+ kind
27
+ ref
28
+
29
+ # agent_runner.api_types.InitResult
30
+ work_dir
31
+ files_created
32
+ committed
33
+ preset
34
+
35
+ # agent_runner.api_types.InstallResult
36
+ unit_path
37
+ monitor_unit_path
38
+ enabled
39
+ started
40
+
41
+ # agent_runner.api_types.ProjectState
42
+ project
43
+ status
44
+ defenses
45
+ current_round
46
+ recent_rounds
47
+ orphan
48
+ system
49
+ service
50
+ recent_events
51
+ recent_hook_failures
52
+ recent_blips
53
+
54
+ # agent_runner.api_types.RateLimitState
55
+ throttled_until_epoch
56
+ limit_type
57
+ agent
58
+ since_round
59
+
60
+ # agent_runner.api_types.RoundResult
61
+ round_num
62
+ phase
63
+ started_at
64
+ ended_at
65
+ exit_code
66
+ duration_s
67
+ timed_out
68
+ log_path
69
+ dirty_files
70
+ stashed
71
+ dirty_outcome
72
+ killed_for_grace
73
+
74
+ # agent_runner.api_types.RoundView
75
+ round_num
76
+ phase
77
+ started_at
78
+ duration_so_far_s
79
+ pid
80
+ exit_code
81
+ timed_out
82
+ log_path
83
+ log_tail
84
+ recent_events
85
+
86
+ # agent_runner.api_types.ServiceStatus
87
+ mode
88
+ active
89
+ pid
90
+ uptime_s
91
+ unit_file
92
+ rate_limit
93
+
94
+ # agent_runner.api_types.SystemMetrics
95
+ mem_total_mb
96
+ mem_available_mb
97
+ disk_used_pct
98
+ disk_free_gb
99
+ load_1m
100
+ cpu_pct
101
+ agent_process_count
102
+
103
+ # agent_runner.api_types.TransientErrorState
104
+ reset_at_epoch
105
+ classification
106
+ agent
107
+ since_round
108
+
109
+ # agent_runner.config.AgentConfig
110
+ command
111
+ prompt_arg_template
112
+ name
113
+ env
114
+ prompt_delivery
115
+
116
+ # agent_runner.config.Config
117
+ agent
118
+ runtime
119
+ prompt
120
+ vcs
121
+ monitor
122
+ phases
123
+ plugins
124
+
125
+ # agent_runner.config.MonitorConfig
126
+ auth_fail_patterns
127
+ auth_fail_hint
128
+ auto_stop_on
129
+ remote_failure_tolerance_s
130
+ anomaly_repetitive_window
131
+ anomaly_repetitive_threshold
132
+ host_health
133
+ round_progress_interval_s
134
+ supervisor_stale_threshold_s
135
+
136
+ # agent_runner.config.MonitorHostHealthConfig
137
+ mem_avail_min_mb
138
+ disk_warning_pct
139
+ disk_critical_pct
140
+
141
+ # agent_runner.config.PhaseOverride
142
+ round_timeout_s
143
+ disable_pre_round_hooks
144
+ prompt_files
145
+
146
+ # agent_runner.config.PhasesConfig
147
+ list
148
+ overrides
149
+
150
+ # agent_runner.config.PluginsConfig
151
+ disable
152
+ raw
153
+
154
+ # agent_runner.config.PromptConfig
155
+ file
156
+ files
157
+ inject_context
158
+ context_injection_mode
159
+ concat_separator
160
+ strip_yaml_frontmatter
161
+
162
+ # agent_runner.config.RuntimeConfig
163
+ work_dir
164
+ log_dir
165
+ round_timeout_s
166
+ restart_delay_s
167
+ disable_pre_round_hooks
168
+ round_log_retention
169
+ narrative_file
170
+ transient_error_action
171
+ max_rounds
172
+ stop_file
173
+ substrate_fingerprint_paths
174
+ fresh_eyes_every_n
175
+ dry_run
176
+ max_grace_after_result_s
177
+ grace_kill_ignore_patterns
178
+
179
+ # agent_runner.config.VcsConfig
180
+ stash_idempotency_s
181
+ dirty_action
182
+
183
+ # agent_runner.context_store.OrphanState
184
+ round_num
185
+ files
186
+ stashed_ref
187
+ stash_message
188
+ timestamp
189
+ phase
190
+
191
+ # agent_runner.context_store.Status
192
+ round_num
193
+ running
194
+ last_completed_at
195
+ last_exit_code
196
+ last_duration_s
197
+ current_phase
198
+ phase_index
199
+
200
+ # agent_runner.defenses.Defense
201
+ name
202
+ value
203
+ codifies
204
+ guarded_by
205
+ current_state
206
+
207
+ # agent_runner.hooks.HookContext
208
+ work_dir
209
+ log_dir
210
+ project
211
+ round_num
212
+ phase
213
+ agent_name
214
+ agent_binary
215
+ agent_log_path
216
+ dry_run
217
+ anomaly_repetitive_window
218
+ anomaly_repetitive_threshold
219
+ vcs
220
+
221
+ # agent_runner.hooks.VcsHookView
222
+ dirty_action
223
+ stash_idempotency_s
224
+
225
+ # agent_runner.lifecycle.PIDFile
226
+ path
227
+
228
+ # agent_runner.monitor.LocalSource
229
+ log_dir
230
+
231
+ # agent_runner.monitor.RemoteSource
232
+ host
233
+ project
234
+
235
+ # agent_runner.startup_check.CheckResult
236
+ name
237
+ ok
238
+ reason
239
+ how_to_fix
240
+
241
+ # agent_runner.vcs_state.StashRef
242
+ sha
243
+ message
244
+ reused
@@ -0,0 +1,90 @@
1
+ # Backlog
2
+
3
+ Known follow-ups that are real design or behavior decisions, not defects. Each
4
+ entry carries enough context to act on without re-deriving it.
5
+
6
+ Items marked **NEEDS_DESIGN** change user-visible behavior and are held for an
7
+ explicit decision — they are not shipped silently in a maintenance release.
8
+
9
+ ## `try_auto_commit` sweeps plugin-owned paths
10
+
11
+ `vcs_state.try_auto_commit` runs `git add -A` with an exclude pathspec built only
12
+ from the log directory (`_log_dir_exclude_pathspec`), so under
13
+ `vcs.dirty_action = "auto_commit"` paths registered via
14
+ `register_plugin_owned_paths` are committed along with the agent's work.
15
+
16
+ 0.2.2 made the registry authoritative at the git boundary for `stash_orphan`
17
+ only. That fix discharged the specific promise in `docs/plugins.md` that owned
18
+ paths are not silently swept into an orphan stash. `auto_commit` is a different
19
+ promise with a different harm (a commit, not a hidden stash) and no equivalent
20
+ published guarantee, so it needs its own call: should `auto_commit` honor the
21
+ registry, or is committing a plugin deliverable alongside agent work the
22
+ intended behavior?
23
+
24
+ ## `_tail_events` / `_tail_events_jsonl` duplication
25
+
26
+ `cli/events_cmd.py:_tail_events` and `api.py:_tail_events_jsonl` both tail the
27
+ event stream, deliberately at different scopes: `api` globs every
28
+ `events-*.jsonl`; `events_cmd` is current-month-scoped and resets its offset to
29
+ 0 on truncation. They are not accidental copies. Collapsing them means first
30
+ deciding which scope is correct for each caller.
31
+
32
+ Related, from the same area: the kind-read loops in `_throttle.py` and
33
+ `monitor.py` (`kind = ev.get("event")` over `reversed(events)`) are
34
+ character-identical after the 0.2.2 constant normalization. Extracting a shared
35
+ helper is a structural change, not a literal swap.
36
+
37
+ ## Detector fixtures pin raw event-kind literals
38
+
39
+ Detector test fixtures construct events from hardcoded strings (`{"event":
40
+ "round_start"}`) rather than emitting through `events.emit()`. Fixture and
41
+ reader therefore agree with each other while both can diverge from what the
42
+ supervisor actually writes.
43
+
44
+ Proven by mutation: point the emitter at a different kind value and the 28
45
+ `test_claude_error_detector.py` tests stay green while every detector is blind.
46
+ The 0.2.2 constant normalization does not close this — it moves the pin from the
47
+ reader to the fixture. Closing it means fixtures emitting through
48
+ `events.emit()`, or an end-to-end test asserting a real round produces events
49
+ the detectors actually match.
50
+
51
+ Bounded by `docs/events.md`, which forbids renaming a kind value outright (the
52
+ kind name is the version discriminator; a breaking change ships as
53
+ `round_start_v2`). Reaching this failure requires a contract violation — but the
54
+ suite would not report it.
55
+
56
+ ## `remote_failure_tolerance_s` does not cover `subprocess.TimeoutExpired` — NEEDS_DESIGN
57
+
58
+ The tolerance window covers ssh exit code 255 only. `monitor.run_remote_command`
59
+ raises `MonitorRemoteError` on rc=255, and `api.monitor_loop`'s retry loop
60
+ catches exactly that. A hung (not failed) ssh connection instead trips the
61
+ `subprocess.run(timeout=...)` guard and raises `subprocess.TimeoutExpired`,
62
+ which bypasses the window and kills the monitor.
63
+
64
+ The rc=255-only scope is intentional and pinned by the monitor-signals design,
65
+ and the documentation matches the code exactly — there is no drift here. Widening
66
+ it changes a documented recovery path, so it is a design decision, not debt.
67
+
68
+ ## `anomaly_repetitive_*` default — NEEDS_DESIGN
69
+
70
+ `monitor.anomaly_repetitive_window` and `anomaly_repetitive_threshold` both
71
+ default to `0`, which disables the detector. This is a deliberate opt-in kill
72
+ switch, documented since the field's first commit, and the detector fires
73
+ correctly the moment an operator opts in.
74
+
75
+ The open question is whether opt-in is the right default, or whether this belongs
76
+ in a preset. Flipping the default would arm a new alert for every existing
77
+ deployment.
78
+
79
+ ## `stash_orphan` loses the ref when the post-push listing fails
80
+
81
+ `vcs_state.stash_orphan` returns `None` in three cases; two are true no-ops. The
82
+ third — `git stash push` succeeded but the follow-up `git stash list` failed —
83
+ means the WIP *is* stashed yet callers read "nothing stashed" and report the
84
+ tree as ignored. It is already documented in-code as a KNOWN GAP and judged
85
+ effectively unreachable (a listing that fails microseconds after a push that
86
+ just succeeded in the same repo).
87
+
88
+ Deferred because closing it is a naming decision, not cleanup: no event kind
89
+ carries the meaning "stashed but ref lost" (`orphan_stash_failed` would be wrong
90
+ — the stash exists). Left as-is until that kind is designed.
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.2] - 2026-07-18
9
+
10
+ ### Removed
11
+ - `agent_runner.detector_helpers` and its three helpers (`cumulative_window_check`, `dual_source_silence`, `phase_filter`) — **breaking** for any out-of-tree detector that imported them; vendor the ~115 LOC.
12
+ - `agent-runner cancel` and `api.cancel()` — **breaking**; they never delivered the documented SIGINT-to-round semantics (nothing ever wrote the `round.pid` they read), so `cancel` was only a slower alias for `stop`. Use `stop`; `serve` no longer traps `SIGUSR1`.
13
+ - `events.SIGTERM_RECEIVED` (the `sigterm_received` kind) — declared but never emitted, so nothing could ever observe it.
14
+
15
+ ### Changed
16
+ - Paths registered with `register_plugin_owned_paths` are no longer swept into the orphan stash: all three documented pattern forms (trailing-slash prefix, glob, `**/*`) are now honored at the git boundary, not only the report boundary, so a deliverable is no longer silently stashed — and possibly lost — when the agent also touches a non-owned file. `**/*` patterns now also match files directly in the registered directory, changing which paths `detect_dirty_files` filters. **Check `git stash list` if you register owned paths.**
17
+ - Config-load rejections now raise `ConfigError` (a subclass of `ValueError`) consistently; fully backward compatible — any `except ValueError` still catches them.
18
+ - `[monitor.host_health]` percent thresholds, `[prompt] inject_context`, and `[monitor] auth_fail_patterns` are validated at load, so configs that were previously accepted-but-silently-broken now fail loudly at startup.
19
+
20
+ ### Fixed
21
+ - `agent-runner events --tail` no longer re-emits events appended while a poll's read loop was in flight.
22
+ - The orphan-stash defense now signals push failure (`orphan_stash_failed`) and idempotent reuse (`orphan_idempotent_skip`) — both kinds were declared but never emitted — instead of degrading silently; `stash_orphan` raises `StashError` (re-exported from `agent_runner.api`) on push failure.
23
+ - Plugin-supplied transient-error classifications no longer crash the supervisor with `KeyError`.
24
+ - Defense-catalog honesty (every advertised defense now names a real guarding test) and ~31 false claims across README, docs, and docstrings.
25
+
26
+ ### Added
27
+ - Housekeeping: the build gate runs `vulture` (dead-code scan, whitelist auto-generated from `@dataclass` fields); `AGENT_RUNNER_FRESH_EYES` is now a documented, tested round-subprocess env-var contract; `SECURITY.md` gains a threat model (agent-runner is a supervisor, not a sandbox).
28
+
29
+ See `docs/migrations/0.2.2.md`.
30
+
8
31
  ## [0.2.1] - 2026-07-16
9
32
 
10
33
  ### Added
@@ -29,6 +29,26 @@ what GitHub Actions runs on every push and PR.
29
29
  reject auto-generated trailers and robot signatures — keep messages
30
30
  human-authored.
31
31
 
32
+ ## Migration docs
33
+
34
+ A `docs/migrations/<version>.md` guide is written **only** when a release:
35
+
36
+ - makes a **breaking change** — a removed or renamed public symbol, or a
37
+ config that used to load and now does not; or
38
+ - **requires operator action** — a TOML edit, a manual recovery step, or a
39
+ data/format migration.
40
+
41
+ Otherwise `CHANGELOG.md` carries the whole story; a guide that only restates
42
+ the changelog is upkeep with no reader. When a guide does exist, keep the
43
+ CHANGELOG entry terse and link it (`See docs/migrations/<version>.md.`).
44
+
45
+ 0.2.2 is the first release written under this policy: it removes two public
46
+ symbols — the `detector_helpers` module and the `cancel` verb — so it ships a
47
+ guide. Existing `docs/migrations/` files are history: append-only, never
48
+ rewritten or deleted, including ones this policy would not have produced.
49
+ Correct a shipped guide with a dated `**Correction (<version>)**:` note, not
50
+ an edit in place.
51
+
32
52
  ## Architecture / docs
33
53
 
34
54
  - [`docs/architecture.md`](docs/architecture.md) — 3-layer model, defenses-as-data.
@@ -40,6 +60,12 @@ table in `docs/*.md` are auto-generated by `./build.sh docs`. The CI gate
40
60
  fails if those regions drift from the code's source of truth. Don't edit
41
61
  them manually — edit the generator or the data source.
42
62
 
63
+ `.vulture-whitelist.py` is likewise generated — from every `@dataclass` field
64
+ in `agent_runner/`. If you add or rename a dataclass field, run
65
+ `./build.sh vulture-whitelist` and commit the regenerated file;
66
+ `tests/invariants/test_vulture_whitelist_generated.py` fails the gate if it
67
+ drifts.
68
+
43
69
  ## Tests
44
70
 
45
71
  - Unit + integration: `pytest -q --ignore=tests/e2e --ignore=tests/literate`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-agent-runner
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Restart-on-exit supervisor for autonomous CLI agents
5
5
  Project-URL: Homepage, https://github.com/wan9yu/cli-agent-runner
6
6
  Project-URL: Documentation, https://github.com/wan9yu/cli-agent-runner#readme
@@ -31,7 +31,6 @@ Requires-Dist: pytest>=8; extra == 'dev'
31
31
  Requires-Dist: ruff>=0.5; extra == 'dev'
32
32
  Requires-Dist: vulture>=2.13; extra == 'dev'
33
33
  Provides-Extra: e2e
34
- Requires-Dist: fabric>=3; extra == 'e2e'
35
34
  Requires-Dist: pytest>=8; extra == 'e2e'
36
35
  Description-Content-Type: text/markdown
37
36
 
@@ -41,9 +40,10 @@ Description-Content-Type: text/markdown
41
40
 
42
41
  # agent-runner
43
42
 
44
- A restart-on-exit supervisor for autonomous coding CLIs. Tested with Claude
45
- Code and aider out of the box; any prompt-arg CLI via custom config. Spawn
46
- the agent round-after-round under defenses that prevent the failure modes
43
+ A restart-on-exit supervisor for autonomous coding CLIs. Ships presets for
44
+ Claude Code, aider, gemini and codewhale; any prompt-arg CLI via custom
45
+ config. Spawn the agent round-after-round under defenses that prevent the
46
+ failure modes
47
47
  that bite in production: stuck rounds, orphan commits, OAuth burn loops,
48
48
  full disks, runaway memory.
49
49
 
@@ -80,12 +80,12 @@ agent-runner monitor # live anomaly detection
80
80
 
81
81
  Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
82
82
 
83
- ## 16 verbs
83
+ ## 15 verbs
84
84
 
85
85
  | Lifecycle | Observation |
86
86
  |---|---|
87
87
  | `init` / `install` / `uninstall` | `peek` — state snapshot |
88
- | `start` / `stop` / `kill` / `cancel` | `watch` — peek in a refresh loop |
88
+ | `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
89
89
  | `restart` / `status` | `monitor` — 11 detectors, alerts, auto-stop |
90
90
  | `round` / `serve` / `upgrade` | `events` — query / stream events.jsonl |
91
91
 
@@ -4,9 +4,10 @@
4
4
 
5
5
  # agent-runner
6
6
 
7
- A restart-on-exit supervisor for autonomous coding CLIs. Tested with Claude
8
- Code and aider out of the box; any prompt-arg CLI via custom config. Spawn
9
- the agent round-after-round under defenses that prevent the failure modes
7
+ A restart-on-exit supervisor for autonomous coding CLIs. Ships presets for
8
+ Claude Code, aider, gemini and codewhale; any prompt-arg CLI via custom
9
+ config. Spawn the agent round-after-round under defenses that prevent the
10
+ failure modes
10
11
  that bite in production: stuck rounds, orphan commits, OAuth burn loops,
11
12
  full disks, runaway memory.
12
13
 
@@ -43,12 +44,12 @@ agent-runner monitor # live anomaly detection
43
44
 
44
45
  Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
45
46
 
46
- ## 16 verbs
47
+ ## 15 verbs
47
48
 
48
49
  | Lifecycle | Observation |
49
50
  |---|---|
50
51
  | `init` / `install` / `uninstall` | `peek` — state snapshot |
51
- | `start` / `stop` / `kill` / `cancel` | `watch` — peek in a refresh loop |
52
+ | `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
52
53
  | `restart` / `status` | `monitor` — 11 detectors, alerts, auto-stop |
53
54
  | `round` / `serve` / `upgrade` | `events` — query / stream events.jsonl |
54
55
 
@@ -0,0 +1,37 @@
1
+ > 中文 · **[English](README.md)**
2
+
3
+ # agent-runner
4
+
5
+ 把任意 CLI agent(Claude Code、aider、任何接受 prompt 参数的命令)包装成能被
6
+ systemd / launchd 拉起、能被远程观测的服务。**每轮跑完进程退出**,外层
7
+ supervisor 重启 —— 这是核心模式。中间穿插一组防御,避开 production 上最容易
8
+ 翻车的几条路:轮卡死、脏文件没 commit、OAuth 失效烧 quota、磁盘写满、
9
+ 两个 supervisor 抢同一个仓库。
10
+
11
+ 三层,每层都能独立运行,上层都是可选的:`round` 跑一次 agent 跑完即退;
12
+ `serve` 捕获信号循环拉起 round;`monitor` 检测器 + 告警,critical 时自动停服
13
+ (本机或 ssh 远程)。
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ pip install cli-agent-runner
19
+ ```
20
+
21
+ CLI 命令是 `agent-runner`(PyPI 包名加 `cli-` 前缀只是为了规避命名冲突,
22
+ 导入名 `agent_runner` 和命令名都不变)。
23
+
24
+ ## 完整文档
25
+
26
+ 文档以英文为准。下面 `docs/` 各页末尾都附有中文摘要,完整细节见英文正文:
27
+
28
+ - [`docs/quickstart.md`](docs/quickstart.md) —— 5 步装好并跑通第一轮
29
+ - [`README.md`](README.md) —— 定位、动词表、内置防御、Monitor 检测器
30
+ - [`docs/architecture.md`](docs/architecture.md) —— 三层模型 + defenses-as-data
31
+ - [`docs/commands.md`](docs/commands.md) —— 动词参考
32
+ - [`docs/configuration.md`](docs/configuration.md) —— `agent-runner.toml` schema
33
+ - [`docs/runbook.md`](docs/runbook.md) —— 操作手册 + 故障排查
34
+
35
+ ## License
36
+
37
+ [Apache License 2.0](LICENSE).
@@ -20,6 +20,23 @@ Please **do not** open a public issue for security problems.
20
20
  - An initial assessment within 7 business days.
21
21
  - A fix and coordinated disclosure timeline once the scope is understood.
22
22
 
23
+ ## Threat model
24
+
25
+ agent-runner is a **supervisor**, not a sandbox. It provides observability
26
+ (structured events, peek/watch/monitor) and lifecycle safety (round timeouts,
27
+ process-group reaping, orphan stashing, auto-stop on critical alerts). It does
28
+ **not** confine the agent process: the agent runs with the invoking user's full
29
+ privileges and can read, write, and execute anything that user can.
30
+
31
+ Prompt injection from untrusted repository content is unsolved and out of the
32
+ supervisor's reach — a malicious file in the work tree can steer the agent, and
33
+ no defense in this project prevents that.
34
+
35
+ Operators supervising untrusted input should run the agent as a dedicated
36
+ unprivileged user, inside a container or VM, without passwordless sudo, and
37
+ with egress limits. The supervisor's defenses bound the blast radius of a
38
+ *misbehaving* agent, not a *hostile* one.
39
+
23
40
  ## Scope
24
41
 
25
42
  In scope: the `agent_runner` Python package, its CLI, its bundled systemd
@@ -17,6 +17,9 @@ from agent_runner.config import (
17
17
  AgentConfig,
18
18
  Config,
19
19
  MonitorConfig,
20
+ MonitorHostHealthConfig,
21
+ PhasesConfig,
22
+ PluginsConfig,
20
23
  PromptConfig,
21
24
  RuntimeConfig,
22
25
  VcsConfig,
@@ -25,41 +28,60 @@ from agent_runner.defenses import catalog
25
28
  from agent_runner.events import KNOWN_EVENT_KINDS
26
29
  from agent_runner.monitor import AUTO_STOP_ALERTS, KNOWN_ALERT_KINDS
27
30
 
28
- _SECTIONS = [
29
- ("agent", AgentConfig),
30
- ("runtime", RuntimeConfig),
31
- ("prompt", PromptConfig),
32
- ("vcs", VcsConfig),
33
- ("monitor", MonitorConfig),
31
+ # (toml_section_name, dataclass, nested_sub_sections). Nesting is an EXPLICIT
32
+ # registry, not reflection, on two counts:
33
+ # - field name != TOML key: PhaseOverride.prompt_files is written as
34
+ # [phases.<name>.prompt] files=[...], and PluginsConfig.raw is a catch-all
35
+ # for unknown keys. Recursing generically would document TOML keys that do
36
+ # not exist.
37
+ # - typing.get_type_hints() raises TypeError on PhasesConfig — its `list`
38
+ # field shadows the builtin, so `list[str] | None` resolves against
39
+ # None. Do not reach for it here.
40
+ _SECTIONS: list[tuple[str, type, list[tuple[str, type]]]] = [
41
+ ("agent", AgentConfig, []),
42
+ ("runtime", RuntimeConfig, []),
43
+ ("prompt", PromptConfig, []),
44
+ ("vcs", VcsConfig, []),
45
+ ("monitor", MonitorConfig, [("monitor.host_health", MonitorHostHealthConfig)]),
46
+ ("phases", PhasesConfig, []),
47
+ ("plugins", PluginsConfig, []),
34
48
  ]
35
49
 
36
50
 
51
+ def _cell(s: str) -> str:
52
+ # A bare `|` ends the markdown cell. Live sources: `X | None` annotations
53
+ # and the auth_fail_patterns regex default.
54
+ return s.replace("|", "\\|")
55
+
56
+
37
57
  def _type_label(t: typing.Any) -> str:
38
58
  # dataclasses.fields exposes `.type` as a string (PEP 563), so we render
39
59
  # the raw annotation. Strip ``Path`` / ``Path | None`` wrappers cosmetically.
40
- s = str(t).replace("pathlib.", "")
41
- return s
60
+ return _cell(str(t).replace("pathlib.", ""))
42
61
 
43
62
 
44
63
  def _default_label(field: dataclasses.Field) -> str:
45
64
  if field.default is not dataclasses.MISSING:
46
- return repr(field.default)
65
+ return _cell(repr(field.default))
47
66
  if field.default_factory is not dataclasses.MISSING: # type: ignore[misc]
48
- return repr(field.default_factory())
67
+ return _cell(repr(field.default_factory()))
49
68
  return "—"
50
69
 
51
70
 
71
+ def _field_table(dc: type) -> list[str]:
72
+ rows = ["| Field | Type | Default |", "|---|---|---|"]
73
+ for f in dataclasses.fields(dc):
74
+ rows.append(f"| `{f.name}` | `{_type_label(f.type)}` | {_default_label(f)} |")
75
+ return rows
76
+
77
+
52
78
  def render_config_schema_table() -> str:
53
79
  """Markdown sub-sections per Config dataclass with field/type/default."""
54
80
  parts: list[str] = []
55
- for name, dc in _SECTIONS:
56
- parts.append(f"### `[{name}]`")
57
- parts.append("")
58
- parts.append("| Field | Type | Default |")
59
- parts.append("|---|---|---|")
60
- for f in dataclasses.fields(dc):
61
- parts.append(f"| `{f.name}` | `{_type_label(f.type)}` | {_default_label(f)} |")
62
- parts.append("")
81
+ for name, dc, nested in _SECTIONS:
82
+ parts += [f"### `[{name}]`", "", *_field_table(dc), ""]
83
+ for sub_name, sub_dc in nested:
84
+ parts += [f"#### `[{sub_name}]`", "", *_field_table(sub_dc), ""]
63
85
  return "\n".join(parts).rstrip()
64
86
 
65
87
 
@@ -80,7 +102,11 @@ def replace_block(text: str, name: str, new_content: str) -> str:
80
102
  re.escape(open_tag) + r".*?" + re.escape(close_tag),
81
103
  re.DOTALL,
82
104
  )
83
- return pattern.sub(f"{open_tag}\n{new_content}\n{close_tag}", text)
105
+ replacement = f"{open_tag}\n{new_content}\n{close_tag}"
106
+ # A string repl is a TEMPLATE (expands \n, \t, \1, \g<...>); a callable repl
107
+ # is inserted literally. new_content carries rendered config defaults, which
108
+ # may contain backslashes.
109
+ return pattern.sub(lambda _m: replacement, text)
84
110
 
85
111
 
86
112
  def _default_cfg() -> Config: