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.
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/PKG-INFO +13 -12
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/README.md +12 -11
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/pyproject.toml +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/__init__.py +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/agents.py +34 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/__init__.py +2 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/_common.py +4 -1
- brigade_cli-0.12.0/src/brigade/cli/completions.py +17 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/doctor.py +2 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/friction.py +10 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/init.py +10 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/memory.py +19 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/operator.py +15 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/projects.py +5 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/repos.py +6 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/run.py +43 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/runbook.py +16 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/scrub.py +11 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/security.py +30 -2
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/skills.py +12 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/status.py +2 -1
- brigade_cli-0.12.0/src/brigade/completions.py +91 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/doctor.py +115 -24
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/friction_cmd.py +27 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/install.py +49 -8
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/localio.py +24 -3
- brigade_cli-0.12.0/src/brigade/mcp_server.py +96 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/memory_cmd.py +199 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/__init__.py +4 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/lifecycle.py +79 -2
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/projects_cmd.py +15 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/repos_cmd.py +78 -2
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/runbook_cmd.py +51 -3
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/scrub.py +63 -3
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/security_cmd.py +103 -2
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/skills_cmd.py +55 -82
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/status.py +25 -4
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/depth/workspace.json +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/memory-handoff.harness.json +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/model-lanes.harness.json +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/workspace.harness.json +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hooks/pre-push +15 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/chat-memory-sweep.example.json +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/memory-care.example.json +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/policies/personal.json +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/policies/public-content.json +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/policies/public-repo.json +1 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/AGENTS.md +9 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/helpers.py +14 -7
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/PKG-INFO +13 -12
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/SOURCES.txt +14 -0
- brigade_cli-0.12.0/tests/test_completions.py +46 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_doctor.py +104 -1
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_init.py +56 -0
- brigade_cli-0.12.0/tests/test_localio.py +38 -0
- brigade_cli-0.12.0/tests/test_mcp_server.py +60 -0
- brigade_cli-0.12.0/tests/test_memory_search_mcp.py +89 -0
- brigade_cli-0.12.0/tests/test_operator_checkup.py +72 -0
- brigade_cli-0.12.0/tests/test_parity_backlog.py +92 -0
- brigade_cli-0.12.0/tests/test_pre_push_hook.py +30 -0
- brigade_cli-0.12.0/tests/test_read_only_enforcement.py +47 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_repos_cmd.py +41 -1
- brigade_cli-0.12.0/tests/test_runbook_import.py +48 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_security_cmd.py +2 -2
- brigade_cli-0.12.0/tests/test_security_diff.py +62 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_status.py +17 -0
- brigade_cli-0.12.0/tests/test_subprocess_guards.py +51 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/LICENSE +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/MANIFEST.in +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/QUICKSTART.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/setup.cfg +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/__main__.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/aboyeur.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/actionqueue.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/add.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/budgets.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/budgets_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/center_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/chat_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/add.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/budgets.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/center.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/chat.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/context.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/daily.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/dogfood.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/handoff.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/handoff_template.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/hermes_fragments.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/ingest.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/learn.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/notifications.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/openclaw_fragments.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/pantry.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/reconfigure.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/release.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/research.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/roadmap.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/roster.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/runs.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/tools.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/untrusted.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/cli/work.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/config.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/context_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/daily_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/dogfood_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/fragments.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/handoff.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/handoff_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/hermes_adapter.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/ingest.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/learn_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/managed.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/notifications_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/adoption.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/guide.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/health.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/migration.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/operator_cmd/surfaces.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/pantry_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/phases_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/proc.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/prompt.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/py.typed +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/reconfigure.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/registry.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/release_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/reportstore.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/__init__.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/config.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/engine.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/extract.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/handoff.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/llm.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/registry.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/report.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/sources/__init__.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/sources/cli.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/sources/local.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/sources/web.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research/types.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/research_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/roadmap_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/roster.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/roster_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/runguard.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/runs_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/selection.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/station.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/adal/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/aider/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/amp/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/antigravity/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/claude/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/codex/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/continue/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/copilot/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/crush/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/cursor/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/depth/repo.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/generic/harness-adapter-checklist.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/generic/memory-contract.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/goose/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/grok/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/handoff/handoff-sources.example.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/handoff/openclaw-ingest-receipt.example.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/adal.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/aider.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/amp.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/antigravity.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/claude.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/codex.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/continue.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/copilot.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/crush.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/cursor.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/goose.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/grok.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/hermes.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/kimi.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/openclaw.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/opencode.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/openhands.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/pi.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/qwen.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/README.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/hermes/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/includes/publisher.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/kimi/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/backup-restic.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/chat-surface-crawlers.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/content-safety.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/handoff-flow.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-architecture.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-care-staleness.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-scanner.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/multi-workspace-handoff-admin.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/obsidian-notes.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/pipeline-standups.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/tokenjuice-output-compaction.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/README.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/acp-escalation.openclaw.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/memory-sweep-cron.openclaw.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/model-aliases.openclaw.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/ollama-memory-search.openclaw.json +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/opencode/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/openhands/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/pi/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/qwen/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/scripts/backup-restic.sh +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/skills/note/SKILL.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/CLAUDE.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/HEARTBEAT.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/IDENTITY.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/INSTALL_FOR_AGENTS.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/MEMORY.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/SAFETY_RULES.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/SOUL.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/TOOLS.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/USER.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/rules/acceptance-driven-work.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates/workspace/rules/issue-tdd-loop.md +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/templates.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/toml_compat.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/tools_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/untrusted.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/untrusted_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/__init__.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/backup.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/config.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/constants.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/imports.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/ledger.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/reviews.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/scanners.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/services.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/session.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/sweeps.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade/work_cmd/verification.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/dependency_links.txt +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/entry_points.txt +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/requires.txt +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/top_level.txt +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_aboyeur.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_actionqueue.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_add.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_agents.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_budgets.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_cli_alias.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_cli_help.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_config.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_dogfood_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_fragments.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_friction_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_gitignore.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_handoff.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_handoff_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_ingest.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_install.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_managed.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_memory_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_neutrality.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_notifications_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_operator_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_pantry_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase100_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase101_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase165_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase36_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase37_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase38_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase39_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase40_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase41_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase42_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase43_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase44_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase45_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase46_50_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase51_55_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase56_60_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase96_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase97_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase98_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_phase99_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_privacy_regression.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_proc.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_prompt.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_reconfigure.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_registry.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_release_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_reportstore.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_cli_sources.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_config.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_engine.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_extract.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_handoff.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_llm.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_local_sources.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_registry.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_report.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_types.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_research_web.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_roadmap_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_roster.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_roster_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_run_cli.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_runbook_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_runguard.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_runs_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_scrub.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_selection.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_skills_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_station.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_toml_compat.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_untrusted.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_untrusted_cmd.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_backup.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_facade.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_imports.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_ledger.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_reviews.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_scanners.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_services.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_session.py +0 -0
- {brigade_cli-0.11.0 → brigade_cli-0.12.0}/tests/test_work_cmd_sweeps.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
76
|
-
3. safe targeted notes
|
|
77
|
-
4. ambiguous or risky
|
|
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 ·
|
|
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 -->
|
|
89
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
46
|
-
3. safe targeted notes
|
|
47
|
-
4. ambiguous or risky
|
|
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 ·
|
|
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 -->
|
|
59
|
-
|
|
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
|
|
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.
|
|
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"
|
|
@@ -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
|
-
(
|
|
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,
|
|
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(
|
|
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(
|
|
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)
|