brigade-cli 0.11.0__tar.gz → 0.12.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 (329) hide show
  1. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/PKG-INFO +13 -12
  2. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/README.md +12 -11
  3. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/pyproject.toml +1 -1
  4. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/__init__.py +1 -1
  5. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/agents.py +34 -0
  6. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/__init__.py +2 -0
  7. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/_common.py +4 -1
  8. brigade_cli-0.12.0/src/brigade/cli/completions.py +17 -0
  9. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/doctor.py +2 -1
  10. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/friction.py +10 -0
  11. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/init.py +10 -0
  12. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/memory.py +19 -0
  13. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/operator.py +15 -0
  14. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/projects.py +5 -0
  15. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/repos.py +6 -1
  16. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/run.py +43 -0
  17. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/runbook.py +16 -1
  18. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/scrub.py +11 -1
  19. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/security.py +30 -2
  20. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/skills.py +12 -0
  21. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/status.py +2 -1
  22. brigade_cli-0.12.0/src/brigade/completions.py +91 -0
  23. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/doctor.py +115 -24
  24. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/friction_cmd.py +27 -0
  25. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/install.py +49 -8
  26. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/localio.py +24 -3
  27. brigade_cli-0.12.0/src/brigade/mcp_server.py +96 -0
  28. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/memory_cmd.py +199 -1
  29. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/__init__.py +4 -0
  30. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/lifecycle.py +79 -2
  31. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/projects_cmd.py +15 -0
  32. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/repos_cmd.py +78 -2
  33. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/runbook_cmd.py +51 -3
  34. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/scrub.py +63 -3
  35. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/security_cmd.py +103 -2
  36. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/skills_cmd.py +55 -82
  37. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/status.py +25 -4
  38. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/depth/workspace.json +1 -1
  39. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/memory-handoff.harness.json +1 -1
  40. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/model-lanes.harness.json +1 -1
  41. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/workspace.harness.json +1 -1
  42. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hooks/pre-push +15 -1
  43. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/chat-memory-sweep.example.json +1 -1
  44. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/memory-care.example.json +1 -1
  45. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/policies/personal.json +1 -1
  46. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/policies/public-content.json +1 -1
  47. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/policies/public-repo.json +1 -1
  48. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/AGENTS.md +9 -0
  49. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/helpers.py +14 -7
  50. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/PKG-INFO +13 -12
  51. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/SOURCES.txt +14 -0
  52. brigade_cli-0.12.0/tests/test_completions.py +46 -0
  53. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_doctor.py +104 -1
  54. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_init.py +56 -0
  55. brigade_cli-0.12.0/tests/test_localio.py +38 -0
  56. brigade_cli-0.12.0/tests/test_mcp_server.py +60 -0
  57. brigade_cli-0.12.0/tests/test_memory_search_mcp.py +89 -0
  58. brigade_cli-0.12.0/tests/test_operator_checkup.py +72 -0
  59. brigade_cli-0.12.0/tests/test_parity_backlog.py +92 -0
  60. brigade_cli-0.12.0/tests/test_pre_push_hook.py +30 -0
  61. brigade_cli-0.12.0/tests/test_read_only_enforcement.py +47 -0
  62. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_repos_cmd.py +41 -1
  63. brigade_cli-0.12.0/tests/test_runbook_import.py +48 -0
  64. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_security_cmd.py +2 -2
  65. brigade_cli-0.12.0/tests/test_security_diff.py +62 -0
  66. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_status.py +17 -0
  67. brigade_cli-0.12.0/tests/test_subprocess_guards.py +51 -0
  68. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/LICENSE +0 -0
  69. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/MANIFEST.in +0 -0
  70. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/QUICKSTART.md +0 -0
  71. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/setup.cfg +0 -0
  72. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/__main__.py +0 -0
  73. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/aboyeur.py +0 -0
  74. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/actionqueue.py +0 -0
  75. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/add.py +0 -0
  76. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/budgets.py +0 -0
  77. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/budgets_cmd.py +0 -0
  78. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/center_cmd.py +0 -0
  79. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/chat_cmd.py +0 -0
  80. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/add.py +0 -0
  81. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/budgets.py +0 -0
  82. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/center.py +0 -0
  83. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/chat.py +0 -0
  84. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/context.py +0 -0
  85. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/daily.py +0 -0
  86. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/dogfood.py +0 -0
  87. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/handoff.py +0 -0
  88. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/handoff_template.py +0 -0
  89. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/hermes_fragments.py +0 -0
  90. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/ingest.py +0 -0
  91. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/learn.py +0 -0
  92. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/notifications.py +0 -0
  93. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/openclaw_fragments.py +0 -0
  94. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/pantry.py +0 -0
  95. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/reconfigure.py +0 -0
  96. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/release.py +0 -0
  97. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/research.py +0 -0
  98. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/roadmap.py +0 -0
  99. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/roster.py +0 -0
  100. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/runs.py +0 -0
  101. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/tools.py +0 -0
  102. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/untrusted.py +0 -0
  103. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/work.py +0 -0
  104. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/config.py +0 -0
  105. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/context_cmd.py +0 -0
  106. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/daily_cmd.py +0 -0
  107. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/dogfood_cmd.py +0 -0
  108. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/fragments.py +0 -0
  109. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/handoff.py +0 -0
  110. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/handoff_cmd.py +0 -0
  111. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/hermes_adapter.py +0 -0
  112. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/ingest.py +0 -0
  113. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/learn_cmd.py +0 -0
  114. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/managed.py +0 -0
  115. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/notifications_cmd.py +0 -0
  116. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/adoption.py +0 -0
  117. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/guide.py +0 -0
  118. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/health.py +0 -0
  119. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/migration.py +0 -0
  120. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/surfaces.py +0 -0
  121. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/pantry_cmd.py +0 -0
  122. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/phases_cmd.py +0 -0
  123. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/proc.py +0 -0
  124. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/prompt.py +0 -0
  125. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/py.typed +0 -0
  126. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/reconfigure.py +0 -0
  127. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/registry.py +0 -0
  128. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/release_cmd.py +0 -0
  129. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/reportstore.py +0 -0
  130. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/__init__.py +0 -0
  131. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/config.py +0 -0
  132. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/engine.py +0 -0
  133. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/extract.py +0 -0
  134. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/handoff.py +0 -0
  135. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/llm.py +0 -0
  136. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/registry.py +0 -0
  137. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/report.py +0 -0
  138. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/sources/__init__.py +0 -0
  139. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/sources/cli.py +0 -0
  140. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/sources/local.py +0 -0
  141. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/sources/web.py +0 -0
  142. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/types.py +0 -0
  143. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research_cmd.py +0 -0
  144. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/roadmap_cmd.py +0 -0
  145. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/roster.py +0 -0
  146. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/roster_cmd.py +0 -0
  147. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/runguard.py +0 -0
  148. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/runs_cmd.py +0 -0
  149. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/selection.py +0 -0
  150. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/station.py +0 -0
  151. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/adal/memory-handoffs/TEMPLATE.md +0 -0
  152. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/aider/memory-handoffs/TEMPLATE.md +0 -0
  153. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/amp/memory-handoffs/TEMPLATE.md +0 -0
  154. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/antigravity/memory-handoffs/TEMPLATE.md +0 -0
  155. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/claude/memory-handoffs/TEMPLATE.md +0 -0
  156. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/codex/memory-handoffs/TEMPLATE.md +0 -0
  157. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/continue/memory-handoffs/TEMPLATE.md +0 -0
  158. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/copilot/memory-handoffs/TEMPLATE.md +0 -0
  159. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/crush/memory-handoffs/TEMPLATE.md +0 -0
  160. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/cursor/memory-handoffs/TEMPLATE.md +0 -0
  161. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/depth/repo.json +0 -0
  162. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/generic/harness-adapter-checklist.md +0 -0
  163. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/generic/memory-contract.md +0 -0
  164. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/goose/memory-handoffs/TEMPLATE.md +0 -0
  165. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/grok/memory-handoffs/TEMPLATE.md +0 -0
  166. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/handoff/handoff-sources.example.json +0 -0
  167. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/handoff/openclaw-ingest-receipt.example.json +0 -0
  168. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/adal.json +0 -0
  169. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/aider.json +0 -0
  170. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/amp.json +0 -0
  171. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/antigravity.json +0 -0
  172. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/claude.json +0 -0
  173. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/codex.json +0 -0
  174. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/continue.json +0 -0
  175. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/copilot.json +0 -0
  176. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/crush.json +0 -0
  177. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/cursor.json +0 -0
  178. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/goose.json +0 -0
  179. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/grok.json +0 -0
  180. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/hermes.json +0 -0
  181. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/kimi.json +0 -0
  182. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/openclaw.json +0 -0
  183. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/opencode.json +0 -0
  184. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/openhands.json +0 -0
  185. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/pi.json +0 -0
  186. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/qwen.json +0 -0
  187. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/README.md +0 -0
  188. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/memory-handoffs/TEMPLATE.md +0 -0
  189. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/includes/publisher.json +0 -0
  190. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/kimi/memory-handoffs/TEMPLATE.md +0 -0
  191. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/backup-restic.md +0 -0
  192. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/chat-surface-crawlers.md +0 -0
  193. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/content-safety.md +0 -0
  194. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/handoff-flow.md +0 -0
  195. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-architecture.md +0 -0
  196. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-care-staleness.md +0 -0
  197. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-scanner.md +0 -0
  198. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/multi-workspace-handoff-admin.md +0 -0
  199. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/obsidian-notes.md +0 -0
  200. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/pipeline-standups.md +0 -0
  201. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/tokenjuice-output-compaction.md +0 -0
  202. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/README.md +0 -0
  203. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/acp-escalation.openclaw.json +0 -0
  204. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/memory-sweep-cron.openclaw.json +0 -0
  205. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/model-aliases.openclaw.json +0 -0
  206. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/ollama-memory-search.openclaw.json +0 -0
  207. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/opencode/memory-handoffs/TEMPLATE.md +0 -0
  208. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openhands/memory-handoffs/TEMPLATE.md +0 -0
  209. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/pi/memory-handoffs/TEMPLATE.md +0 -0
  210. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/qwen/memory-handoffs/TEMPLATE.md +0 -0
  211. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/scripts/backup-restic.sh +0 -0
  212. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/skills/note/SKILL.md +0 -0
  213. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/CLAUDE.md +0 -0
  214. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/HEARTBEAT.md +0 -0
  215. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/IDENTITY.md +0 -0
  216. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/INSTALL_FOR_AGENTS.md +0 -0
  217. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/MEMORY.md +0 -0
  218. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/SAFETY_RULES.md +0 -0
  219. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/SOUL.md +0 -0
  220. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/TOOLS.md +0 -0
  221. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/USER.md +0 -0
  222. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/rules/acceptance-driven-work.md +0 -0
  223. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/rules/issue-tdd-loop.md +0 -0
  224. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates.py +0 -0
  225. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/toml_compat.py +0 -0
  226. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/tools_cmd.py +0 -0
  227. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/untrusted.py +0 -0
  228. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/untrusted_cmd.py +0 -0
  229. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/__init__.py +0 -0
  230. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/backup.py +0 -0
  231. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/config.py +0 -0
  232. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/constants.py +0 -0
  233. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/imports.py +0 -0
  234. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/ledger.py +0 -0
  235. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/reviews.py +0 -0
  236. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/scanners.py +0 -0
  237. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/services.py +0 -0
  238. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/session.py +0 -0
  239. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/sweeps.py +0 -0
  240. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/verification.py +0 -0
  241. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/dependency_links.txt +0 -0
  242. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/entry_points.txt +0 -0
  243. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/requires.txt +0 -0
  244. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/top_level.txt +0 -0
  245. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_aboyeur.py +0 -0
  246. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_actionqueue.py +0 -0
  247. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_add.py +0 -0
  248. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_agents.py +0 -0
  249. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_budgets.py +0 -0
  250. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_cli_alias.py +0 -0
  251. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_cli_help.py +0 -0
  252. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_config.py +0 -0
  253. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_dogfood_cmd.py +0 -0
  254. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_fragments.py +0 -0
  255. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_friction_cmd.py +0 -0
  256. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_gitignore.py +0 -0
  257. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_handoff.py +0 -0
  258. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_handoff_cmd.py +0 -0
  259. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_ingest.py +0 -0
  260. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_install.py +0 -0
  261. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_managed.py +0 -0
  262. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_memory_cmd.py +0 -0
  263. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_neutrality.py +0 -0
  264. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_notifications_cmd.py +0 -0
  265. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_operator_cmd.py +0 -0
  266. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_pantry_cmd.py +0 -0
  267. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase100_cmd.py +0 -0
  268. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase101_cmd.py +0 -0
  269. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase165_cmd.py +0 -0
  270. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase36_cmd.py +0 -0
  271. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase37_cmd.py +0 -0
  272. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase38_cmd.py +0 -0
  273. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase39_cmd.py +0 -0
  274. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase40_cmd.py +0 -0
  275. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase41_cmd.py +0 -0
  276. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase42_cmd.py +0 -0
  277. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase43_cmd.py +0 -0
  278. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase44_cmd.py +0 -0
  279. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase45_cmd.py +0 -0
  280. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase46_50_cmd.py +0 -0
  281. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase51_55_cmd.py +0 -0
  282. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase56_60_cmd.py +0 -0
  283. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase96_cmd.py +0 -0
  284. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase97_cmd.py +0 -0
  285. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase98_cmd.py +0 -0
  286. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase99_cmd.py +0 -0
  287. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_privacy_regression.py +0 -0
  288. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_proc.py +0 -0
  289. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_prompt.py +0 -0
  290. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_reconfigure.py +0 -0
  291. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_registry.py +0 -0
  292. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_release_cmd.py +0 -0
  293. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_reportstore.py +0 -0
  294. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_cli_sources.py +0 -0
  295. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_cmd.py +0 -0
  296. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_config.py +0 -0
  297. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_engine.py +0 -0
  298. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_extract.py +0 -0
  299. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_handoff.py +0 -0
  300. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_llm.py +0 -0
  301. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_local_sources.py +0 -0
  302. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_registry.py +0 -0
  303. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_report.py +0 -0
  304. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_types.py +0 -0
  305. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_web.py +0 -0
  306. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_roadmap_cmd.py +0 -0
  307. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_roster.py +0 -0
  308. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_roster_cmd.py +0 -0
  309. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_run_cli.py +0 -0
  310. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_runbook_cmd.py +0 -0
  311. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_runguard.py +0 -0
  312. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_runs_cmd.py +0 -0
  313. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_scrub.py +0 -0
  314. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_selection.py +0 -0
  315. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_skills_cmd.py +0 -0
  316. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_station.py +0 -0
  317. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_toml_compat.py +0 -0
  318. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_untrusted.py +0 -0
  319. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_untrusted_cmd.py +0 -0
  320. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_backup.py +0 -0
  321. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_facade.py +0 -0
  322. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_imports.py +0 -0
  323. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_ledger.py +0 -0
  324. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_reviews.py +0 -0
  325. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_scanners.py +0 -0
  326. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_services.py +0 -0
  327. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_session.py +0 -0
  328. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_sweeps.py +0 -0
  329. {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_verification.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brigade-cli
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, Hermes, and OpenClaw.
5
5
  Author-email: Solomon Neas <me@solomonneas.dev>
6
6
  License: MIT
@@ -63,30 +63,31 @@ I run an always-on OpenClaw agent next to daily Codex and Claude Code sessions,
63
63
 
64
64
  So I hand-rolled the fixes, one incident at a time: a slim `MEMORY.md` index pointing at small memory cards instead of one giant file, a handoff note format every harness could write, an ingest cron that filed the good notes into durable memory every 30 minutes, staleness checks so old cards stopped being trusted forever.
65
65
 
66
- Two incidents shaped the design more than anything I planned. First, a nightly "dreaming" job that auto-promoted session fragments bloated `MEMORY.md` to 41KB, way past the 12KB bootstrap budget, so every session started with truncated memory and nobody noticed for weeks. Auto-promotion died that day. Everything goes through review now. Second, I found 195 handoff notes sitting unread across 35 repos because the ingester had a hardcoded three-repo allowlist and nothing warned about the coverage gap. Silence is the failure mode. Every part of Brigade that lints, warns, or writes a receipt exists because something once failed in silence.
66
+ Two incidents shaped the design more than anything I planned. First, a nightly "dreaming" job that promoted raw session fragments straight into memory bloated `MEMORY.md` to 41KB, way past the 12KB bootstrap budget, so every session started with truncated memory and nobody noticed for weeks. Blind auto-promotion died that day. Now nothing reaches memory unlinted: a note has to name a target and clear the guards, the safe ones file themselves, and only the risky few wait for review. Second, I found 195 handoff notes sitting unread across 35 repos because the ingester had a hardcoded three-repo allowlist and nothing warned about the coverage gap. Silence is the failure mode. Every part of Brigade that lints, warns, or writes a receipt exists because something once failed in silence.
67
67
 
68
- That system now runs 482 memory cards and survives daily multi-agent work. But explaining it to anyone meant: clone six repos, write these crons, keep your index slim, watch for staleness, and whatever you do, turn auto-promotion off. Brigade is that setup packaged as one installable CLI. The full production stack is documented in the [solos-cookbook](https://github.com/escoffier-labs/solos-cookbook) if you want to see where it came from.
68
+ That system now runs 482 memory cards and survives daily multi-agent work. But explaining it to anyone meant: clone six repos, write these crons, keep your index slim, watch for staleness, and never let a note reach memory unlinted. Brigade is that setup packaged as one installable CLI. The full production stack is documented in the [solos-cookbook](https://github.com/escoffier-labs/solos-cookbook) if you want to see where it came from.
69
69
 
70
70
  ## The loop
71
71
 
72
- Writer harnesses leave handoff notes as they work. A memory owner (OpenClaw, Hermes, or just you) ingests the ones worth keeping. Brigade lints, guards, and routes everything in between, and every consequential action lands a receipt in a plain file you can grep, diff, and prune.
72
+ Writer harnesses leave handoff notes as they work. Brigade lints, guards, and classifies each one, then files the safe, targeted notes into durable memory on its own. A memory owner (OpenClaw, Hermes, or just you) only steps in for the ambiguous few. Every consequential action lands a receipt in a plain file you can grep, diff, and prune.
73
73
 
74
74
  1. agents write handoff notes into their own local inboxes
75
- 2. Brigade lints and scans them before they can become memory
76
- 3. safe targeted notes get filed into durable memory by the owner
77
- 4. ambiguous or risky notes wait for your review
75
+ 2. Brigade lints and classifies each one before it can become memory
76
+ 3. safe, targeted notes file themselves into durable memory automatically
77
+ 4. only the ambiguous or risky few wait for your review
78
78
  5. future sessions start with better context, and receipts show what happened
79
79
 
80
80
  ```mermaid
81
81
  flowchart LR
82
82
  WRITERS["writer harnesses<br/>Codex · Claude Code · OpenCode · ..."]
83
- BRIGADE["Brigade<br/>lint · guard · route · receipts"]
84
- REVIEW["operator review<br/>safe · ambiguous · risky"]
83
+ BRIGADE["Brigade<br/>lint · guard · classify · receipts"]
85
84
  OWNER["memory owner<br/>OpenClaw / Hermes / you"]
86
85
  MEM["durable memory<br/>MEMORY.md index · memory cards"]
86
+ REVIEW["review inbox<br/>ambiguous · risky"]
87
87
 
88
- WRITERS -- handoff notes --> BRIGADE --> REVIEW
89
- REVIEW -- safe targeted notes --> OWNER --> MEM
88
+ WRITERS -- handoff notes --> BRIGADE --> OWNER
89
+ OWNER -- safe targeted, auto-filed --> MEM
90
+ OWNER -. ambiguous or risky .-> REVIEW
90
91
  MEM -. context .-> WRITERS
91
92
 
92
93
  classDef brigade fill:#2563eb,stroke:#1d4ed8,color:#fff;
@@ -156,7 +157,7 @@ Each writer gets its own local inbox; one canonical owner ingests. Brigade keeps
156
157
  | Hermes | `hermes` | `.hermes/memory-handoffs/` |
157
158
  | OpenClaw | `openclaw` | usually the memory owner, not a writer |
158
159
 
159
- All of them get handoff templates, ingest source coverage, and projected tools/skills. Per-harness details are in the [technical guide](docs/technical-guide.md).
160
+ All of them get handoff templates and ingest source coverage. Most also get projected tools and skills in their native format (some as `rules` or `instructions`, a few not yet); the per-harness matrix is in the [technical guide](docs/technical-guide.md).
160
161
 
161
162
  ## Beyond memory
162
163
 
@@ -33,30 +33,31 @@ I run an always-on OpenClaw agent next to daily Codex and Claude Code sessions,
33
33
 
34
34
  So I hand-rolled the fixes, one incident at a time: a slim `MEMORY.md` index pointing at small memory cards instead of one giant file, a handoff note format every harness could write, an ingest cron that filed the good notes into durable memory every 30 minutes, staleness checks so old cards stopped being trusted forever.
35
35
 
36
- Two incidents shaped the design more than anything I planned. First, a nightly "dreaming" job that auto-promoted session fragments bloated `MEMORY.md` to 41KB, way past the 12KB bootstrap budget, so every session started with truncated memory and nobody noticed for weeks. Auto-promotion died that day. Everything goes through review now. Second, I found 195 handoff notes sitting unread across 35 repos because the ingester had a hardcoded three-repo allowlist and nothing warned about the coverage gap. Silence is the failure mode. Every part of Brigade that lints, warns, or writes a receipt exists because something once failed in silence.
36
+ Two incidents shaped the design more than anything I planned. First, a nightly "dreaming" job that promoted raw session fragments straight into memory bloated `MEMORY.md` to 41KB, way past the 12KB bootstrap budget, so every session started with truncated memory and nobody noticed for weeks. Blind auto-promotion died that day. Now nothing reaches memory unlinted: a note has to name a target and clear the guards, the safe ones file themselves, and only the risky few wait for review. Second, I found 195 handoff notes sitting unread across 35 repos because the ingester had a hardcoded three-repo allowlist and nothing warned about the coverage gap. Silence is the failure mode. Every part of Brigade that lints, warns, or writes a receipt exists because something once failed in silence.
37
37
 
38
- That system now runs 482 memory cards and survives daily multi-agent work. But explaining it to anyone meant: clone six repos, write these crons, keep your index slim, watch for staleness, and whatever you do, turn auto-promotion off. Brigade is that setup packaged as one installable CLI. The full production stack is documented in the [solos-cookbook](https://github.com/escoffier-labs/solos-cookbook) if you want to see where it came from.
38
+ That system now runs 482 memory cards and survives daily multi-agent work. But explaining it to anyone meant: clone six repos, write these crons, keep your index slim, watch for staleness, and never let a note reach memory unlinted. Brigade is that setup packaged as one installable CLI. The full production stack is documented in the [solos-cookbook](https://github.com/escoffier-labs/solos-cookbook) if you want to see where it came from.
39
39
 
40
40
  ## The loop
41
41
 
42
- Writer harnesses leave handoff notes as they work. A memory owner (OpenClaw, Hermes, or just you) ingests the ones worth keeping. Brigade lints, guards, and routes everything in between, and every consequential action lands a receipt in a plain file you can grep, diff, and prune.
42
+ Writer harnesses leave handoff notes as they work. Brigade lints, guards, and classifies each one, then files the safe, targeted notes into durable memory on its own. A memory owner (OpenClaw, Hermes, or just you) only steps in for the ambiguous few. Every consequential action lands a receipt in a plain file you can grep, diff, and prune.
43
43
 
44
44
  1. agents write handoff notes into their own local inboxes
45
- 2. Brigade lints and scans them before they can become memory
46
- 3. safe targeted notes get filed into durable memory by the owner
47
- 4. ambiguous or risky notes wait for your review
45
+ 2. Brigade lints and classifies each one before it can become memory
46
+ 3. safe, targeted notes file themselves into durable memory automatically
47
+ 4. only the ambiguous or risky few wait for your review
48
48
  5. future sessions start with better context, and receipts show what happened
49
49
 
50
50
  ```mermaid
51
51
  flowchart LR
52
52
  WRITERS["writer harnesses<br/>Codex · Claude Code · OpenCode · ..."]
53
- BRIGADE["Brigade<br/>lint · guard · route · receipts"]
54
- REVIEW["operator review<br/>safe · ambiguous · risky"]
53
+ BRIGADE["Brigade<br/>lint · guard · classify · receipts"]
55
54
  OWNER["memory owner<br/>OpenClaw / Hermes / you"]
56
55
  MEM["durable memory<br/>MEMORY.md index · memory cards"]
56
+ REVIEW["review inbox<br/>ambiguous · risky"]
57
57
 
58
- WRITERS -- handoff notes --> BRIGADE --> REVIEW
59
- REVIEW -- safe targeted notes --> OWNER --> MEM
58
+ WRITERS -- handoff notes --> BRIGADE --> OWNER
59
+ OWNER -- safe targeted, auto-filed --> MEM
60
+ OWNER -. ambiguous or risky .-> REVIEW
60
61
  MEM -. context .-> WRITERS
61
62
 
62
63
  classDef brigade fill:#2563eb,stroke:#1d4ed8,color:#fff;
@@ -126,7 +127,7 @@ Each writer gets its own local inbox; one canonical owner ingests. Brigade keeps
126
127
  | Hermes | `hermes` | `.hermes/memory-handoffs/` |
127
128
  | OpenClaw | `openclaw` | usually the memory owner, not a writer |
128
129
 
129
- All of them get handoff templates, ingest source coverage, and projected tools/skills. Per-harness details are in the [technical guide](docs/technical-guide.md).
130
+ All of them get handoff templates and ingest source coverage. Most also get projected tools and skills in their native format (some as `rules` or `instructions`, a few not yet); the per-harness matrix is in the [technical guide](docs/technical-guide.md).
130
131
 
131
132
  ## Beyond memory
132
133
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "brigade-cli"
7
- version = "0.11.0"
7
+ version = "0.12.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"
@@ -1,3 +1,3 @@
1
1
  """Brigade: local operator-system CLI for agent workspaces."""
2
2
 
3
- __version__ = "0.11.0"
3
+ __version__ = "0.12.0"
@@ -133,6 +133,40 @@ _ADAPTERS: dict[str, Callable[[str, bool, str | None], List[str]]] = {
133
133
  }
134
134
 
135
135
 
136
+ # How strongly each adapter enforces a read-only run:
137
+ # hard - a native sandbox or tool allowlist the model cannot escape
138
+ # soft - read-only is only a prompt instruction the model may ignore
139
+ # none - read_only is not applied to this CLI at all
140
+ # Brigade is "loud about exceptions", so `brigade run --read-only` warns when an
141
+ # assigned harness is soft or none rather than implying a guarantee it cannot make.
142
+ READ_ONLY_ENFORCEMENT: dict[str, str] = {
143
+ "codex": "hard",
144
+ "antigravity": "hard",
145
+ "pi": "hard",
146
+ "cursor": "hard",
147
+ "aider": "hard",
148
+ "continue": "hard",
149
+ "qwen": "hard",
150
+ "kimi": "hard",
151
+ "goose": "soft",
152
+ "copilot": "soft",
153
+ "adal": "soft",
154
+ "openhands": "soft",
155
+ "grok": "soft",
156
+ "amp": "soft",
157
+ "crush": "soft",
158
+ "claude": "none",
159
+ "opencode": "none",
160
+ }
161
+
162
+
163
+ def read_only_enforcement(cli_ref: str) -> str:
164
+ """Return how strongly cli_ref enforces read-only: 'hard', 'soft', or 'none'."""
165
+ if cli_ref.startswith(_OLLAMA_PREFIX):
166
+ return "none"
167
+ return READ_ONLY_ENFORCEMENT.get(cli_ref, "none")
168
+
169
+
136
170
  @dataclass(frozen=True)
137
171
  class AgentResult:
138
172
  text: str
@@ -60,6 +60,7 @@ from . import (
60
60
  openclaw_fragments as _openclaw_fragments_group,
61
61
  hermes_fragments as _hermes_fragments_group,
62
62
  reconfigure as _reconfigure_group,
63
+ completions as _completions_group,
63
64
  )
64
65
 
65
66
 
@@ -115,6 +116,7 @@ def _build_parser() -> argparse.ArgumentParser:
115
116
  _openclaw_fragments_group.register(sub)
116
117
  _hermes_fragments_group.register(sub)
117
118
  _reconfigure_group.register(sub)
119
+ _completions_group.register(sub)
118
120
 
119
121
  parser.epilog = _grouped_epilog(sub)
120
122
  return parser
@@ -19,7 +19,10 @@ COMMAND_GROUPS: list[tuple[str, list[str]]] = [
19
19
  "Review, security, and research",
20
20
  ["security", "scrub", "untrusted", "research", "learn", "chat", "context", "projects"],
21
21
  ),
22
- ("Wiring and advanced", ["release", "roadmap", "repos", "reconfigure", "openclaw-fragments", "hermes-fragments"]),
22
+ (
23
+ "Wiring and advanced",
24
+ ["release", "roadmap", "repos", "reconfigure", "completions", "openclaw-fragments", "hermes-fragments"],
25
+ ),
23
26
  ]
24
27
 
25
28
  _START_HERE = """Brigade: run your agent brigade. Operator-system CLI for agent workspaces.
@@ -0,0 +1,17 @@
1
+ """brigade completions command group."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+
8
+ def register(sub: argparse._SubParsersAction) -> None:
9
+ p = sub.add_parser("completions", help="Print a shell completion script (bash, zsh, or fish).")
10
+ p.add_argument("shell", choices=["bash", "zsh", "fish"], help="Shell to generate completions for.")
11
+ p.set_defaults(func=dispatch)
12
+
13
+
14
+ def dispatch(args) -> int:
15
+ from .. import completions as completions_mod
16
+
17
+ return completions_mod.emit(shell=args.shell)
@@ -15,10 +15,11 @@ def register(sub: argparse._SubParsersAction) -> None:
15
15
  choices=["generic", "openclaw", "hermes"],
16
16
  default="generic",
17
17
  )
18
+ p_doctor.add_argument("--json", action="store_true", help="Emit machine-readable JSON instead of text.")
18
19
  p_doctor.set_defaults(func=dispatch)
19
20
 
20
21
 
21
22
  def dispatch(args) -> int:
22
23
  from .. import doctor as doctor_mod
23
24
 
24
- return doctor_mod.run(target=args.target, harness=args.harness)
25
+ return doctor_mod.run(target=args.target, harness=args.harness, json_output=args.json)
@@ -51,6 +51,14 @@ def register(sub: argparse._SubParsersAction) -> None:
51
51
  p_friction_add.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
52
52
  p_friction_add.set_defaults(func=dispatch)
53
53
 
54
+ p_friction_show = friction_sub.add_parser("show", help="Show the latest friction scan results.")
55
+ p_friction_show.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
56
+ p_friction_show.add_argument(
57
+ "--severity", choices=["low", "medium", "high"], default=None, help="Only show this severity."
58
+ )
59
+ p_friction_show.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
60
+ p_friction_show.set_defaults(func=dispatch)
61
+
54
62
 
55
63
  def dispatch(args) -> int:
56
64
  from .. import friction_cmd
@@ -68,6 +76,8 @@ def dispatch(args) -> int:
68
76
  dry_run=args.dry_run,
69
77
  json_output=args.json,
70
78
  )
