Skip to content

Commit f2a83b3

Browse files
authored
Merge pull request #7 from solomonneas/feat/brigade-foundation
Rebrand solo-mise to Brigade with station registry
2 parents 0918445 + 70e697b commit f2a83b3

85 files changed

Lines changed: 554 additions & 263 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.5.0] - 2026-05-24
9+
10+
### Changed
11+
- Renamed the project to **Brigade**. The PyPI distribution is now `brigade-cli` and the command is `brigade`. The workspace config directory is now `.brigade`, with a `.solo-mise` read fallback so older installs keep working.
12+
13+
### Added
14+
- Built-in station registry that drives the doctor checks.
15+
- `brigade status` command, alongside `brigade init` and `brigade doctor`, reporting over the station registry.
16+
17+
### Deprecated
18+
- The `solo-mise` command is kept as a deprecated alias for `brigade`.
19+
820
## [0.4.0] - 2026-05-17
921

1022
### Breaking
@@ -103,7 +115,8 @@ Initial release.
103115
- OpenClaw adapter fragments and harness-aware doctor checks.
104116
- Experimental Hermes adapter fragments.
105117

106-
[Unreleased]: https://github.com/solomonneas/solo-mise/compare/v0.4.0...HEAD
118+
[Unreleased]: https://github.com/solomonneas/solo-mise/compare/v0.5.0...HEAD
119+
[0.5.0]: https://github.com/solomonneas/solo-mise/compare/v0.4.0...v0.5.0
107120
[0.4.0]: https://github.com/solomonneas/solo-mise/compare/v0.3.0...v0.4.0
108121
[0.3.0]: https://github.com/solomonneas/solo-mise/compare/v0.2.0...v0.3.0
109122
[0.2.0]: https://github.com/solomonneas/solo-mise/compare/v0.1.0...v0.2.0

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ The cookbook explains the why. This package gives you the kitchen.
5555
## Install
5656

5757
```bash
58-
pipx install solo-mise
58+
pipx install brigade-cli
5959
```
6060

6161
Or, to track `main`:
@@ -64,22 +64,26 @@ Or, to track `main`:
6464
pipx install git+https://github.com/solomonneas/solo-mise
6565
```
6666

67+
The workspace config directory is `.brigade` (older `.solo-mise` installs are still read), and the `solo-mise` command is a deprecated alias for `brigade`.
68+
6769
## Quick path
6870

69-
Run `solo-mise init` with no flags for the interactive picker:
71+
Run `brigade init` with no flags for the interactive picker:
7072

7173
```bash
72-
solo-mise init --target ~/agent-kitchen
74+
brigade init --target ~/agent-kitchen
7375
```
7476

7577
For CI or scripts, pass flags directly:
7678

7779
```bash
78-
solo-mise init --target ~/agent-kitchen --depth workspace --harnesses claude,codex,openclaw
79-
solo-mise init --target ./repo --depth repo --harnesses codex
80-
solo-mise init --target ./repo --harnesses none # generic install
80+
brigade init --target ~/agent-kitchen --depth workspace --harnesses claude,codex,openclaw
81+
brigade init --target ./repo --depth repo --harnesses codex
82+
brigade init --target ./repo --harnesses none # generic install
8183
```
8284

85+
Once installed, `brigade doctor` verifies the wiring and `brigade status` reports over the station registry.
86+
8387
## Two axes: depth + harnesses
8488

8589
solo-mise installs material on two independent axes:
@@ -117,14 +121,14 @@ Four common combos:
117121

118122
The canonical memory owner is picked automatically by priority (`openclaw > hermes > claude > codex > this-repo`). Override with `--owner`.
119123

120-
Re-running `solo-mise init` against an existing target is safe. It refuses to overwrite tracked files without `--force`, and the `.gitignore` block it manages is replaced between its markers without touching the rest of your file.
124+
Re-running `brigade init` against an existing target is safe. It refuses to overwrite tracked files without `--force`, and the `.gitignore` block it manages is replaced between its markers without touching the rest of your file.
121125

122126
See [QUICKSTART.md](QUICKSTART.md) for setup, verification, and the ingest flow.
123127

124128
### What a green doctor looks like
125129

126130
```text
127-
solo-mise doctor: target /home/you/agent-kitchen (generic)
131+
brigade doctor: target /home/you/agent-kitchen (generic)
128132
[ok] bootstrap: AGENTS.md /home/you/agent-kitchen/AGENTS.md
129133
[ok] bootstrap: CLAUDE.md /home/you/agent-kitchen/CLAUDE.md
130134
[ok] bootstrap: MEMORY.md /home/you/agent-kitchen/MEMORY.md
@@ -160,7 +164,7 @@ Claude Code Codex
160164
\ /
161165
\ /
162166
v v
163-
solo-mise ingest
167+
brigade ingest
164168
|
165169
v
166170
memory/cards/*.md, TOOLS.md, USER.md,

pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ requires = ["setuptools>=68", "wheel"]
33
build-backend = "setuptools.build_meta"
44

55
[project]
6-
name = "solo-mise"
7-
version = "0.4.0"
6+
name = "brigade-cli"
7+
version = "0.5.0"
88
description = "Mise en place for agent memory. Installable starter kit for a harness-agnostic agent workspace."
99
readme = "README.md"
1010
requires-python = ">=3.10"
@@ -30,13 +30,14 @@ Cookbook = "https://github.com/solomonneas/solos-cookbook"
3030
Issues = "https://github.com/solomonneas/solo-mise/issues"
3131

3232
[project.scripts]
33-
solo-mise = "solo_mise.cli:main"
33+
brigade = "brigade.cli:main"
34+
solo-mise = "brigade.cli:main_deprecated"
3435

3536
[tool.setuptools.packages.find]
3637
where = ["src"]
3738

3839
[tool.setuptools.package-data]
39-
solo_mise = ["py.typed", "templates/**/*"]
40+
brigade = ["py.typed", "templates/**/*"]
4041

