## What
Adds `pr-simplify`, a Fabro workflow that runs a "simplify" code-review
pass over an existing PR and updates that same PR in place.
## How it works
- **One agent, three parallel reviews.** A single agent node runs the
pass and uses `spawn_agent` to fan out three reviewers — code reuse,
code quality, and efficiency — concurrently, then aggregates their
findings. Sub-agent results return directly to the orchestrator, which
is the clean way to aggregate multiple perspectives. (A fork +
`tripleoctagon` fan-in was the wrong primitive here: fan-in selects a
single "best" branch and merges only its worktree, so it would silently
drop two of the three reviews.)
- **Updates the existing PR — no new PR.** The agent runs `gh pr
checkout` on the PR's branch, applies the fixes, commits, and pushes —
landing one fixup commit on the existing PR, plus a summary comment and
a `simplify:<model>` label. `[run.pull_request] enabled = false` keeps
Fabro from opening a second PR from its run branch.
- **Fable by default, overridable.** The graph sets
`default_model=claude-fable-5`, which floors the orchestrator and all
three reviewers to Fable. `--model <id>` wins over it per run
(`configured model → graph default_model → catalog default`), and the
label reflects whatever actually ran.
## Usage
```bash
fabro run pr-simplify -I pr=<number> # Fable (default)
fabro run pr-simplify -I pr=<number> --model gpt-55 # override the model
```
Requires GitHub token permissions `contents` / `pull_requests` /
`issues` = write (declared in the workflow) so it can push the commit,
comment, and label.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Bryan Helmkamp <19+brynary@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Bryan Helmkamp <19+brynary@users.noreply.github.com>
Adds a `patch-cves` workflow that triages GitHub Dependabot alerts and
opens verified dependency-patch PRs, one per alert group. Intended to be
driven by a scheduled automation targeting this repo.
## What's included
- **`.fabro/workflows/patch-cves/workflow.fabro`** — single agent stage
pinned to `claude-opus-4-8`.
- **`.fabro/workflows/patch-cves/prompts/patch-cves.md`** — the bundled
prompt with the full CVE-patching procedure: query Dependabot alerts,
rank and group them, choose the smallest safe fix, patch + regenerate
lockfiles, verify (local gates + GitHub checks), and re-query alerts.
Ecosystem rules cover Rust/Cargo and TypeScript/Bun (Bun only — never
npm/npx/yarn/pnpm). Treats all advisory/package/log text as untrusted
data.
- **`.fabro/workflows/patch-cves/workflow.toml`** — requests the GitHub
App installation-token permissions the run needs:
`vulnerability_alerts=read`, `contents=write`, `pull_requests=write`,
`checks=read`. Sets `run.pull_request.enabled = false` so fabro's
run-branch finalization PR doesn't race the per-group PRs the agent
opens directly via `gh`.
## Design
The instructions ship as a bundled prompt file
(`@prompts/patch-cves.md`) that travels in the run manifest, so the
workflow is fully self-contained — no external skill or runtime
discovery involved.
Validated with `fabro validate patch-cves` (OK).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Adds the `rust-style-guide` Agent Skill under
`.fabro/skills/rust-style-guide/` so Fabro workflow agent stages can
apply the project's Rust conventions when writing or reviewing Rust
code.
Skills are discovered by convention from
`{git_root}/.fabro/skills/*/SKILL.md` at agent-session startup — there's
no manifest wiring or per-workflow declaration. Once present, every
agent stage lists the skill in its system prompt and registers the
`use_skill` tool, so an agent can load it (or a node prompt can
reference `/rust-style-guide`). Committing it here (rather than relying
on a local `~/.fabro/skills` copy) is what makes it available to
**remote, clone-based runs** (Docker/Daytona), which only see committed
+ pushed files.
## Contents (44 files)
- `SKILL.md` — entry point (with a short note pointing the agent at the
in-repo location of the supporting files, since Fabro hands the agent
the `SKILL.md` body and it reads the rest itself)
- `guidelines.md` + `guidelines/` — 38 Rust style policy pages
- `workflows/` — 4 procedure pages (new project, library release,
performance investigation, code review/refactor)
## Source / attribution
Vendored from https://github.com/brynary/rust-style-guide (commit
`8fd2a4f`), trimmed to the runtime skill payload; the upstream repo's
mdBook site and authoring scaffolding are omitted. Note: the upstream
repo has **no LICENSE file** — flagging for a call on
attribution/licensing before merge.
## Notes
- No behavior/code change — this is skill content only; nothing is
compiled or bundled.
- No `{{user_input}}` placeholder was added; agents reference the skill
in prose or via `use_skill`. (If we later want deterministic
`/rust-style-guide <task>` slash expansion in node prompts, add the
placeholder to `SKILL.md` then.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Runs were falling back to the built-in `default` environment (a bare
daytona-medium snapshot with no Rust toolchain), so every Rust stage in
the smoke workflow failed with exit 127 (cargo/rustc not found). The old
[run.sandbox] config that built a custom snapshot was dropped in the
move to named environments (#360) and never ported.
Add a `fabro-dev` named environment that builds the Daytona snapshot from
.fabro/Dockerfile (Rust + nightly-2026-04-14 + cargo-nextest + bun), with
8 CPU / 16GB RAM, and select it via [run.environment].
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Secures Daytona custom snapshot creation by removing user-controlled
snapshot/image references and replacing them with deterministic names
Fabro computes internally. Docker image selection now uses
`image.docker`, while Daytona only accepts `image.dockerfile` for custom
snapshots and continues to use `daytona-medium` when no Dockerfile is
configured.
## Changes
- Replaces public `image.ref` config/API shape with Docker-specific
`image.docker` across Rust settings, OpenAPI, generated TypeScript
client, docs, defaults, examples, and web samples.
- Adds Daytona snapshot identity generation using HMAC-SHA256 over a
canonical manifest keyed by the Daytona API key, producing
`fabro-<uuid>` snapshot names without exposing Dockerfile text or key
material.
- Routes Daytona custom Dockerfiles, including devcontainer-generated
Dockerfiles, through the same computed identity path before calling
Daytona snapshot APIs.
- Updates sandbox initialization events and store projections so
initialized run state can show the resolved image and computed Daytona
snapshot after startup.
- Updates legacy config migration behavior so Docker image refs map to
`image.docker`, while Daytona legacy snapshot names are not preserved.
## Breaking Changes
- `image.ref` is no longer accepted in new environment config.
- Docker environments should use `image.docker` for image selection.
- Daytona environments reject `image.docker`; use `image.dockerfile` to
request a custom computed snapshot.
## Verification
- `cargo build -p fabro-api`
- `cd lib/packages/fabro-api-client && bun run generate`
- `cd lib/packages/fabro-api-client && bun run typecheck`
- `cd apps/fabro-web && bun run typecheck`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `ulimit -n 4096 && cargo nextest run --no-fail-fast -p fabro-cli -p
fabro-config -p fabro-sandbox -p fabro-workflow -p fabro-store -p
fabro-server -p fabro-api`
- `cargo insta pending-snapshots`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
Switches the goal workflow's work and audit nodes from the default
claude-sonnet to gpt-55 with xhigh reasoning, matching the implement
node in implement-plan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Adds a reusable `goal` workflow that runs an immutable user goal through
a Work -> Completion Audit loop. The work prompt keeps the full
objective intact, while the audit prompt uses validated routing JSON to
either exit when the goal is proven complete or loop back with concrete
remaining work.
## Workflow Diagram

## Verification
- `cargo run -q -p fabro-cli -- validate
.fabro/workflows/goal/workflow.fabro`
- `cargo run -q -p fabro-cli -- run goal --goal "Test the reusable goal
workflow" --dry-run`
- `cargo run -q -p fabro-cli -- preflight
.fabro/workflows/goal/workflow.toml --goal "Test the reusable goal
workflow"`
- `xmllint --noout .fabro/workflows/goal/workflow.svg`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
Generated with GPT-5 via Codex
Fixes implement-plan runs failing at verify time when cloned sandboxes
lack Git identity, and prevents the verify forbidden-pattern scan from
being silently skipped when `rg` is unavailable.
## Changes
- Install `ripgrep` in the Fabro Daytona image and bump the snapshot ref
to `fabro-v12` so Daytona rebuilds it.
- Configure repository-local Git `user.name` and `user.email` from
`run.git.author` during workflow initialization before lifecycle setup
commands or workflow stages run.
- Make the implement-plan verify stage fail explicitly if `rg` is
missing.
## Validation
- `cargo test -p fabro-workflow
configure_sandbox_git_identity_uses_run_author --quiet`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-workflow --all-targets --
-D warnings`
- `cargo run -p fabro-cli -- validate
.fabro/workflows/implement-plan/workflow.fabro`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
## What
Adds a **Context** tab to the stage detail view
(`/runs/:id/stages/:stageId`), beside the existing primary tab (Thread /
Logs / …) and Debug tab.
It surfaces a stage's *deliberate per-visit outputs* — the data the
workflow author makes a stage write into shared context, plus the
routing hints it emitted:
- **Routing** — `preferred_label` and `suggested_next_ids`
- **Context writes** — author-set `context_updates` keys
This data flow was previously invisible in the UI, which made it hard to
debug "why did the next stage get the wrong input / take the wrong
edge".
## Why no backend change
The per-visit `stage.completed` event already carries `context_updates`,
`preferred_label`, and `suggested_next_ids`, and the web UI already
fetches it via `useRunStageEvents`. The checkpoint's `node_outcomes` map
was rejected as a source: it is keyed by `node_id` only, so it is lossy
across visits (`implement@2` would overwrite `implement@1`).
## How
- `extractStageContext` (in `stage-renderers/helpers.ts`) reads the
`stage.completed` event and filters `context_updates` through an
engine-key denylist: `last_stage`, `last_response`, `response.*`,
`internal.*`, `current.*`, `command.output`, `human.gate.*`,
`parallel.*`. Those are bookkeeping or already shown in the stage's
primary tab.
- It returns `null` when nothing is left, so the tab stays
**conditional** — same pattern as Thread/Logs. It only appears when a
stage actually wrote something deliberate.
- New `stage-context.tsx` renders the result, reusing `CodeBlock` /
`JsonBlock`.
- `run-stages.tsx` gains a dynamic `availableTabs` list; `effectiveTab`
falls back to `primary` gracefully when the Context tab is absent.
## Verification
- `bun run typecheck` clean, `bun test` — 412 pass / 0 fail (4 new tests
for the denylist + routing extraction).
- Live run `01KS5WBZAE7K8321NHR7KFAHF9` (`context-demo` workflow): the
`emit@1` stage emitted `demo.greeting` / `demo.answer` / `demo.payload`
plus `preferred_label: "Done"` and `suggested_next_ids: ["exit"]`, and
the Context tab rendered them correctly.
## Notes
- The second commit adds a small `context-demo` workflow used for that
verification — kept separate so it can be dropped independently.
- Known limitations (acceptable for v1): data is read from
`stage.completed` only, so a stage ending in `stage.failed` shows no
tab; parallel stages write `parallel.*` directly to context
(denylisted), so they show no tab.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
The run Overview tab's "Created by" cell rendered every user as a
colored circle with the first letter of their login. Reviewers and run
owners expected the same GitHub avatar shown on `/profile` and in the
top-right nav. The cell only had `login` to work with — the
`PrincipalUser` schema carried no avatar URL.
This threads an optional `avatar_url` through `UserPrincipal`
end-to-end: schema, server auth, and frontend. The avatar is captured at
action time from the request's auth context and persisted with the run's
`created_by` principal — a point-in-time snapshot, the same pattern as
audit logs and chat apps.
## What changed
- **`fabro-types`** — `UserPrincipal` gains `avatar_url: Option<String>`
with `#[serde(default, skip_serializing_if)]`, plus a
`Principal::user_with_avatar` constructor. The existing
`Principal::user` constructor is unchanged (sets `None`), so test
fixtures and CLI/replay call sites need no edits.
- **OpenAPI** — `PrincipalUser` gains an optional nullable `avatar_url`;
Rust (progenitor) and TypeScript clients regenerated.
- **`fabro-server`** — `auth_context_from_session` (cookie auth) and
`classify_user_token` (JWT auth) populate the principal's avatar from
the session/JWT, treating an empty string as `None`.
- **`fabro-web`** — the `run-summary-panel` "Created by" cell renders an
`<img>` when `avatar_url` is present, falling back to the initial circle
otherwise.
## Compatibility
The field is optional with serde defaults, so old persisted runs and
`RunEvent.actor` payloads deserialize unchanged — they show the
initial-circle fallback. No migration or backfill.
## Known gap
CLI-initiated runs (`fabro run ...`) still show the initial circle: the
CLI auth flow hardcodes an empty `avatar_url` in the JWT subject
(`cli_flow.rs:508`). Wiring the avatar through CLI login
(`~/.fabro/auth.json`, JWT claims, refresh-token chain) is a deliberate
follow-up. Web-initiated runs get the avatar today.
## Test plan
- `cargo nextest run --workspace` — 5,832 tests pass, including new
`principal.rs` and `principal_round_trip.rs` cases covering avatar
serialization and legacy-JSON (no-field) deserialization.
- `cd apps/fabro-web && bun test run-summary-panel` — 13 tests pass,
including a new case asserting the `<img>` renders with the avatar src.
- `bun run typecheck`, `cargo +nightly-2026-04-14 fmt --check --all`,
and `clippy --workspace --all-targets -- -D warnings` all clean.
- Manual: restart `fabro server`, create a run from the web UI, confirm
the real avatar renders on the Overview tab; confirm an older run falls
back to the initial circle.
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via
[Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Supports `dockerfile = { path = "..." }` for Daytona snapshots declared
in `.fabro/project.toml` and workflow-local `workflow.toml`, resolving
each path relative to the TOML file that declared it. Manifest building
now bundles project-level Dockerfiles into the target workflow file
bundle, and server manifest preparation rewrites bundled Dockerfile
paths to inline content before settings reach sandbox creation.
The repo Daytona snapshot config now uses `.fabro/Dockerfile` instead of
embedding the Dockerfile in TOML, preserving the prior Dockerfile
content exactly.
## Testing
- `cargo nextest run -p fabro-manifest
build_manifest_bundles_project_config_daytona_dockerfile_relative_to_project_config`
- `cargo nextest run -p fabro-manifest`
- `cargo nextest run -p fabro-server
prepare_manifest_inlines_project_config_daytona_dockerfile_from_bundle
prepare_manifest_errors_when_project_config_dockerfile_bundle_is_missing`
- `cargo nextest run -p fabro-server`
- `cargo nextest run -p fabro-config`
- `cargo nextest run -p fabro-manifest -p fabro-server -p fabro-config`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
Optional credentialed Daytona live smoke was not run.
## Post-Deploy Monitoring & Validation
- Log queries/search terms: `missing bundled dockerfile`, `unsupported
dockerfile reference`, `invalid manifest project config path`,
`dockerfile path should have been resolved to inline content before
sandbox creation`, Daytona snapshot creation failures.
- Metrics/dashboards: run submission/preflight failure rate, Daytona
sandbox startup failure rate, snapshot creation failure rate, and run
validation errors for manifests using bundled files.
- Healthy signals: runs using `.fabro/project.toml` with `dockerfile = {
path = "Dockerfile" }` progress past manifest preparation and Daytona
snapshot creation without path-resolution errors.
- Failure signals and rollback trigger: any sustained increase in
manifest preparation failures or Daytona snapshot failures containing
the log terms above; rollback by reverting this PR or temporarily
restoring inline Dockerfile TOML for affected deployments.
- Validation window and owner: first 24 hours after deploy; owner is the
deploying operator/on-call engineer.

## Summary
This PR moves the server-facing model catalog paths onto a resolved
catalog stored in `AppState`, using configured `[llm]` provider/model
overrides layered on top of the built-in catalog.
The server now uses the injected catalog for:
- `/models` listing and model test lookup
- `/completions` default model and provider inference
- manifest preflight materialization and LLM model alias resolution
- diagnostics LLM probes
- pull request default model selection
- runtime settings refresh via `replace_settings`
It also adds catalog overlay helpers in `fabro-model` and converts
resolved server runtime `[llm]` settings into the catalog shape in
`fabro-config`.
This branch also includes the earlier `chore: update dockerfile` commit,
which updates the Daytona snapshot to `fabro-v10` and installs Chromium
through the xtradeb PPA with an XFCE/browser wrapper.
Related: #210
## Tests
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo nextest run -p fabro-config -p fabro-model -p fabro-server`
(844 tests passed)
- `cargo +nightly-2026-04-14 clippy -p fabro-config -p fabro-model -p
fabro-server --all-targets -- -D warnings`
## Summary
Implemented ACP support as a first-class Fabro backend alongside `api`
and `cli`. This adds a new `fabro-acp` crate using the official ACP Rust
crates, routes `backend=\"acp\"` for agent and prompt nodes, adds
sandbox stdio support for local/Docker/test-support paths, emits ACP
workflow events/projections, updates server steerability handling,
validation, documentation, and black-box CLI coverage.
## Test Plan
Passed strict non-live verification:
- `ulimit -n 4096 && cargo nextest run -p fabro-workflow --run-ignored
all --no-fail-fast` — 1162 passed, 0 skipped.
- `ulimit -n 4096 && cargo nextest run -p fabro-acp -p fabro-sandbox -p
fabro-workflow -p fabro-validate -p fabro-store -p fabro-server -p
fabro-cli --run-ignored all --no-fail-fast -E 'not
test(daytona_streaming_live_smoke)'` — 3125 passed.
- `cargo build --workspace` — passed.
- `ulimit -n 4096 && cargo nextest run --workspace --run-ignored all
--no-fail-fast -E 'not test(daytona_streaming_live_smoke)'` — 5666
passed.
- `cargo +nightly-2026-04-14 fmt --check --all` — passed.
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings` — passed.
Live-environment tests skipped/excluded under explicit user override:
- `daytona_streaming_live_smoke` was excluded from final nextest runs
because it requires live Daytona infrastructure and `DAYTONA_API_KEY`.
- Confirmed with `env -u DAYTONA_API_KEY cargo test -p fabro-sandbox
--features daytona --test daytona_streaming_live
daytona_streaming_live::daytona_streaming_live_smoke -- --ignored
--exact --nocapture`: failed fast with `DAYTONA_API_KEY must be set to
run this live smoke test`.
Add a progressive human interview workflow and teach human gates to honor explicit question_type values so the workflow can exercise yes/no, confirmation, multiple-choice, multi-select, and freeform prompts before summarizing the answers.
## Summary
Token scopes describe what *a run* is authorized to do, not server
identity. Today they live under
`[server.integrations.github.permissions]`, which can't be overridden by
`workflow.toml` / `project.toml` (server keys are stripped from
per-workflow layers) — so projects and workflows can't tighten or relax
permissions despite the docs already advertising a per-run config. This
PR moves them under `[run.integrations.github.permissions]`, where the
standard layer-merge (workflow > project > user > defaults) Just Works.
Greenfield, no migration shim.
## What changed
- **New layer/resolved types** in `fabro-config` and `fabro-types`:
`RunIntegrationsLayer`, `RunIntegrationsGithubLayer`, and resolved
counterparts. `permissions` becomes a flat `HashMap<String,
InterpString>` post-resolve; empty = no token requested.
- **Server schema**: `permissions` removed from `GithubIntegrationLayer`
/ `GithubIntegrationSettings`. `deny_unknown_fields` rejects the stale
path.
- **Bundled `workflow.toml` parsing** (`run_manifest.rs`): now goes
through `SettingsLayer` via the new `parse_run_layer_from_settings_toml`
helper, so stale `[server.integrations.github.permissions]` errors
instead of being silently dropped by the old `toml::Table` lift-out.
- **Consumers updated**: server preflight, run launch path, and the CLI
worker (`runner.rs`) all read run-level permissions. CLI worker
previously hardcoded `HashMap::new()` — runs launched via the local CLI
path were getting no `GITHUB_TOKEN` regardless of TOML.
- **Shared helpers** on `RunIntegrationsGithubSettings`:
`is_token_requested()` and `resolve_permissions(lookup)` so server and
CLI don't drift.
- **OpenAPI + TS client** regenerated; new `RunIntegrationsSettings` /
`RunIntegrationsGithubSettings` schemas added, `permissions` removed
from `GithubIntegrationSettings`.
- **Repo workflows + docs** rewritten to the new path. Docs gain a
security-model note (boundary = installation grants; no Fabro-side cap).
## Key design decision: hand-rolled `Combine` for
`RunIntegrationsGithubLayer`
`ReplaceMap`'s "empty inherits from below" semantics (`maps.rs:76-80`)
are wrong here — we want `permissions = {}` in a higher layer to act as
an explicit clear. So the layer field is `Option<HashMap<...>>` with
hand-rolled `Combine`:
| Higher layer | Lower layer | Result |
|---|---|---|
| `None` | anything | lower (inherit) |
| `Some(map)` | anything | `Some(map)` (full replace, including
`Some({})` = clear) |
Not derived: the blanket `Option<T: Combine>` impl would recurse into
the inner `HashMap` and reintroduce empty-fallback. Documented inline in
`layers/run.rs`.
`InterpString` is preserved through resolve and only flattened to
`String` at the start-services boundary, matching the existing pattern.
### Plan Summary
- New `[run.integrations.github.permissions]` layer + resolved types;
remove from server side.
- Hand-rolled `Combine` so empty-wins-as-clear; no change to
`ReplaceMap` semantics for other consumers.
- Strict `SettingsLayer` parse for bundled `workflow.toml` so stale
schema errors loudly.
- Both server and CLI worker paths read run-level permissions via shared
helpers.
- OpenAPI + TS client regenerated; parity test added.
- Repo workflow TOMLs and `integrations/github.mdx` rewritten.
### Fabro Details
<details>
<summary>Ran 0 stages in 61m 23s for $53.41</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| **Total** | **61m 23s** | **$53.41** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (12 nodes and 15
edges)</summary>
```dot
digraph ImplementPlan {
graph [
goal="Implement and simplify",
model_stylesheet="
* { model: claude-opus-4-7; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD."]
simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
simplify_gpt [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
verify [label="Verify", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1 && cargo dev docs refresh 2>&1 && cargo dev docs check 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings, test failures, and generated docs errors.", max_visits=3]
fmt [label="Format", shape=parallelogram, script="cargo +nightly-2026-04-14 fmt --all 2>&1", max_retries=0]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=succeeded"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=succeeded"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=succeeded"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify_opus -> simplify_gpt -> verify
verify -> fmt [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
fmt -> exit
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #202 shipped a new CLI subcommand without regenerating
docs/public/reference/cli.mdx, so the Generated Docs CI job failed on
push. The implement-plan workflow's verify gate had no equivalent of
`cargo dev docs check`.
Append `cargo dev docs refresh && cargo dev docs check` to verify so
the gate auto-fixes drift and surfaces real authoring errors (missing
help text, removed generated-region fences) through the fixup loop.
Also broaden the fixup prompt to cover docs errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The implement-plan and smoke workflows ran clippy without --all-targets,
so test, example, and bench targets were skipped. CI runs clippy with
--all-targets, so lint errors in test code passed the workflow's verify
gate but failed CI on push. Aligns the workflow lint commands with CI
and CLAUDE.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The smoke workflow's Lint Rust stage runs `cargo +nightly-2026-04-14
fmt`/`clippy`. The previous fabro-v7 snapshot only had stable, so rustup
silently synced the nightly channel on every run. Bump to fabro-v8 and
add `rustup toolchain install nightly-2026-04-14` with clippy+rustfmt so
lint starts immediately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The smoke and implement-plan workflows ran cargo clippy without a
toolchain prefix, so on the Daytona snapshot they fell through to the
baked-in stable toolchain. clippy.toml now uses allow-unwrap-types
(added in clippy 1.95), which the stable in fabro-v7 doesn't recognize.
Pin every fmt and clippy invocation to nightly-2026-04-14 so they match
.github/workflows/rust.yml. Also update the public repl-handoff example
to keep the documented template consistent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add [GRAPH_PATH] filter to run_output_filters so dry_run_simple
snapshot is path-independent
- Double fabro-cli slow-timeout (3s → 6s) to prevent ps test timeouts
- Preserve cloud sandboxes; bump snapshot to fabro-v7 with 8 CPU / 16GB
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config discovery walks from the workflow file's parent directory, so
tests using fixtures at their repo path (test/simple.fabro) would find
the repo's .fabro/project.toml. This caused settings like preserve=true
to leak into tests and break sandbox cleanup event assertions.
Add TestContext::install_fixture() which copies fixtures into the test's
temp dir. Update all CLI run/attach/start tests to use it. Remove the
now-unused example_fixture() function.
Restore preserve=true in .fabro/project.toml — tests are now isolated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove EnvGuard and env-mutating test from fabro-auth (shared mutable state)
- Revert project.toml preserve=true that broke sandbox cleanup event tests
- Fix clippy: use is_some_and, scoped imports for StageStatus and render
- Update snapshot tests for new Run: ULID line and model_test output
- Fix preflight test assertion (name said "allows", asserted failure)
- Update cancel_queued_run test: cancelled runs now appear on board
- Add graph direction query param support to get_graph endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep project config and checked-in workflows under .fabro so they stay out of
normal repo listings. Update config discovery, CLI project commands, fixtures,
docs, and checked-in workflow paths to use .fabro/project.toml and
.fabro/workflows/*.