79
+ if args.friction_command == "show":
80
+ return friction_cmd.show(target=args.target, severity=args.severity, json_output=args.json)
71
81
  if args.friction_command == "add":
72
82
  return friction_cmd.add(
73
83
  target=args.target,
@@ -24,6 +24,12 @@ def register(sub: argparse._SubParsersAction) -> None:
24
24
  default=True,
25
25
  help="Do not create or update the target's .gitignore.",
26
26
  )
27
+ p_init.add_argument(
28
+ "--git-exclude",
29
+ action="store_true",
30
+ help="Write Brigade ignores to .git/info/exclude (local-only) instead of the tracked .gitignore. "
31
+ "Use this in a third-party clone you do not want to commit Brigade ignores into.",
32
+ )
27
33
  p_init.add_argument("--dry-run", action="store_true", help="Show what would happen.")
28
34
  p_init.add_argument(
29
35
  "--depth",
@@ -81,6 +87,8 @@ def dispatch(args) -> int:
81
87
  force=getattr(args, "force", False),
82
88
  dry_run=getattr(args, "dry_run", False),
83
89
  allow_home=getattr(args, "allow_home", False),
90
+ use_git_exclude=getattr(args, "git_exclude", False),
91
+ update_gitignore=getattr(args, "update_gitignore", True),
84
92
  )
