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
6 changes: 3 additions & 3 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,13 +337,13 @@ Safety and operations tools:

- Content Guard is embedded in Brigade and powers `brigade scrub`, publish checks, and the seeded pre-push hook. `CONTENT_GUARD_DIR` remains an explicit compatibility override for older standalone checkouts.
- [Agent Pantry](https://github.com/escoffier-labs/agentpantry): encrypted browser session, cookie, and secret sync for agent machines.
- [agent-notify](https://github.com/escoffier-labs/agent-notify) (`stations/notify/` in this monorepo): optional notification hooks for long-running agent work. Released installs resolve the pinned managed `agent-notify` binary through `brigade setup` once a stable manifest publishes its assets. The standalone repository carries a migration notice and is not archived until a containing Brigade release ships and published acceptance passes.
- [agent-notify](https://github.com/escoffier-labs/brigade/tree/main/stations/notify) (`stations/notify/` in this monorepo): optional notification hooks for long-running agent work. Released installs resolve the pinned managed `agent-notify` binary through `brigade setup` once a stable manifest publishes its assets. The standalone [agent-notify](https://github.com/escoffier-labs/agent-notify) repository carries a migration notice pointing here.
- [Token Glace](https://github.com/escoffier-labs/token-glace): output compaction for terminal-heavy agent workflows.
- Built-in Scout skills: Brigade wires `brigade-work` and `ultra-work-scout` during `brigade init`; use Skillet when you want the full optional skill roster.

Evidence ledger tools:

- [MiseLedger](https://github.com/escoffier-labs/miseledger): local-first evidence ledger. One binary crawls sessions, files, git history, and chat sources (`miseledger crawl ...`), stores `miseledger.adapter.v1` JSONL in SQLite with FTS5, and emits Brigade-ready evidence bundles. No separate exporter install.
- [MiseLedger](https://github.com/escoffier-labs/brigade/tree/main/engines/evidence-ledger) (`engines/evidence-ledger/` in this monorepo): local-first evidence ledger. One binary crawls sessions, files, git history, and chat sources (`miseledger crawl ...`), stores `miseledger.adapter.v1` JSONL in SQLite with FTS5, and emits Brigade-ready evidence bundles. The archived [miseledger](https://github.com/escoffier-labs/miseledger) repository is a frozen history mirror.
- Brigade station CLI (process boundary):
- `brigade setup` installs GraphTrail, `graphtrail-mcp`, MiseLedger, SessionFind, and `agent-notify` (when published on the release manifest) from the exact release manifest
- `brigade add evidence` is a one-release compatibility fallback for an independent MiseLedger install
Expand Down Expand Up @@ -509,7 +509,7 @@ brigade pantry expiry-alert --send # optional agent-notify (install notificati

The `notifications` station wires optional `agent-notify` into the same operator workflow: private Discord, Telegram, or Signal delivery for long-running agent work. Source lives in [`stations/notify/`](../stations/notify/) in this repository. `agent-notify` stays a **separate Go binary** (process boundary). Brigade installs it, plans setup, and health-checks it; it does not send messages from doctor, status, or brief flows.

Released pipx installs resolve `agent-notify` from the pinned unified release manifest through `brigade setup` once stable publishes its assets. `go install github.com/escoffier-labs/agent-notify/cmd/agent-notify@latest` is the explicit fallback when you are on a source checkout or the component is not yet published on the running manifest. The standalone [agent-notify](https://github.com/escoffier-labs/agent-notify) repository carries a migration notice pointing here; it is not archived until a containing Brigade release ships and published acceptance passes.
Released pipx installs resolve `agent-notify` from the pinned unified release manifest through `brigade setup` once stable publishes its assets. `go install github.com/escoffier-labs/agent-notify/cmd/agent-notify@latest` is the explicit fallback when you are on a source checkout or the component is not yet published on the running manifest. Source lives in [`stations/notify/`](../stations/notify/); the standalone [agent-notify](https://github.com/escoffier-labs/agent-notify) repository carries a migration notice pointing here.

```bash
brigade add notifications
Expand Down
2 changes: 1 addition & 1 deletion docs/phase-4a-compatibility-and-archive.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ guidance from documentation.
- [x] Migrate operator MCP client configuration to the Brigade-managed absolute path installed by `brigade setup` (operator machine migrated 2026-07-21: codex, Cursor, OpenClaw, and Claude configs plus the capped MCP wrapper now use the managed set).
- [x] Confirm migration notices as ordinary commits on both mirrors (graphtrail PR #44 and miseledger PR #44, merged 2026-07-21).
- [x] Verify that neither standalone `master` branch was rewritten or force-pushed (ancestry-checked against the pre-notice heads on 2026-07-21).
- [ ] Update product and documentation links to the Brigade release path.
- [x] Update product and documentation links to the Brigade release path.
- [x] Archive `escoffier-labs/graphtrail` (archived 2026-07-21).
- [x] Archive `escoffier-labs/miseledger` (archived 2026-07-21).

Expand Down
4 changes: 2 additions & 2 deletions docs/technical-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@ Shared skill registry commands:
- `brigade skills sync --workspace . --target all --trust workspace` scans the reviewed registry once and reports each selected skill and harness as current, missing, changed, blocked, or excluded. It is a dry run unless `--write` is present. Writes use explicit registry identities and the existing per-target receipt and rollback path, so completed targets survive a later target failure.
- `brigade skills compatibility security-review` reports supported, installed, planned, and blocked harness targets for a skill, plus independent source, renderer, and local-copy drift, install history counts, trust score, and changelog status.
- `brigade skills history security-review --harness codex` lists local install receipts for one skill and harness from `.brigade/skills/installs/history.jsonl`.
- `brigade skills diff security-review --harness codex` compares the installed harness file against the current rendered resolved source. Bundled skills compare with the current Brigade package and report source, renderer, and local-edit drift separately. Receipts from older schemas report unknown provenance instead of guessing.
- `brigade skills diff security-review --harness codex` compares the installed harness file against the bundled Brigade package template by default, even when a same-named registry entry exists. Pass `--against registry` to diff against the local `.brigade/skills/registry/` copy instead. Bundled skills report source, renderer, and local-edit drift separately. Receipts from older schemas report unknown provenance instead of guessing.
- `brigade skills fleet status` reports installed skill copies across harnesses in stable order and prints one forced reinstall command for each supported stale or missing copy. Copies whose current metadata no longer supports their harness are listed separately as unsupported and get an uninstall command. Unknown legacy copies are listed without an automatic repair command.
- `brigade skills rollback security-review --target claude` restores the latest rollback snapshot captured before a forced reinstall.
- `brigade skills inbox add ./some-skill`, `list`, `show`, `diff`, `accept`, and `reject` keep agent-proposed skills in review before they enter the registry.
Expand Down Expand Up @@ -1378,7 +1378,7 @@ Use `brigade pantry status` and `brigade pantry doctor` for pantry-specific heal
Use `brigade pantry expiry-alert` to report near-expiry sessions and preview the `agent-notify` message Brigade would send. Add `--send` only after `brigade add notifications` if you want delivery.
These plan commands do not generate or copy PSKs, start services, or mutate browser, GitHub, OpenClaw, or other auth files. Product page: https://brigade.tools/agentpantry.

`notifications` is the operator notification station. `agent-notify` remains a process-boundary Go binary; Brigade never imports it. Source lives in `stations/notify/` in this repository. Released pipx installs resolve `agent-notify` from the pinned unified release manifest through `brigade setup` once stable publishes its assets. `go install github.com/escoffier-labs/agent-notify/cmd/agent-notify@latest` is the explicit fallback when you are on a source checkout or the component is not yet published on the running manifest. The standalone [agent-notify](https://github.com/escoffier-labs/agent-notify) repository carries a migration notice pointing at the monorepo; it is not archived until a containing Brigade release ships and published acceptance passes.
`notifications` is the operator notification station. `agent-notify` remains a process-boundary Go binary; Brigade never imports it. Source lives in [`stations/notify/`](../stations/notify/) in this repository. Released pipx installs resolve `agent-notify` from the pinned unified release manifest through `brigade setup` once stable publishes its assets. `go install github.com/escoffier-labs/agent-notify/cmd/agent-notify@latest` is the explicit fallback when you are on a source checkout or the component is not yet published on the running manifest. The standalone [agent-notify](https://github.com/escoffier-labs/agent-notify) repository carries a migration notice pointing at the monorepo.
`brigade add notifications` installs `agent-notify` when missing and prints manual wiring steps.
Use `brigade notifications status` and `brigade notifications setup plan` for advisory health and reviewed hook snippets without sending.
`brigade work brief`, `brigade center status`, and `brigade daily status/plan` may surface notification readiness or suggest installing the station; Brigade never sends unless the operator uses an explicit send action such as `brigade pantry expiry-alert --send`.
Expand Down
17 changes: 15 additions & 2 deletions src/brigade/cli/skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,18 @@ def register(sub: argparse._SubParsersAction) -> None:
p_skills_history.add_argument("--limit", type=int, default=20, help="Maximum history rows to show.")
p_skills_history.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
p_skills_diff = skills_sub.add_parser(
"diff", help="Diff an installed skill against the current rendered registry version."
"diff",
help="Diff an installed skill against the bundled Brigade template (default) or the local registry.",
)
p_skills_diff.add_argument("skill", help="Skill id, path, or directory.")
p_skills_diff.add_argument("--target", "-t", type=Path, default=Path("."), help="Workspace registry to inspect.")
p_skills_diff.add_argument("--harness", required=True, help="Harness target to compare.")
p_skills_diff.add_argument(
"--against",
choices=["bundled", "registry"],
default="bundled",
help="Comparison baseline: bundled package template (default) or local registry entry.",
)
p_skills_diff.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
p_skills_fleet = skills_sub.add_parser("fleet", help="Inspect installed skill copies across harnesses.")
skills_fleet_sub = p_skills_fleet.add_subparsers(dest="skills_fleet_command", metavar="<skills-fleet-command>")
Expand Down Expand Up @@ -251,7 +258,13 @@ def dispatch(args) -> int:
json_output=args.json,
)
if args.skills_command == "diff":
return skills_cmd.diff(target=args.target, skill=args.skill, harness=args.harness, json_output=args.json)
return skills_cmd.diff(
target=args.target,
skill=args.skill,
harness=args.harness,
against=args.against,
json_output=args.json,
)
if args.skills_command == "fleet":
if args.skills_fleet_command == "status":
return skills_cmd.fleet_status(target=args.target, json_output=args.json)
Expand Down
38 changes: 36 additions & 2 deletions src/brigade/skills_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,37 @@ def _bundled_skill_path(skill_id: str) -> Path:
return template_root() / "skills" / _slug(skill_id)


def _bundled_skill_exists(skill_id: str) -> bool:
return _skill_md_path(_bundled_skill_path(skill_id)).is_file()


def _resolve_diff_baseline(target: Path, skill_or_path: str, *, against: str = "bundled") -> str:
requested = str(skill_or_path)
candidate = Path(requested).expanduser()
if candidate.exists():
return requested
if requested.startswith("registry:"):
skill_id = _slug(requested.removeprefix("registry:"))
elif requested.startswith("bundled:"):
skill_id = _slug(requested.removeprefix("bundled:"))
else:
skill_id = _slug(requested)
registry_dir = _skill_path(target, skill_id)
registry_exists = _skill_md_path(registry_dir).is_file()
bundled_exists = _bundled_skill_exists(skill_id)
if against == "registry":
if registry_exists:
return f"registry:{skill_id}"
if bundled_exists:
return f"bundled:{skill_id}"
return requested
if bundled_exists:
return f"bundled:{skill_id}"
if registry_exists:
return f"registry:{skill_id}"
return requested


def _source_identity(*, skill_dir: Path, skill_id: str, kind: str, reviewed: bool) -> dict[str, Any]:
if kind == "brigade-bundle":
identity = f"{BUNDLED_SOURCE_PREFIX}{skill_id}"
Expand Down Expand Up @@ -1540,9 +1571,10 @@ def history(
return 0


def diff(*, target: Path, skill: str, harness: str, json_output: bool = False) -> int:
def diff(*, target: Path, skill: str, harness: str, against: str = "bundled", json_output: bool = False) -> int:
target = target.expanduser().resolve()
lint_payload = _lint_payload(target, skill)
baseline_skill = _resolve_diff_baseline(target, skill, against=against)
lint_payload = _lint_payload(target, baseline_skill)
if not lint_payload["valid"]:
if json_output:
print(
Expand Down Expand Up @@ -1588,6 +1620,8 @@ def diff(*, target: Path, skill: str, harness: str, json_output: bool = False) -
"target": str(target),
"skill_id": skill_id,
"harness": harness,
"against": against,
"baseline_skill": baseline_skill,
"installed": installed_skill.is_file(),
"installed_path": str(installed_skill),
"changed": bool(diff_lines),
Expand Down
15 changes: 13 additions & 2 deletions stations/notify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,25 @@
<img src="https://shieldcn.dev/badge/license-MIT-green.svg" alt="MIT license">
</p>

> [!IMPORTANT]
> **Inside Brigade:** agent-notify lives in [escoffier-labs/brigade](https://github.com/escoffier-labs/brigade) (source under `stations/notify`). Install `brigade-cli` and run `brigade setup`, which installs the managed `agent-notify` binary and records its absolute path. Use `brigade add notifications` for wiring plans. This standalone repository remains available during the transition; report issues on the [Brigade tracker](https://github.com/escoffier-labs/brigade/issues).

## Install

For a Brigade-managed installation:

```bash
go install github.com/escoffier-labs/agent-notify/cmd/agent-notify@latest
# Brigade station
pipx install brigade-cli
brigade setup
brigade add notifications
```

Standalone fallback:

```bash
go install github.com/escoffier-labs/agent-notify/cmd/agent-notify@latest
```

## What it does

| | Job | What you get |
Expand Down
6 changes: 3 additions & 3 deletions tests/test_release_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def test_phase_4a_compatibility_and_archive_policy_is_tracked():
"Archiving a mirror freezes it read-only on GitHub. It deletes nothing.",
"- [x] Confirm migration notices as ordinary commits on both mirrors",
"- [x] Verify that neither standalone `master` branch was rewritten or force-pushed",
"- [ ] Update product and documentation links to the Brigade release path.",
"- [x] Update product and documentation links to the Brigade release path.",
"- [x] Archive `escoffier-labs/graphtrail` (archived 2026-07-21).",
"- [x] Archive `escoffier-labs/miseledger` (archived 2026-07-21).",
):
Expand All @@ -117,5 +117,5 @@ def test_phase_4a_compatibility_and_archive_policy_is_tracked():
assert "| Published at | 2026-07-21T00:50:15Z |" in text
assert "| Original dual gate | v0.27.0 + 2026-10-19 calendar gate (waived 2026-07-21) |" in text
assert "| Current status | Window compressed. Phase 4B authorized pending checklist completion |" in text
assert text.count("- [x]") == 7
assert text.count("- [ ]") == 1
assert text.count("- [x]") == 8
assert text.count("- [ ]") == 0
70 changes: 70 additions & 0 deletions tests/test_skills_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,76 @@ def test_bundled_install_and_diff_share_canonical_source(tmp_path, capsys):
}


def test_skills_diff_defaults_to_bundled_template_over_stale_registry(tmp_path, capsys):
stale = _write_skill(tmp_path / ".brigade" / "skills" / "registry", name="brigade-work")
(stale / "SKILL.md").write_text("# stale registry copy\n")

assert (
skills_cmd.install(workspace=tmp_path, skill="registry:brigade-work", harness="claude", json_output=True) == 0
)
capsys.readouterr()

assert skills_cmd.diff(target=tmp_path, skill="brigade-work", harness="claude", json_output=True) == 0
diff = json.loads(capsys.readouterr().out)
assert diff["against"] == "bundled"
assert diff["baseline_skill"] == "bundled:brigade-work"
assert diff["source"]["kind"] == "brigade-bundle"
assert diff["changed"] is True
assert diff["drift"]["content_changed"] is True


def test_skills_diff_registry_selector_still_uses_bundled_by_default(tmp_path, capsys):
stale = _write_skill(tmp_path / ".brigade" / "skills" / "registry", name="brigade-work")
(stale / "SKILL.md").write_text("# stale registry copy\n")

assert (
skills_cmd.install(workspace=tmp_path, skill="registry:brigade-work", harness="claude", json_output=True) == 0
)
capsys.readouterr()

assert skills_cmd.diff(target=tmp_path, skill="registry:brigade-work", harness="claude", json_output=True) == 0
diff = json.loads(capsys.readouterr().out)
assert diff["baseline_skill"] == "bundled:brigade-work"
assert diff["changed"] is True


def test_skills_diff_against_registry_compares_installed_to_registry(tmp_path, capsys):
stale = _write_skill(tmp_path / ".brigade" / "skills" / "registry", name="brigade-work")
(stale / "SKILL.md").write_text("# stale registry copy\n")

assert (
skills_cmd.install(workspace=tmp_path, skill="registry:brigade-work", harness="claude", json_output=True) == 0
)
capsys.readouterr()

assert (
skills_cmd.diff(
target=tmp_path,
skill="brigade-work",
harness="claude",
against="registry",
json_output=True,
)
== 0
)
diff = json.loads(capsys.readouterr().out)
assert diff["against"] == "registry"
assert diff["baseline_skill"] == "registry:brigade-work"
assert diff["source"]["kind"] == "registry"
assert diff["changed"] is False


def test_skills_diff_matches_bundled_template_reports_clean(tmp_path, capsys):
assert skills_cmd.install(workspace=tmp_path, skill="brigade-work", harness="cursor", json_output=True) == 0
capsys.readouterr()

assert skills_cmd.diff(target=tmp_path, skill="brigade-work", harness="cursor", json_output=True) == 0
diff = json.loads(capsys.readouterr().out)
assert diff["baseline_skill"] == "bundled:brigade-work"
assert diff["changed"] is False
assert diff["drift"]["content_changed"] is False


def test_explicit_registry_source_does_not_inherit_bundled_review(tmp_path, capsys):
source = _write_skill(tmp_path / "source", name="brigade-work")
assert skills_cmd.import_skill(target=tmp_path, source=source, json_output=True) == 0
Expand Down
Loading