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