85
93
 
86
94
  # No selection flags: interactive prompt.
@@ -99,4 +107,6 @@ def dispatch(args) -> int:
99
107
  force=getattr(args, "force", False),
100
108
  dry_run=getattr(args, "dry_run", False),
101
109
  allow_home=getattr(args, "allow_home", False),
110
+ use_git_exclude=getattr(args, "git_exclude", False),
111
+ update_gitignore=getattr(args, "update_gitignore", True),
102
112
  )
@@ -67,6 +67,21 @@ def register(sub: argparse._SubParsersAction) -> None:
67
67
  "--defer", action="store_true", help="Mark current queue deferred instead of reviewed."
68
68
  )
69
69
  p_memory_care_closeout.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
70
+ p_memory_search = memory_sub.add_parser("search", help="Keyword-search local memory cards.")
71
+ p_memory_search.add_argument("query", help="Search terms (matched against title, tags, summary, and body).")
72
+ p_memory_search.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to search.")
73
+ p_memory_search.add_argument("--limit", type=int, default=20, help="Maximum matches to show.")
74
+ p_memory_search.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
75
+ p_memory_serve_mcp = memory_sub.add_parser(
76
+ "serve-mcp", help="Expose memory cards over a read-only MCP stdio server (card:// scheme)."
77
+ )
78
+ p_memory_serve_mcp.add_argument(
79
+ "--target", "-t", type=Path, default=Path("."), help="Repo or workspace whose cards to serve."
80
+ )
81
+ p_memory_serve_mcp.add_argument(
82
+ "--stdio", action="store_true", help="Run the JSON-RPC stdio server (otherwise print the contract)."
83
+ )
84
+ p_memory_serve_mcp.add_argument("--json", action="store_true", help="Print the contract as JSON.")
70
85
  p_memory.set_defaults(func=dispatch)