4142
[tool.pytest.ini_options]
4243
testpaths = ["tests"]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Solomon's Mise en Place: installable starter kit for an agent kitchen."""
22

3-
__version__ = "0.4.0"
3+
__version__ = "0.5.0"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Enable `python -m solo_mise`."""
1+
"""Enable `python -m brigade`."""
22
from .cli import main
33

44
if __name__ == "__main__":
Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""solo-mise command-line entrypoint."""
1+
"""brigade command-line entrypoint."""
22
from __future__ import annotations
33

44
import argparse
@@ -11,11 +11,11 @@
1111

1212
def _build_parser() -> argparse.ArgumentParser:
1313
parser = argparse.ArgumentParser(
14-
prog="solo-mise",
15-
description="Solomon's Mise en Place: installable agent kitchen starter kit.",
14+
prog="brigade",
15+
description="Brigade: run your agent brigade. Operator-system CLI for agent workspaces.",
1616
)
1717
parser.add_argument(
18-
"--version", action="version", version=f"solo-mise {__version__}"
18+
"--version", action="version", version=f"brigade {__version__}"
1919
)
2020
sub = parser.add_subparsers(dest="command", metavar="<command>")
2121
sub.required = True
@@ -72,13 +72,17 @@ def _build_parser() -> argparse.ArgumentParser:
7272
default="generic",
7373
)
7474

75+
# status
76+
p_status = sub.add_parser("status", help="Show which stations are present and healthy.")
77+
p_status.add_argument("--target", "-t", type=Path, default=Path("."))
78+
7579
# scrub
7680
p_scrub = sub.add_parser("scrub", help="Run content-guard against a target.")
7781
p_scrub.add_argument("--target", "-t", type=Path, default=Path("."))
7882
p_scrub.add_argument(
7983
"--policy",
8084
default="public-repo",
81-
help="Policy file name (looks under .solo-mise/policies, then content-guard/policies) or path.",
85+
help="Policy file name (looks under .brigade/policies, then content-guard/policies) or path.",
8286
)
8387
p_scrub.add_argument("--dry-run", action="store_true")
8488

@@ -183,6 +187,10 @@ def main(argv=None) -> int:
183187
from . import doctor as doctor_mod
184188

