## Summary
Removes Fabro's automatic retro generation stage so workflow runs go
directly from execution to finalization and optional PR creation. This
drops the retro-specific crate, events, projection fields, config/API
knobs, and user-facing docs in favor of the existing durable run
observability surfaces.
## What Changed
- Deleted the `fabro-retro` crate and the workflow `retro` pipeline
phase, with finalization now consuming `Executed` state directly.
- Removed retro configuration and API surface area, including
`--no-retro`, `[run.execution].retros`, manifest `no_retro`,
`features.retros`, and run projection `retro*` fields.
- Retired typed `retro.*` events while keeping historical event logs
readable by deserializing retired retro event names as `Unknown`.
- Stopped appending retro sections to generated PR bodies and updated
docs, marketing copy, screenshots, and navigation to point users toward
observability/event-stream inspection.
## Testing
Not run during PR creation; this branch already contained the
implementation commit.
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (unknown context, reasoning unspecified) via
[Codex](https://openai.com/codex)
Replace 16 inline copies of the `[cli.target] type = "http"` settings TOML across CLI integration tests with a single `set_http_target(&base_url)` method on `TestContext`. Removes a brittle format string that was maintained in ten files but only meaningfully asserted-against in one.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop --allow-no-checkpoints and the paired ManifestArgs in_place /
allow_no_checkpoints fields. The CLI now translates --in-place into a
single ManifestArgs.worktree_mode = "never" signal that flows through
the existing args→layer pipeline as run.sandbox.local.worktree_mode =
Never. The server computes prepared.in_place from the resolved settings
once, replacing the trio of bail!s and the sandbox-default fixup.
Wrap root CLI errors at the main boundary so fatal diagnostics use miette's styled renderer while preserving existing telemetry, exit codes, and auth help hints.
Implements the plan at
docs/plans/2026-04-20-003-refactor-unify-run-vocabulary-metadata-plan.md.
- Rename RunRecord to RunSpec and RunProjection.run to .spec everywhere
in Rust source, tests, helpers, test names, and error messages.
- Introduce SerializableProjection wrapper that trims bulky node text
fields (prompt, response, diff, stdout, stderr) for run.json snapshots.
- Collapse metadata-branch and CLI export to one RunDump::from_projection
builder emitting run.json + graph.fabro + stages/{stage_id}/... and
drop legacy top-level start/status/checkpoint/sandbox/retro/conclusion
split files.
- Replace MetadataStore::write_checkpoint with write_snapshot returning
the commit SHA; add read_run_projection/read_run_spec; demote
read_checkpoint/read_start_record to projection-field extractors.
- Switch fork, rewind, rebuild_meta, CLI rewind recovery, and retro
upload to read the unified projection layout.
- Add additive query methods on RunSpec and RunProjection.
Serde-level `alias = "spec"` shim dropped; `rename = "run"` retained to
keep the server API wire format stable per the plan's scope boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve CLI settings once from user config plus process-local overrides
and pass the resolved view through command dispatch and CommandContext.
This keeps config-driven cli.output, cli.updates, and cli.logging
behavior working while preserving commands that only reject explicit
--json overrides. It also removes the implicit auto-approve coupling
from JSON run output.
Add a shared MiniJinja-based template crate and migrate workflow prompts,
imports, hooks, and InterpString env references to the new {{ ... }}
syntax. This also threads typed run inputs through workflow rendering and
updates docs and tests to match the new templating model.
Resolve every clippy warning across the workspace when running with
--tests enabled. Previously only library code was lint-clean; test
code had accumulated issues that were invisible without --tests.
Fixes:
- redundant_closure_for_method_calls: |s| s.as_source() -> InterpString::as_source
(effective_settings, resolve_cli/root/server/features, run_event/record_serde,
materialize_run) — add InterpString imports where needed
- absolute_paths: inline fabro_types::settings::* paths -> use imports;
add #![allow(clippy::absolute_paths)] to fabro-cli and fabro-server
IT test harnesses (matching the existing pattern in integration.rs)
- bool_assert_comparison: assert_eq!(x, true) -> assert!(x)
- needless_raw_string_hashes: r#"..."# -> r"..." where no inner quotes
- field_reassign_with_default: mut + field assign -> struct literal with ..Default
- match_same_arms: merge Timeout | Disconnected arms in attach.rs
- needless_pass_by_value: signal_rx by ref in attach.rs
- unreadable_literal: 9999999999 -> 9_999_999_999
- default_trait_access: Default::default() -> BTreeMap::default()
- items_after_statements: move use to function top
- large_futures: allow in integration.rs test module (test-only, not prod)
- filter_map_bool_then: .filter_map(bool::then) -> .filter().map()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`--goal-file` was broken in the v2 path: `TryFrom<&RunArgs> for ConfigLayer`
did `let _ = &args.goal_file;`, so clap accepted the flag listed in
`--help` and then silently dropped it. Users running
`fabro run demo --goal-file prompts/goal.md` ended up with no goal at
all (or the DOT graph-level fallback), a regression from the legacy
flat `Settings` shape.
This commit adds first-class support for both inline and file-sourced
goals via a tagged union on `run.goal`. Greenfield decisions:
- **Single field, two variants.** `RunGoalLayer` is an untagged enum
of `Inline(InterpString)` and `File { file: InterpString }`. Makes
`goal XOR goal_file` un-representable in the type system and lets
the v2 merge matrix treat `run.goal` as a single scalar
(last-writer-wins) instead of needing a custom mutual-exclusion
merge rule. Matches the existing `DaytonaDockerfileLayer` pattern.
- **Relative paths are anchored at the file that declared them.**
`ConfigLayer::load(path)` walks the just-parsed `SettingsFile` and
rewrites any literal relative `run.goal.file` path to absolute
using `path.parent()` as the base, via new
`fabro_config::config::resolve_goal_file_paths`. CLI-sourced paths
via `--goal-file` are anchored at CWD in
`overrides::goal_layer_from_args`. Env-interpolated paths
(`${env.GOALS_DIR}/goal.md`) are left unresolved until consume time
and then resolved against the run's working_directory.
- **New accessors, no shims.**
- `run_goal_layer() -> Option<&RunGoalLayer>` — raw variant access.
- `run_goal_inline_str() -> Option<String>` — inline-only, returns
`None` for file-sourced goals.
- `resolve_run_goal(base_dir) -> Result<Option<ResolvedRunGoal>>` —
reads the file from disk if needed, returns text + provenance
(`ResolvedGoalSource::Inline | File { path }`).
- New `ResolveGoalError` enum covers env-lookup and I/O failures.
- Old `run_goal() / run_goal_str()` are **deleted** outright; every
call site has been updated to pick the right variant.
- **CLI wiring (the actual bug fix).** `overrides::goal_layer_from_args`
replaces the two `let _ = &args.goal_file;` lines with real
resolution: `(Some(text), None)` → `Inline`, `(None, Some(path))` →
`File { file: absolute }`. Both-set is rejected by a helper error
and clap already had `conflicts_with = "goal"` as a belt-and-
braces check. Applied to both `RunArgs` and `PreflightArgs`.
- **Manifest builder.** `resolve_manifest_goal` now calls
`args_layer.as_v2().resolve_run_goal()` and
`settings.resolve_run_goal()` in precedence order, then falls
through to the graph-level `@file` sugar if both are absent. The
resolved goal is translated to a `ManifestGoal { text, type_, path }`
by a new `resolved_goal_to_manifest` helper — inline goals get
`type = Value`, file-sourced goals get `type = File` with the
absolute path echoed for provenance.
- **Workflow pipeline.** `fabro-workflow::operations::source::
resolve_goal_override` is rewritten to use `resolve_run_goal`
against the working_directory. The orphaned helper `resolve_goal_file`
(a stub from Stage 4 that was always called with `None`) is
deleted.
- **Server-side manifest.** `fabro-server::run_manifest::
prepare_manifest` stores the CLI-resolved goal as
`RunGoalLayer::Inline`, matching the Stage 4 plan's "CLI owns goal
file reads; server never touches the filesystem for goals"
contract.
## Tests
**Schema** (`fabro-types::settings::accessors`):
- `run_goal_inline_str_returns_source_value` — literal inline variant
- `run_goal_inline_str_is_none_for_file_variant` — file variant
explicitly yields `None` from the inline accessor
- `resolve_run_goal_reads_file_variant_from_disk` — end-to-end file
read with provenance assertion
- `resolve_run_goal_inline_passes_text_through` — inline passthrough
**Config load** (`fabro-config::config`):
- `parse_accepts_inline_goal` + `parse_accepts_file_variant`
- `parse_rejects_goal_with_unknown_sibling_fields` — untagged enum
correctly rejects mixed-shape TOML
- `combine_replaces_file_goal_with_inline_from_higher_layer` and the
reverse — confirms the tagged union merges as a single scalar with
no custom rule needed
- `load_rewrites_relative_goal_file_to_absolute`
- `load_leaves_absolute_goal_file_untouched`
- `load_leaves_env_interpolated_goal_file_untouched`
**CLI overrides** (`fabro-cli::commands::run::overrides`):
- `goal_and_goal_file_together_is_rejected`
- `goal_file_is_anchored_at_cwd_when_relative`
- `absolute_goal_file_is_preserved`
- `inline_goal_builds_inline_variant`
- `empty_args_produce_no_goal_layer`
**CLI integration** (`fabro-cli::tests:🇮🇹:cmd::run`):
- `dry_run_with_goal_file_reads_contents_into_goal` — end-to-end
`fabro run --dry-run --auto-approve --goal-file <path>` and asserts
the file contents appear in the preflight summary. Explicit
regression test for the silently-ignored flag.
- `dry_run_rejects_goal_and_goal_file_together` — clap conflicts_with
## Callsite churn
Every `run_goal() / run_goal_str()` call site updated:
- `fabro-config/src/effective_settings.rs` — 2 test assertions →
`run_goal_inline_str()`
- `fabro-cli/tests/it/cmd/{config,create}.rs` — 3 sites → inline
- `fabro-cli/src/manifest_builder.rs` — rewritten to use
`resolve_run_goal`
- `fabro-workflow/src/operations/create.rs` — 2 sites, test + set
- `fabro-workflow/src/operations/source.rs` — rewritten
- `fabro-server/src/{run_manifest,server}.rs` — set + test assertion
3,782 workspace tests pass (was 3,765, +17 new). `cargo fmt
--check --all` and `cargo clippy --workspace -- -D warnings` are
clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extends the stage 6.1 WIP into a compiling state across the workspace.
Most crates and their unit/integration tests now read run.* / cli.* /
server.* v2 layers directly or through targeted bridge helpers.
Key moves in this commit:
fabro-server
- AppState.settings: Arc<RwLock<SettingsFile>> -- all helpers,
create_app_state_with_* factories, and tests updated.
- api_server_settings bridges SettingsFile -> legacy Settings via the
transitional bridge so /api/v1/settings still emits the legacy DTO
shape until Stage 6.6 replaces it with an allow-list DTO.
- get_system_info, get_system_df, get_github_repo, webhook startup, and
other read sites use the v2 accessors (github_app_id_str,
server_web, run_sandbox, run_model_*).
- web_auth.rs wraps each oauth / register / setup-status handler in a
local `bridged` helper that produces a legacy Settings from the v2
state, so the complex oauth mutation flow keeps working until its
Stage 6.6 rewrite.
- diagnostics::check_github_app reads via github_*_str accessors;
check_crypto bridges to the legacy shape inline.
- serve.rs: load_settings returns SettingsFile; apply_serve_overrides /
apply_runtime_settings mutate v2 subtrees directly; the config poll
loop and TLS/webhook startup use bridged() for legacy-shape reads.
- Tests in tests/it/{helpers,api/*,scenario/*} rewritten to construct
SettingsFile via ConfigLayer::parse or v2 struct literals.
fabro-workflow
- Every test fixture in pipeline/{finalize,initialize,pull_request,retro,
execute,persist}, operations/{create,rebuild_meta,start}, run_lookup,
runtime_store, handler/manager_loop, and tests/it/{integration,
daytona_integration}.rs now uses SettingsFile.
- start.rs hooks into the bridge helpers directly via use-imports.
- run_graph / run_graph_from_checkpoint / initialize / finalize /
pull_request calls are Box::pin'd to stay under clippy's large-future
threshold after the v2 tree brought RunOptions size up.
- resolve_run_settings writes resolved model/provider back into
run.model as InterpStrings; tests assert via run_model_*_str().
- preprocess_and_validate pulls vars from run_inputs_as_strings().
fabro-cli
- manifest_builder uses ConfigLayer.combine(...).into() to get a v2
SettingsFile for the manifest goal resolution path; file-based
goal_file handling is deferred to 6.6 when the manifest schema catches
up.
- runner::maybe_build_github_app_credentials and
tests/it/cmd/{create,runner}.rs read from v2 accessors.
- commands/config/mod.rs::merged_config returns SettingsFile; the
server-side retrieve_server_settings is bridged via a stopgap
legacy_settings_to_v2 shim that Stage 6.6 replaces.
- commands/store/dump.rs sample_run_record constructs SettingsFile.
fabro-store, fabro-checkpoint
- Test fixtures constructing RunRecord values updated to SettingsFile.
- fabro-checkpoint/src/author.rs stays (v2 From impl landed in a
previous additive commit).
fabro-config
- effective_settings.rs rewrite compiles and passes its unit tests.
- project::resolve_working_directory takes &SettingsFile.
Build status: `cargo build --workspace --tests`, `cargo clippy
--workspace -- -D warnings`, and `cargo fmt --check --all` all pass.
`cargo nextest run --workspace` passes 3,749 of 3,764 tests; the 15
remaining failures are fabro-cli integration tests whose snapshot +
TOML fixture shapes still need manual updates:
- cmd::config::* (seven tests): fixture TOML files still use v1
top-level keys and the snapshot outputs expect the legacy flat JSON
shape.
- cmd::inspect::* (four tests): run-record JSON snapshots embed the
flat Settings shape.
- cmd::run::dry_run_persists_event_history_in_store and
json_run_implies_auto_approve_for_human_gates: check `settings.dry_run
== Some(true)` directly on the v2 file; should assert
dry_run_enabled() instead.
- cmd::attach::attach_json_errors_without_prompting_for_human_input:
unrelated insta snapshot drift caused by the new SettingsFile JSON
shape leaking into an events-log snapshot.
Follow-up work for this stage also includes:
- Rewriting web_auth.rs register flow to emit v2 TOML directly and to
re-parse the written file back into state.settings so in-memory
state doesn't lag the on-disk file.
- Removing the legacy_settings_to_v2 shim in fabro-cli/config once
the server-side settings endpoint returns v2 shapes (Stage 6.6).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep local server targeting based on explicit server targets instead of
implicitly deriving a socket from storage_dir. This makes ~/.fabro/fabro.sock
the default local socket again, keeps storage under ~/.fabro/storage, threads
FABRO_CONFIG through server autostart paths, and updates the CLI test harness
for the new split.
Consolidate CLI and server machine defaults under settings.toml,
including loader renames, writer preservation fixes, same-machine
manifest handling, and docs/test updates for the new config model.
Allow run and create to resolve the same explicit or configured server
connection model used by preflight, validate, and graph. This removes the
last local-only submission assumption from the CLI surface while keeping
local storage-backed behavior intact when no remote target is selected.
Move --storage-dir and --server-url off GlobalArgs and onto the
leaf commands that actually honor them.
This aligns help, parser behavior, and env-var wiring with the
current command architecture while preserving the intended model
and exec targeting semantics.
Move durable run access and execution control onto the server-backed client,
canonicalize run APIs under /api/v1/runs, and switch CLI integration tests
to a shared test daemon/storage model with shared-state-safe assertions.
InMemoryStore duplicated SlateStore's interface and was unused in
production. RunSnapshot/NodeSnapshot were intermediate projections that
tests consumed — replaced with RunState to eliminate the indirection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The server subcommand and related code were gated behind
cfg(feature = "server"). This removes the feature flag entirely,
making fabro-server a required dependency so the server command
is always available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>