71
86
 
72
87
 
@@ -105,5 +120,9 @@ def dispatch(args) -> int:
105
120
  )
106
121
  args._brigade_parser.error(f"unknown memory care command: {args.memory_care_command}")
107
122
  return 2
123
+ if args.memory_command == "search":
124
+ return memory_cmd.search(target=args.target, query=args.query, limit=args.limit, json_output=args.json)
125
+ if args.memory_command == "serve-mcp":
126
+ return memory_cmd.serve_mcp(target=args.target, stdio=args.stdio, json_output=args.json)
108
127
  args._brigade_parser.error(f"unknown memory command: {args.memory_command}")
109
128
  return 2
@@ -213,6 +213,19 @@ def register(sub: argparse._SubParsersAction) -> None:
213
213
  help="Operator profile to inspect.",
214
214
  )
215
215
  p_operator_doctor.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
216
+ p_operator_checkup = operator_sub.add_parser(
217
+ "checkup", help="Run every read-only first-run doctor at once and roll up the verdict."
218
+ )
219
+ p_operator_checkup.add_argument(
220
+ "--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect."
221
+ )
222
+ p_operator_checkup.add_argument(
223
+ "--profile",
224
+ choices=["local-operator", "internal-dogfood"],
225
+ default="internal-dogfood",
226
+ help="Operator profile to inspect.",
227
+ )
228
+ p_operator_checkup.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
216
229
  p_operator_verify_harness = operator_sub.add_parser(
217
230
  "verify-harness", help="Verify repo-local wiring for one harness."
218
231
  )
