brigade-cli 0.9.1__tar.gz → 0.9.3__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.9.1 → brigade_cli-0.9.3}/PKG-INFO +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/pyproject.toml +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/__init__.py +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/handoff_cmd.py +32 -3
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/operator_cmd.py +39 -7
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/scrub.py +32 -3
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/hermes/memory-handoff.harness.json +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/hermes/model-lanes.harness.json +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/hermes/workspace.harness.json +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/chat-memory-sweep.example.json +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/memory-care.example.json +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/policies/personal.json +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/policies/public-content.json +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/policies/public-repo.json +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/untrusted_cmd.py +7 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade_cli.egg-info/PKG-INFO +1 -1
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade_cli.egg-info/SOURCES.txt +1 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_handoff_cmd.py +25 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_operator_cmd.py +20 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_scrub.py +57 -0
- brigade_cli-0.9.3/tests/test_untrusted_cmd.py +10 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/LICENSE +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/MANIFEST.in +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/QUICKSTART.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/README.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/setup.cfg +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/__main__.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/aboyeur.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/add.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/agents.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/budgets.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/budgets_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/center_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/chat_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/cli.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/config.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/context_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/daily_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/doctor.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/dogfood_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/fragments.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/handoff.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/hermes_adapter.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/ingest.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/install.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/learn_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/managed.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/memory_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/notifications_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/pantry_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/phases_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/proc.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/projects_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/prompt.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/py.typed +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/reconfigure.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/registry.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/release_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/repos_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/__init__.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/config.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/engine.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/extract.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/handoff.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/llm.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/registry.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/report.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/sources/__init__.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/sources/cli.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/sources/local.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/sources/web.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research/types.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/research_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/roadmap_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/roster.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/roster_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/runbook_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/runs_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/security_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/selection.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/skills_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/station.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/status.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/adal/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/aider/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/antigravity/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/claude/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/codex/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/continue/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/copilot/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/cursor/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/depth/repo.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/depth/workspace.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/generic/harness-adapter-checklist.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/generic/memory-contract.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/goose/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/handoff/handoff-sources.example.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/handoff/openclaw-ingest-receipt.example.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/adal.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/aider.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/antigravity.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/claude.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/codex.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/continue.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/copilot.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/cursor.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/goose.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/hermes.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/kimi.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/openclaw.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/opencode.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/openhands.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/pi.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/harnesses/qwen.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/hermes/README.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/hermes/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/hooks/pre-push +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/includes/publisher.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/kimi/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/backup-restic.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/chat-surface-crawlers.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/content-safety.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/handoff-flow.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/memory-architecture.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/memory-care-staleness.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/memory-scanner.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/multi-workspace-handoff-admin.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/obsidian-notes.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/pipeline-standups.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/cards/tokenjuice-output-compaction.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/openclaw/README.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/openclaw/acp-escalation.openclaw.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/openclaw/memory-sweep-cron.openclaw.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/openclaw/model-aliases.openclaw.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/openclaw/ollama-memory-search.openclaw.json +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/opencode/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/openhands/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/pi/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/qwen/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/scripts/backup-restic.sh +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/skills/note/SKILL.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/AGENTS.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/CLAUDE.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/HEARTBEAT.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/IDENTITY.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/INSTALL_FOR_AGENTS.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/MEMORY.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/SAFETY_RULES.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/SOUL.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/TOOLS.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/USER.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/rules/acceptance-driven-work.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/workspace/rules/issue-tdd-loop.md +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/toml_compat.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/tools_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/untrusted.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/work_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade_cli.egg-info/dependency_links.txt +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade_cli.egg-info/entry_points.txt +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade_cli.egg-info/requires.txt +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade_cli.egg-info/top_level.txt +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_aboyeur.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_add.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_agents.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_budgets.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_cli_alias.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_cli_help.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_config.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_doctor.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_dogfood_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_fragments.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_gitignore.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_handoff.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_ingest.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_init.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_install.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_managed.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_memory_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_neutrality.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_notifications_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_pantry_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase100_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase101_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase165_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase36_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase37_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase38_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase39_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase40_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase41_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase42_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase43_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase44_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase45_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase46_50_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase51_55_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase56_60_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase96_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase97_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase98_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_phase99_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_privacy_regression.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_proc.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_prompt.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_reconfigure.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_registry.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_release_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_repos_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_cli_sources.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_config.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_engine.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_extract.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_handoff.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_llm.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_local_sources.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_registry.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_report.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_types.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_research_web.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_roadmap_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_roster.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_roster_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_run_cli.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_runbook_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_runs_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_security_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_selection.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_skills_cmd.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_station.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_status.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_toml_compat.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_untrusted.py +0 -0
- {brigade_cli-0.9.1 → brigade_cli-0.9.3}/tests/test_work_cmd.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "brigade-cli"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.3"
|
|
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"
|
|
@@ -16,6 +16,7 @@ WARN = "warn"
|
|
|
16
16
|
FAIL = "fail"
|
|
17
17
|
|
|
18
18
|
from . import scrub
|
|
19
|
+
from .config import load_config as load_brigade_config
|
|
19
20
|
from .selection import WRITER_INBOXES as _WRITER_INBOX_MAP
|
|
20
21
|
|
|
21
22
|
WRITER_INBOXES = tuple(_WRITER_INBOX_MAP.values())
|
|
@@ -320,11 +321,16 @@ def doctor_checks(target: Path, sources: Path | None = None) -> list[tuple[str,
|
|
|
320
321
|
level = WARN if pending_total else OK
|
|
321
322
|
checks.append((level, "handoff_sources", "not configured; no pending handoffs" if not pending_total else "not configured"))
|
|
322
323
|
|
|
324
|
+
quiet_inboxes = [
|
|
325
|
+
inbox
|
|
326
|
+
for inbox in health.inboxes
|
|
327
|
+
if not inbox.exists and not inbox.watched and not inbox.pending and not inbox.processed
|
|
328
|
+
]
|
|
323
329
|
for inbox in health.inboxes:
|
|
330
|
+
if inbox in quiet_inboxes:
|
|
331
|
+
continue
|
|
324
332
|
if inbox.pending and not inbox.watched:
|
|
325
333
|
level = WARN
|
|
326
|
-
elif not inbox.exists:
|
|
327
|
-
level = OK
|
|
328
334
|
else:
|
|
329
335
|
level = OK
|
|
330
336
|
watched = "yes" if inbox.watched else "no"
|
|
@@ -334,6 +340,14 @@ def doctor_checks(target: Path, sources: Path | None = None) -> list[tuple[str,
|
|
|
334
340
|
f"(exists={exists}, pending={inbox.pending}, processed={inbox.processed}, watched={watched})"
|
|
335
341
|
)
|
|
336
342
|
checks.append((level, f"handoff_watch: {inbox.inbox}", detail))
|
|
343
|
+
if quiet_inboxes:
|
|
344
|
+
checks.append(
|
|
345
|
+
(
|
|
346
|
+
OK,
|
|
347
|
+
"handoff_watch: other inboxes",
|
|
348
|
+
f"{len(quiet_inboxes)} writer inbox(es) absent and unwatched",
|
|
349
|
+
)
|
|
350
|
+
)
|
|
337
351
|
|
|
338
352
|
stale_backlog = [
|
|
339
353
|
inbox
|
|
@@ -2220,7 +2234,22 @@ def sources_init(*, target: Path, force: bool = False, inboxes: list[str] | None
|
|
|
2220
2234
|
if path.exists() and not force:
|
|
2221
2235
|
print(f"error: handoff source config already exists: {path}", file=sys.stderr)
|
|
2222
2236
|
return 2
|
|
2223
|
-
|
|
2237
|
+
if inboxes is not None:
|
|
2238
|
+
inbox_values = list(inboxes)
|
|
2239
|
+
else:
|
|
2240
|
+
inbox_values = list(WRITER_INBOXES)
|
|
2241
|
+
try:
|
|
2242
|
+
config = load_brigade_config(target)
|
|
2243
|
+
except Exception:
|
|
2244
|
+
config = None
|
|
2245
|
+
if config is not None and config.selection.harnesses:
|
|
2246
|
+
selected = [
|
|
2247
|
+
_WRITER_INBOX_MAP[h]
|
|
2248
|
+
for h in config.selection.harnesses
|
|
2249
|
+
if h in _WRITER_INBOX_MAP
|
|
2250
|
+
]
|
|
2251
|
+
if selected:
|
|
2252
|
+
inbox_values = selected
|
|
2224
2253
|
payload = {
|
|
2225
2254
|
"_description": "Local handoff source coverage. Relative roots resolve from this repo or workspace target.",
|
|
2226
2255
|
"canonical_owner": "openclaw",
|
|
@@ -40,7 +40,7 @@ def guide_payload(*, profile: str = "internal-dogfood") -> dict[str, Any]:
|
|
|
40
40
|
],
|
|
41
41
|
"boundaries": [
|
|
42
42
|
"Brigade does not run automatically.",
|
|
43
|
-
"Brigade does not start daemons
|
|
43
|
+
"Brigade does not start daemons. Templates may include an inactive hooks/pre-push file, but Brigade never activates hooks (git config core.hooksPath stays your call).",
|
|
44
44
|
"Brigade does not send notifications unless a separate hook is explicitly configured.",
|
|
45
45
|
"Brigade does not ingest handoffs into canonical memory from operator init/status.",
|
|
46
46
|
"Brigade does not publish, push, tag, or mutate remotes.",
|
|
@@ -176,7 +176,7 @@ def adoption_plan_payload(target: Path) -> dict[str, Any]:
|
|
|
176
176
|
"suggested_next_commands": commands,
|
|
177
177
|
"boundaries": [
|
|
178
178
|
"Read-only: does not write files.",
|
|
179
|
-
"Does not start services,
|
|
179
|
+
"Does not start services, activate hooks, install schedulers, or mutate remotes.",
|
|
180
180
|
"Does not include raw crontab lines, OpenClaw job names, PM2 process names, command paths, or environment values.",
|
|
181
181
|
],
|
|
182
182
|
}
|
|
@@ -348,7 +348,7 @@ def migration_status_payload(target: Path) -> dict[str, Any]:
|
|
|
348
348
|
"boundaries": [
|
|
349
349
|
"Uses redacted adoption, surface capture, review, work import, and task metadata.",
|
|
350
350
|
"Does not include raw scheduler lines, job names, process names, command paths, environment values, host details, or private paths.",
|
|
351
|
-
"Does not start services,
|
|
351
|
+
"Does not start services, activate hooks, mutate schedulers, mutate remotes, ingest memory, migrate secrets, or rotate credentials.",
|
|
352
352
|
],
|
|
353
353
|
}
|
|
354
354
|
|
|
@@ -559,7 +559,7 @@ def surfaces_capture_payload(target: Path) -> dict[str, Any]:
|
|
|
559
559
|
"source_fingerprint": _surface_capture_fingerprint(capture),
|
|
560
560
|
"boundaries": [
|
|
561
561
|
"Reads external scheduler and process surfaces only when the operator runs this command.",
|
|
562
|
-
"Does not start services,
|
|
562
|
+
"Does not start services, activate hooks, install schedulers, kill processes, or mutate remotes.",
|
|
563
563
|
"Does not include raw crontab lines, job names, process names, command paths, environment values, hostnames, or private paths.",
|
|
564
564
|
],
|
|
565
565
|
}
|
|
@@ -2385,7 +2385,7 @@ def doctor_payload(target: Path, *, profile: str = "internal-dogfood") -> dict[s
|
|
|
2385
2385
|
},
|
|
2386
2386
|
"local_only_notes": [
|
|
2387
2387
|
".brigade/ stores local config, receipts, scans, reports, waivers, and run artifacts.",
|
|
2388
|
-
"Brigade does not run automatically, start daemons,
|
|
2388
|
+
"Brigade does not run automatically, start daemons, activate hooks, send notifications, publish, push, tag, or mutate remotes.",
|
|
2389
2389
|
],
|
|
2390
2390
|
"tracked_vs_generated": [
|
|
2391
2391
|
"Track reviewed cross-harness source docs under tools/.",
|
|
@@ -2505,6 +2505,35 @@ def verify_harness_payload(target: Path, *, harness: str) -> dict[str, Any]:
|
|
|
2505
2505
|
else:
|
|
2506
2506
|
checks.append({"status": "warn", "name": "handoff_inbox_gitignored", "detail": f"gitignore status: {gitignored}"})
|
|
2507
2507
|
|
|
2508
|
+
# The managed .gitignore un-ignores each inbox's TEMPLATE.md so the format
|
|
2509
|
+
# travels with the repo. Git cannot re-include a file whose parent dir is
|
|
2510
|
+
# excluded by another source (commonly a global gitignore with a bare
|
|
2511
|
+
# `.claude/` or `.codex/` entry), and that shadowing is otherwise silent.
|
|
2512
|
+
template_path = inbox_path / "TEMPLATE.md"
|
|
2513
|
+
if template_path.is_file():
|
|
2514
|
+
template_ignored = dogfood_cmd._check_git_ignored(target, template_path)
|
|
2515
|
+
if template_ignored == "yes":
|
|
2516
|
+
inbox_root = inbox_rel.split("/")[0]
|
|
2517
|
+
checks.append(
|
|
2518
|
+
{
|
|
2519
|
+
"status": "warn",
|
|
2520
|
+
"name": "handoff_template_shadowed",
|
|
2521
|
+
"detail": (
|
|
2522
|
+
f"{inbox_rel}/TEMPLATE.md is gitignored despite the managed un-ignore rule; "
|
|
2523
|
+
f"an external ignore source (often a global gitignore entry like `{inbox_root}/`) "
|
|
2524
|
+
"is shadowing it, so the template will not travel with the repo"
|
|
2525
|
+
),
|
|
2526
|
+
}
|
|
2527
|
+
)
|
|
2528
|
+
else:
|
|
2529
|
+
checks.append(
|
|
2530
|
+
{
|
|
2531
|
+
"status": "ok",
|
|
2532
|
+
"name": "handoff_template_shadowed",
|
|
2533
|
+
"detail": f"{inbox_rel}/TEMPLATE.md is visible to git",
|
|
2534
|
+
}
|
|
2535
|
+
)
|
|
2536
|
+
|
|
2508
2537
|
lint_results = [
|
|
2509
2538
|
result
|
|
2510
2539
|
for result in health.lint
|
|
@@ -2795,6 +2824,7 @@ def quickstart(
|
|
|
2795
2824
|
"depth": depth,
|
|
2796
2825
|
"harnesses": selected_harnesses,
|
|
2797
2826
|
"owner": memory_owner,
|
|
2827
|
+
"owner_override": owner is not None,
|
|
2798
2828
|
"dry_run": dry_run,
|
|
2799
2829
|
"force": force,
|
|
2800
2830
|
"steps": steps,
|
|
@@ -2845,6 +2875,7 @@ def quickstart(
|
|
|
2845
2875
|
"depth": depth,
|
|
2846
2876
|
"harnesses": selected_harnesses,
|
|
2847
2877
|
"owner": memory_owner,
|
|
2878
|
+
"owner_override": owner is not None,
|
|
2848
2879
|
"dry_run": dry_run,
|
|
2849
2880
|
"force": force,
|
|
2850
2881
|
"steps": steps,
|
|
@@ -2877,7 +2908,7 @@ def _quickstart_local_notes() -> list[str]:
|
|
|
2877
2908
|
return [
|
|
2878
2909
|
".brigade/ stores local config, receipts, scans, reports, waivers, and run artifacts.",
|
|
2879
2910
|
"Generated harness projections and handoff inboxes are local ignored state.",
|
|
2880
|
-
"Brigade does not start daemons,
|
|
2911
|
+
"Brigade does not start daemons, activate hooks (the pre-push hook file ships inactive), publish, push, tag, or mutate remotes from quickstart.",
|
|
2881
2912
|
]
|
|
2882
2913
|
|
|
2883
2914
|
|
|
@@ -2921,7 +2952,8 @@ def _print_quickstart(payload: dict[str, Any]) -> None:
|
|
|
2921
2952
|
print(f"operator quickstart: {payload['target']}")
|
|
2922
2953
|
print(f"depth: {payload['depth']}")
|
|
2923
2954
|
print(f"harnesses: {','.join(payload['harnesses']) or 'none'}")
|
|
2924
|
-
|
|
2955
|
+
owner_note = "" if payload.get("owner_override") else " (auto-selected; override with --owner)"
|
|
2956
|
+
print(f"owner: {payload['owner']}{owner_note}")
|
|
2925
2957
|
print(f"dry_run: {payload['dry_run']}")
|
|
2926
2958
|
for step in payload["steps"]:
|
|
2927
2959
|
print(f"[{step.get('status')}] {step.get('id')}")
|
|
@@ -27,6 +27,7 @@ def hook_status(target: Path, policy: str = "public-repo") -> dict[str, Any]:
|
|
|
27
27
|
target = target.expanduser().resolve()
|
|
28
28
|
hook = target / "hooks" / "pre-push"
|
|
29
29
|
hooks_path = _git_config(target, "core.hooksPath")
|
|
30
|
+
local_hooks_path = _git_config(target, "core.hooksPath", local_only=True)
|
|
30
31
|
git_hook = _git_pre_push_hook(target)
|
|
31
32
|
configured_hook = _configured_pre_push_hook(target, hooks_path)
|
|
32
33
|
active_hook = configured_hook if hooks_path else git_hook
|
|
@@ -40,6 +41,15 @@ def hook_status(target: Path, policy: str = "public-repo") -> dict[str, Any]:
|
|
|
40
41
|
hook_mode = "configured-hooks-path"
|
|
41
42
|
elif hook_enabled:
|
|
42
43
|
hook_mode = "git-hooks"
|
|
44
|
+
# A core.hooksPath inherited from global/system git config can point at a
|
|
45
|
+
# personal pre-push that has nothing to do with content-guard. Reporting
|
|
46
|
+
# that as "installed" is a false positive; only trust an inherited hook
|
|
47
|
+
# when it actually runs content-guard.
|
|
48
|
+
hook_inherited = bool(hooks_path) and not local_hooks_path
|
|
49
|
+
if hook_mode == "configured-hooks-path" and hook_inherited and active_hook is not None:
|
|
50
|
+
if not _hook_runs_content_guard(active_hook):
|
|
51
|
+
hook_mode = "external-hooks-path"
|
|
52
|
+
hook_enabled = False
|
|
43
53
|
try:
|
|
44
54
|
resolved_policy = str(policy_path(target, policy))
|
|
45
55
|
except ValueError as exc:
|
|
@@ -53,7 +63,17 @@ def hook_status(target: Path, policy: str = "public-repo") -> dict[str, Any]:
|
|
|
53
63
|
if not policy_exists:
|
|
54
64
|
checks.append({"status": "warn", "name": "content_guard_policy_missing", "detail": f"policy not found: {resolved_policy}"})
|
|
55
65
|
suggestions.append(f"brigade scrub --target . --policy {policy} --dry-run")
|
|
56
|
-
if
|
|
66
|
+
if hook_mode == "external-hooks-path":
|
|
67
|
+
checks.append(
|
|
68
|
+
{
|
|
69
|
+
"status": "warn",
|
|
70
|
+
"name": "content_guard_hook_unrelated",
|
|
71
|
+
"detail": "the active pre-push comes from a global core.hooksPath outside this repo and does not run content-guard; the repo's hook is not active",
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
if hook.is_file():
|
|
75
|
+
suggestions.append("git config core.hooksPath hooks")
|
|
76
|
+
elif not hook_enabled:
|
|
57
77
|
checks.append({"status": "warn", "name": "content_guard_hook_not_enabled", "detail": "no executable pre-push hook found in the active Git hooks path"})
|
|
58
78
|
if hook.is_file():
|
|
59
79
|
suggestions.append("git config core.hooksPath hooks")
|
|
@@ -85,10 +105,19 @@ def hook_status(target: Path, policy: str = "public-repo") -> dict[str, Any]:
|
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
|
|
88
|
-
def
|
|
108
|
+
def _hook_runs_content_guard(path: Path) -> bool:
|
|
109
|
+
try:
|
|
110
|
+
text = path.read_text(errors="replace")
|
|
111
|
+
except OSError:
|
|
112
|
+
return False
|
|
113
|
+
return any(marker in text for marker in ("content-guard", "content_guard", "brigade scrub"))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _git_config(target: Path, key: str, *, local_only: bool = False) -> str | None:
|
|
117
|
+
scope = ["--local"] if local_only else []
|
|
89
118
|
try:
|
|
90
119
|
result = subprocess.run(
|
|
91
|
-
["git", "-C", str(target), "config", "--get", key],
|
|
120
|
+
["git", "-C", str(target), "config", *scope, "--get", key],
|
|
92
121
|
check=False,
|
|
93
122
|
stdout=subprocess.PIPE,
|
|
94
123
|
stderr=subprocess.DEVNULL,
|
{brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/hermes/memory-handoff.harness.json
RENAMED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"Describes the handoff inbox + routing targets that Hermes should treat",
|
|
6
6
|
"as the canonical memory contract."
|
|
7
7
|
],
|
|
8
|
-
"_brigade_version": "0.9.
|
|
8
|
+
"_brigade_version": "0.9.3",
|
|
9
9
|
"_brigade_status": "experimental",
|
|
10
10
|
"memory_handoff": {
|
|
11
11
|
"inbox_dir": ".hermes/memory-handoffs",
|
{brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/hermes/model-lanes.harness.json
RENAMED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"Suggested model lane names. Same shape as the OpenClaw alias map so",
|
|
6
6
|
"knowledge cards and runbooks can talk about lanes without naming providers."
|
|
7
7
|
],
|
|
8
|
-
"_brigade_version": "0.9.
|
|
8
|
+
"_brigade_version": "0.9.3",
|
|
9
9
|
"_brigade_status": "experimental",
|
|
10
10
|
"model_lanes": {
|
|
11
11
|
"main": "<provider/main-model-id>",
|
{brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/chat-memory-sweep.example.json
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_brigade_version": "0.9.
|
|
2
|
+
"_brigade_version": "0.9.3",
|
|
3
3
|
"_description": "Example output contract for a nightly chat/session memory sweep. Keep raw chat transcripts in crawler archives; this file contains summaries and local source locators only.",
|
|
4
4
|
"generated_at": "2026-05-26T22:09:00-04:00",
|
|
5
5
|
"window": {
|
{brigade_cli-0.9.1 → brigade_cli-0.9.3}/src/brigade/templates/memory/memory-care.example.json
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_brigade_version": "0.9.
|
|
2
|
+
"_brigade_version": "0.9.3",
|
|
3
3
|
"_description": "Example config for a local memory-care scanner. Brigade does not run this scanner for you; it validates the output with `brigade doctor`.",
|
|
4
4
|
"cards_glob": "memory/cards/*.md",
|
|
5
5
|
"decay_dir": ".brigade/memory-care/decay",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"It blocks secrets and attribution trailers, while warning on personal or infrastructure-like context.",
|
|
5
5
|
"Use via: brigade handoff lint --content-guard --guard-policy personal"
|
|
6
6
|
],
|
|
7
|
-
"_brigade_version": "0.9.
|
|
7
|
+
"_brigade_version": "0.9.3",
|
|
8
8
|
"categories": {
|
|
9
9
|
"secret": "block",
|
|
10
10
|
"private-network": "warn",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"Used by brigade pre-push hook and `brigade scrub`.",
|
|
5
5
|
"Override by pointing CONTENT_GUARD_POLICY at your own copy of this file."
|
|
6
6
|
],
|
|
7
|
-
"_brigade_version": "0.9.
|
|
7
|
+
"_brigade_version": "0.9.3",
|
|
8
8
|
"categories": {
|
|
9
9
|
"secret": "block",
|
|
10
10
|
"private-network": "block",
|
|
@@ -17,6 +17,13 @@ def _read_input(*, text: list[str], from_file: Path | None) -> tuple[str | None,
|
|
|
17
17
|
except OSError as exc:
|
|
18
18
|
return None, f"cannot read input file: {exc}"
|
|
19
19
|
if text:
|
|
20
|
+
if len(text) == 1 and "\n" not in text[0]:
|
|
21
|
+
candidate = Path(text[0]).expanduser()
|
|
22
|
+
if candidate.is_file():
|
|
23
|
+
return None, (
|
|
24
|
+
f"{text[0]} is a file path; pass --from-file {text[0]} to scan its contents "
|
|
25
|
+
"(positional text is scanned literally)"
|
|
26
|
+
)
|
|
20
27
|
return " ".join(text), None
|
|
21
28
|
return None, "provide text or --from-file"
|
|
22
29
|
|
|
@@ -1731,3 +1731,28 @@ def test_openclaw_ingest_receipt_contract_updates_handoff_status(tmp_path, capsy
|
|
|
1731
1731
|
assert handoff_cmd.show_draft(target=tmp_path, draft_id="20260603-210000-hermes-note", json_output=True) == 0
|
|
1732
1732
|
draft_payload = json.loads(capsys.readouterr().out)
|
|
1733
1733
|
assert draft_payload["draft"]["ingestion_status"] == "ingested"
|
|
1734
|
+
|
|
1735
|
+
|
|
1736
|
+
def test_handoff_sources_init_scopes_to_configured_selection(tmp_path, capsys):
|
|
1737
|
+
from brigade.install import install_selection
|
|
1738
|
+
from brigade.selection import Selection
|
|
1739
|
+
|
|
1740
|
+
install_selection(tmp_path, Selection(depth="repo", harnesses=["codex"], owner="codex", includes=[]))
|
|
1741
|
+
capsys.readouterr()
|
|
1742
|
+
assert handoff_cmd.sources_init(target=tmp_path, force=True, json_output=True) == 0
|
|
1743
|
+
payload = json.loads(capsys.readouterr().out)
|
|
1744
|
+
assert payload["inboxes"] == [".codex/memory-handoffs"]
|
|
1745
|
+
|
|
1746
|
+
|
|
1747
|
+
def test_handoff_doctor_collapses_absent_unwatched_inboxes(tmp_path, capsys):
|
|
1748
|
+
from brigade.install import install_selection
|
|
1749
|
+
from brigade.selection import Selection
|
|
1750
|
+
|
|
1751
|
+
install_selection(tmp_path, Selection(depth="repo", harnesses=["codex"], owner="codex", includes=[]))
|
|
1752
|
+
capsys.readouterr()
|
|
1753
|
+
assert handoff_cmd.doctor(target=tmp_path) == 0
|
|
1754
|
+
out = capsys.readouterr().out
|
|
1755
|
+
assert "handoff_watch: .codex/memory-handoffs" in out
|
|
1756
|
+
# absent, unwatched inboxes collapse to one summary line instead of 14 rows
|
|
1757
|
+
assert ".qwen/memory-handoffs" not in out
|
|
1758
|
+
assert "absent and unwatched" in out
|
|
@@ -1259,3 +1259,23 @@ def test_operator_quickstart_gitignore_covers_all_selected_inboxes(tmp_path, cap
|
|
|
1259
1259
|
gitignore = (tmp_path / ".gitignore").read_text()
|
|
1260
1260
|
assert ".codex/memory-handoffs/*" in gitignore
|
|
1261
1261
|
assert ".claude/memory-handoffs/*" in gitignore
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
def test_verify_harness_warns_when_inbox_template_shadowed_by_external_ignore(tmp_path, capsys):
|
|
1265
|
+
import subprocess
|
|
1266
|
+
|
|
1267
|
+
subprocess.run(["git", "init", "-q", str(tmp_path)], check=True)
|
|
1268
|
+
assert cli.main(["operator", "quickstart", "--target", str(tmp_path), "--harnesses", "codex", "--json"]) == 0
|
|
1269
|
+
capsys.readouterr()
|
|
1270
|
+
|
|
1271
|
+
cli.main(["operator", "verify-harness", "--target", str(tmp_path), "--harness", "codex", "--json"])
|
|
1272
|
+
payload = json.loads(capsys.readouterr().out)
|
|
1273
|
+
shadow = [c for c in payload["checks"] if c["name"] == "handoff_template_shadowed"]
|
|
1274
|
+
assert shadow and shadow[0]["status"] == "ok"
|
|
1275
|
+
|
|
1276
|
+
(tmp_path / ".git" / "info" / "exclude").write_text(".codex/\n")
|
|
1277
|
+
cli.main(["operator", "verify-harness", "--target", str(tmp_path), "--harness", "codex", "--json"])
|
|
1278
|
+
payload = json.loads(capsys.readouterr().out)
|
|
1279
|
+
shadow = [c for c in payload["checks"] if c["name"] == "handoff_template_shadowed"]
|
|
1280
|
+
assert shadow and shadow[0]["status"] == "warn"
|
|
1281
|
+
assert "shadow" in shadow[0]["detail"] or "global" in shadow[0]["detail"]
|
|
@@ -134,3 +134,60 @@ def test_scrub_returns_4_on_unsafe_bare_name(tmp_path: Path, monkeypatch):
|
|
|
134
134
|
monkeypatch.setenv("CONTENT_GUARD_DIR", str(scanner))
|
|
135
135
|
rc = scrub_mod.run(target=target, policy="..", dry_run=True)
|
|
136
136
|
assert rc == 4
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _fake_git_run(target, *, global_hooks, local_hooks_path=None):
|
|
140
|
+
def fake_run(argv, **kwargs):
|
|
141
|
+
if argv[:3] == ["git", "-C", str(target)] and argv[-1] == "core.hooksPath":
|
|
142
|
+
if "--local" in argv:
|
|
143
|
+
class Result:
|
|
144
|
+
returncode = 0 if local_hooks_path else 1
|
|
145
|
+
stdout = local_hooks_path or ""
|
|
146
|
+
return Result()
|
|
147
|
+
|
|
148
|
+
class Result:
|
|
149
|
+
returncode = 0
|
|
150
|
+
stdout = str(global_hooks)
|
|
151
|
+
return Result()
|
|
152
|
+
if argv[:3] == ["git", "-C", str(target)] and argv[-1] == "--git-dir":
|
|
153
|
+
class Result:
|
|
154
|
+
returncode = 0
|
|
155
|
+
stdout = ".git"
|
|
156
|
+
return Result()
|
|
157
|
+
raise AssertionError(argv)
|
|
158
|
+
return fake_run
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_hook_status_flags_inherited_hookspath_without_content_guard(tmp_path: Path, monkeypatch):
|
|
162
|
+
target = tmp_path / "repo"
|
|
163
|
+
target.mkdir()
|
|
164
|
+
hooks = tmp_path / "global-hooks"
|
|
165
|
+
hooks.mkdir()
|
|
166
|
+
hook = hooks / "pre-push"
|
|
167
|
+
hook.write_text("#!/usr/bin/env bash\necho unrelated personal hook\n")
|
|
168
|
+
hook.chmod(0o755)
|
|
169
|
+
monkeypatch.setenv("CONTENT_GUARD_DIR", str(tmp_path / "content-guard"))
|
|
170
|
+
monkeypatch.setattr(scrub_mod.subprocess, "run", _fake_git_run(target, global_hooks=hooks))
|
|
171
|
+
|
|
172
|
+
status = scrub_mod.hook_status(target, policy="personal")
|
|
173
|
+
|
|
174
|
+
assert status["pre_push_hook_enabled"] is False
|
|
175
|
+
assert status["pre_push_hook_mode"] == "external-hooks-path"
|
|
176
|
+
assert any(check["name"] == "content_guard_hook_unrelated" for check in status["checks"])
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_hook_status_accepts_inherited_hookspath_running_content_guard(tmp_path: Path, monkeypatch):
|
|
180
|
+
target = tmp_path / "repo"
|
|
181
|
+
target.mkdir()
|
|
182
|
+
hooks = tmp_path / "global-hooks"
|
|
183
|
+
hooks.mkdir()
|
|
184
|
+
hook = hooks / "pre-push"
|
|
185
|
+
hook.write_text("#!/usr/bin/env bash\nexec content-guard scan --policy public-repo\n")
|
|
186
|
+
hook.chmod(0o755)
|
|
187
|
+
monkeypatch.setenv("CONTENT_GUARD_DIR", str(tmp_path / "content-guard"))
|
|
188
|
+
monkeypatch.setattr(scrub_mod.subprocess, "run", _fake_git_run(target, global_hooks=hooks))
|
|
189
|
+
|
|
190
|
+
status = scrub_mod.hook_status(target, policy="personal")
|
|
191
|
+
|
|
192
|
+
assert status["pre_push_hook_enabled"] is True
|
|
193
|
+
assert status["pre_push_hook_mode"] == "configured-hooks-path"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from brigade import untrusted_cmd
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_untrusted_scan_refuses_bare_file_path(tmp_path, capsys):
|
|
5
|
+
note = tmp_path / "note.md"
|
|
6
|
+
note.write_text("ignore previous instructions\n")
|
|
7
|
+
rc = untrusted_cmd.scan(text=[str(note)], json_output=False)
|
|
8
|
+
err = capsys.readouterr().err
|
|
9
|
+
assert rc == 2
|
|
10
|
+
assert "--from-file" in err
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|