brigade-cli 0.10.2__tar.gz → 0.11.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 (321) hide show
  1. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/PKG-INFO +21 -4
  2. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/QUICKSTART.md +44 -11
  3. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/README.md +18 -2
  4. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/pyproject.toml +14 -3
  5. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/__init__.py +1 -1
  6. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/__main__.py +1 -0
  7. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/aboyeur.py +118 -51
  8. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/actionqueue.py +1 -0
  9. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/add.py +1 -0
  10. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/agents.py +46 -6
  11. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/budgets.py +1 -0
  12. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/budgets_cmd.py +2 -4
  13. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/center_cmd.py +921 -131
  14. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/chat_cmd.py +82 -17
  15. brigade_cli-0.11.0/src/brigade/cli/__init__.py +147 -0
  16. brigade_cli-0.11.0/src/brigade/cli/_common.py +56 -0
  17. brigade_cli-0.11.0/src/brigade/cli/add.py +20 -0
  18. brigade_cli-0.11.0/src/brigade/cli/budgets.py +30 -0
  19. brigade_cli-0.11.0/src/brigade/cli/center.py +326 -0
  20. brigade_cli-0.11.0/src/brigade/cli/chat.py +91 -0
  21. brigade_cli-0.11.0/src/brigade/cli/context.py +87 -0
  22. brigade_cli-0.11.0/src/brigade/cli/daily.py +225 -0
  23. brigade_cli-0.11.0/src/brigade/cli/doctor.py +24 -0
  24. brigade_cli-0.11.0/src/brigade/cli/dogfood.py +90 -0
  25. brigade_cli-0.11.0/src/brigade/cli/friction.py +84 -0
  26. brigade_cli-0.11.0/src/brigade/cli/handoff.py +390 -0
  27. brigade_cli-0.11.0/src/brigade/cli/handoff_template.py +25 -0
  28. brigade_cli-0.11.0/src/brigade/cli/hermes_fragments.py +19 -0
  29. brigade_cli-0.11.0/src/brigade/cli/ingest.py +35 -0
  30. brigade_cli-0.11.0/src/brigade/cli/init.py +102 -0
  31. brigade_cli-0.11.0/src/brigade/cli/learn.py +189 -0
  32. brigade_cli-0.11.0/src/brigade/cli/memory.py +109 -0
  33. brigade_cli-0.11.0/src/brigade/cli/notifications.py +106 -0
  34. brigade_cli-0.11.0/src/brigade/cli/openclaw_fragments.py +19 -0
  35. brigade_cli-0.11.0/src/brigade/cli/operator.py +404 -0
  36. brigade_cli-0.11.0/src/brigade/cli/pantry.py +90 -0
  37. brigade_cli-0.11.0/src/brigade/cli/projects.py +122 -0
  38. brigade_cli-0.11.0/src/brigade/cli/reconfigure.py +46 -0
  39. brigade_cli-0.11.0/src/brigade/cli/release.py +289 -0
  40. brigade_cli-0.11.0/src/brigade/cli/repos.py +862 -0
  41. brigade_cli-0.11.0/src/brigade/cli/research.py +183 -0
  42. brigade_cli-0.11.0/src/brigade/cli/roadmap.py +58 -0
  43. brigade_cli-0.11.0/src/brigade/cli/roster.py +47 -0
  44. brigade_cli-0.11.0/src/brigade/cli/run.py +195 -0
  45. brigade_cli-0.11.0/src/brigade/cli/runbook.py +75 -0
  46. brigade_cli-0.11.0/src/brigade/cli/runs.py +56 -0
  47. brigade_cli-0.11.0/src/brigade/cli/scrub.py +25 -0
  48. brigade_cli-0.11.0/src/brigade/cli/security.py +203 -0
  49. brigade_cli-0.11.0/src/brigade/cli/skills.py +268 -0
  50. brigade_cli-0.11.0/src/brigade/cli/status.py +19 -0
  51. brigade_cli-0.11.0/src/brigade/cli/tools.py +494 -0
  52. brigade_cli-0.11.0/src/brigade/cli/untrusted.py +47 -0
  53. brigade_cli-0.11.0/src/brigade/cli/work.py +2044 -0
  54. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/config.py +2 -3
  55. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/context_cmd.py +78 -13
  56. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/daily_cmd.py +860 -171
  57. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/doctor.py +50 -49
  58. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/dogfood_cmd.py +14 -37
  59. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/fragments.py +4 -10
  60. brigade_cli-0.11.0/src/brigade/friction_cmd.py +588 -0
  61. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/handoff.py +1 -0
  62. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/handoff_cmd.py +145 -78
  63. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/hermes_adapter.py +1 -0
  64. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/ingest.py +2 -3
  65. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/install.py +93 -80
  66. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/learn_cmd.py +102 -22
  67. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/localio.py +38 -0
  68. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/managed.py +71 -31
  69. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/memory_cmd.py +106 -31
  70. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/notifications_cmd.py +18 -6
  71. brigade_cli-0.11.0/src/brigade/operator_cmd/__init__.py +91 -0
  72. brigade_cli-0.11.0/src/brigade/operator_cmd/adoption.py +407 -0
  73. brigade_cli-0.11.0/src/brigade/operator_cmd/guide.py +190 -0
  74. brigade_cli-0.11.0/src/brigade/operator_cmd/health.py +528 -0
  75. brigade_cli-0.11.0/src/brigade/operator_cmd/lifecycle.py +628 -0
  76. brigade_cli-0.11.0/src/brigade/operator_cmd/migration.py +589 -0
  77. brigade_cli-0.11.0/src/brigade/operator_cmd/surfaces.py +1045 -0
  78. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/pantry_cmd.py +4 -6
  79. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/phases_cmd.py +1220 -205
  80. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/proc.py +2 -0
  81. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/projects_cmd.py +49 -9
  82. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/prompt.py +8 -6
  83. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/reconfigure.py +3 -3
  84. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/registry.py +1 -0
  85. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/release_cmd.py +669 -158
  86. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/reportstore.py +1 -0
  87. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/repos_cmd.py +1439 -280
  88. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/config.py +10 -3
  89. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/engine.py +64 -32
  90. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/extract.py +26 -12
  91. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/handoff.py +3 -2
  92. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/llm.py +19 -10
  93. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/registry.py +50 -12
  94. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/report.py +26 -8
  95. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/sources/cli.py +13 -2
  96. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/sources/local.py +23 -7
  97. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/sources/web.py +18 -5
  98. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/types.py +13 -5
  99. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research_cmd.py +141 -60
  100. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/roadmap_cmd.py +22 -26
  101. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/roster.py +15 -0
  102. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/roster_cmd.py +19 -0
  103. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/runbook_cmd.py +48 -11
  104. brigade_cli-0.11.0/src/brigade/runguard.py +192 -0
  105. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/runs_cmd.py +1 -0
  106. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/scrub.py +22 -6
  107. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/security_cmd.py +226 -53
  108. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/selection.py +12 -15
  109. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/skills_cmd.py +171 -44
  110. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/station.py +3 -0
  111. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/status.py +2 -4
  112. brigade_cli-0.11.0/src/brigade/templates/harnesses/amp.json +11 -0
  113. brigade_cli-0.11.0/src/brigade/templates/harnesses/crush.json +11 -0
  114. brigade_cli-0.11.0/src/brigade/templates/harnesses/grok.json +11 -0
  115. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/hermes/memory-handoff.harness.json +1 -1
  116. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/hermes/model-lanes.harness.json +1 -1
  117. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/hermes/workspace.harness.json +1 -1
  118. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/chat-memory-sweep.example.json +1 -1
  119. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/memory-care.example.json +1 -1
  120. brigade_cli-0.11.0/src/brigade/templates/openhands/memory-handoffs/TEMPLATE.md +64 -0
  121. brigade_cli-0.11.0/src/brigade/templates/pi/memory-handoffs/TEMPLATE.md +64 -0
  122. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/policies/personal.json +1 -1
  123. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/policies/public-content.json +1 -1
  124. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/policies/public-repo.json +1 -1
  125. brigade_cli-0.11.0/src/brigade/templates/qwen/memory-handoffs/TEMPLATE.md +64 -0
  126. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates.py +1 -0
  127. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/toml_compat.py +10 -0
  128. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/tools_cmd.py +516 -133
  129. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/untrusted.py +3 -4
  130. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/untrusted_cmd.py +2 -1
  131. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/work_cmd/__init__.py +108 -80
  132. brigade_cli-0.11.0/src/brigade/work_cmd/backup.py +174 -0
  133. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/work_cmd/config.py +121 -32
  134. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/work_cmd/helpers.py +12 -13
  135. brigade_cli-0.11.0/src/brigade/work_cmd/imports.py +934 -0
  136. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/work_cmd/ledger.py +28 -20
  137. brigade_cli-0.11.0/src/brigade/work_cmd/reviews.py +1328 -0
  138. brigade_cli-0.11.0/src/brigade/work_cmd/scanners.py +1178 -0
  139. brigade_cli-0.11.0/src/brigade/work_cmd/services.py +1105 -0
  140. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/work_cmd/session.py +269 -104
  141. brigade_cli-0.11.0/src/brigade/work_cmd/sweeps.py +771 -0
  142. brigade_cli-0.11.0/src/brigade/work_cmd/verification.py +724 -0
  143. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade_cli.egg-info/PKG-INFO +21 -4
  144. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade_cli.egg-info/SOURCES.txt +74 -4
  145. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade_cli.egg-info/requires.txt +1 -0
  146. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_aboyeur.py +177 -0
  147. brigade_cli-0.11.0/tests/test_actionqueue.py +143 -0
  148. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_add.py +0 -1
  149. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_agents.py +100 -3
  150. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_budgets.py +3 -0
  151. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_config.py +1 -1
  152. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_doctor.py +10 -9
  153. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_dogfood_cmd.py +4 -5
  154. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_fragments.py +3 -0
  155. brigade_cli-0.11.0/tests/test_friction_cmd.py +117 -0
  156. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_gitignore.py +7 -0
  157. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_handoff.py +1 -0
  158. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_handoff_cmd.py +197 -144
  159. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_ingest.py +87 -10
  160. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_init.py +29 -13
  161. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_install.py +8 -2
  162. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_managed.py +14 -13
  163. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_memory_cmd.py +92 -17
  164. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_notifications_cmd.py +27 -27
  165. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_operator_cmd.py +287 -117
  166. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_pantry_cmd.py +9 -3
  167. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase100_cmd.py +6 -2
  168. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase101_cmd.py +183 -33
  169. brigade_cli-0.11.0/tests/test_phase165_cmd.py +3030 -0
  170. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase36_cmd.py +88 -28
  171. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase37_cmd.py +54 -8
  172. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase38_cmd.py +105 -20
  173. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase39_cmd.py +60 -9
  174. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase40_cmd.py +53 -10
  175. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase41_cmd.py +30 -5
  176. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase42_cmd.py +70 -13
  177. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase43_cmd.py +92 -15
  178. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase44_cmd.py +148 -37
  179. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase45_cmd.py +49 -6
  180. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase46_50_cmd.py +26 -5
  181. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase51_55_cmd.py +82 -10
  182. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase56_60_cmd.py +146 -50
  183. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase97_cmd.py +44 -30
  184. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_prompt.py +4 -4
  185. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_registry.py +1 -0
  186. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_release_cmd.py +92 -25
  187. brigade_cli-0.11.0/tests/test_reportstore.py +150 -0
  188. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_repos_cmd.py +4 -6
  189. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_cmd.py +38 -12
  190. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_config.py +5 -2
  191. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_engine.py +29 -12
  192. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_extract.py +21 -7
  193. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_handoff.py +12 -6
  194. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_llm.py +36 -6
  195. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_local_sources.py +3 -0
  196. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_registry.py +3 -0
  197. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_report.py +11 -7
  198. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_types.py +3 -1
  199. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_web.py +19 -5
  200. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_roadmap_cmd.py +1 -3
  201. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_roster.py +43 -3
  202. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_roster_cmd.py +7 -0
  203. brigade_cli-0.11.0/tests/test_run_cli.py +688 -0
  204. brigade_cli-0.11.0/tests/test_runguard.py +141 -0
  205. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_scrub.py +9 -0
  206. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_security_cmd.py +62 -17
  207. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_selection.py +28 -7
  208. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_skills_cmd.py +93 -18
  209. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_untrusted.py +3 -0
  210. brigade_cli-0.11.0/tests/test_work_cmd_backup.py +448 -0
  211. brigade_cli-0.11.0/tests/test_work_cmd_imports.py +3170 -0
  212. brigade_cli-0.11.0/tests/test_work_cmd_ledger.py +1055 -0
  213. brigade_cli-0.11.0/tests/test_work_cmd_reviews.py +727 -0
  214. brigade_cli-0.11.0/tests/test_work_cmd_scanners.py +719 -0
  215. brigade_cli-0.11.0/tests/test_work_cmd_services.py +2699 -0
  216. brigade_cli-0.11.0/tests/test_work_cmd_session.py +1699 -0
  217. brigade_cli-0.11.0/tests/test_work_cmd_sweeps.py +699 -0
  218. brigade_cli-0.11.0/tests/test_work_cmd_verification.py +259 -0
  219. brigade_cli-0.10.2/src/brigade/cli.py +0 -5063
  220. brigade_cli-0.10.2/src/brigade/operator_cmd.py +0 -3040
  221. brigade_cli-0.10.2/src/brigade/work_cmd/services.py +0 -5983
  222. brigade_cli-0.10.2/tests/test_phase165_cmd.py +0 -1294
  223. brigade_cli-0.10.2/tests/test_run_cli.py +0 -242
  224. brigade_cli-0.10.2/tests/test_work_cmd.py +0 -11417
  225. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/LICENSE +0 -0
  226. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/MANIFEST.in +0 -0
  227. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/setup.cfg +0 -0
  228. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/py.typed +0 -0
  229. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/__init__.py +0 -0
  230. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/research/sources/__init__.py +0 -0
  231. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/adal/memory-handoffs/TEMPLATE.md +0 -0
  232. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/aider/memory-handoffs/TEMPLATE.md +0 -0
  233. {brigade_cli-0.10.2/src/brigade/templates/antigravity → brigade_cli-0.11.0/src/brigade/templates/amp}/memory-handoffs/TEMPLATE.md +0 -0
  234. {brigade_cli-0.10.2/src/brigade/templates/claude → brigade_cli-0.11.0/src/brigade/templates/antigravity}/memory-handoffs/TEMPLATE.md +0 -0
  235. {brigade_cli-0.10.2/src/brigade/templates/codex → brigade_cli-0.11.0/src/brigade/templates/claude}/memory-handoffs/TEMPLATE.md +0 -0
  236. {brigade_cli-0.10.2/src/brigade/templates/continue → brigade_cli-0.11.0/src/brigade/templates/codex}/memory-handoffs/TEMPLATE.md +0 -0
  237. {brigade_cli-0.10.2/src/brigade/templates/copilot → brigade_cli-0.11.0/src/brigade/templates/continue}/memory-handoffs/TEMPLATE.md +0 -0
  238. {brigade_cli-0.10.2/src/brigade/templates/cursor → brigade_cli-0.11.0/src/brigade/templates/copilot}/memory-handoffs/TEMPLATE.md +0 -0
  239. {brigade_cli-0.10.2/src/brigade/templates/goose → brigade_cli-0.11.0/src/brigade/templates/crush}/memory-handoffs/TEMPLATE.md +0 -0
  240. {brigade_cli-0.10.2/src/brigade/templates/hermes → brigade_cli-0.11.0/src/brigade/templates/cursor}/memory-handoffs/TEMPLATE.md +0 -0
  241. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/depth/repo.json +0 -0
  242. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/depth/workspace.json +0 -0
  243. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/generic/harness-adapter-checklist.md +0 -0
  244. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/generic/memory-contract.md +0 -0
  245. {brigade_cli-0.10.2/src/brigade/templates/kimi → brigade_cli-0.11.0/src/brigade/templates/goose}/memory-handoffs/TEMPLATE.md +0 -0
  246. {brigade_cli-0.10.2/src/brigade/templates/opencode → brigade_cli-0.11.0/src/brigade/templates/grok}/memory-handoffs/TEMPLATE.md +0 -0
  247. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/handoff/handoff-sources.example.json +0 -0
  248. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/handoff/openclaw-ingest-receipt.example.json +0 -0
  249. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/adal.json +0 -0
  250. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/aider.json +0 -0
  251. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/antigravity.json +0 -0
  252. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/claude.json +0 -0
  253. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/codex.json +0 -0
  254. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/continue.json +0 -0
  255. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/copilot.json +0 -0
  256. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/cursor.json +0 -0
  257. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/goose.json +0 -0
  258. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/hermes.json +0 -0
  259. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/kimi.json +0 -0
  260. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/openclaw.json +0 -0
  261. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/opencode.json +0 -0
  262. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/openhands.json +0 -0
  263. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/pi.json +0 -0
  264. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/harnesses/qwen.json +0 -0
  265. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/hermes/README.md +0 -0
  266. {brigade_cli-0.10.2/src/brigade/templates/openhands → brigade_cli-0.11.0/src/brigade/templates/hermes}/memory-handoffs/TEMPLATE.md +0 -0
  267. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/hooks/pre-push +0 -0
  268. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/includes/publisher.json +0 -0
  269. {brigade_cli-0.10.2/src/brigade/templates/pi → brigade_cli-0.11.0/src/brigade/templates/kimi}/memory-handoffs/TEMPLATE.md +0 -0
  270. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/backup-restic.md +0 -0
  271. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/chat-surface-crawlers.md +0 -0
  272. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/content-safety.md +0 -0
  273. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/handoff-flow.md +0 -0
  274. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/memory-architecture.md +0 -0
  275. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/memory-care-staleness.md +0 -0
  276. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/memory-scanner.md +0 -0
  277. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/multi-workspace-handoff-admin.md +0 -0
  278. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/obsidian-notes.md +0 -0
  279. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/pipeline-standups.md +0 -0
  280. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/memory/cards/tokenjuice-output-compaction.md +0 -0
  281. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/openclaw/README.md +0 -0
  282. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/openclaw/acp-escalation.openclaw.json +0 -0
  283. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/openclaw/memory-sweep-cron.openclaw.json +0 -0
  284. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/openclaw/model-aliases.openclaw.json +0 -0
  285. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/openclaw/ollama-memory-search.openclaw.json +0 -0
  286. {brigade_cli-0.10.2/src/brigade/templates/qwen → brigade_cli-0.11.0/src/brigade/templates/opencode}/memory-handoffs/TEMPLATE.md +0 -0
  287. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/scripts/backup-restic.sh +0 -0
  288. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/skills/note/SKILL.md +0 -0
  289. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/AGENTS.md +0 -0
  290. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/CLAUDE.md +0 -0
  291. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/HEARTBEAT.md +0 -0
  292. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/IDENTITY.md +0 -0
  293. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/INSTALL_FOR_AGENTS.md +0 -0
  294. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/MEMORY.md +0 -0
  295. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/SAFETY_RULES.md +0 -0
  296. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/SOUL.md +0 -0
  297. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/TOOLS.md +0 -0
  298. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/USER.md +0 -0
  299. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/rules/acceptance-driven-work.md +0 -0
  300. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/templates/workspace/rules/issue-tdd-loop.md +0 -0
  301. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade/work_cmd/constants.py +0 -0
  302. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade_cli.egg-info/dependency_links.txt +0 -0
  303. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade_cli.egg-info/entry_points.txt +0 -0
  304. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/src/brigade_cli.egg-info/top_level.txt +0 -0
  305. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_cli_alias.py +0 -0
  306. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_cli_help.py +0 -0
  307. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_neutrality.py +0 -0
  308. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase96_cmd.py +0 -0
  309. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase98_cmd.py +0 -0
  310. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_phase99_cmd.py +0 -0
  311. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_privacy_regression.py +0 -0
  312. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_proc.py +0 -0
  313. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_reconfigure.py +0 -0
  314. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_research_cli_sources.py +0 -0
  315. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_runbook_cmd.py +0 -0
  316. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_runs_cmd.py +0 -0
  317. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_station.py +0 -0
  318. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_status.py +0 -0
  319. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_toml_compat.py +0 -0
  320. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_untrusted_cmd.py +0 -0
  321. {brigade_cli-0.10.2 → brigade_cli-0.11.0}/tests/test_work_cmd_facade.py +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brigade-cli