@@ -373,6 +386,8 @@ def dispatch(args) -> int:
373
386
  return operator_cmd.status(target=args.target, profile=args.profile, json_output=args.json)
374
387
  if args.operator_command == "doctor":
375
388
  return operator_cmd.doctor(target=args.target, profile=args.profile, json_output=args.json)
389
+ if args.operator_command == "checkup":
390
+ return operator_cmd.checkup(target=args.target, profile=args.profile, json_output=args.json)
376
391
  if args.operator_command == "verify-harness":
377
392
  return operator_cmd.verify_harness(target=args.target, harness=args.harness, json_output=args.json)
378
393
  if args.operator_command == "sync-tools":
@@ -16,6 +16,9 @@ def register(sub: argparse._SubParsersAction) -> None:
16
16
  p_projects_audit = projects_sub.add_parser("audit", help="Audit configured project consolidation records.")
17
17
  p_projects_audit.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
18
18
  p_projects_audit.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
19
+ p_projects_doctor = projects_sub.add_parser("doctor", help="Check local project consolidation health.")
20
+ p_projects_doctor.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
21
+ p_projects_doctor.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
19
22
  p_projects_import = projects_sub.add_parser(
20
23
  "import-issues", help="Import project consolidation issues into the work inbox."
21
24
  )