185189
return doctor_mod.run(target=args.target, harness=args.harness)
190+
if cmd == "status":
191+
from . import status as status_mod
192+
193+
return status_mod.run(target=args.target)
186194
if cmd == "scrub":
187195
from . import scrub as scrub_mod
188196

@@ -215,7 +223,7 @@ def main(argv=None) -> int:
215223

216224
existing = load_config(args.target)
217225
if existing is None:
218-
print("error: no .solo-mise/config.json in target. Run `solo-mise init` first.", file=sys.stderr)
226+
print("error: no .brigade/config.json in target. Run `brigade init` first.", file=sys.stderr)
219227
return 2
220228

221229
depth = args.depth or existing.selection.depth
@@ -238,5 +246,13 @@ def main(argv=None) -> int:
238246
return 2
239247

240248

249+
def main_deprecated(argv=None) -> int:
250+
print(
251+
"warning: the 'solo-mise' command is deprecated; use 'brigade' instead.",
252+
file=sys.stderr,
253+
)
254+
return main(argv)
255+
256+
241257
if __name__ == "__main__": # pragma: no cover
242258
sys.exit(main())
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Read/write .solo-mise/config.json - the per-target source of truth."""
1+
"""Read/write .brigade/config.json - the per-target source of truth."""
22
from __future__ import annotations
33

44
import json
@@ -9,7 +9,9 @@
99
from .selection import Selection
1010

1111

12-
CONFIG_REL_PATH = ".solo-mise/config.json"
12+
WORKSPACE_DIRNAME = ".brigade"
13+
LEGACY_WORKSPACE_DIRNAMES = (".solo-mise",)
14+
CONFIG_REL_PATH = f"{WORKSPACE_DIRNAME}/config.json"
1315
SUPPORTED_VERSIONS = (1,)
1416

1517

@@ -40,7 +42,13 @@ def write_config(target: Path, cfg: Config) -> None:
4042
def load_config(target: Path) -> Optional[Config]:
4143
path = config_path(target)
4244
if not path.is_file():
43-
return None
45+
for legacy in LEGACY_WORKSPACE_DIRNAMES:
46+
legacy_path = target / legacy / "config.json"
47+
if legacy_path.is_file():
48+
path = legacy_path
49+
break
50+
else:
51+
return None
4452
data = json.loads(path.read_text())
4553
version = data.get("version")
4654
if version not in SUPPORTED_VERSIONS:
Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""`solo-mise doctor` verify a target workspace is wired correctly."""
1+
"""`brigade doctor` - verify a target workspace is wired correctly."""
22
from __future__ import annotations
33

44
import json
@@ -15,56 +15,70 @@
1515
FAIL = "FAIL"
1616
MANUAL = "MANUAL"
1717

18+
from .station import DoctorContext
1819

19-
def run(target: Path, harness: str = "generic") -> int:
20-
target = target.expanduser().resolve()
21-
print(f"solo-mise doctor: target {target}")
2220

21+
def build_context(target: Path, harness: str = "generic") -> DoctorContext:
22+
target = target.expanduser().resolve()
2323
from .config import load_config
2424

