Skip to content

Commit e8f820f

Browse files
solomonneascodex
andauthored
feat(cli): complete code and evidence facades (#390)
Co-authored-by: Codex <codex@openai.com>
1 parent 2e81063 commit e8f820f

20 files changed

Lines changed: 913 additions & 61 deletions

QUICKSTART.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,14 @@ Capture against an id you actually have: a skill you followed, a memory card (`-
194194
```bash
195195
# optional stations (fail-open everywhere if absent)
196196
brigade add graphtrail # or: cargo install graphtrail
197-
graphtrail sync # builds .graphtrail/graphtrail.db in the repo
197+
brigade code sync . # explicitly runs local GraphTrail and builds .graphtrail/graphtrail.db
198+
brigade code context "auth receipt flow"
199+
brigade code impact brigade.work.verify.run
198200
brigade add evidence # miseledger (process-boundary Go binary)
199201

200-
# evidence station CLI (install / plan / health; does not crawl for you)
202+
# evidence station CLI: explicit local operations
203+
brigade evidence crawl sessions
204+
brigade evidence search "auth receipt flow"
201205
brigade evidence crawl plan # review-only miseledger init + crawl commands
202206
brigade evidence doctor
203207
brigade evidence export plan # review-only receipts export path
@@ -236,7 +240,11 @@ brigade pantry expiry-alert --send
236240
```bash
237241
# GraphTrail code-graph + optional local semantic search
238242
brigade add search # or: brigade add graphtrail
239-
brigade search sync plan # review-only; does not run sync
243+
brigade code sync . # preferred GraphTrail facade
244+
brigade code context "auth receipt flow"
245+
brigade code impact brigade.work.verify.run
246+
# `brigade search sync|context|impact` remain compatibility aliases.
247+
brigade search sync plan # review-only; does not run GraphTrail
240248
brigade search doctor
241249

242250
# Token Glace (output compaction; TokenJuice was the old name) + optional usage export

docs/command-inventory.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ enabled: run `brigade extras on` once, or set `BRIGADE_EXTRAS=1`.
1919
- `brigade budgets` (extras): 2 command path(s)
2020
- `brigade center` (extras): 29 command path(s)
2121
- `brigade chat` (extras): 7 command path(s)
22+
- `brigade code`: 3 command path(s)
2223
- `brigade completions`: 1 command path(s)
2324
- `brigade context` (extras): 8 command path(s)
2425
- `brigade daily`: 26 command path(s)
2526
- `brigade doctor`: 1 command path(s)
2627
- `brigade dogfood` (extras): 1 command path(s)
27-
- `brigade evidence`: 4 command path(s)
28+
- `brigade evidence`: 6 command path(s)
2829
- `brigade extras`: 3 command path(s)
2930
- `brigade friction` (extras): 3 command path(s)
3031
- `brigade guard`: 1 command path(s)
@@ -57,7 +58,7 @@ enabled: run `brigade extras on` once, or set `BRIGADE_EXTRAS=1`.
5758
- `brigade runbook` (extras): 5 command path(s)
5859
- `brigade runs`: 8 command path(s)
5960
- `brigade scrub`: 1 command path(s)
60-
- `brigade search`: 3 command path(s)
61+
- `brigade search`: 6 command path(s)
6162
- `brigade security`: 15 command path(s)
6263
- `brigade setup`: 1 command path(s)
6364
- `brigade skills`: 29 command path(s)
@@ -111,6 +112,9 @@ enabled: run `brigade extras on` once, or set `BRIGADE_EXTRAS=1`.
111112
- `brigade chat sweep import-issues` (extras)
112113
- `brigade chat sweep ingest` (extras)
113114
- `brigade chat sweep validate` (extras)
115+
- `brigade code context`
116+
- `brigade code impact`
117+
- `brigade code sync`
114118
- `brigade completions`
115119
- `brigade context archive` (extras)
116120
- `brigade context build` (extras)
@@ -148,9 +152,11 @@ enabled: run `brigade extras on` once, or set `BRIGADE_EXTRAS=1`.
148152
- `brigade daily unblock`
149153
- `brigade doctor`
150154
- `brigade dogfood` (extras)
155+
- `brigade evidence crawl`
151156
- `brigade evidence crawl plan`
152157
- `brigade evidence doctor`
153158
- `brigade evidence export plan`
159+
- `brigade evidence search`
154160
- `brigade evidence status`
155161
- `brigade extras off`
156162
- `brigade extras on`
@@ -416,8 +422,11 @@ enabled: run `brigade extras on` once, or set `BRIGADE_EXTRAS=1`.
416422
- `brigade runs steer`
417423
- `brigade runs watch`
418424
- `brigade scrub`
425+
- `brigade search context`
419426
- `brigade search doctor`
427+
- `brigade search impact`
420428
- `brigade search status`
429+
- `brigade search sync`
421430
- `brigade search sync plan`
422431
- `brigade security closeout`
423432
- `brigade security config`

docs/overview.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,10 @@ Safety and operations tools:
343343
Evidence ledger tools:
344344

345345
- [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.
346-
- Brigade station CLI (process boundary; does not crawl for you):
346+
- Brigade station CLI (process boundary):
347347
- `brigade add evidence` installs miseledger and prints the crawl/export path
348348
- `brigade evidence status` / `doctor` — advisory health + next commands
349+
- `brigade evidence crawl <args...>` / `search <args...>` - transparent MiseLedger execution; engine output and exit code pass through
349350
- `brigade evidence crawl plan` / `export plan` — review-only plans under `.brigade/evidence/plans/`
350351
- `brigade receipts export miseledger --new-only --import` — export verify/run receipts into the ledger
351352
- Historical note only: StationTrail and SourceHarvest were absorbed into MiseLedger crawl in v0.3.0; their archived repos are migration notes, not active products.

docs/station-contract.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Fresh repo installs use the `repo` profile: core, skills, memory, guard, securit
1818
| `memory` | bootstrap-doctor (optional); memory maintenance is built in | `brigade memory status|lint|compact` plus memory-care for card freshness |
1919
| `pantry` | agentpantry (Go sidecar) | plans and health-checks sealed browser-session sync; never starts source/sink |
2020
| `search` | code-search, graphtrail | local semantic search plus a code-graph CLI for callers, impact, and structural diffs |
21-
| `evidence` | miseledger (Go sidecar) | plans crawl/export and health-checks the local evidence ledger; does not crawl for you |
21+
| `evidence` | miseledger (Go sidecar) | explicitly runs local MiseLedger for `brigade evidence crawl|search`; crawl/export plans are review-only, and Brigade neither starts daemons nor uploads data |
2222

2323
## Inspecting a station before install
2424

docs/technical-guide.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,8 +1360,9 @@ First-class station CLIs (always registered; not extras-gated):
13601360

13611361
| Command group | Plans / health |
13621362
|---|---|
1363-
| `brigade evidence` | `status`, `doctor`, `crawl plan`, `export plan` |
1364-
| `brigade search` | `status`, `doctor`, `sync plan` |
1363+
| `brigade evidence` | `status`, `doctor`, `crawl`, `search`, `crawl plan`, `export plan` |
1364+
| `brigade code` | `sync`, `context`, `impact` |
1365+
| `brigade search` | `status`, `doctor`, `sync`, `context`, `impact`, `sync plan` |
13651366
| `brigade tokens` | `status`, `doctor`, `wire plan` |
13661367
| `brigade stations` | `list`, `discover` |
13671368

@@ -1377,11 +1378,9 @@ These plan commands do not generate or copy PSKs, start services, or mutate brow
13771378

13781379
`evidence` (alias `ledger`) is the local evidence-ledger station. MiseLedger remains a process-boundary Go binary; Brigade never imports it.
13791380
`brigade add evidence` installs miseledger and prints the crawl/export path.
1380-
Use `brigade evidence status` and `brigade evidence doctor` for advisory health with explicit `next` commands, `brigade evidence crawl plan` to preview miseledger init/crawl/doctor commands, and `brigade evidence export plan` to preview `brigade receipts export miseledger --new-only --import`.
1381-
These plan commands do not execute crawl or import. Product page: https://brigade.tools/miseledger.
1381+
Use `brigade evidence status` and `brigade evidence doctor` for advisory health with explicit `next` commands. `brigade evidence crawl <args...>` and `brigade evidence search <args...>` relay a safe argv list to MiseLedger, preserving its text or JSON output and exit status. `--code-reference <brigade.code-reference.v1 JSON>` is passed to MiseLedger unchanged, so its exact code-reference lookup runs before lexical fallback. Crawl defaults to 900 seconds and can be changed with the positive finite numeric `BRIGADE_EVIDENCE_CRAWL_TIMEOUT_SECONDS`; search remains at 30 seconds. An invalid crawl timeout reports a diagnostic and exits 2 before starting MiseLedger. `brigade evidence crawl plan` still previews miseledger init/crawl/doctor commands, and `brigade evidence export plan` still previews `brigade receipts export miseledger --new-only --import`. Product page: https://brigade.tools/miseledger.
13821382

1383-
`search` (alias `code-search`) wires GraphTrail and optional code-search-api. The `code-search-mcp` compatibility key points to the bridge maintained under `code-search-api/mcp`.
1384-
Use `brigade search status` / `doctor` and review-only `brigade search sync plan`. Brigade does not run `graphtrail sync` or start the search API for you.
1383+
`code` runs GraphTrail through a process boundary: `brigade code sync|context|impact <args...>` uses safe argument forwarding and preserves engine text, JSON, and exit status. Sync defaults to 900 seconds and can be changed with the positive finite numeric `BRIGADE_CODE_SYNC_TIMEOUT_SECONDS`; context and impact remain at 30 seconds. An invalid sync timeout reports a diagnostic and exits 2 before starting GraphTrail. `search` retains its `status`, `doctor`, and `sync plan` surfaces; its executable `sync`, `context`, and `impact` forms are compatibility aliases for `code` for at least two minor releases or 90 days, whichever is longer. `search` still wires optional code-search-api, and the `code-search-mcp` compatibility key points to the bridge maintained under `code-search-api/mcp`.
13851384

13861385
`tokens` wires Token Glace (current name; TokenJuice is the old name) and optional usage-tracker spend export.
13871386
Use `brigade tokens status` / `doctor` and review-only `brigade tokens wire plan`.
@@ -1390,8 +1389,7 @@ Use `brigade tokens status` / `doctor` and review-only `brigade tokens wire plan
13901389

13911390
`evidence` (alias `ledger`) is the local evidence-ledger station. MiseLedger remains a process-boundary Go binary; Brigade never imports it.
13921391
`brigade add evidence` installs miseledger and prints the crawl/export path.
1393-
Use `brigade evidence status` and `brigade evidence doctor` for advisory health with explicit `next` commands, `brigade evidence crawl plan` to preview miseledger init/crawl/doctor commands, and `brigade evidence export plan` to preview `brigade receipts export miseledger --new-only --import`.
1394-
These plan commands do not execute crawl or import. Product page: https://brigade.tools/miseledger.
1392+
Use `brigade evidence status` and `brigade evidence doctor` for advisory health with explicit `next` commands. `brigade evidence crawl <args...>` and `brigade evidence search <args...>` relay a safe argv list to MiseLedger, preserving its text or JSON output and exit status. `brigade evidence crawl plan` still previews miseledger init/crawl/doctor commands, and `brigade evidence export plan` still previews `brigade receipts export miseledger --new-only --import`. Product page: https://brigade.tools/miseledger.
13951393

13961394
Security commands:
13971395

engines/evidence-ledger/internal/app/app.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,20 +1541,20 @@ func cmdSearch(args []string, out, errw io.Writer) int {
15411541
if err != nil {
15421542
return fatalf(errw, "search: %s", err)
15431543
}
1544-
if len(rest) < 1 {
1545-
return fatalf(errw, "usage: miseledger search <query>")
1546-
}
15471544
limit := 20
15481545
if values["limit"] != "" {
15491546
if _, err := fmt.Sscan(values["limit"], &limit); err != nil {
15501547
return fatalf(errw, "search: invalid --limit")
15511548
}
15521549
}
1553-
query := strings.Join(rest, " ")
15541550
codeReference, err := parseCodeReference(values["code-reference"])
15551551
if err != nil {
15561552
return fatalf(errw, "search: %s", err)
15571553
}
1554+
if len(rest) < 1 && codeReference == nil {
1555+
return fatalf(errw, "usage: miseledger search <query>")
1556+
}
1557+
query := strings.Join(rest, " ")
15581558
db, _, err := openMigrated()
15591559
if err != nil {
15601560
return fatalf(errw, "search: %s", err)
@@ -1907,6 +1907,9 @@ func search(db *sql.DB, opts SearchOpts) ([]SearchResult, error) {
19071907
if len(exact) != 0 {
19081908
return exact, nil
19091909
}
1910+
if opts.Query == "" {
1911+
return exact, nil
1912+
}
19101913
}
19111914
sqlText, params := buildSearchQuery(opts)
19121915
rows, err := db.Query(sqlText, params...)

engines/evidence-ledger/internal/app/app_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,67 @@ func TestSearchUsesExactCodeReferenceBeforeLexicalFallbackAndLegacyItemsStillSea
20822082
}
20832083
}
20842084

2085+
func TestCLISearchAllowsOnlyValidExactCodeReferenceWithoutLexicalQuery(t *testing.T) {
2086+
withTempHome(t)
2087+
runOK(t, "init")
2088+
db, _, err := openMigrated()
2089+
if err != nil {
2090+
t.Fatal(err)
2091+
}
2092+
defer db.Close()
2093+
insertSyntheticSearchArchive(t, db, 40)
2094+
2095+
reference := CodeReference{
2096+
Schema: "brigade.code-reference.v1",
2097+
Repository: "escoffier-labs/brigade",
2098+
Revision: CodeRevision{Commit: strings.Repeat("a", 40)},
2099+
FilePath: "src/brigade/receipts_cmd.py",
2100+
QualifiedName: "brigade.receipts_cmd._metadata_with_delta",
2101+
SymbolKind: "function",
2102+
SourceSpan: SourceSpan{StartLine: 787, LineCount: 3},
2103+
ChangeKind: "changed",
2104+
}
2105+
metadata, err := json.Marshal(map[string]any{"code_references": []CodeReference{reference}})
2106+
if err != nil {
2107+
t.Fatal(err)
2108+
}
2109+
if _, err := db.Exec(`update items set metadata_json = ? where id = 'item-001'`, string(metadata)); err != nil {
2110+
t.Fatal(err)
2111+
}
2112+
encoded, err := json.Marshal(reference)
2113+
if err != nil {
2114+
t.Fatal(err)
2115+
}
2116+
2117+
exactOnly := runJSON(t, "search", "--code-reference", string(encoded), "--json")
2118+
if results := exactOnly["results"].([]any); len(results) != 1 || results[0].(map[string]any)["id"] != "item-001" {
2119+
t.Fatalf("exact-only CLI search = %#v", exactOnly)
2120+
}
2121+
2122+
missing := reference
2123+
missing.ChangeKind = "removed"
2124+
missingEncoded, err := json.Marshal(missing)
2125+
if err != nil {
2126+
t.Fatal(err)
2127+
}
2128+
exactOnlyMiss := runJSON(t, "search", "--code-reference", string(missingEncoded), "--json")
2129+
if results := exactOnlyMiss["results"].([]any); len(results) != 0 {
2130+
t.Fatalf("exact-only nonmatch fell through to lexical search: %#v", exactOnlyMiss)
2131+
}
2132+
2133+
combinedFallback := runJSON(t, "search", "needle", "--code-reference", string(missingEncoded), "--limit", "5", "--json")
2134+
if results := combinedFallback["results"].([]any); len(results) != 5 || results[0].(map[string]any)["id"] != "item-030" {
2135+
t.Fatalf("combined CLI fallback = %#v", combinedFallback)
2136+
}
2137+
2138+
if code, _, stderr := run("search", "--code-reference", "{}", "--json"); code == 0 || !strings.Contains(stderr, "invalid code reference") {
2139+
t.Fatalf("malformed exact-only reference code=%d stderr=%q", code, stderr)
2140+
}
2141+
if code, _, stderr := run("search", "--json"); code == 0 || !strings.Contains(stderr, "usage: miseledger search <query>") {
2142+
t.Fatalf("missing query and reference code=%d stderr=%q", code, stderr)
2143+
}
2144+
}
2145+
20852146
func TestCodeReferenceIntegerVectorsAreAcceptedAndCanonicalized(t *testing.T) {
20862147
data, err := os.ReadFile(repoPath(t, "../../schemas/code-reference.v1.integer-vectors.json"))
20872148
if err != nil {

src/brigade/cli/__init__.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
add as _add_group,
3535
setup as _setup_group,
3636
stations as _stations_group,
37+
code as _code_group,
3738
pantry as _pantry_group,
3839
evidence as _evidence_group,
3940
search as _search_group,
@@ -105,6 +106,20 @@
105106
}
106107

107108

109+
_PASSTHROUGH_LEAVES = {
110+
("code", "sync"),
111+
("code", "context"),
112+
("code", "impact"),
113+
("evidence", "crawl"),
114+
("evidence", "search"),
115+
("search", "sync"),
116+
("search", "context"),
117+
("search", "impact"),
118+
}
119+
120+
_PLAN_LEAVES = {("evidence", "crawl"), ("search", "sync")}
121+
122+
108123
def _register_extras(sub: argparse._SubParsersAction, name: str, extras_enabled: bool) -> None:
109124
if extras_enabled:
110125
_EXTRAS_MODULES[name].register(sub)
@@ -134,6 +149,7 @@ def _build_parser() -> argparse.ArgumentParser:
134149
_add_group.register(sub)
135150
_setup_group.register(sub)
136151
_stations_group.register(sub)
152+
_code_group.register(sub)
137153
_evidence_group.register(sub)
138154
_search_group.register(sub)
139155
_tokens_group.register(sub)
@@ -188,9 +204,24 @@ def _build_parser() -> argparse.ArgumentParser:
188204
return parser
189205

190206

207+
def _peel_passthrough_engine_args(argv: list[str]) -> tuple[list[str], list[str] | None]:
208+
"""Keep opaque engine flags out of argparse for known passthrough leaves."""
209+
210+
leaf = tuple(argv[:2])
211+
if leaf not in _PASSTHROUGH_LEAVES:
212+
return argv, None
213+
if leaf in _PLAN_LEAVES and len(argv) > 2 and argv[2] == "plan":
214+
return argv, None
215+
return argv[:2], argv[2:]
216+
217+
191218
def main(argv=None) -> int:
219+
raw_argv = list(sys.argv[1:] if argv is None else argv)
220+
parse_argv, engine_args = _peel_passthrough_engine_args(raw_argv)
192221
parser = _build_parser()
193-
args = parser.parse_args(argv)
222+
args = parser.parse_args(parse_argv)
223+
if engine_args is not None:
224+
args.engine_args = engine_args
194225

195226
# Command groups dispatch via set_defaults(func=...). The parser is attached
196227
# so dispatch functions can call parser.error for unreachable

src/brigade/cli/_common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"tools",
4343
"mcp",
4444
"evidence",
45+
"code",
4546
"search",
4647
"tokens",
4748
"pantry",

src/brigade/cli/code.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Executable GraphTrail facade commands."""
2+
3+
from __future__ import annotations
4+
5+
import argparse
6+
7+
8+
def register(sub: argparse._SubParsersAction) -> None:
9+
parser = sub.add_parser("code", help="Run GraphTrail graph commands through Brigade.")
10+
commands = parser.add_subparsers(dest="code_command", metavar="<code-command>")
11+
commands.required = True
12+
for verb in ("sync", "context", "impact"):
13+
command = commands.add_parser(verb, help=f"Run `graphtrail {verb}`.")
14+
command.add_argument("engine_args", nargs=argparse.REMAINDER, help=argparse.SUPPRESS)
15+
parser.set_defaults(func=dispatch)
16+
17+
18+
def dispatch(args) -> int:
19+
from .. import code_cmd
20+
21+
return code_cmd.run(args.code_command, args.engine_args)

0 commit comments

Comments
 (0)