@@ -91,6 +94,8 @@ def dispatch(args) -> int:
91
94
 
92
95
  if args.projects_command == "audit":
93
96
  return projects_cmd.audit(target=args.target, json_output=args.json)
97
+ if args.projects_command == "doctor":
98
+ return projects_cmd.doctor(target=args.target, json_output=args.json)
94
99
  if args.projects_command == "import-issues":
95
100
  return projects_cmd.import_issues(target=args.target, dry_run=args.dry_run, json_output=args.json)
96
101
  if args.projects_command == "closeout":
@@ -31,6 +31,11 @@ def register(sub: argparse._SubParsersAction) -> None:
31
31
  p_repos_doctor = repos_sub.add_parser("doctor", help="Report repo fleet health.")
32
32
  p_repos_doctor.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
33
33
  p_repos_doctor.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
34
+ p_repos_doctor.add_argument(
35
+ "--deep",
36
+ action="store_true",
37
+ help="Run the operator checkup (every first-run doctor) in each enabled repo and aggregate.",
38
+ )
34
39
  p_repos_import = repos_sub.add_parser("import-issues", help="Import repo fleet health issues into the work inbox.")
35
40
  p_repos_import.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
36
41
  p_repos_import.add_argument("--dry-run", action="store_true", help="Show counts without writing imports.")
