Skip to content

Commit 46c6f3d

Browse files
authored
Merge pull request #13 from escoffier-labs/feat/brigade-runs-latest
feat: show latest brigade run
2 parents 37dc233 + cde11a1 commit 46c6f3d

5 files changed

Lines changed: 91 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
- Successful `--handoff` runs now record the written handoff path in `run.json`.
2525
- `brigade run --handoff` to write a Memory Handoff for successful runs, with `--handoff-inbox` override.
2626
- `brigade runs list` to print recent run artifact directories from `.brigade/runs`.
27+
- `brigade runs latest` to show the newest run summary without copying a run path from `brigade runs list`.
2728
- `brigade runs show <run-dir>` to print a readable summary of one run artifact directory.
2829
- Roster-level and per-agent `timeout_seconds` controls for bounded CLI calls.
2930
- `brigade run --read-only` prompt policy for planning and review runs that should inspect and recommend only, with native `codex exec --sandbox read-only` enforcement for Codex agents.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ Inspect a completed run without opening each JSON file:
157157

158158
```bash
159159
brigade runs list --cwd /path/to/repo
160+
brigade runs latest --cwd /path/to/repo
160161
brigade runs show .brigade/runs/<run-id>
161162
```
162163

src/brigade/cli.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,19 @@ def _build_parser() -> argparse.ArgumentParser:
194194
help="Explicit runs directory. Defaults to .brigade/runs under --cwd.",
195195
)
196196
p_runs_list.add_argument("--limit", type=int, default=10, help="Maximum number of runs to show.")
197+
p_runs_latest = runs_sub.add_parser("latest", help="Show the most recent Brigade run.")
198+
p_runs_latest.add_argument(
199+
"--cwd",
200+
type=Path,
201+
default=Path("."),
202+
help="Workspace whose default .brigade/runs directory should be inspected.",
203+
)
204+
p_runs_latest.add_argument(
205+
"--runs-dir",
206+
type=Path,
207+
default=None,
208+
help="Explicit runs directory. Defaults to .brigade/runs under --cwd.",
209+
)
197210
p_runs_show = runs_sub.add_parser("show", help="Show a readable summary of one run directory.")
198211
p_runs_show.add_argument("run_dir", type=Path, help="Path to a Brigade run artifact directory.")
199212

@@ -419,6 +432,8 @@ def main(argv=None) -> int:
419432

420433
if args.runs_command == "list":
421434
return runs_cmd.list_runs(cwd=args.cwd, runs_dir=args.runs_dir, limit=args.limit)
435+
if args.runs_command == "latest":
436+
return runs_cmd.show_latest(cwd=args.cwd, runs_dir=args.runs_dir)
422437
if args.runs_command == "show":
423438
return runs_cmd.show(args.run_dir)
424439
parser.error(f"unknown runs command: {args.runs_command}")

src/brigade/runs_cmd.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,7 @@ def _run_sort_key(item: tuple[Path, dict[str, Any]]) -> str:
118118
return str(value) if value else path.name
119119

120120

