mirror of
https://github.com/block/buzz.git
synced 2026-11-08 18:31:56 +00:00
# Kubernetes backend plugin (crates/buzz-backend-kubernetes) + desktop deploy path Implements docs/remote-agents.md (merged @28ae6cd21) as ONE PR: the provider binary, the desktop changes that make it work, the harness inactivity reaper, the Sprig image, and the conformance/live-test suites. Channel: buzz-remote-agents (29414326-dba7-402d-b384-b1b34d63a2e6), thread c42b70ef. ## What's here (by lane) - **crates/buzz-backend-kubernetes** (Dawn): stdin/stdout JSON provider, info + deploy; pure classify.rs (one match arm per spec state-machine row); reconcile/GC with ownership-marker gate + same-clock orphan check; per-attempt immutable Secrets; three-tier env with clear-then-write authoritative tier. - **Desktop** (Mari): KD3 launch block from resolved descriptor, KD5 pre-secret negotiation gate (resolve-once → stage-and-digest → info → protocol gate → deploy), KD1 Windows extension strip, bundling (externalBin + Justfile + release/canary workflows + stub loops), tauri.windows.conf.json platform override (Decision B: no Windows artifact). - **buzz-acp** (Max): KD4 BUZZ_ACP_EXIT_AFTER_INACTIVITY reaper (pool-independent; reset only at accepted dispatch; in-flight turn/heartbeat defers, never resets); BUZZ_ACP_EXIT_AFTER_INACTIVITY + BUZZ_ACP_NO_PRESENCE reserved. KD8 fix. - **Image + tests** (Perci): Dockerfile.sprig (digest-pinned bases, exec buzz-acp PID 1, relay-scoped credential config), image contract script, provider conformance suites (golden wire fixtures shared with desktop tests), live-local runbook (namespace-scoped, shared-cluster safe). - **Docs** (Sami, first commit): citation re-pin c1bca1b56 →28ae6cd21(44/49 were already byte-exact; 3 offsets fixed) + I3 presence-bound correction (below). ## Named spec deviations (deliberate, each with rationale) 1. **No baked default image yet.** ghcr.io/block/buzz-sprig is unpublished (verified: anonymous pull 403 vs control 200). Omitted `image` returns an in-band field-required error instead of a default. 2. **Image override STRICTER than spec §Image:** digest-only (`name@sha256:<64hex>`); ALL tags rejected; `name:tag@digest` normalized. With no baked default the override is the only path, so tag-acceptance would make mutability the v1 norm. Strictness is reversible; a moved tag under an nsec is not. Baked digest default + tag re-acceptance = follow-up with image publish. 3. **imagePullSecrets not in schema (v1).** Explicit user images may rely on namespace-preprovisioned pull credentials — the substrate boundary. Field added only if the publish decision proves it necessary. 9-field budget intact. 4. **Decision A closed: writable empty workspace.** Nest projection = named follow-up; no image-side scaffolding. 5. **Decision D overridden by Tyler (event b55398d8):** provider ships bundled with the desktop like buzz-acp/buzz-agent; spec §Distribution's separate release workflow deleted for v1. 6. **I3/vision presence bound corrected 90s → 180s.** PRESENCE_TTL_SECS moved in #3783 during this spec's base→merge window; the number was inherited, not chosen. Spec :206/:216/:928 + inline quote + VISION_REMOTE_AGENTS.md:59 corrected. ← Tyler: the vision is your document; this edit is flagged for your explicit eyes. 7. **Spec citations are pinned to 28ae6cd21** (main at spec merge) and resolve there, not at this PR's head — this PR's own lanes move crates/buzz-acp/src/lib.rs by ~100 lines (19 citations across KD4/KD6/KD7/ §Stop/§Launch data). Known Defects rows fixed BY this PR retire on merge; the section documents main as of the pin. 8. **KD7 grace tension declared:** pod terminationGracePeriodSeconds=60 vs KD7's measured ~87s shutdown tail at parallelism 10 (~197s at cap 32). KD7 is ruled out of scope, so L1-3's "enough grace for full graceful shutdown" is NOT met at default config — deliberate, resolved by the KD7 follow-up, not silently. ## Question for Tyler Will ghcr.io/block/buzz-sprig publish PUBLIC? If private-by-policy, §Image needs an imagePullSecrets story before the baked-default follow-up can land. ## Out of scope (named follow-ups) KD6 exit-code contract + KD7 shutdown budget (gate OnFailure), OnFailure restart policy, Windows provider binary, PVCs/nest projection, mesh deployability, sprig image publish workflow + baked multi-arch digest default. ## Reproduce locally (four traps that cost us real time) **1. Git hooks inherit the invoking shell's PATH — pin the shell, not just your verification commands.** `rust-toolchain.toml` pins `1.95.0`, but the rustup shim that honors that pin lives in `~/.cargo/bin`. If Homebrew's cargo is earlier on PATH, `cargo` in this repo is 1.89.0, which cannot build the workspace at all: ``` $ /opt/homebrew/bin/cargo check -p buzz-db error: rustc 1.89.0 is not supported by the following packages: sqlx@0.9.0 requires rustc 1.94.0 ... # exit 101 ``` Verifying with `PATH="$HOME/.cargo/bin:$PATH" cargo test` does *not* protect the push: lefthook's `pre-push` → `just test-unit` re-resolves `cargo` from the shell's own PATH, so a green local run is followed by a hook failure on a crate you never touched. Export the PATH for the whole shell, not per-command. This bit twice. **2. Line-scope your mutations, or the mutation edits its own detector.** When mutation-testing the respond-to guard, a whole-file `sed` on the mode literal touches 5 sites — the guard *and* the fixtures/assertions that test it. The mutation and its detector move together and the suite stays green, which reads as "this code is dead" when it actually means "you deleted the experiment": ``` # WRONG — 5 sites, guard and tests mutate together $ sed -i '' 's/"allowlist"/"allowlist-DISABLED"/g' src/env.rs test result: ok. 145 passed; 0 failed # false survivor # RIGHT — 1 site, anchored to the guard's own definition line $ sed -i '' '/^const RESPOND_TO_ALLOWLIST/s/"allowlist"/"allowlist-DISABLED"/' src/env.rs failures: env::tests::allowlist_mode_with_an_empty_list_is_refused env::tests::an_allowlist_entry_that_is_not_64_hex_is_refused test result: FAILED. 143 passed; 2 failed # real kill ``` Restore by copying a pristine file back and confirming `git diff --stat` is empty, not by re-running an inverse `sed`. **3. A completeness guard is not a correctness guard.** The shared wire fixture `tests/fixtures/provider-wire/deploy-full-launch.request.json` passed every test we had while containing four classes of invented data (wrong `respond_to` encoding, an env key no emitter writes, allowlist entries that fail the harness's own 64-hex rule, a `launch.env` key from no descriptor layer). The provider's tests could not have caught this: its types are deliberately indifferent to these values (`Option<String>`, `Vec<String>`, arbitrary map), so "the provider parses it" was never evidence that the desktop emits it. The fix was not a stronger provider assertion but a rule about provenance — "recorded" means executed-and-transcribed, and the desktop's whole-object equality test is the only enforcement that can exist. See the fixture README. **4. Every drift this arc was a value that agreed with itself.** Five invented values were found, and not one was caught by an assertion failing — each was caught by someone asking where a value came from. A named constant referenced symbolically on both the fixture and assertion side. A `sed` that mutated its own detector. Six probe rows that all died at the same unrelated error. A descriptor struct literal compared against a fixture built from that literal (`launch.args: ["run","--session"]`, which the resolver actually returns as `["acp"]`). The general defense is not more assertions but provenance: a stub is a control that varies nothing, and the more faithful it looks the better it hides. Ask what executed, not what passed. *Fixture-test determinism caveat (post-verification, Quinn + Dawn).* The desktop's whole-object fixture test calls the real resolver, which consults a process-global harness registry whose own docs require `registry_test_lock` for any test touching it. The fixture test holds no lock and is nonetheless deterministic — but by containment, not by ordering. Measured, not derived: planting a definition with `id: "goose"` directly into the registry (bypassing the loader) changes the resolved descriptor from `args: ["acp"]` to `args: ["--poisoned"]`, so `resolve_effective_harness_descriptor` **does** reach the registry for this id — it does not short-circuit on the builtin table first. Two controls discriminate: an empty registry and a registry poisoned under a *different* id both return `["acp"]`. What actually protects the test is that the registry has exactly one writer (`update_loaded_harness_registry`, reached only via `warm_harness_registry_from_dir`) — but that writer concatenates **two** sources of unequal strength (`custom_harnesses.rs:319-326`). Custom files pass through `load_custom_harnesses`, whose `check_id_collision` rejects the reserved builtin id `goose` case-insensitively at the loader — and that leg is tested (`load_applies_id_collision_check` writes a real `goose.json` and asserts the loader drops it). Preset definitions (`preset_harness_definitions`, `presets.rs:177-193`) are a bare `.map` over `PRESET_HARNESSES` with **no collision check** — exhaustive call-site enumeration at `60007fda4` finds four production `check_id_collision` sites, none on the preset path. That leg holds only because `goose` is not in the preset table today (intersection of TIER1 and preset ids is empty) — executed, not just read: adding a preset with `id: "goose"`, `args: ["--poisoned"]` and warming via the normal preset-only path (`warm_harness_registry_from_dir(None)`, no custom dir, no direct writer) flips the fixture's emitted `launch.args` from `["acp"]` to `["--poisoned"]` at `60007fda4`, command/env/policy_env unchanged. So: no test in the suite can put a `goose` entry in the registry via the custom path, and no preset currently carries one, so no interleaving can perturb this fixture — containment with one checked leg and one coincidental one. A future fixture built on a **non-builtin** runtime id has no containment at all — it would be order-dependent against whatever registry-writing test ran last and must take the lock. *Late instance, found while reviewing the mode guard.* The guard exact-matches `respond_to` untrimmed and case-sensitively, which is only correct if clap's `ValueEnum` derive is case-sensitive. `config.rs` gives two answers: the derive at `:448-453` carries no `ignore_case`, while the crate's own tests call `RespondTo::from_str(s, true)` — `ignore_case = true`. Reading the source supports either. Measured on the built binary instead: `owner-only` starts, `OWNER-ONLY` / `Owner-Only` / `ALLOWLIST` / `NOBODY` all exit rc=2 `invalid value`. Case-sensitive at the CLI, so the guard is right — and right for a reason the source does not state. The `from_str(_, true)` tests exercise a different surface and are not evidence about the CLI. *Corollary, and the sharper half.* When a test helper **reimplements** production instead of calling it, the helper is a fork — and a fork can be right while production is wrong, or wrong in the same way, and the suite reports green either way. Both `BUZZ_ACP_ALLOWED_*` gates are forked like this: production compares **strings** while the helpers compare **post-parse enums** (`config.rs:2623`) or re-derive the split (`buzz-cli/.../channels.rs:1296`). Production and the helper each carry their *own* copy of the empty-entry filter (`:1025` and `:1300`), so fixing one says nothing about the other. Measured on `buzz-cli`, restoring byte-exact between runs: | tree | result | |---|---| | baseline | 274 passed | | drop the empty-filter in **production** only (the real fix) | **274 passed** — no signal | | drop it in the **test helper** only | **273 passed, 1 failed** (`channels.rs:1338`) | Two independent defects, stacked, and worse together than either alone: production can be fixed with no test ever noticing, *and* the helper cannot be corrected without a false alarm demanding the bug back. The root cause is one bit of type information — `check_allowed_channel_add_policy(allowed_raw: &str, ..)` cannot represent "unset", while production reads `env::var(..) -> Result`, where unset and `""` are different states. A helper whose parameter type can't represent all of production's input states isn't testing production's states — it's testing a subset it silently chose. Same family as the struct-literal descriptor and the fixture drift: the test and the thing it tests agreeing with each other, rather than the test measuring the thing. Neither defect is in this PR's diff (`git diff --name-only28ae6cd21<head> -- crates/buzz-cli` is empty); both are now filed as NIP-34 issues on this repo: the fail-open + fork-helper defect at issue event `0524a4113f2d97fd…` and the respond-to self-lock at `e32837498969b5e7…` (filed 2026-08-02 after Quinn measured that no prior filing existed — zero hits on GitHub `block/buzz` open *or* closed and zero on the relay's kind:1621 issues, against working positive controls). The prescription was itself mutation-tested before being written down: repairing the fork's signature (`Option<&str>` + assertion → `None`) still let the reintroduced production bug ship 274-green — an expressive fork is still a fork; it never executes production. So the `buzz-cli` fix has **three parts and one explicit keep**: drop the production filter; **delete** the helper and point its tests at the real `cmd_set_add_policy` (which self-discriminates by error variant — `Usage` = refused, `Network(BadScheme)` = passed the gate — no relay needed); serialize the env-var tests behind one **`tokio::sync::Mutex::const_new`** lock taken with `.lock().await`, including the pre-existing `:1362` integration test (the fork was silently buying test isolation — without the lock, parallel runs flake nondeterministically; a `std::sync::Mutex` held across `.await` trips `clippy::await_holding_lock` under `-D warnings`); and **keep** the then-dead `!allowed.is_empty()` clause with a comment saying why. It is unreachable-false (`split(',')` never yields an empty vec), but it is the only thing that keeps the reintroduced production bug detectable — mutation-tested: on a tree that deletes the clause, reintroducing the empty-filter bug survives 275/0, because `""`/`","`/`" "` refuse either way and the filter goes semantically inert. Dead code can be load-bearing for tests: "provably unreachable" is an argument about behavior, never about coverage. When a helper forks production, the fix has to delete the fork: any change that leaves two implementations standing can only ever be verified against the one the tests call. *Final shape:* the keep and the broad lock are both artifacts of the fork surviving in some form. The extraction variant (Dawn, mutation-tested at `60007fda4`) removes the tension: extract one `check_channel_add_policy_allowed(Option<&str>, &str)` that **production calls**, with the `Option` placed at the env boundary where the `Result<String, VarError>` bit actually lives. 5/6 mutants killed; the empty-filter survivor is proven **equivalent** (exhaustive 6174-pair check, 0 divergences, with a diverging negative control; independently re-derived by a second generator — different tokens and shape — 0 divergences on admitted policies, 500 on a non-admitted control), not a coverage hole — on a one-implementation tree there is no fork left to witness, so no dead clause needs keeping. One scope line on that equivalence: it is **caller-conditional**, a property of the only current caller, not of the gate function — `cmd_set_add_policy`'s own match at `:1027-1034` admits only three policies before the gate runs; a second caller reaching the gate with arbitrary strings resurrects m1 as a real hole. The lock does not disappear, it narrows (Dawn's own correction, caught by Mari): lock exactly the tests that mutate the process env — three-plus-one on a fork tree, two on the extraction tree — behind one `tokio::sync::Mutex`, and the lock is part of the assertion, not hygiene: with it deleted, the gate test fails 8/8 runs deterministically by receiving `Network(BadScheme)` where it expects `Usage` — the unset test's `remove_var` clobbers the other's `set_var`, and **the gate test passes straight through the gate**, a false negative on the exact authz assertion the test exists to make. State it as an outcome: these two tests must not observe each other's env writes. 276/0 stable across 5 parallel runs, clippy `-D warnings` clean; independently verified (patch applied to a second worktree: result blob `d67e584be` matches the patch index, full mutant matrix reproduces row for row). One new row no earlier prescription covered: collapsing unset into `Some("")` fails **closed** — an unconfigured deployment refuses every policy — killed by the unset test. Patch: `OUTBOX/BUZZ_CLI_ADD_POLICY_GATE_EXTRACT_FIX.patch`. The filed issue (`0524a411…`) carries the fork-shape prescription; whoever picks it up should prefer the extraction shape, drop the dead-clause keep with it, and keep part 3 outcome-shaped: serialize whichever tests mutate the env. ## Verification (final HEAD `60007fda4`) - Full touched-package suites at each integration merge (log in plan file). At candidate parent `00e5b5fe9`: buzz-backend-kubernetes 154, buzz-acp 673, desktop tauri 2100+3, pnpm 3908, workspace clippy/fmt/tsc all clean. The only delta to `60007fda4` is one character in `scripts/test-k8s-sprig-image-live.sh` (heredoc escape so the readlink probe evaluates pod-side, not host-side at render); `crates/` tree hash is byte-identical at both SHAs, so the Rust receipts attach by tree identity. buzz-backend-kubernetes suite re-run in-shell at `HEAD == 60007fda4`: 154 passed. - Adversarial one-HEAD gate (Sami): guard matrix 12/12, predicate mutants 7/7, doomed-invocation finding closed end-to-end; tree-hash carry to `60007fda4` confirmed (crates/buzz-backend-kubernetes blob unchanged). - Live-local pass per TESTING.md + skill-buzz-testing (Perci, at `60007fda4`): explicit `docker-desktop` context, digest-qualified image imported into node containerd `k8s.io` namespace, pull policy `Never`; pod printed `DIGEST_ABI_OK`, `resolved_spec` and `image_id` both the exact requested digest, script exit 0. Dedicated per-run namespace, ownership labels on every object, scoped cleanup verified empty after. - Implementation review (Wren) at `60007fda4`: 9.6 minimalness / 9.4 elegance / 9.3 correctness, no blocker. - `origin/eva/k8s-backend` == `60007fda4` (ls-remote verified; SHA identity is byte identity). --------- Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Tyler <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz>
950 lines
41 KiB
Makefile
950 lines
41 KiB
Makefile
# Buzz — development task runner
|
|
|
|
set dotenv-load := true
|
|
|
|
desktop_dir := "desktop"
|
|
desktop_tauri_manifest := "desktop/src-tauri/Cargo.toml"
|
|
web_dir := "web"
|
|
|
|
# Opt-in mesh-llm. Off by default so `just dev`/`just staging`/`just production`
|
|
# skip ~420 extra crates + the llama.cpp native runtime build and stay fast to
|
|
# iterate on. Turn on to test mesh compute features: `just mesh=1 dev` /
|
|
# `just mesh=1 staging` / `just mesh=1 production`.
|
|
mesh := ""
|
|
|
|
# Reset only the current standalone desktop instance before launch.
|
|
# Usage: `just fresh=1 desktop-standalone`.
|
|
fresh := ""
|
|
|
|
# List all available tasks
|
|
default:
|
|
@just --list
|
|
|
|
# ─── Dev Environment ─────────────────────────────────────────────────────────
|
|
|
|
# Install required dev tools via Hermit and create .env (safe to re-run)
|
|
bootstrap:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
# Hermit's bin/ symlinks auto-download pinned tool versions on first use.
|
|
# Running each tool once triggers the download if not already cached.
|
|
echo "Ensuring toolchain via Hermit..."
|
|
cargo --version &
|
|
node --version &
|
|
pnpm --version &
|
|
wait
|
|
if ! command -v docker &>/dev/null; then
|
|
echo "Error: Docker is required but not installed."
|
|
echo "Install it from https://docs.docker.com/get-docker/"
|
|
exit 1
|
|
fi
|
|
if [[ ! -f .env ]]; then
|
|
cp .env.example .env
|
|
echo "Created .env from .env.example — review it before running just dev."
|
|
fi
|
|
|
|
# Start Docker services, run migrations, install desktop deps
|
|
setup: bootstrap
|
|
./scripts/dev-setup.sh
|
|
|
|
# Install git hooks via lefthook (dispatches from the shared .git/hooks dir so all
|
|
# linked worktrees inherit the same hooks without a worktree-relative .hooks path)
|
|
hooks:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# Use the Hermit-pinned lefthook (bin/lefthook self-downloads on first use):
|
|
# works with no pre-installed lefthook and guarantees the pinned version
|
|
# rather than whatever happens to be on PATH.
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
# --path-format=absolute guarantees an absolute path from every invocation context:
|
|
# without it, --git-common-dir returns ".git" from the main checkout and a
|
|
# relative hooksPath would break linked-worktree dispatch just like .hooks did.
|
|
HOOKS_DIR="$(git rev-parse --path-format=absolute --git-common-dir)/hooks"
|
|
git config --local core.hooksPath "$HOOKS_DIR"
|
|
lefthook install --force
|
|
|
|
# Wipe development state and recreate a clean environment. Installed Buzz is preserved.
|
|
[confirm("This will DELETE all development data and preserve installed Buzz. Continue? (y/N)")]
|
|
reset:
|
|
./scripts/dev-reset.sh --yes
|
|
|
|
# Stop all dev services (keep data)
|
|
down:
|
|
docker compose down
|
|
|
|
# Show dev service status
|
|
ps:
|
|
docker compose ps
|
|
|
|
# Tail all service logs
|
|
logs *ARGS:
|
|
docker compose logs -f {{ARGS}}
|
|
|
|
# ─── Build & Check ───────────────────────────────────────────────────────────
|
|
|
|
# Build the Rust workspace
|
|
build:
|
|
cargo build --workspace
|
|
|
|
# Build the Rust workspace in release mode
|
|
build-release:
|
|
cargo build --workspace --release
|
|
|
|
# Run repo lint and formatting checks
|
|
check: fmt-check clippy desktop-check desktop-tauri-fmt-check desktop-tauri-clippy web-check mobile-check
|
|
|
|
# Format all Rust code
|
|
fmt:
|
|
cargo fmt --all
|
|
|
|
# Check formatting without modifying files
|
|
fmt-check:
|
|
cargo fmt --all -- --check
|
|
|
|
# Run clippy with warnings as errors
|
|
clippy:
|
|
cargo clippy --workspace --all-targets -- -D warnings
|
|
|
|
# Install JS dependencies (pnpm workspace — installs all packages from root)
|
|
desktop-install:
|
|
pnpm install
|
|
|
|
# Install JS dependencies reproducibly for CI (pnpm workspace)
|
|
desktop-install-ci:
|
|
pnpm install --frozen-lockfile
|
|
|
|
# Run desktop lint and format checks
|
|
desktop-check:
|
|
cd {{desktop_dir}} && pnpm check
|
|
|
|
# Fix desktop lint and format issues
|
|
desktop-fix:
|
|
cd {{desktop_dir}} && pnpm exec biome check --write . && pnpm check:file-sizes
|
|
|
|
# Run desktop TS helper unit tests
|
|
desktop-test:
|
|
cd {{desktop_dir}} && pnpm test
|
|
|
|
# Run desktop TypeScript checks
|
|
desktop-typecheck:
|
|
cd {{desktop_dir}} && pnpm typecheck
|
|
|
|
# Build desktop frontend assets
|
|
desktop-build:
|
|
cd {{desktop_dir}} && pnpm build
|
|
|
|
# Format desktop Tauri Rust code
|
|
desktop-tauri-fmt:
|
|
cargo fmt --manifest-path {{desktop_tauri_manifest}} --all
|
|
|
|
# Check desktop Tauri Rust formatting
|
|
desktop-tauri-fmt-check:
|
|
cargo fmt --manifest-path {{desktop_tauri_manifest}} --all -- --check
|
|
|
|
# Format all code (Rust + Tauri Rust + Dart)
|
|
fmt-all: fmt desktop-tauri-fmt mobile-fmt
|
|
|
|
# Fix all formatting and lint issues
|
|
fix-all: fmt desktop-tauri-fmt desktop-fix web-fix mobile-fix
|
|
|
|
# Ensure sidecar placeholder binaries exist (Tauri validates externalBin at compile time)
|
|
# Sidecar binary list must stay in sync with desktop-release-build below.
|
|
_ensure-sidecar-stubs:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
|
mkdir -p desktop/src-tauri/binaries
|
|
SIDECARS=(buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz)
|
|
if [[ "$TARGET" != *windows* ]]; then
|
|
SIDECARS+=(buzz-backend-kubernetes)
|
|
fi
|
|
for bin in "${SIDECARS[@]}"; do
|
|
touch "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
done
|
|
|
|
# Ensure Docker dev services (Postgres, Redis, etc.) are running and healthy
|
|
_ensure-services:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-postgres 2>/dev/null || echo "not_found")
|
|
redis=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-redis 2>/dev/null || echo "not_found")
|
|
if [[ "$pg" == "healthy" && "$redis" == "healthy" ]]; then
|
|
echo "Services already healthy"
|
|
exit 0
|
|
fi
|
|
echo "Starting services..."
|
|
docker compose up -d || true
|
|
echo -n "Waiting for services"
|
|
for i in $(seq 1 40); do
|
|
pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-postgres 2>/dev/null || echo "not_found")
|
|
redis=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-redis 2>/dev/null || echo "not_found")
|
|
if [[ "$pg" == "healthy" && "$redis" == "healthy" ]]; then
|
|
echo " ready"
|
|
exit 0
|
|
fi
|
|
echo -n "."
|
|
sleep 3
|
|
done
|
|
echo " timed out"
|
|
exit 1
|
|
|
|
# Apply database migrations and seed the local dev community if the dev database is running
|
|
_ensure-migrations: _ensure-services
|
|
cargo run -p buzz-admin -- migrate
|
|
./scripts/seed-local-community.sh
|
|
|
|
# Run clippy on the desktop Tauri Rust crate
|
|
desktop-tauri-clippy: _ensure-sidecar-stubs
|
|
cargo clippy --manifest-path {{desktop_tauri_manifest}} --all-targets -- -D warnings
|
|
|
|
# Check the desktop Tauri Rust crate compiles
|
|
desktop-tauri-check: _ensure-sidecar-stubs
|
|
cargo check --manifest-path {{desktop_tauri_manifest}}
|
|
|
|
# Run desktop Tauri Rust unit tests
|
|
desktop-tauri-test: _ensure-sidecar-stubs
|
|
cd desktop/src-tauri && cargo test
|
|
|
|
# Verify compiled-flag behavior under both compile states (clean + internal).
|
|
# Runs the observer_archive focused test twice with independently supplied
|
|
# expected values; build.rs rerun-if-env-changed triggers recompilation.
|
|
desktop-tauri-test-compiled-flags: _ensure-sidecar-stubs
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
cd desktop/src-tauri
|
|
echo "=== Clean build (no flag) → expect false ==="
|
|
env -u BUZZ_BUILD_OBSERVER_ARCHIVE_DEFAULT \
|
|
-u BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY \
|
|
BUZZ_TEST_EXPECTED_OBSERVER_ARCHIVE_DEFAULT=false \
|
|
cargo test observer_archive_default_enabled_matches_expected -- --ignored --nocapture
|
|
env -u BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY \
|
|
BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=false \
|
|
cargo test compiled_flag_matches_expected -- --ignored --nocapture
|
|
echo "=== Internal build (flags set) → expect true ==="
|
|
BUZZ_BUILD_OBSERVER_ARCHIVE_DEFAULT=1 \
|
|
BUZZ_TEST_EXPECTED_OBSERVER_ARCHIVE_DEFAULT=true \
|
|
cargo test observer_archive_default_enabled_matches_expected -- --ignored --nocapture
|
|
BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY=1 \
|
|
BUZZ_TEST_EXPECTED_AUTO_CONNECT_DEFAULT_RELAY=true \
|
|
cargo test compiled_flag_matches_expected -- --ignored --nocapture
|
|
echo "Both compiled states verified."
|
|
|
|
# Build the full desktop Tauri app locally (unsigned, for testing)
|
|
# Sidecar binary list must stay in sync with _ensure-sidecar-stubs above.
|
|
# pnpm install is unconditional here: release builds must start from a clean dep tree.
|
|
desktop-release-build target="aarch64-apple-darwin":
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
TARGET={{target}}
|
|
mkdir -p desktop/src-tauri/binaries
|
|
touch "desktop/src-tauri/binaries/buzz-acp-$TARGET"
|
|
touch "desktop/src-tauri/binaries/buzz-agent-$TARGET"
|
|
if [[ "$TARGET" != *windows* ]]; then
|
|
touch "desktop/src-tauri/binaries/buzz-backend-kubernetes-$TARGET"
|
|
fi
|
|
touch "desktop/src-tauri/binaries/buzz-dev-mcp-$TARGET"
|
|
touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET"
|
|
touch "desktop/src-tauri/binaries/buzz-$TARGET"
|
|
pnpm install
|
|
cd {{desktop_dir}} && pnpm tauri build --features mesh-llm --target {{target}}
|
|
|
|
# Run desktop checks suitable for CI / pre-push
|
|
desktop-ci: desktop-check desktop-test desktop-tauri-fmt-check desktop-build desktop-tauri-check desktop-tauri-test
|
|
|
|
# Seed deterministic channel data for desktop Playwright tests
|
|
desktop-e2e-seed: _ensure-migrations
|
|
./scripts/setup-desktop-test-data.sh
|
|
|
|
# Run desktop browser smoke tests
|
|
desktop-e2e-smoke:
|
|
cd {{desktop_dir}} && pnpm test:e2e:smoke
|
|
|
|
# Run desktop relay-backed e2e tests
|
|
desktop-e2e-integration: _ensure-migrations
|
|
cd {{desktop_dir}} && pnpm test:e2e:integration
|
|
|
|
# Run only the e2e specs changed vs origin/main (both projects) before pushing
|
|
desktop-e2e-pre-push: _ensure-migrations
|
|
git fetch origin main
|
|
cd {{desktop_dir}} && pnpm build:e2e && pnpm exec playwright test --only-changed=origin/main
|
|
|
|
# Run all checks suitable for CI / pre-push (no infra needed)
|
|
ci: check test-unit desktop-test desktop-build desktop-tauri-check desktop-tauri-test web-build mobile-test
|
|
|
|
# ─── Test ─────────────────────────────────────────────────────────────────────
|
|
|
|
# Run all tests (unit + integration)
|
|
test:
|
|
./scripts/run-tests.sh all
|
|
|
|
# Run unit tests only (no infra needed)
|
|
test-unit:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
if command -v cargo-nextest &>/dev/null; then
|
|
cargo nextest run -p buzz-core -p buzz-auth --lib
|
|
cargo nextest run -p buzz-voice --lib
|
|
cargo nextest run -p buzz-cli
|
|
# buzz-db migrator/lint tests: pure SQL-parsing unit tests (no infra).
|
|
# They guard the embedded-migrator invariant (exactly the consolidated
|
|
# 0001; cutover/backfill stays an operator script, not startup state)
|
|
# and the tenant-scoping lints. The Postgres-backed buzz-db tests are
|
|
# #[ignore]d, so --lib runs only the infra-free set. Without this gate a
|
|
# stray file in migrations/ or a broken lint ships green.
|
|
cargo nextest run -p buzz-db --lib
|
|
# Multi-tenant conformance gate (buzz-conformance): the independent
|
|
# replay checker + golden fixtures. No infra — pure in-process trace
|
|
# replay — so it belongs in the unit job. Run all targets (lib + the
|
|
# tests/replay_fixtures.rs integration test), not just --lib.
|
|
cargo nextest run -p buzz-conformance
|
|
# Gateway unit and black-box HTTP tests are infra-free. Postgres-backed
|
|
# contract/race tests run in the dedicated CI job below.
|
|
cargo nextest run -p buzz-push-gateway
|
|
# Kubernetes backend provider: the decision layers (state machine, GC
|
|
# planner, env precedence, naming, wire) are pure functions with a fake
|
|
# substrate, so they belong in the unit job. Enumerated explicitly
|
|
# because nothing in CI runs `cargo test --workspace` — workspace
|
|
# membership alone buys clippy/check, not a single executed test.
|
|
cargo nextest run -p buzz-backend-kubernetes
|
|
else
|
|
./scripts/run-tests.sh unit
|
|
fi
|
|
|
|
# Run integration tests only (starts services if needed)
|
|
test-integration:
|
|
./scripts/run-tests.sh integration
|
|
|
|
# Buzz shared compute e2e: current desktop discovery/admission logic and
|
|
# Playwright UI coverage.
|
|
mesh-e2e:
|
|
cargo test --manifest-path {{desktop_dir}}/src-tauri/Cargo.toml --features mesh-llm mesh_llm --lib
|
|
cd {{desktop_dir}} && pnpm test:e2e:smoke -- mesh-compute.spec.ts
|
|
|
|
# Reset only development state, seed deterministic local channels, and launch
|
|
# the mesh-enabled desktop with the repository's public Tyler test identity.
|
|
# This is for local verification only; never point this identity at staging/prod.
|
|
[confirm("This will reset development data, preserve installed Buzz, then launch a seeded mesh dev app. Continue? (y/N)")]
|
|
mesh-dev-fresh:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
./scripts/dev-reset.sh --yes
|
|
./scripts/setup-desktop-test-data.sh
|
|
export BUZZ_PRIVATE_KEY="3dbaebadb5dfd777ff25149ee230d907a15a9e1294b40b830661e65bb42f6c03"
|
|
export BUZZ_REQUIRE_RELAY_MEMBERSHIP=true
|
|
export BUZZ_ALLOW_NIP_OA_AUTH=true
|
|
export RELAY_OWNER_PUBKEY="e5ebc6cdb579be112e336cc319b5989b4bb6af11786ea90dbe52b5f08d741b34"
|
|
export BUZZ_RELAY_PRIVATE_KEY="0000000000000000000000000000000000000000000000000000000000000001"
|
|
export BUZZ_RECONCILE_CHANNELS=true
|
|
export BUZZ_RESET_WEBVIEW_STATE=1
|
|
exec just mesh=1 dev
|
|
|
|
# Real serve->client->inference on this machine (not CI).
|
|
mesh-e2e-hardware:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
|
cargo run -p buzz-relay --example mesh_serve_client_smoke
|
|
|
|
# Three isolated node processes: trusted member joins and infers; stranger is rejected.
|
|
# Uses temp homes and explicit mesh owner keystores. Never reads the Buzz Keychain.
|
|
mesh-e2e-admission:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
|
cargo run -p buzz-relay --example mesh_admission_smoke
|
|
|
|
# Full hardware confidence suite: routing, owner admission, and real agent inference.
|
|
mesh-e2e-confidence:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
|
cargo build --release -p buzz-agent -p buzz-dev-mcp
|
|
cargo run -p buzz-relay --example mesh_serve_client_smoke
|
|
cargo run -p buzz-relay --example mesh_admission_smoke
|
|
cargo run -p buzz-relay --example mesh_agent_e2e
|
|
|
|
# Take desktop screenshots using the mock bridge
|
|
desktop-screenshot *ARGS:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
pnpm -C {{desktop_dir}} build:e2e
|
|
cd {{desktop_dir}}
|
|
if ! curl -sf http://127.0.0.1:4173/ >/dev/null 2>&1; then
|
|
python3 -m http.server 4173 -d dist >/dev/null 2>&1 &
|
|
trap "kill $! 2>/dev/null || true" EXIT
|
|
for i in $(seq 1 20); do curl -sf http://127.0.0.1:4173/ >/dev/null && break; sleep 0.5; done
|
|
fi
|
|
node tests/helpers/screenshot.mjs {{ARGS}}
|
|
|
|
# ─── Run ──────────────────────────────────────────────────────────────────────
|
|
|
|
# Start the relay server (auto-starts Docker services if needed)
|
|
relay: bootstrap _ensure-migrations
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
cargo run -p buzz-relay
|
|
|
|
# Start the relay with the built web UI served from it
|
|
relay-web: bootstrap _ensure-migrations
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
[[ -d node_modules ]] || pnpm install
|
|
pnpm -C web build
|
|
BUZZ_WEB_DIR=./web/dist cargo run -p buzz-relay
|
|
|
|
# Build and run the private read-only admin dashboard
|
|
admin: bootstrap _ensure-migrations
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
[[ -d node_modules ]] || pnpm install
|
|
pnpm -C admin-web build
|
|
export BUZZ_ADMIN_HOST="${BUZZ_ADMIN_HOST:-admin.localhost:3000}"
|
|
export BUZZ_ADMIN_WEB_DIR="${BUZZ_ADMIN_WEB_DIR:-{{justfile_directory()}}/admin-web/dist}"
|
|
echo "Admin dashboard: http://${BUZZ_ADMIN_HOST}/reports"
|
|
cargo run -p buzz-relay
|
|
|
|
# Seed deterministic reports and product feedback for local admin dashboard review
|
|
admin-seed: _ensure-migrations
|
|
./scripts/seed-admin-dashboard.sh
|
|
|
|
# Run focused relay and browser checks for the read-only admin dashboard
|
|
admin-check: fmt-check
|
|
cargo check -p buzz-relay --all-targets
|
|
cargo test -p buzz-relay api::admin
|
|
cargo test -p buzz-relay router::tests
|
|
pnpm -C admin-web check
|
|
pnpm -C admin-web exec playwright test
|
|
|
|
# Start the relay server in release mode
|
|
relay-release: _ensure-migrations
|
|
cargo run -p buzz-relay --release
|
|
|
|
|
|
# Run the desktop Tauri app in dev mode with a local relay (ports and identity derived from worktree)
|
|
dev *ARGS: bootstrap _ensure-sidecar-stubs _ensure-migrations
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
bind_addr="${BUZZ_BIND_ADDR:-0.0.0.0:3000}"
|
|
relay_port="${bind_addr##*:}"; [[ -n "$relay_port" ]] || relay_port=3000
|
|
health_port="${BUZZ_HEALTH_PORT:-8080}"
|
|
metrics_port="${BUZZ_METRICS_PORT:-9102}"
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
for spec in "relay:$relay_port" "health:$health_port" "metrics:$metrics_port"; do
|
|
name="${spec%%:*}"; port="${spec##*:}"
|
|
if lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
|
|
echo "Error: $name port $port is already in use; refusing to launch desktop against a stale relay." >&2
|
|
lsof -nP -iTCP:"$port" -sTCP:LISTEN >&2 || true
|
|
echo "Stop the process above (often a stale buzz-relay) and rerun: just dev" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
cargo build -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr -p buzz-relay
|
|
if [[ -n "{{mesh}}" ]]; then
|
|
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
|
fi
|
|
# Docker Desktop's forwarded MinIO port can stall under the deployment
|
|
# probe's 32 concurrent writers. Keep the gate enabled in local dev, using
|
|
# the bounded profile already used by the relay test launcher.
|
|
export BUZZ_GIT_PROBE_WRITERS="${BUZZ_GIT_PROBE_WRITERS:-8}"
|
|
export BUZZ_GIT_PROBE_ROUNDS="${BUZZ_GIT_PROBE_ROUNDS:-2}"
|
|
./target/debug/buzz-relay &
|
|
RELAY_PID=$!
|
|
cleanup() {
|
|
[[ -n "${INSTANCE_ID:-}" ]] && ../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true
|
|
kill "$RELAY_PID" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
relay_ready=false
|
|
for _ in $(seq 1 120); do
|
|
if ! kill -0 "$RELAY_PID" 2>/dev/null; then
|
|
echo "Error: buzz-relay exited during startup; refusing to launch desktop." >&2
|
|
wait "$RELAY_PID" || true
|
|
exit 1
|
|
fi
|
|
if curl --silent --fail --max-time 1 "http://127.0.0.1:${health_port}/_readiness" >/dev/null; then
|
|
relay_ready=true
|
|
break
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
if [[ "$relay_ready" != true ]]; then
|
|
echo "Error: buzz-relay did not become healthy within 60 seconds; refusing to launch desktop." >&2
|
|
exit 1
|
|
fi
|
|
cd {{desktop_dir}}
|
|
[[ -d node_modules ]] || pnpm install
|
|
source ../scripts/instance-env.sh
|
|
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.BUZZ_TAURI_CONFIG).identifier)")
|
|
echo "Starting on Vite port ${BUZZ_VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
FEATURES=(); [[ -n "{{mesh}}" ]] && FEATURES=(--features mesh-llm)
|
|
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
|
|
|
# Run only the desktop app. No relay, database, Docker, migrations, or .env are needed.
|
|
# The app opens normally and asks for a community before making a relay connection.
|
|
desktop-standalone *ARGS: _ensure-sidecar-stubs
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
cargo build -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
|
|
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
|
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
|
for bin in buzz-acp buzz-agent buzz-backend-kubernetes buzz-dev-mcp git-credential-nostr buzz; do
|
|
cp "${TARGET_DIR}/debug/${bin}" "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
chmod +x "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
|
done
|
|
cd {{desktop_dir}}
|
|
[[ -d node_modules ]] || pnpm install
|
|
unset BUZZ_PRIVATE_KEY BUZZ_SHARE_IDENTITY
|
|
if [[ -n "{{fresh}}" ]]; then
|
|
export BUZZ_RESET_WEBVIEW_STATE=1
|
|
fi
|
|
source ../scripts/instance-env.sh
|
|
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.BUZZ_TAURI_CONFIG).identifier)")
|
|
export BUZZ_DEV_KEYRING_SERVICE="buzz-desktop-dev.${BUZZ_INSTANCE_SLUG:-main}"
|
|
if [[ -n "{{fresh}}" ]]; then
|
|
../scripts/reset-desktop-standalone-state.sh "$INSTANCE_ID" "$BUZZ_DEV_KEYRING_SERVICE"
|
|
fi
|
|
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
|
echo "Starting standalone desktop on Vite port ${BUZZ_VITE_PORT}; no relay services were started"
|
|
pnpm exec tauri dev --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
|
|
|
# Run the desktop app against the internal staging relay (installs deps + builds agent tools automatically)
|
|
staging *ARGS: bootstrap _ensure-sidecar-stubs
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
pnpm install # unconditional: staging must always start with a clean dep tree
|
|
cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
|
|
FEATURES=()
|
|
if [[ -n "{{mesh}}" ]]; then
|
|
FEATURES=(--features mesh-llm)
|
|
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
|
fi
|
|
# Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up.
|
|
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
|
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
|
cp "${TARGET_DIR}/release/buzz" "desktop/src-tauri/binaries/buzz-${TARGET}"
|
|
chmod +x "desktop/src-tauri/binaries/buzz-${TARGET}"
|
|
cd {{desktop_dir}}
|
|
export BUZZ_RELAY_URL="wss://sprout-oss.stage.blox.sqprod.co"
|
|
source ../scripts/instance-env.sh
|
|
# Ctrl+C kills the Tauri app before its in-process sweep finishes, leaking
|
|
# agent workers. Reap this instance's agents on exit as a backstop.
|
|
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.BUZZ_TAURI_CONFIG).identifier)")
|
|
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
|
echo "Starting staging on Vite port ${BUZZ_VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
|
|
|
# Run the desktop app against the production relay (installs deps + builds agent tools automatically)
|
|
production *ARGS: bootstrap _ensure-sidecar-stubs
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
pnpm install # unconditional: production must always start with a clean dep tree
|
|
cargo build --release -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr
|
|
FEATURES=()
|
|
if [[ -n "{{mesh}}" ]]; then
|
|
FEATURES=(--features mesh-llm)
|
|
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
|
fi
|
|
# Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up.
|
|
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
|
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
|
cp "${TARGET_DIR}/release/buzz" "desktop/src-tauri/binaries/buzz-${TARGET}"
|
|
chmod +x "desktop/src-tauri/binaries/buzz-${TARGET}"
|
|
cd {{desktop_dir}}
|
|
export BUZZ_RELAY_URL="wss://buzz.block.builderlab.xyz"
|
|
source ../scripts/instance-env.sh
|
|
# Ctrl+C kills the Tauri app before its in-process sweep finishes, leaking
|
|
# agent workers. Reap this instance's agents on exit as a backstop.
|
|
INSTANCE_ID=$(node -e "console.log(JSON.parse(process.env.BUZZ_TAURI_CONFIG).identifier)")
|
|
trap '../scripts/cleanup-instance-agents.sh "$INSTANCE_ID" || true' EXIT
|
|
echo "Starting production on Vite port ${BUZZ_VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
pnpm exec tauri dev ${FEATURES[@]+"${FEATURES[@]}"} --config "$BUZZ_TAURI_CONFIG" {{ARGS}}
|
|
|
|
# Run the desktop frontend dev server (port derived from worktree)
|
|
desktop-dev:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
cd {{desktop_dir}}
|
|
[[ -d node_modules ]] || pnpm install
|
|
source ../scripts/instance-env.sh
|
|
echo "Starting frontend dev server on Vite port ${BUZZ_VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
pnpm exec vite --port "${BUZZ_VITE_PORT}" --strictPort
|
|
|
|
# ─── Web ─────────────────────────────────────────────────────────────────────
|
|
|
|
# Run the web frontend dev server (port derived from worktree to avoid collisions)
|
|
web:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
[[ -d node_modules ]] || pnpm install
|
|
source scripts/instance-env.sh
|
|
export VITE_PORT=$((BUZZ_VITE_PORT + 100))
|
|
export VITE_RELAY_URL="${BUZZ_RELAY_URL}"
|
|
echo "Starting web dev server on port ${VITE_PORT}, relay ${BUZZ_RELAY_URL}"
|
|
cd {{web_dir}}
|
|
pnpm exec vite --port "${VITE_PORT}" --strictPort
|
|
|
|
# Run web lint and format checks
|
|
web-check:
|
|
cd {{web_dir}} && pnpm check
|
|
|
|
# Fix web lint and format issues
|
|
web-fix:
|
|
cd {{web_dir}} && pnpm exec biome check --write . && pnpm check:file-sizes
|
|
|
|
# Run web TypeScript checks
|
|
web-typecheck:
|
|
cd {{web_dir}} && pnpm typecheck
|
|
|
|
# Build web frontend assets
|
|
web-build:
|
|
cd {{web_dir}} && pnpm build
|
|
|
|
# Run web browser smoke tests
|
|
web-e2e-smoke:
|
|
cd {{web_dir}} && pnpm test:e2e:smoke
|
|
|
|
# ─── Mobile ──────────────────────────────────────────────────────────────────
|
|
|
|
mobile_dir := "mobile"
|
|
|
|
# Install mobile Flutter dependencies
|
|
mobile-install:
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter pub get
|
|
|
|
# Format all Dart code
|
|
mobile-fmt:
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format .
|
|
|
|
# Fix mobile formatting and run analysis
|
|
mobile-fix:
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format . && flutter analyze
|
|
|
|
# Run mobile lint and format checks
|
|
mobile-check:
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format --output=none --set-exit-if-changed . && flutter analyze && node ./scripts/check-file-sizes.mjs
|
|
|
|
# Run mobile tests
|
|
mobile-test:
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter test
|
|
|
|
# Regenerate the emoji dataset asset from desktop's emoji-mart install.
|
|
# Output is committed — rerun after bumping @emoji-mart/data.
|
|
mobile-emoji-data:
|
|
node {{mobile_dir}}/scripts/generate-emoji-data.mjs
|
|
|
|
# Compile an unsigned Android debug APK (worktree-aware debug identity)
|
|
mobile-build-android:
|
|
./scripts/mobile-worktree-overrides.sh
|
|
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter build apk --debug --no-pub
|
|
|
|
# Run the mobile app on iOS simulator (worktree-aware debug identity)
|
|
mobile-dev:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
if ! pgrep -x Simulator &>/dev/null; then
|
|
open -a Simulator
|
|
sleep 3
|
|
fi
|
|
./scripts/mobile-worktree-overrides.sh
|
|
cd {{mobile_dir}}
|
|
unset GIT_DIR GIT_WORK_TREE
|
|
flutter run
|
|
|
|
# Uninstall stale worktree-suffixed Buzz debug installs (production apps kept)
|
|
mobile-clean:
|
|
./scripts/mobile-worktree-clean.sh
|
|
|
|
# ─── Database ─────────────────────────────────────────────────────────────────
|
|
|
|
# Apply database migrations
|
|
migrate: _ensure-migrations
|
|
|
|
# ─── Utilities ────────────────────────────────────────────────────────────────
|
|
|
|
# Remove build artifacts
|
|
clean:
|
|
cargo clean
|
|
cargo clean --manifest-path desktop/src-tauri/Cargo.toml
|
|
|
|
# Check the Rust workspace compiles without producing binaries
|
|
check-compile:
|
|
cargo check --workspace --all-targets
|
|
|
|
# ─── Release ─────────────────────────────────────────────────────────────────
|
|
|
|
# Read the current desktop version from package.json
|
|
get-current-version:
|
|
@node -p "require('./desktop/package.json').version"
|
|
|
|
# Read the current relay version from its crate manifest
|
|
get-current-relay-version:
|
|
@grep -m1 '^version = ' crates/buzz-relay/Cargo.toml | sed -E 's/version = "(.*)"/\1/'
|
|
|
|
# Compute next minor version (e.g., 0.3.0 → 0.4.0)
|
|
get-next-minor-version:
|
|
@python3 -c "v='$(just get-current-version)'.split('.'); print(f'{v[0]}.{int(v[1])+1}.0')"
|
|
|
|
# Compute next patch version (e.g., 0.3.0 → 0.3.1)
|
|
get-next-patch-version:
|
|
@python3 -c "v='$(just get-current-version)'.split('.'); print(f'{v[0]}.{v[1]}.{int(v[2])+1}')"
|
|
|
|
# Compute next relay patch version (e.g., 0.3.0 → 0.3.1)
|
|
get-next-relay-patch-version:
|
|
@python3 -c "v='$(just get-current-relay-version)'.split('.'); print(f'{v[0]}.{v[1]}.{int(v[2])+1}')"
|
|
|
|
# Update version in desktop package manifests and regenerate lockfiles
|
|
bump-desktop-version version:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# desktop/package.json
|
|
cd desktop && npm pkg set "version={{ version }}" && cd ..
|
|
# desktop/src-tauri/tauri.conf.json
|
|
node -e "
|
|
const fs = require('fs');
|
|
const p = 'desktop/src-tauri/tauri.conf.json';
|
|
const c = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
c.version = '{{ version }}';
|
|
fs.writeFileSync(p, JSON.stringify(c, null, 2) + '\n');
|
|
"
|
|
# JSON.stringify expands arrays/objects in a way biome rejects; reformat to match.
|
|
(cd desktop && pnpm exec biome format --write src-tauri/tauri.conf.json)
|
|
# desktop/src-tauri/Cargo.toml — only first version line (under [package])
|
|
node -e "
|
|
const fs = require('fs');
|
|
const p = 'desktop/src-tauri/Cargo.toml';
|
|
let t = fs.readFileSync(p, 'utf8');
|
|
t = t.replace(/^version = \".*\"/m, 'version = \"{{ version }}\"');
|
|
fs.writeFileSync(p, t);
|
|
"
|
|
# Regenerate lockfiles
|
|
pnpm install --lockfile-only
|
|
cargo update -p buzz-desktop --manifest-path desktop/src-tauri/Cargo.toml
|
|
echo "Bumped desktop manifests to {{ version }} and regenerated lockfiles"
|
|
|
|
# Bump the relay crate version and regenerate the lockfile
|
|
bump-relay-version version:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# buzz-relay carries its own `version =` (not version.workspace), so the
|
|
# replace targets the package version line only.
|
|
perl -i -pe 's/^version = ".*"/version = "{{ version }}"/' crates/buzz-relay/Cargo.toml
|
|
cargo update -p buzz-relay
|
|
echo "Bumped buzz-relay to {{ version }} and regenerated Cargo.lock"
|
|
|
|
# Open or update the desktop release PR from an immutable origin/main snapshot
|
|
release-desktop *ARGS:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
ARG="{{ ARGS }}"
|
|
if [[ -z "$ARG" || "$ARG" == "patch" ]]; then
|
|
VERSION=$(just get-next-patch-version)
|
|
else
|
|
VERSION="$ARG"
|
|
fi
|
|
scripts/prepare-desktop-release.sh "$VERSION"
|
|
|
|
# Open or update the relay release PR (ghcr.io/block/buzz image)
|
|
release-relay *ARGS:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
ARG="{{ ARGS }}"
|
|
if [[ -z "$ARG" || "$ARG" == "patch" ]]; then
|
|
VERSION=$(just get-next-relay-patch-version)
|
|
else
|
|
VERSION="$ARG"
|
|
fi
|
|
just _release-pr relay "$VERSION"
|
|
|
|
# Shared release-PR engine for desktop and relay. Mobile publishes immutable
|
|
# candidate tags directly from remote main instead of using metadata-only PRs.
|
|
_release-pr lane version:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
VERSION="{{ version }}"
|
|
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
|
|
echo "Error: '$VERSION' is not valid semver (expected X.Y.Z)"
|
|
exit 1
|
|
fi
|
|
# Lane-specific identifiers. The bump command runs after the branch switch.
|
|
case "{{ lane }}" in
|
|
desktop)
|
|
BRANCH_PREFIX="version-bump"
|
|
TAG_FETCH='v*'
|
|
TAG_MATCH='v[0-9]*'
|
|
TAG_EXCLUDE='*-*'
|
|
TAG_PREFIX="v"
|
|
CHANGELOG="CHANGELOG.md"
|
|
ADD_FILES=(desktop/package.json desktop/src-tauri/tauri.conf.json desktop/src-tauri/Cargo.toml desktop/src-tauri/Cargo.lock pnpm-lock.yaml CHANGELOG.md)
|
|
LOG_PATHS=(desktop/ crates/buzz-core/ crates/buzz-persona/ crates/buzz-sdk/ crates/buzz-agent/)
|
|
ARTIFACT="Buzz Desktop" ;;
|
|
relay)
|
|
BRANCH_PREFIX="relay-release"
|
|
TAG_FETCH='relay-v*'
|
|
TAG_MATCH='relay-v[0-9]*'
|
|
TAG_EXCLUDE='relay-v*-*'
|
|
TAG_PREFIX="relay-v"
|
|
CHANGELOG="crates/buzz-relay/CHANGELOG.md"
|
|
ADD_FILES=(crates/buzz-relay/Cargo.toml Cargo.lock crates/buzz-relay/CHANGELOG.md)
|
|
LOG_PATHS=(crates/buzz-relay/ crates/buzz-core/ crates/buzz-db/ crates/buzz-auth/ crates/buzz-pubsub/ crates/buzz-search/ crates/buzz-audit/ crates/buzz-media/ crates/buzz-sdk/ crates/buzz-workflow/ crates/buzz-conformance/ migrations/)
|
|
ARTIFACT="Buzz Relay" ;;
|
|
*)
|
|
echo "Error: unknown release lane '{{ lane }}'"
|
|
exit 1 ;;
|
|
esac
|
|
echo "Preparing ${ARTIFACT} release v${VERSION}..."
|
|
# Must run on main with a clean, up-to-date tree.
|
|
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
|
|
if [[ "$CURRENT_BRANCH" != "main" ]]; then
|
|
echo "Error: must be on main branch (currently on '$CURRENT_BRANCH')"
|
|
exit 1
|
|
fi
|
|
git fetch origin refs/heads/main:refs/remotes/origin/main --no-tags
|
|
# Release tags are remote-owned state; sync only this lane's tags so stale
|
|
# local tags from older histories do not make release preflight fail.
|
|
git fetch origin "+refs/tags/${TAG_FETCH}:refs/tags/${TAG_FETCH}"
|
|
if [[ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]]; then
|
|
echo "Error: local main is not up-to-date with origin/main. Run 'git pull' first."
|
|
exit 1
|
|
fi
|
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
echo "Error: working tree is dirty. Commit or stash changes first."
|
|
exit 1
|
|
fi
|
|
# Switch to the release branch (create, or reset to main if it exists).
|
|
BRANCH="${BRANCH_PREFIX}/${VERSION}"
|
|
if git rev-parse --verify "refs/heads/$BRANCH" >/dev/null 2>&1; then
|
|
echo "Branch '$BRANCH' already exists — resetting to origin/main..."
|
|
git switch "$BRANCH"
|
|
git reset --hard origin/main
|
|
elif git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
|
|
echo "Branch '$BRANCH' exists on remote — checking out and resetting to origin/main..."
|
|
git switch -c "$BRANCH" --track "origin/$BRANCH"
|
|
git reset --hard origin/main
|
|
else
|
|
git switch -c "$BRANCH"
|
|
fi
|
|
# Lane-specific bump (the one diverging step).
|
|
case "{{ lane }}" in
|
|
desktop) just bump-desktop-version "$VERSION" ;;
|
|
relay) just bump-relay-version "$VERSION" ;;
|
|
esac
|
|
# Generate the changelog from commits since this lane's last release tag.
|
|
LAST_TAG=$(git describe --tags --abbrev=0 --match "$TAG_MATCH" --exclude "$TAG_EXCLUDE" 2>/dev/null || echo "")
|
|
REPO=$(git remote get-url origin | sed -E 's|.*github\.com[:/]||; s|\.git$||')
|
|
format_log() {
|
|
local range="$1"
|
|
git log "$range" --format="%h %H %s" --no-merges -- "${LOG_PATHS[@]}" | while IFS=' ' read -r short full rest; do
|
|
local pr subject
|
|
pr=$(printf '%s' "$rest" | grep -oE '\(#[0-9]+\)$' | grep -oE '[0-9]+' || true)
|
|
if [[ -n "$pr" ]]; then
|
|
subject=$(printf '%s' "$rest" | sed -E 's/ \(#[0-9]+\)$//')
|
|
printf -- '- %s ([#%s](https://github.com/%s/pull/%s)) ([`%s`](https://github.com/%s/commit/%s))\n' \
|
|
"$subject" "$pr" "$REPO" "$pr" "$short" "$REPO" "$full"
|
|
else
|
|
printf -- '- %s ([`%s`](https://github.com/%s/commit/%s))\n' \
|
|
"$rest" "$short" "$REPO" "$full"
|
|
fi
|
|
done
|
|
}
|
|
TMPFILE=$(mktemp)
|
|
{
|
|
echo "# Changelog"
|
|
echo ""
|
|
echo "## ${TAG_PREFIX}${VERSION}"
|
|
echo ""
|
|
if [[ -n "$LAST_TAG" ]]; then
|
|
format_log "${LAST_TAG}..HEAD"
|
|
else
|
|
echo "- Initial release"
|
|
fi
|
|
echo ""
|
|
if [[ -f "$CHANGELOG" ]]; then
|
|
tail -n +2 "$CHANGELOG"
|
|
fi
|
|
} > "$TMPFILE"
|
|
mkdir -p "$(dirname "$CHANGELOG")"
|
|
mv "$TMPFILE" "$CHANGELOG"
|
|
# Commit.
|
|
git add "${ADD_FILES[@]}"
|
|
RELEASE_MSG="chore(release): release ${ARTIFACT} version ${VERSION}"
|
|
if [[ "$(git log -1 --format='%s' 2>/dev/null)" == "$RELEASE_MSG" ]]; then
|
|
git commit --amend --no-edit
|
|
else
|
|
git commit -m "$RELEASE_MSG"
|
|
fi
|
|
# Push and open/update the PR.
|
|
git push --force-with-lease -u origin "$BRANCH"
|
|
PR_BODY="## ${ARTIFACT} release v${VERSION}"$'\n\n'
|
|
if [[ -n "$LAST_TAG" ]]; then
|
|
PR_BODY+="### Changes since ${LAST_TAG}:"$'\n\n'
|
|
CHANGELOG_BODY=$(format_log "${LAST_TAG}..HEAD~1")
|
|
MAX_LOG=62000
|
|
if (( ${#CHANGELOG_BODY} > MAX_LOG )); then
|
|
TRUNCATED=$(printf '%s' "$CHANGELOG_BODY" | awk -v max="$MAX_LOG" \
|
|
'BEGIN{n=0} {line_len=length($0)+1; if(n+line_len>max) exit; n+=line_len; print}')
|
|
SHOWN=$(printf '%s\n' "$TRUNCATED" | grep -c '^-' || true)
|
|
TOTAL=$(printf '%s\n' "$CHANGELOG_BODY" | grep -c '^-' || true)
|
|
SKIPPED=$(( TOTAL - SHOWN ))
|
|
CHANGELOG_BODY="${TRUNCATED}"$'\n'"_… and ${SKIPPED} more commits — [compare ${LAST_TAG}…${TAG_PREFIX}${VERSION}](https://github.com/${REPO}/compare/${LAST_TAG}...${TAG_PREFIX}${VERSION})_"
|
|
fi
|
|
PR_BODY+="${CHANGELOG_BODY}"$'\n\n'
|
|
else
|
|
PR_BODY+="Initial release."$'\n\n'
|
|
fi
|
|
PR_BODY+="**To release:** merge this PR. The tag and build will happen automatically."
|
|
PR_TITLE="chore(release): release ${ARTIFACT} version ${VERSION}"
|
|
EXISTING_PR=$(gh pr list --head "$BRANCH" --json url --jq '.[0].url' 2>/dev/null || true)
|
|
if [[ -n "$EXISTING_PR" ]]; then
|
|
gh pr edit "$BRANCH" --title "$PR_TITLE" --body "$PR_BODY"
|
|
PR_URL="$EXISTING_PR"
|
|
echo ""
|
|
echo "Updated existing release PR: ${PR_URL}"
|
|
else
|
|
PR_URL=$(gh pr create --title "$PR_TITLE" --body "$PR_BODY")
|
|
echo ""
|
|
echo "Release PR opened: ${PR_URL}"
|
|
fi
|
|
echo "Merge it to trigger the release build."
|
|
|
|
# ─── Agent Harness ────────────────────────────────────────────────────────────
|
|
|
|
# Run a goose agent connected to a Buzz relay (foreground)
|
|
goose relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$BUZZ_PRIVATE_KEY":
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
source ./scripts/_goose-env.sh "{{relay}}" "{{key}}" "{{agents}}" "{{heartbeat}}" "{{prompt}}"
|
|
exec env "${env_args[@]}" ./target/release/buzz-acp
|
|
|
|
# Run a goose agent in the background (screen session named 'goose-agent-N')
|
|
goose-bg relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$BUZZ_PRIVATE_KEY":
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
source ./scripts/_goose-env.sh "{{relay}}" "{{key}}" "{{agents}}" "{{heartbeat}}" "{{prompt}}"
|
|
screen -dmS goose-agent-{{agents}} bash -c "$(printf '%q ' env "${env_args[@]}") ./target/release/buzz-acp"
|
|
echo "Agent running in screen session 'goose-agent-{{agents}}'. Attach with: screen -r goose-agent-{{agents}}"
|
|
|
|
# ─── Benchmarking ─────────────────────────────────────────────────────────────
|
|
|
|
# Run the Buzz orchestra benchmark — leaderboard-eligible by default (TB 2.1, k=5, Sonnet+Haiku). Stands up its own Docker stack; --gui opens a live spectator desktop app; other flags pass to benchmark.py (--dataset/--path, --include-task, --attempts, --manifest, --dry-run, ...)
|
|
benchmark *ARGS:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export PATH="{{justfile_directory()}}/bin:$PATH"
|
|
uv run --project benchmarks/harbor-buzz-orchestra/testbed \
|
|
benchmarks/harbor-buzz-orchestra/scripts/benchmark.py {{ARGS}}
|
|
|
|
# Stop the benchmark Docker stack (state and channels are kept)
|
|
benchmark-down:
|
|
docker compose --project-name buzz-benchmark down
|