@@ -562,7 +567,7 @@ def dispatch(args) -> int:
562
567
  if args.repos_command == "scan":
563
568
  return repos_cmd.scan(target=args.target, json_output=args.json)
564
569
  if args.repos_command == "doctor":
565
- return repos_cmd.doctor(target=args.target, json_output=args.json)
570
+ return repos_cmd.doctor(target=args.target, json_output=args.json, deep=args.deep)
566
571
  if args.repos_command == "import-issues":
567
572
  return repos_cmd.import_issues(target=args.target, dry_run=args.dry_run, json_output=args.json)
568
573
  if args.repos_command == "first-run":
@@ -135,6 +135,23 @@ def dispatch(args) -> int:
135
135
  if args.handoff:
136
136
  handoff_inbox = args.handoff_inbox or (run_cwd / ".claude" / "memory-handoffs")
137
137
  effective_sandbox = args.sandbox if args.sandbox is not None else loaded_roster.sandbox
138
+ if args.read_only:
139
+ advisory = _read_only_advisory(loaded_roster, effective_sandbox)
140
+ if advisory:
141
+ print("warning: --read-only is best-effort for some agents in this run:", file=sys.stderr)
142
+ for line in advisory:
143
+ print(f" - {line}", file=sys.stderr)
144
+ print(
145
+ " brigade cannot guarantee these agents leave the tree untouched; review the run output.",
146
+ file=sys.stderr,
147
+ )
148
+ if output_dir is not None:
149
+ from .. import localio
150
+
151
+ localio.write_json(
152
+ output_dir / "read-only-enforcement.json",
153
+ {"read_only": True, "sandbox": effective_sandbox, "best_effort_agents": advisory},
154
+ )
138
155
  # The dirty guard protects write runs from mixing agent edits with uncommitted
139
156
  # work. Dry, read-only, and worktree runs never edit the tree, so reviewing
140
157
  # uncommitted changes stays possible without --allow-dirty.
@@ -193,3 +210,29 @@ def dispatch(args) -> int:
193
210
  def _worktree_checkout_path(repo_root: Path, output_dir: Path) -> Path:
194
211
  run_id = output_dir.expanduser().resolve().name
195
212
  return Path.home() / ".cache" / "brigade" / "worktrees" / f"{repo_root.name}-{run_id}"
213
+
214
+
215
+ def _read_only_advisory(roster, effective_sandbox) -> list[str]:
216
+ """Lines describing which worker agents do not hard-enforce read-only.
217
+
218
+ A writable --sandbox override downgrades even natively-sandboxed CLIs to
219
+ prompt-only, so the advisory reflects the sandbox actually in effect.
220
+ """
221
+ from .. import agents as agents_mod
222
+ from .. import roster as roster_mod
223
+
224
+ sandbox_overrides_native = effective_sandbox in ("workspace-write", "danger-full-access")
225
+ lines: list[str] = []
226
+ # The orchestrator runs too (it plans), so include it alongside the workers.
227
+ orchestrator = roster.agents.get(roster.orchestrator)
228
+ agents_to_check = [orchestrator, *roster_mod.workers(roster)] if orchestrator else roster_mod.workers(roster)
229
+ for agent in agents_to_check:
230
+ cli = agent.cli or ""
231
+ enforcement = agents_mod.read_only_enforcement(cli)
232
+ if sandbox_overrides_native and enforcement == "hard":
233
+ enforcement = "soft"
234
+ if enforcement == "hard":
235
+ continue
236
+ how = "prompt-only (the model may ignore it)" if enforcement == "soft" else "not applied for this CLI"
237
+ lines.append(f"{agent.name} ({cli or 'unknown'}): read-only is {how}")
238
+ return lines
@@ -37,6 +37,15 @@ def register(sub: argparse._SubParsersAction) -> None:
37
37
  "--status", choices=["reviewed", "deferred", "blocked", "archived"], default="reviewed"
38
38
  )
39
39
  p_runbook_closeout.add_argument("--reason", default=None, help="Closeout reason.")
40
+ p_runbook_closeout.add_argument(
41
+ "--import-issues",
42
+ dest="import_issues",
43
+ action="store_true",
44
+ help="Route each failed step into the work import inbox for review.",
45
+ )
46
+ p_runbook_closeout.add_argument(
47
+ "--dry-run", action="store_true", help="With --import-issues, report records without writing imports."
48
+ )
40
49
  p_runbook_closeout.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
41
50
  p_runbook.set_defaults(func=dispatch)
42
51
 
@@ -69,7 +78,13 @@ def dispatch(args) -> int:
69
78
  return runbook_cmd.resume(target=args.target, run_id=args.run_id, json_output=args.json)
70
79
  if args.runbook_command == "closeout":
71
80
  return runbook_cmd.closeout(
72
- target=args.target, run_id=args.run_id, status=args.status, reason=args.reason, json_output=args.json
81
+ target=args.target,
82
+ run_id=args.run_id,
83
+ status=args.status,
84
+ reason=args.reason,
85
+ import_issues=args.import_issues,
86
+ dry_run=args.dry_run,
87
+ json_output=args.json,
73
88
  )
74
89
  args._brigade_parser.error(f"unknown runbook command: {args.runbook_command}")
75
90
  return 2
@@ -16,10 +16,20 @@ def register(sub: argparse._SubParsersAction) -> None:
16
16
  help="Policy file name (looks under .brigade/policies, then content-guard/policies) or path.",
17
17
  )
18
18
  p_scrub.add_argument("--dry-run", action="store_true")
19
+ p_scrub.add_argument("--json", action="store_true", help="Print a machine-readable summary (no matched snippets).")
20
+ p_scrub.add_argument(
21
+ "--no-receipt", action="store_true", help="Do not write the .brigade/scrub/latest.json receipt."
22
+ )
19
23
  p_scrub.set_defaults(func=dispatch)
20
24
 
21
25
 
22
26
  def dispatch(args) -> int:
23
27
  from .. import scrub as scrub_mod
