Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Built-in `brigade doctor` bootstrap budget checks that fail hard when installed bootstrap files exceed conservative byte limits.
- Built-in `brigade doctor` memory-index checks that fail when `MEMORY.md` links to missing `memory/cards/*.md` files.
- `brigade run "<task>"`, a bounded aboyeur flow that asks one rostered orchestrator to plan assignments, dispatches worker CLIs in parallel, then asks the orchestrator to synthesize the final answer.
- `.brigade/roster.toml` loading for cross-model agent rosters using the user's installed CLIs (`codex`, `claude`, or `ollama:<model>`). Claude is optional, not required.
- `brigade roster init` and `brigade roster doctor` to scaffold a Codex/Ollama starter roster and validate roster syntax plus installed CLI availability.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ Use `--output-dir <path>` to pick the artifact directory, or `--no-artifacts` fo

Use `brigade work status` as the quick daily dashboard for a repo. It reports the current branch, dirty files, dogfood readiness, configured dogfood paths, latest dogfood run, and extracted next step without starting a new orchestration. Use `brigade work doctor` to check whether the repo is ready for the daily loop: dogfood config, Codex CLI, local artifact paths, handoff inbox, ignore coverage, and latest run context. Use `brigade work resume` when returning to a repo; it shows the active or latest work session, latest dogfood run, extracted next step, and the suggested command to continue. `brigade work run` is the one-command daily loop: it starts a work session, runs `brigade dogfood`, ends the session, writes a work-session Memory Handoff by default, and prints a compact recap. Pass a task to override the default next-slice review, `--title` to name the session, `--no-handoff` to skip the work handoff, or `--dogfood-handoff` to also let the underlying dogfood run write its own handoff. `brigade work start "title"` opens a local work session under `.brigade/work/<id>/`, records the starting git and dogfood context, and writes `start.md`. `brigade work note "checkpoint"` appends a timestamped note to the active session without ending it. `brigade work end --note "what happened"` closes the active session, records ending context, and writes `end.md`. Add `--handoff` to also write a Memory Handoff for the closed session; it defaults to the configured dogfood handoff inbox or `.codex/memory-handoffs`.

Memory and bootstrap readiness are part of the same operating-system health story. `brigade doctor` checks installed bootstrap files against built-in hard byte budgets so overgrown files fail before agents load truncated context. Bootstrap truncation is treated as a hard failure to prevent, not a cosmetic warning; move durable detail into `memory/cards/` and keep bootstrap files as slim indexes.
Memory and bootstrap readiness are part of the same operating-system health story. `brigade doctor` checks installed bootstrap files against built-in hard byte budgets so overgrown files fail before agents load truncated context. It also verifies that `MEMORY.md` card links resolve under `memory/cards/`, so the bootstrap index cannot quietly point at missing durable memory. Bootstrap truncation is treated as a hard failure to prevent, not a cosmetic warning; move durable detail into `memory/cards/` and keep bootstrap files as slim indexes.

Inspect local work sessions with `brigade work list`, `brigade work latest`, or `brigade work show <session-id-or-path>`. Use `brigade work recap` for a compact summary of recent sessions, or add `--since YYYY-MM-DD` for a day-range recap.

Expand Down
35 changes: 34 additions & 1 deletion src/brigade/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import json
import os
import re
import shutil
import subprocess
import sys
Expand Down Expand Up @@ -64,6 +65,7 @@ def core_station_checks(ctx: DoctorContext) -> List[CheckResult]:
def memory_station_checks(ctx: DoctorContext) -> List[CheckResult]:
checks: List[CheckResult] = []
checks.extend(_check_handoff_inboxes(ctx.target, ctx.selection, ctx.harnesses))
checks.extend(_check_memory_index(ctx.target))
checks.extend(_check_memory_care(ctx.target))
return checks

Expand Down Expand Up @@ -197,7 +199,8 @@ def _check_handoff_inboxes(
)
cards = target / "memory" / "cards"
if cards.is_dir():
results.append((OK, "memory: cards/", str(cards)))
card_count = len([path for path in cards.rglob("*.md") if path.is_file()])
results.append((OK, "memory: cards/", f"{cards} ({card_count} card{'s' if card_count != 1 else ''})"))
else:
results.append(
(
Expand All @@ -209,6 +212,36 @@ def _check_handoff_inboxes(
return results


def _check_memory_index(target: Path) -> List[CheckResult]:
index = target / "MEMORY.md"
if not index.is_file():
return []
try:
text = index.read_text()
except OSError as exc:
return [(FAIL, "memory-index: MEMORY.md", f"unreadable: {exc}")]

linked_cards = sorted(
{
match.group("path")
for match in re.finditer(
r"\[[^\]]+\]\((?P<path>memory/cards/[^)#\s]+\.md)(?:#[^)]+)?\)",
text,
)
}
)
if not linked_cards:
return [(WARN, "memory-index: card links", "MEMORY.md links no memory cards")]

missing = [path for path in linked_cards if not (target / path).is_file()]
if missing:
preview = ", ".join(missing[:5])
if len(missing) > 5:
preview += f", ... {len(missing) - 5} more"
return [(FAIL, "memory-index: card links", f"{len(missing)} broken link{'s' if len(missing) != 1 else ''}: {preview}")]
return [(OK, "memory-index: card links", f"{len(linked_cards)} verified")]


def _check_orphan_inboxes(
target: Path, selected_harnesses: List[str]
) -> List[CheckResult]:
Expand Down
29 changes: 29 additions & 0 deletions tests/test_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,35 @@ def test_doctor_reports_memory_care_files(tmp_target: Path, capsys):
assert "1 queued" in out


def test_doctor_verifies_memory_index_card_links(tmp_target: Path, capsys):
install_selection(
tmp_target,
Selection(depth="workspace", harnesses=["claude"], owner="claude", includes=[]),
)

rc = doctor_mod.run(target=tmp_target, harness="generic")
out = capsys.readouterr().out
assert rc == 0
assert "memory-index: card links" in out
assert "verified" in out


def test_doctor_fails_broken_memory_index_card_link(tmp_target: Path, capsys):
install_selection(
tmp_target,
Selection(depth="workspace", harnesses=["claude"], owner="claude", includes=[]),
)
memory = tmp_target / "MEMORY.md"
memory.write_text(memory.read_text() + "\n- [missing-card](memory/cards/missing-card.md)\n")

rc = doctor_mod.run(target=tmp_target, harness="generic")
out = capsys.readouterr().out
assert rc == 1
assert "memory-index: card links" in out
assert "broken link" in out
assert "memory/cards/missing-card.md" in out


def test_doctor_openclaw_reports_cron_memory_jobs(tmp_target: Path, monkeypatch, capsys):
install_selection(
tmp_target,
Expand Down
Loading