3
- Version: 0.10.2
3
+ Version: 0.11.0
4
4
  Summary: AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, Hermes, and OpenClaw.
5
- Author-email: Solomon Neas <srneas@gmail.com>
5
+ Author-email: Solomon Neas <me@solomonneas.dev>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://brigade.tools
8
8
  Project-URL: Repository, https://github.com/escoffier-labs/brigade
@@ -23,6 +23,7 @@ Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: pytest>=7; extra == "dev"
26
+ Requires-Dist: ruff>=0.15; extra == "dev"
26
27
  Provides-Extra: research
27
28
  Requires-Dist: playwright>=1.40; extra == "research"
28
29
  Dynamic: license-file
@@ -34,7 +35,7 @@ Dynamic: license-file
34
35
  <h1 align="center">Brigade CLI</h1>
35
36
 
36
37
  <p align="center">
37
- <strong>AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, and a dozen other harnesses.</strong>
38
+ <strong>AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, and over a dozen other harnesses.</strong>
38
39
  </p>
39
40
 
40
41
  <p align="center">
@@ -46,6 +47,16 @@ Dynamic: license-file
46
47
 
47
48
  Your agents run loops. Brigade keeps the receipts.
48
49
 
50
+ ## Try it in 60 seconds
51
+
52
+ ```bash
53
+ pipx install brigade-cli
54
+ brigade operator quickstart --target ./my-repo --harnesses codex # wire one repo
55
+ brigade operator doctor --target ./my-repo --profile local-operator # verify
56
+ ```
57
+
58
+ That installs the CLI, wires memory, handoffs, and local guardrails into one repo for a single harness, and prints a readiness check. Nothing leaves your machine and no daemon is started. Add `--dry-run` to preview the file-by-file plan before anything is written. More harnesses, workspace setups, and the homegrown-adoption path are under [Install](#install).
59
+
49
60
  ## Why I built this
50
61
 
51
62
  I run an always-on OpenClaw agent next to daily Codex and Claude Code sessions, and I have since January. Every one of those tools wakes up empty. Whatever a session learned about my machine, my rules, or yesterday's dead ends scattered across tool-specific folders and died there.
@@ -117,7 +128,7 @@ brigade handoff lint --target ./my-repo
117
128
  brigade handoff doctor --target ./my-repo
118
129
  ```
119
130
 
120
- New here? Start with [docs/first-10-minutes.md](docs/first-10-minutes.md). Already have a homegrown setup with scripts, crons, and handoff folders? Brigade has an adoption path that inventories what you have before changing anything: start with `brigade operator adopt plan` and see the [technical guide](docs/technical-guide.md). Want an agent to set this up for you? Point it at this repo; [AGENTS.md](AGENTS.md) tells it exactly what to do and where to stop.
131
+ New here? Start with [QUICKSTART.md](QUICKSTART.md) for the five-minute install, then [docs/first-10-minutes.md](docs/first-10-minutes.md) for the guided first session. Already have a homegrown setup with scripts, crons, and handoff folders? Brigade has an adoption path that inventories what you have before changing anything: start with `brigade operator adopt plan` and see the [technical guide](docs/technical-guide.md). Want an agent to set this up for you? Point it at this repo; [AGENTS.md](AGENTS.md) tells it exactly what to do and where to stop.
121
132
 
122
133
  ## Harness support
123
134
 
@@ -139,6 +150,9 @@ Each writer gets its own local inbox; one canonical owner ingests. Brigade keeps
139
150
  | Kimi Code | `kimi` | `.kimi/memory-handoffs/` |
140
151
  | AdaL | `adal` | `.adal/memory-handoffs/` |
141
152
  | OpenHands | `openhands` | `.openhands/memory-handoffs/` |
153
+ | Grok CLI | `grok` | `.grok/memory-handoffs/` |
154
+ | Amp | `amp` | `.amp/memory-handoffs/` |
155
+ | Crush | `crush` | `.crush/memory-handoffs/` |
142
156
  | Hermes | `hermes` | `.hermes/memory-handoffs/` |
143
157
  | OpenClaw | `openclaw` | usually the memory owner, not a writer |
144
158
 
@@ -149,9 +163,11 @@ All of them get handoff templates, ingest source coverage, and projected tools/s
149
163
  The memory loop is the core. Around it, the same review-and-receipt pattern covers the rest of an operator's day, and you can ignore all of it until you need it:
150
164
 
151
165
  - **Daily loop**: `brigade work brief` shows pending work, imports, and warnings; `brigade daily status` keeps it bounded and cheap.
166
+ - **Friction logs**: `brigade friction scan --days 30 --import-candidates` mines recent notes, handoffs, session artifacts, and optional local agent logs for reviewable workflow friction.
152
167
  - **Security**: `brigade security scan` is a local read-only scanner for agent workspaces (secrets, risky hooks, MCP configs, prompt-injection patterns); `brigade scrub` gates content before it leaves the machine.
153
168
  - **Tools and skills**: one reviewed catalog projected into every harness's native format, with approval gates for anything that executes.
154
169
  - **Research**: `brigade research run` turns a question into a cited local report and a reviewable memory handoff.
170
+ - **Cross-model runs**: `brigade run "<task>"` plans, dispatches, and synthesizes one bounded task across the agent CLIs in your roster, so an expensive model can think while cheaper ones do the grunt work. Rosters pin a model per agent, plans can stage dependent workers, and `--worktree` runs everything in a detached git checkout that comes back as a reviewable `changes.patch`. A dirty-tree guard and a run lock keep agents away from your work in progress.
155
171
  - **Fleet and release**: health evidence across your local repos and release-readiness receipts, with no publish step.
156
172
 
157
173
  The full tour of every station lives in [docs/overview.md](docs/overview.md).
@@ -187,6 +203,7 @@ That pause is the point. Agent memory should be useful, not noisy.
187
203
  - [Handoff promotion](docs/handoff-promotion.md): how notes move toward memory.
188
204
  - [Repo fleet](docs/repo-fleet.md) and [Tool catalog](docs/tool-catalog.md).
189
205
  - [Command inventory](docs/command-inventory.md): every public CLI command.
206
+ - [Maintainers](MAINTAINERS.md), [Governance](GOVERNANCE.md), [Security](SECURITY.md), and [Contributing](CONTRIBUTING.md).
190
207
  - [Roadmap](ROADMAP.md) and [roadmap archive](docs/roadmap-archive.md).
191
208
 
192
209
  Project identity: GitHub [`escoffier-labs/brigade`](https://github.com/escoffier-labs/brigade), website [brigade.tools](https://brigade.tools), PyPI [`brigade-cli`](https://pypi.org/project/brigade-cli/), command `brigade`. The name comes from the kitchen: a *brigade de cuisine* runs the line, and *mise en place* means the station is prepped before service. Set up the rules, memory, tools, and receipts before the session gets expensive.
@@ -1,6 +1,6 @@
1
1
  # Quickstart
2
2
 
3
- Five minutes from clone to a working agent kitchen.
3
+ Five minutes from install to a working agent kitchen.
4
4
 
5
5
  ## 1. Install
6
6
 
@@ -23,7 +23,19 @@ python3 -m pipx ensurepath
23
23
 
24
24
  ## First install
25
25
 
26
- The fastest path is to run `brigade init` with no flags and answer the prompts:
26
+ The canonical first-run command is `brigade operator quickstart`. It installs the template files, wires the operator config, and runs the health checks in one shot:
27
+
28
+ ```bash
29
+ # Code repo with Codex as the writer
30
+ brigade operator quickstart --target ./my-repo --harnesses codex
31
+
32
+ # OpenClaw or Hermes workspace instead of a code repo
33
+ brigade operator quickstart --target ~/agent-workspace --depth workspace --harnesses openclaw,hermes --owner openclaw
34
+ ```
35
+
36
+ Pass `--dry-run` first to preview the planned steps without writing anything.
37
+
38
+ Two commands share this surface: `brigade init` installs the template files only, and `brigade operator quickstart` wraps it (init, then operator config, then doctor). Use `init` when you want the interactive harness picker or just the files:
27
39
 
28
40
  ```bash
29
41
  $ brigade init --target ~/agent-kitchen
@@ -31,8 +43,23 @@ $ brigade init --target ~/agent-kitchen
31
43
  Which harnesses do you use? (type numbers separated by space/comma to toggle, enter to confirm)
32
44
  [x] 1. Claude Code
33
45
  [ ] 2. Codex
34
- [ ] 3. OpenClaw
35
- [ ] 4. Hermes (experimental)
46
+ [ ] 3. OpenCode
47
+ [ ] 4. Antigravity
48
+ [ ] 5. Pi
49
+ [ ] 6. Cursor
50
+ [ ] 7. Aider
51
+ [ ] 8. Goose
52
+ [ ] 9. Continue
53
+ [ ] 10. GitHub Copilot CLI
54
+ [ ] 11. Qwen Code
55
+ [ ] 12. Kimi Code
56
+ [ ] 13. AdaL
57
+ [ ] 14. OpenHands
58
+ [ ] 15. Grok CLI
59
+ [ ] 16. Amp
60
+ [ ] 17. Crush
61
+ [ ] 18. OpenClaw
62
+ [ ] 19. Hermes (experimental)
36
63
 
37
64
  Depth? (type a number, enter for default)
38
65
  * 1. repo (handoff flow + publish guard)
@@ -46,24 +73,30 @@ Defaults are claude harness, repo depth, no includes. Enter ships the install.
46
73
 
47
74
  ## CI / scripted install
48
75
 
49
- Pass flags directly to skip the prompt:
76
+ Pass flags directly to skip the prompt. The same flags work on `operator quickstart`:
50
77
 
51
78
  ```bash
52
79
  # Claude Code + Codex + OpenClaw, full workspace
53
- brigade init --target ~/agent-kitchen \
80
+ brigade operator quickstart --target ~/agent-kitchen \
54
81
  --depth workspace \
55
82
  --harnesses claude,codex,openclaw
56
83
 
57
84
  # Codex-only project, minimal install
58
- brigade init --target ./my-project --depth repo --harnesses codex
85
+ brigade operator quickstart --target ./my-project --depth repo --harnesses codex
59
86
 
60
- # Generic layout, no harness-specific files
87
+ # Template files only, no harness-specific files
61
88
  brigade init --target ./my-project --harnesses none
62
89
  ```
63
90
 
64
91
  ## Verifying
65
92
 
66
- After install, `brigade doctor --target <path>` reports the apparent harness shape and checks every configured inbox and adapter:
93
+ After install, check the operator profile:
94
+
95
+ ```bash
96
+ brigade operator doctor --target <path> --profile local-operator
97
+ ```
98
+
99
+ For the file-by-file view, `brigade doctor --target <path>` reports the apparent harness shape and checks every configured inbox and adapter:
67
100
 
68
101
  ```
69
102
  brigade doctor: target /home/you/agent-kitchen
@@ -103,6 +136,6 @@ See the [Solo Cookbook](https://github.com/escoffier-labs/solos-cookbook) for th
103
136
  - Read [the cookbook](https://github.com/escoffier-labs/solos-cookbook) for the deep version of every concept here.
104
137
  - Customize `USER.md` and `TOOLS.md` with your real preferences and runbooks (kept private; do not commit personal details).
105
138
  - Wire the ingester on a cron or a manual end-of-day workflow.
106
- - Add a memory-care staleness scan when your card set starts to matter. See `memory/cards/memory-care-staleness.md`.
107
- - If you use TokenJuice, wire Claude Code and Codex hooks deliberately and tell agents what the wrapper means. See `memory/cards/tokenjuice-output-compaction.md`.
139
+ - Add a memory-care staleness scan when your card set starts to matter. See [docs/memory-care.md](docs/memory-care.md).
140
+ - If you use TokenJuice, wire Claude Code and Codex hooks deliberately and tell agents what the wrapper means. See the tokens station in [docs/technical-guide.md](docs/technical-guide.md#managed-stations).
108
141
  - Run `brigade work bootstrap` inside active repos when you want the dogfood-backed daily work loop, scanner inbox, and local evidence receipts.
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">Brigade CLI</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, and a dozen other harnesses.</strong>
8
+ <strong>AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, and over a dozen other harnesses.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -17,6 +17,16 @@
17
17
 
18
18
  Your agents run loops. Brigade keeps the receipts.
19
19
 
20
+ ## Try it in 60 seconds
21
+
22
+ ```bash
23
+ pipx install brigade-cli
24
+ brigade operator quickstart --target ./my-repo --harnesses codex # wire one repo
25
+ brigade operator doctor --target ./my-repo --profile local-operator # verify
26
+ ```
27
+
28
+ That installs the CLI, wires memory, handoffs, and local guardrails into one repo for a single harness, and prints a readiness check. Nothing leaves your machine and no daemon is started. Add `--dry-run` to preview the file-by-file plan before anything is written. More harnesses, workspace setups, and the homegrown-adoption path are under [Install](#install).
29
+
20
30
  ## Why I built this
21
31
 
22
32
  I run an always-on OpenClaw agent next to daily Codex and Claude Code sessions, and I have since January. Every one of those tools wakes up empty. Whatever a session learned about my machine, my rules, or yesterday's dead ends scattered across tool-specific folders and died there.
@@ -88,7 +98,7 @@ brigade handoff lint --target ./my-repo
88
98
  brigade handoff doctor --target ./my-repo
89
99
  ```
90
100
 
91
- New here? Start with [docs/first-10-minutes.md](docs/first-10-minutes.md). Already have a homegrown setup with scripts, crons, and handoff folders? Brigade has an adoption path that inventories what you have before changing anything: start with `brigade operator adopt plan` and see the [technical guide](docs/technical-guide.md). Want an agent to set this up for you? Point it at this repo; [AGENTS.md](AGENTS.md) tells it exactly what to do and where to stop.
101
+ New here? Start with [QUICKSTART.md](QUICKSTART.md) for the five-minute install, then [docs/first-10-minutes.md](docs/first-10-minutes.md) for the guided first session. Already have a homegrown setup with scripts, crons, and handoff folders? Brigade has an adoption path that inventories what you have before changing anything: start with `brigade operator adopt plan` and see the [technical guide](docs/technical-guide.md). Want an agent to set this up for you? Point it at this repo; [AGENTS.md](AGENTS.md) tells it exactly what to do and where to stop.
92
102
 
93
103
  ## Harness support
94
104
 
@@ -110,6 +120,9 @@ Each writer gets its own local inbox; one canonical owner ingests. Brigade keeps
110
120
  | Kimi Code | `kimi` | `.kimi/memory-handoffs/` |
111
121
  | AdaL | `adal` | `.adal/memory-handoffs/` |
112
122
  | OpenHands | `openhands` | `.openhands/memory-handoffs/` |
123
+ | Grok CLI | `grok` | `.grok/memory-handoffs/` |
124
+ | Amp | `amp` | `.amp/memory-handoffs/` |
125
+ | Crush | `crush` | `.crush/memory-handoffs/` |
113
126
  | Hermes | `hermes` | `.hermes/memory-handoffs/` |
114
127
  | OpenClaw | `openclaw` | usually the memory owner, not a writer |
115
128
 
@@ -120,9 +133,11 @@ All of them get handoff templates, ingest source coverage, and projected tools/s
120
133
  The memory loop is the core. Around it, the same review-and-receipt pattern covers the rest of an operator's day, and you can ignore all of it until you need it:
121
134
 
122
135
  - **Daily loop**: `brigade work brief` shows pending work, imports, and warnings; `brigade daily status` keeps it bounded and cheap.
136
+ - **Friction logs**: `brigade friction scan --days 30 --import-candidates` mines recent notes, handoffs, session artifacts, and optional local agent logs for reviewable workflow friction.
123
137
  - **Security**: `brigade security scan` is a local read-only scanner for agent workspaces (secrets, risky hooks, MCP configs, prompt-injection patterns); `brigade scrub` gates content before it leaves the machine.
124
138
  - **Tools and skills**: one reviewed catalog projected into every harness's native format, with approval gates for anything that executes.
125
139
  - **Research**: `brigade research run` turns a question into a cited local report and a reviewable memory handoff.
140
+ - **Cross-model runs**: `brigade run "<task>"` plans, dispatches, and synthesizes one bounded task across the agent CLIs in your roster, so an expensive model can think while cheaper ones do the grunt work. Rosters pin a model per agent, plans can stage dependent workers, and `--worktree` runs everything in a detached git checkout that comes back as a reviewable `changes.patch`. A dirty-tree guard and a run lock keep agents away from your work in progress.
126
141
  - **Fleet and release**: health evidence across your local repos and release-readiness receipts, with no publish step.
127
142
 
128
143
  The full tour of every station lives in [docs/overview.md](docs/overview.md).
@@ -158,6 +173,7 @@ That pause is the point. Agent memory should be useful, not noisy.
158
173
  - [Handoff promotion](docs/handoff-promotion.md): how notes move toward memory.
159
174
  - [Repo fleet](docs/repo-fleet.md) and [Tool catalog](docs/tool-catalog.md).
160
175
  - [Command inventory](docs/command-inventory.md): every public CLI command.
176
+ - [Maintainers](MAINTAINERS.md), [Governance](GOVERNANCE.md), [Security](SECURITY.md), and [Contributing](CONTRIBUTING.md).
161
177
  - [Roadmap](ROADMAP.md) and [roadmap archive](docs/roadmap-archive.md).
162
178
 
163
179
  Project identity: GitHub [`escoffier-labs/brigade`](https://github.com/escoffier-labs/brigade), website [brigade.tools](https://brigade.tools), PyPI [`brigade-cli`](https://pypi.org/project/brigade-cli/), command `brigade`. The name comes from the kitchen: a *brigade de cuisine* runs the line, and *mise en place* means the station is prepped before service. Set up the rules, memory, tools, and receipts before the session gets expensive.
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "brigade-cli"
7
- version = "0.10.2"
7
+ version = "0.11.0"
8
8
  description = "AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, Hermes, and OpenClaw."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  license = { text = "MIT" }
12
- authors = [{ name = "Solomon Neas", email = "srneas@gmail.com" }] # content-guard: allow email
12
+ authors = [{ name = "Solomon Neas", email = "me@solomonneas.dev" }] # content-guard: allow email
13
13
  keywords = ["agents", "ai-agents", "agent-memory", "agent-handoffs", "ai-memory", "openclaw", "claude-code", "codex", "opencode", "memory", "bootstrap", "brigade", "brigade-cli", "operator", "local-first", "guardrails", "agents-md"]
14
14
  classifiers = [
15
15
  "Development Status :: 3 - Alpha",
@@ -22,7 +22,7 @@ classifiers = [
22
22
  dependencies = []
23
23
 
24
24
  [project.optional-dependencies]
25
- dev = ["pytest>=7"]
25
+ dev = ["pytest>=7", "ruff>=0.15"]
26
26
  research = ["playwright>=1.40"]
27
27
 
28
28
  [project.urls]
@@ -47,3 +47,14 @@ brigade = ["py.typed", "templates/**/*"]
47
47
  [tool.pytest.ini_options]
48
48
  testpaths = ["tests"]
49
49
  addopts = "-ra"
50
+
51
+ [tool.ruff]
52
+ line-length = 120
53
+ src = ["src", "tests"]
54
+
55
+ [tool.ruff.lint]
56
+ # Pragmatic gate: ruff's default rule set (pyflakes plus the E4/E7/E9
57
+ # pycodestyle errors) plus bugbear (B). Deferred for later, separate
58
+ # passes: import sorting (I), pyupgrade (UP), the full pycodestyle E/W
59
+ # set, and any type checking (mypy/pyright).
60
+ select = ["B", "E4", "E7", "E9", "F"]
@@ -1,3 +1,3 @@
1
1
  """Brigade: local operator-system CLI for agent workspaces."""
2
2
 
3
- __version__ = "0.10.2"
3
+ __version__ = "0.11.0"
@@ -1,4 +1,5 @@
1
1
  """Enable `python -m brigade`."""
2
+
2
3
  from .cli import main
3
4
 
4
5
  if __name__ == "__main__":
@@ -1,4 +1,5 @@
1
1
  """Bounded cross-model orchestration for `brigade run`."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import json
@@ -19,6 +20,7 @@ from .roster import Agent, Roster, is_cli_allowed, timeout_for, workers
19
20
  class Assignment:
20
21
  worker: str
21
22
  task: str
23
+ stage: int = 1
22
24
 
23
25
 
24
26
  @dataclass(frozen=True)
@@ -36,9 +38,7 @@ def build_plan_prompt(
36
38
  corrective_note: str | None = None,
37
39
  read_only: bool = False,
38
40
  ) -> str:
39
- worker_lines = "\n".join(
40
- f"- {agent.name}: cli={agent.cli}; role={agent.role}" for agent in workers(roster)
41
- )
41
+ worker_lines = "\n".join(f"- {agent.name}: cli={agent.cli}; role={agent.role}" for agent in workers(roster))
42
42
  if not worker_lines:
43
43
  worker_lines = "- no workers configured"
44
44
 
@@ -47,11 +47,14 @@ def build_plan_prompt(
47
47
  return (
48
48
  "You are the Brigade aboyeur. Split the user's task across the available workers.\n"
49
49
  "Return exactly one JSON object, with no prose outside JSON:\n"
50
- '{"assignments":[{"worker":"<worker-name>","task":"<specific sub-task>"}]}\n'
50
+ '{"assignments":[{"stage":1,"worker":"<worker-name>","task":"<specific sub-task>"}]}\n'
51
51
  f"{note}\n"
52
52
  f"User task:\n{task}\n\n"
53
53
  f"Available workers, excluding you:\n{worker_lines}\n\n"
54
- f"Rules:\n- Use at most {roster.max_workers} assignments.\n"
54
+ f"Rules:\n- Use at most {roster.max_workers} assignments per stage.\n"
55
+ "- Stage must be a positive integer starting at stage 1.\n"
56
+ "- Assignments in the same stage run in parallel; later stages receive earlier-stage worker results.\n"
57
+ "- Omit stage only for backwards-compatible stage 1 assignments.\n"
55
58
  "- Assign only listed workers.\n"
56
59
  "- Use zero assignments only if no worker is useful."
57
60
  f"{policy}"
@@ -139,14 +142,21 @@ def write_run_handoff(
139
142
  timestamp = (now or datetime.now(timezone.utc)).strftime("%Y-%m-%d-%H%M")
140
143
  safe_task = _one_line(task)
141
144
  path = inbox / f"{timestamp}-brigade-run-{_slug(safe_task)}.md"
142
- worker_summary = "\n".join(
143
- f"- {result.worker}: {'ok' if result.ok else 'failed'}"
144
- + (f" ({_one_line(result.detail)})" if result.detail else "")
145
- for result in worker_results
146
- ) or "- no workers dispatched"
147
- assignment_summary = "\n".join(
148
- f"- {assignment.worker}: {_one_line(assignment.task)}" for assignment in assignments
149
- ) or "- no worker assignments"
145
+ worker_summary = (
146
+ "\n".join(
147
+ f"- {result.worker}: {'ok' if result.ok else 'failed'}"
148
+ + (f" ({_one_line(result.detail)})" if result.detail else "")
149
+ for result in worker_results
150
+ )
151
+ or "- no workers dispatched"
152
+ )
153
+ assignment_summary = (
154
+ "\n".join(
155
+ f"- stage {assignment.stage} -> {assignment.worker}: {_one_line(assignment.task)}"
156
+ for assignment in assignments
157
+ )
158
+ or "- no worker assignments"
159
+ )
150
160
  artifact_line = f"- artifacts: `{output_dir}`" if output_dir is not None else "- artifacts: none"
151
161
  cwd_line = f"- cwd: `{cwd}`" if cwd is not None else "- cwd: not set"
152
162
  mode_line = "- mode: read-only" if read_only else "- mode: normal"
@@ -229,30 +239,38 @@ def parse_plan(text: str, roster: Roster) -> list[Assignment]:
229
239
  raw_assignments = payload.get("assignments")
230
240
  if not isinstance(raw_assignments, list):
231
241
  raise ValueError("plan JSON needs an assignments list")
232
- if len(raw_assignments) > roster.max_workers:
233
- raise ValueError(f"plan has {len(raw_assignments)} assignments, limit is {roster.max_workers}")
234
242
 
235
243
  assignments: list[Assignment] = []
236
- seen: set[tuple[str, str]] = set()
244
+ seen: set[tuple[int, str, str]] = set()
245
+ stage_counts: dict[int, int] = {}
237
246
  for item in raw_assignments:
238
247
  if not isinstance(item, dict):
239
248
  raise ValueError("each assignment must be an object")
240
- worker = item.get("worker")
249
+ stage = item.get("stage", 1)
250
+ if isinstance(stage, bool) or not isinstance(stage, int) or stage < 1:
251
+ raise ValueError("assignment.stage must be a positive integer")
252
+ raw_worker = item.get("worker")
241
253
  subtask = item.get("task")
242
- if not isinstance(worker, str) or not worker.strip():
254
+ if not isinstance(raw_worker, str) or not raw_worker.strip():
243
255
  raise ValueError("assignment.worker must be a non-empty string")
256
+ worker = raw_worker.strip()
244
257
  if worker not in roster.agents:
245
258
  raise ValueError(f"assignment references unknown worker: {worker!r}")
246
259
  if worker == roster.orchestrator:
247
260
  raise ValueError("assignment cannot target the orchestrator")
248
261
  if not isinstance(subtask, str) or not subtask.strip():
249
262
  raise ValueError("assignment.task must be a non-empty string")
250
- assignment = Assignment(worker=worker.strip(), task=subtask.strip())
251
- key = (assignment.worker, assignment.task)
263
+ assignment = Assignment(worker=worker, task=subtask.strip(), stage=stage)
264
+ key = (assignment.stage, assignment.worker, assignment.task)
252
265
  if key not in seen:
253
266
  assignments.append(assignment)
254
267
  seen.add(key)
255
- return assignments
268
+ stage_counts[assignment.stage] = stage_counts.get(assignment.stage, 0) + 1
269
+
270
+ for stage, count in stage_counts.items():
271
+ if count > roster.max_workers:
272
+ raise ValueError(f"plan has {count} assignments in stage {stage}, limit is {roster.max_workers}")
273
+ return sorted(assignments, key=lambda assignment: assignment.stage)
256
274
 
257
275
 
258
276
  def _record_plan_attempt(
@@ -299,6 +317,8 @@ def _run_orchestrator(
299
317
  }
300
318
  if sandbox is not None:
301
319
  kwargs["sandbox"] = sandbox
320
+ if orchestrator.model is not None:
321
+ kwargs["model"] = orchestrator.model
302
322
  return agents.run_agent(orchestrator.cli, prompt, **kwargs)
303
323
 
304
324
 
@@ -353,13 +373,39 @@ def plan(
353
373
  raise RuntimeError(f"orchestrator returned an invalid plan: {second_exc}") from second_exc
354
374
 
355
375
 
356
- def _worker_prompt(agent: Agent, assignment: Assignment, read_only: bool = False) -> str:
376
+ def _render_prior_results(results: list[WorkerResult]) -> str:
377
+ return "\n\n".join(
378
+ "\n".join(
379
+ [
380
+ f"Worker: {result.worker}",
381
+ f"Sub-task: {result.task}",
382
+ f"Status: {'ok' if result.ok else 'failed'}",
383
+ f"Detail: {result.detail}" if result.detail else "Detail:",
384
+ "Output:",
385
+ result.text or "(no output)",
386
+ ]
387
+ )
388
+ for result in results
389
+ )
390
+
391
+
392
+ def _worker_prompt(
393
+ agent: Agent,
394
+ assignment: Assignment,
395
+ *,
396
+ prior_results: list[WorkerResult] | None = None,
397
+ read_only: bool = False,
398
+ ) -> str:
399
+ prior_context = ""
400
+ if prior_results:
401
+ prior_context = f"\n\nEarlier-stage context:\n{_render_prior_results(prior_results)}"
357
402
  policy = f"\n\n{_read_only_rules()}" if read_only else ""
358
403
  return (
359
404
  f"You are Brigade worker {agent.name}.\n"
360
405
  f"Role:\n{agent.role}\n\n"
361
406
  f"Sub-task:\n{assignment.task}\n\n"
362
407
  "Return a concise, complete result for the orchestrator to synthesize."
408
+ f"{prior_context}"
363
409
  f"{policy}"
364
410
  )
365
411
 
@@ -372,7 +418,7 @@ def dispatch(
372
418
  sandbox_read_only: bool | None = None,
373
419
  sandbox: str | None = None,
374
420
  ) -> list[WorkerResult]:
375
- def run_one(assignment: Assignment) -> WorkerResult:
421
+ def run_one(assignment: Assignment, prior_results: list[WorkerResult]) -> WorkerResult:
376
422
  agent = roster.agents[assignment.worker]
377
423
  if not is_cli_allowed(agent.cli, roster):
378
424
  return WorkerResult(
@@ -389,7 +435,13 @@ def dispatch(
389
435
  }
390
436
  if sandbox is not None:
391
437
  kwargs["sandbox"] = sandbox
392
- result = agents.run_agent(agent.cli, _worker_prompt(agent, assignment, read_only=read_only), **kwargs)
438
+ if agent.model is not None:
439
+ kwargs["model"] = agent.model
440
+ result = agents.run_agent(
441
+ agent.cli,
442
+ _worker_prompt(agent, assignment, prior_results=prior_results, read_only=read_only),
443
+ **kwargs,
444
+ )
393
445
  return WorkerResult(
394
446
  worker=assignment.worker,
395
447
  task=assignment.task,
@@ -401,28 +453,34 @@ def dispatch(
401
453
  if not assignments:
402
454
  return []
403
455
 
404
- results_by_index: dict[int, WorkerResult] = {}
405
- max_workers = min(roster.max_workers, len(assignments))
406
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
407
- future_to_index = {
408
- executor.submit(run_one, assignment): index
409
- for index, assignment in enumerate(assignments)
410
- }
411
- for future in as_completed(future_to_index):
412
- index = future_to_index[future]
413
- try:
414
- results_by_index[index] = future.result()
415
- except Exception as exc: # pragma: no cover - defensive boundary
416
- assignment = assignments[index]
417
- results_by_index[index] = WorkerResult(
418
- worker=assignment.worker,
419
- task=assignment.task,
420
- text="",
421
- ok=False,
422
- detail=str(exc)[:200],
423
- )
424
-
425
- return [results_by_index[index] for index in range(len(assignments))]
456
+ all_results: list[WorkerResult] = []
457
+ stages = sorted({assignment.stage for assignment in assignments})
458
+ for stage in stages:
459
+ stage_assignments = [assignment for assignment in assignments if assignment.stage == stage]
460
+ stage_results_by_index: dict[int, WorkerResult] = {}
461
+ prior_results = list(all_results)
462
+ max_workers = min(roster.max_workers, len(stage_assignments))
463
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
464
+ future_to_index = {
465
+ executor.submit(run_one, assignment, prior_results): index
466
+ for index, assignment in enumerate(stage_assignments)
467
+ }
468
+ for future in as_completed(future_to_index):
469
+ index = future_to_index[future]
470
+ try:
471
+ stage_results_by_index[index] = future.result()
472
+ except Exception as exc: # pragma: no cover - defensive boundary
473
+ assignment = stage_assignments[index]
474
+ stage_results_by_index[index] = WorkerResult(
475
+ worker=assignment.worker,
476
+ task=assignment.task,
477
+ text="",
478
+ ok=False,
479
+ detail=str(exc)[:200],
480
+ )
481
+ all_results.extend(stage_results_by_index[index] for index in range(len(stage_assignments)))
482
+
483
+ return all_results
426
484
 
427
485
 
428
486
  def build_synth_prompt(task: str, results: list[WorkerResult], read_only: bool = False) -> str:
@@ -458,8 +516,16 @@ def _print_plan(assignments: list[Assignment]) -> None:
458
516
  if not assignments:
459
517
  print(" (no worker assignments)")
460
518
  return
461
- for assignment in assignments:
462
- print(f" -> {assignment.worker}: {assignment.task}")
519
+ stages = sorted({assignment.stage for assignment in assignments})
520
+ if len(stages) == 1:
521
+ for assignment in assignments:
522
+ print(f" -> {assignment.worker}: {assignment.task}")
523
+ return
524
+ for stage in stages:
525
+ print(f" stage {stage}:")
526
+ for assignment in assignments:
527
+ if assignment.stage == stage:
528
+ print(f" -> {assignment.worker}: {assignment.task}")
463
529
 
464
530
 
465
531
  def _print_worker_status(results: list[WorkerResult]) -> None:
@@ -473,10 +539,9 @@ def _print_worker_status(results: list[WorkerResult]) -> None:
473
539
  print(f" [{marker}] {result.worker}{detail}")
474
540
 
475
541
 
476
- def _assignment_payload(assignments: list[Assignment]) -> list[dict[str, str]]:
542
+ def _assignment_payload(assignments: list[Assignment]) -> list[dict[str, object]]:
477
543
  return [
478
- {"worker": assignment.worker, "task": assignment.task}
479
- for assignment in assignments
544
+ {"stage": assignment.stage, "worker": assignment.worker, "task": assignment.task} for assignment in assignments
480
545
  ]
481
546
 
482
547
 
@@ -507,9 +572,11 @@ def _roster_payload(roster: Roster) -> dict[str, object]:
507
572
  "max_workers": roster.max_workers,
508
573
  "timeout_seconds": roster.timeout_seconds,
509
574
  "allow_models": list(roster.allow_models),
575
+ "sandbox": roster.sandbox,
510
576
  "agents": {
511
577
  name: {
512
578
  "cli": agent.cli,
579
+ "model": agent.model,
513
580
  "role": agent.role,
514
581
  "timeout_seconds": agent.timeout_seconds,
515
582
  }
@@ -11,6 +11,7 @@ The phase-ledger queue in phases_cmd stays local: it stores one JSON file
11
11
  per action and stamps `reviewed_at`/`review_reason` instead of the
12
12
  `started_at`/`completed_at`/`deferred_at` lifecycle fields below.
13
13
  """
14
+
14
15
  from __future__ import annotations
15
16
 
16
17
  import json
@@ -1,4 +1,5 @@
1
1
  """`brigade add <station>` - install and wire a station's managed tools."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import sys