121-
def list_runs(*, cwd: Path, runs_dir: Path | None = None, limit: int = 10) -> int:
122-
if limit < 1:
123-
print("error: --limit must be a positive integer", file=sys.stderr)
124-
return 2
125-
126-
cwd = cwd.expanduser().resolve()
127-
if not cwd.is_dir():
128-
print(f"error: --cwd is not a directory: {cwd}", file=sys.stderr)
129-
return 2
130-
root = runs_dir.expanduser() if runs_dir is not None else cwd / ".brigade" / "runs"
131-
if not root.is_dir():
132-
print(f"error: runs directory not found: {root}", file=sys.stderr)
133-
return 2
134-
121+
def _collect_runs(root: Path) -> tuple[list[tuple[Path, dict[str, Any]]], int]:
135122
runs: list[tuple[Path, dict[str, Any]]] = []
136123
skipped = 0
137124
for child in root.iterdir():
@@ -146,8 +133,25 @@ def list_runs(*, cwd: Path, runs_dir: Path | None = None, limit: int = 10) -> in
146133
skipped += 1
147134
continue
148135
runs.append((child, meta))
149-
150136
runs.sort(key=_run_sort_key, reverse=True)
137+
return runs, skipped
138+
139+
140+
def list_runs(*, cwd: Path, runs_dir: Path | None = None, limit: int = 10) -> int:
141+
if limit < 1:
142+
print("error: --limit must be a positive integer", file=sys.stderr)
143+
return 2
144+
145+
cwd = cwd.expanduser().resolve()
146+
if not cwd.is_dir():
147+
print(f"error: --cwd is not a directory: {cwd}", file=sys.stderr)
148+
return 2
149+
root = runs_dir.expanduser() if runs_dir is not None else cwd / ".brigade" / "runs"
150+
if not root.is_dir():
151+
print(f"error: runs directory not found: {root}", file=sys.stderr)
152+
return 2
153+
154+
runs, skipped = _collect_runs(root)
151155
for path, meta in runs[:limit]:
152156
status = meta.get("status", "unknown")
153157
started = meta.get("started_at", path.name)
@@ -167,6 +171,25 @@ def list_runs(*, cwd: Path, runs_dir: Path | None = None, limit: int = 10) -> in
167171
return 0
168172

169173

174+
def show_latest(*, cwd: Path, runs_dir: Path | None = None) -> int:
175+
cwd = cwd.expanduser().resolve()
176+
if not cwd.is_dir():
177+
print(f"error: --cwd is not a directory: {cwd}", file=sys.stderr)
178+
return 2
179+
root = runs_dir.expanduser() if runs_dir is not None else cwd / ".brigade" / "runs"
180+
if not root.is_dir():
181+
print(f"error: runs directory not found: {root}", file=sys.stderr)
182+
return 2
183+
184+
runs, skipped = _collect_runs(root)
185+
if skipped:
186+
print(f"skipped {skipped} invalid run director{'y' if skipped == 1 else 'ies'}", file=sys.stderr)
187+
if not runs:
188+
print(f"error: no runs found in {root}", file=sys.stderr)
189+
return 1
190+
return show(runs[0][0])
191+
192+
170193
def show(run_dir: Path) -> int:
171194
run_dir = run_dir.expanduser()
172195
if not run_dir.is_dir():

tests/test_runs_cmd.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,39 @@ def test_runs_list_cli_with_explicit_runs_dir(tmp_path, capsys):
190190
out = capsys.readouterr().out
191191
assert "cli task" in out
192192
assert "dry-run" in out
193+
194+
195+
def test_runs_latest_shows_newest_run(tmp_path, capsys):
196+
runs_root = tmp_path / ".brigade" / "runs"
197+
_write_minimal_run(
198+
runs_root / "older",
199+
task="older task",
200+
status="failed",
201+
started_at="2026-05-26T13:00:00Z",
202+
)
203+
newest = runs_root / "newer"
204+
_write_run_artifacts(newest)
205+
206+
assert runs_cmd.show_latest(cwd=tmp_path) == 0
207+
out = capsys.readouterr().out
208+
assert f"run: {newest}" in out
209+
assert "task: build feature" in out
210+
assert "final:" in out
211+
212+
213+
def test_runs_latest_reports_no_runs(tmp_path, capsys):
214+
runs_root = tmp_path / ".brigade" / "runs"
215+
runs_root.mkdir(parents=True)
216+
217+
assert runs_cmd.show_latest(cwd=tmp_path) == 1
218+
assert "no runs found" in capsys.readouterr().err
219+
220+
221+
def test_runs_latest_cli_with_explicit_runs_dir(tmp_path, capsys):
222+
runs_root = tmp_path / "runs"
223+
runs_root.mkdir()
224+
run_dir = runs_root / "one"
225+
_write_run_artifacts(run_dir)
226+
227+
assert cli.main(["runs", "latest", "--cwd", str(tmp_path), "--runs-dir", str(runs_root)]) == 0
228+
assert f"run: {run_dir}" in capsys.readouterr().out

0 commit comments

Comments
 (0)