25-
cfg = None
25+
sel = None
2626
try:
2727
cfg = load_config(target)
28-
except (ValueError, json.JSONDecodeError) as exc:
29-
print(f" [fail] config: {exc}")
30-
28+
except (ValueError, json.JSONDecodeError):
29+
cfg = None
3130
if cfg is not None:
3231
sel = cfg.selection
32+
harnesses = list(sel.harnesses)
33+
elif harness in ("openclaw", "hermes"):
34+
harnesses = ["claude", harness]
35+
else:
36+
harnesses = ["claude"]
37+
return DoctorContext(target=target, selection=sel, harnesses=harnesses)
38+
39+
40+
def core_station_checks(ctx: DoctorContext) -> List[CheckResult]:
41+
checks: List[CheckResult] = []
42+
checks.extend(_check_workspace_files(ctx.target))
43+
if "openclaw" in ctx.harnesses:
44+
checks.extend(_check_openclaw())
45+
if "hermes" in ctx.harnesses:
46+
checks.extend(_check_hermes(ctx.target))
47+
checks.extend(_check_orphan_inboxes(ctx.target, ctx.harnesses))
48+
return checks
49+
50+
51+
def memory_station_checks(ctx: DoctorContext) -> List[CheckResult]:
52+
checks: List[CheckResult] = []
53+
checks.extend(_check_handoff_inboxes(ctx.target, ctx.selection, ctx.harnesses))
54+
checks.extend(_check_memory_care(ctx.target))
55+
return checks
56+
57+
58+
def guard_station_checks(ctx: DoctorContext) -> List[CheckResult]:
59+
return _check_publish_gate(ctx.target)
60+
61+
62+
def run(target: Path, harness: str = "generic") -> int:
63+
from .registry import all_stations
64+
65+
ctx = build_context(target, harness)
66+
print(f"brigade doctor: target {ctx.target}")
67+
if ctx.selection is not None:
68+
sel = ctx.selection
3369
print(
3470
f" harnesses: {', '.join(sel.harnesses) or '(none)'} "
3571
f"(owner={sel.owner}, depth={sel.depth})"
3672
)
37-
selected_harnesses = list(sel.harnesses)
3873
else:
39-
# Legacy fall-through: no config.json present.
40-
sel = None
41-
if harness in ("openclaw", "hermes"):
42-
print(
43-
f" harnesses: (legacy target, no .solo-mise/config.json; "
44-
f"--harness={harness} so assuming claude + {harness})"
45-
)
46-
selected_harnesses = ["claude", harness]
47-
else:
48-
print(
49-
" harnesses: (legacy target, no .solo-mise/config.json; "
50-
"assuming claude)"
51-
)
52-
selected_harnesses = ["claude"]
74+
print(
75+
f" harnesses: (legacy target, no config; assuming {', '.join(ctx.harnesses)})"
76+
)
5377

5478
checks: List[CheckResult] = []
55-
checks.extend(_check_workspace_files(target))
56-
checks.extend(_check_handoff_inboxes(target, sel, selected_harnesses))
57-
checks.extend(_check_memory_care(target))
58-
checks.extend(_check_publish_gate(target))
59-
60-
if "openclaw" in selected_harnesses:
61-
checks.extend(_check_openclaw())
62-
if "hermes" in selected_harnesses:
63-
checks.extend(_check_hermes(target))
64-
65-
# Orphan-inbox warnings.
66-
checks.extend(_check_orphan_inboxes(target, selected_harnesses))
67-
79+
for station in all_stations():
80+
if station.doctor is not None:
81+
checks.extend(station.doctor(ctx))
6882
return _report(checks)
6983

7084

@@ -238,7 +252,7 @@ def _check_publish_gate(target: Path) -> List[CheckResult]:
238252

239253

240254
def _check_openclaw() -> List[CheckResult]:
241-
"""Inspect ~/.openclaw/openclaw.json for the wiring solo-mise expects."""
255+
"""Inspect ~/.openclaw/openclaw.json for the wiring brigade expects."""
242256
results: List[CheckResult] = []
243257
config = Path.home() / ".openclaw" / "openclaw.json"
244258
if not config.is_file():
@@ -339,7 +353,7 @@ def _format_schedule(schedule) -> str:
339353

340354
def _check_hermes(target: Path) -> List[CheckResult]:
341355
results: List[CheckResult] = []
342-
fragments_dir = target / ".solo-mise" / "hermes"
356+
fragments_dir = target / ".brigade" / "hermes"
343357
expected = [
344358
"workspace.harness.json",
345359
"memory-handoff.harness.json",
@@ -350,7 +364,7 @@ def _check_hermes(target: Path) -> List[CheckResult]:
350364
if path.is_file():
351365
results.append((OK, f"hermes: {name}", str(path)))
352366
else:
353-
results.append((WARN, f"hermes: {name}", f"missing at {path}; run `solo-mise hermes-fragments`"))
367+
results.append((WARN, f"hermes: {name}", f"missing at {path}; run `brigade hermes-fragments`"))
354368
results.append(
355369
(MANUAL, "hermes: install validation", "Hermes adapter is experimental; validate against your install")
356370
)

0 commit comments

Comments
 (0)