24
28
 
25
- return scrub_mod.run(target=args.target, policy=args.policy, dry_run=args.dry_run)
29
+ return scrub_mod.run(
30
+ target=args.target,
31
+ policy=args.policy,
32
+ dry_run=args.dry_run,
33
+ json_output=args.json,
34
+ write_receipt=not args.no_receipt,
35
+ )
@@ -42,6 +42,23 @@ def register(sub: argparse._SubParsersAction) -> None:
42
42
  "--output-dir", type=Path, default=None, help="Security evidence bundle directory."
43
43
  )
44
44
  p_security_findings.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
45
+ p_security_diff = security_sub.add_parser(
46
+ "diff", help="Compare two security reports (new/resolved/persisting findings)."
47
+ )
48
+ p_security_diff.add_argument(
49
+ "--target", "-t", type=Path, default=Path("."), help="Repo or workspace for config and the default --against."
50
+ )
51
+ p_security_diff.add_argument(
52
+ "--base", dest="base_dir", type=Path, required=True, help="Baseline security evidence bundle directory."
53
+ )
54
+ p_security_diff.add_argument(
55
+ "--against",
56
+ dest="against_dir",
57
+ type=Path,
58
+ default=None,
59
+ help="Bundle to compare against. Defaults to .brigade/security/latest.",
60
+ )
61
+ p_security_diff.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
45
62
  p_security_sarif = security_sub.add_parser("sarif", help="Write SARIF for an existing security report.")
46
63
  p_security_sarif.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to review.")
47
64
  p_security_sarif.add_argument("--output-dir", type=Path, default=None, help="Security evidence bundle directory.")
@@ -82,11 +99,13 @@ def register(sub: argparse._SubParsersAction) -> None:
82
99
  "--target", "-t", type=Path, default=Path("."), help="Repo or workspace to update."
83
100
  )
84
101
  p_security_suppress.add_argument("--reason", required=True, help="Required suppression reason.")
102
+ p_security_suppress.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
85
103
  p_security_unsuppress = security_sub.add_parser("unsuppress", help="Remove a security finding suppression.")
86
104
  p_security_unsuppress.add_argument("fingerprint", help="Finding id, id prefix, or fingerprint to unsuppress.")
87
105
  p_security_unsuppress.add_argument(
88
106
  "--target", "-t", type=Path, default=Path("."), help="Repo or workspace to update."
89
107
  )
108
+ p_security_unsuppress.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
90
109
  p_security_closeout = security_sub.add_parser("closeout", help="Write local security review closeout metadata.")
91
110
  p_security_closeout.add_argument(
92
111
  "--target", "-t", type=Path, default=Path("."), help="Repo or workspace to update."
@@ -158,6 +177,13 @@ def dispatch(args) -> int:
158
177
  return security_cmd.review(target=args.target, output_dir=args.output_dir, json_output=args.json)
159
178
  if args.security_command == "findings":
160
179
  return security_cmd.findings(target=args.target, output_dir=args.output_dir, json_output=args.json)
180
+ if args.security_command == "diff":
181
+ return security_cmd.diff(
182
+ target=args.target,
183
+ base_dir=args.base_dir,
184
+ against_dir=args.against_dir,
185
+ json_output=args.json,
186
+ )
161
187
  if args.security_command == "sarif":
162
188
  return security_cmd.sarif(
163
189
  target=args.target, output_dir=args.output_dir, output_path=args.output_path, json_output=args.json
@@ -178,9 +204,11 @@ def dispatch(args) -> int:
178
204
  json_output=args.json,
179
205
  )
180
206
  if args.security_command == "suppress":
181
- return security_cmd.suppress(target=args.target, fingerprint=args.fingerprint, reason=args.reason)
207
+ return security_cmd.suppress(
208
+ target=args.target, fingerprint=args.fingerprint, reason=args.reason, json_output=args.json
209
+ )
182
210
  if args.security_command == "unsuppress":
183
- return security_cmd.unsuppress(target=args.target, fingerprint=args.fingerprint)
211
+ return security_cmd.unsuppress(target=args.target, fingerprint=args.fingerprint, json_output=args.json)
184
212
  if args.security_command == "closeout":
185
213
  return security_cmd.closeout(
186
214
  target=args.target,
@@ -59,6 +59,11 @@ def register(sub: argparse._SubParsersAction) -> None:
59
59
  p_skills_diff.add_argument("--target", "-t", type=Path, default=Path("."), help="Workspace registry to inspect.")
60
60
  p_skills_diff.add_argument("--harness", required=True, help="Harness target to compare.")
61
61
  p_skills_diff.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
62
+ p_skills_uninstall = skills_sub.add_parser("uninstall", help="Remove an installed skill from one or all harnesses.")
63
+ p_skills_uninstall.add_argument("skill", help="Installed skill id.")
64
+ p_skills_uninstall.add_argument("--workspace", type=Path, default=Path("."), help="Workspace to update.")
65
+ p_skills_uninstall.add_argument("--target", dest="install_target", required=True, help="Harness target or all.")
66
+ p_skills_uninstall.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
62
67
  p_skills_rollback = skills_sub.add_parser(
63
68
  "rollback", help="Rollback one installed skill target to the latest snapshot."
64
69
  )
@@ -194,6 +199,13 @@ def dispatch(args) -> int:
194
199
  force=args.force,
195
200
  json_output=args.json,
196
201
  )
202
+ if args.skills_command == "uninstall":
203
+ return skills_cmd.uninstall(
204
+ workspace=args.workspace,
205
+ skill=args.skill,
206
+ harness=args.install_target,
207
+ json_output=args.json,
208
+ )
197
209
  if args.skills_command == "compatibility":
198
210
  return skills_cmd.compatibility(target=args.target, skill=args.skill, json_output=args.json)
199
211
  if args.skills_command == "history":
@@ -10,10 +10,11 @@ def register(sub: argparse._SubParsersAction) -> None:
10
10
  # status
11
11
  p_status = sub.add_parser("status", help="Show which stations are present and healthy.")
12
12
  p_status.add_argument("--target", "-t", type=Path, default=Path("."))
13
+ p_status.add_argument("--json", action="store_true", help="Emit machine-readable JSON instead of text.")
13
14
  p_status.set_defaults(func=dispatch)
14
15
 
15
16
 
16
17
  def dispatch(args) -> int:
17
18
  from .. import status as status_mod
18
19
 
19
- return status_mod.run(target=args.target)
20
+ return status_mod.run(target=args.target, json_output=args.json)