When `auto_merge = true` is set in `[pull_request]` config, Fabro enables
GitHub's auto-merge on created PRs using the `enablePullRequestAutoMerge`
GraphQL mutation. Auto-merge implies `draft = false` since GitHub doesn't
allow auto-merge on draft PRs. A `merge_strategy` field (squash/merge/rebase,
default squash) controls the merge method. Failures to enable auto-merge
(e.g. repo doesn't have the setting enabled) warn but don't fail the run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The generated fabro.toml now includes an uncommented [pull_request]
section with enabled=true and draft=true, so new projects auto-create
draft PRs on successful workflow runs out of the box.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The `[pull_request]` config in fabro.toml was missing `enabled = true`,
so workflow runs silently skipped PR creation. Additionally, four skip
paths in the PR creation logic had no logging at all, making it hard to
diagnose why a PR wasn't opened. Added debug-level logs for: config not
enabled, dry-run mode, engine error, and non-success run status.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move retro control from [fabro] retro to [features] retros in project
config. Default changes from true to false — retros are now opt-in.
Add retros field to server config Features struct, OpenAPI spec,
TypeScript client, and web app config. Update docs with experimental
warning and new enablement instructions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move Comparison link below Troubleshooting in nav
- Rename "DOT Language" page to "Fabro Language"
- Remove five-tier table from dark factory page, keep link to Dan Shapiro's post
- Add fork command docs and checkpoints section
- Add upgrade, asset list, asset cp command docs
- Add upgrade_check config reference
- Add retros feature flag to server config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This PR introduces a unified dry-run mechanism by adding a
`Handler::simulate()` trait method and a `dispatch_handler()` routing
function that selects between `simulate()` and `execute()` based on
`services.dry_run`. Previously, dry-run behavior was scattered
inconsistently across handlers—`CommandHandler` checked
`services.dry_run` inline, `AgentHandler`/`PromptHandler`/`FanInHandler`
relied on the backend being `None`, and `WaitHandler`/`HumanHandler` had
no dry-run support at all (sleeping or blocking on input for real). This
made dry-run behavior fragile and difficult to extend to new handlers.
The new design adds an `Outcome::simulated(node_id)` factory for
standardized dry-run results, a default `simulate()` implementation on
the `Handler` trait that returns a generic simulated success, and
per-handler overrides where custom context updates are needed.
`CommandHandler` populates empty output/stderr, `AgentHandler` and
`PromptHandler` set simulated
`last_stage`/`last_response`/`response.{id}` context keys,
`FanInHandler` calls `heuristic_select()` without an LLM, `HumanHandler`
auto-selects the first choice, and `ParallelHandler` dispatches child
branches through `dispatch_handler()` while skipping git worktree
operations. The inline `dry_run` check in `CommandHandler::execute()` is
removed, and both call sites in the engine (`execute_with_retry` and
parallel branch dispatch) now route through `dispatch_handler()`.
All existing dry-run tests are updated to test `simulate()` directly,
and new tests verify that `dispatch_handler()` correctly routes based on
the `dry_run` flag and that each handler's `simulate()` produces the
expected context updates and outcome structure.
### Fabro Details
<details>
<summary>Ran 7 stages in 24m 57s for $4.72</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $3.29 | 0 |
| simplify | 0s | $1.42 | 0 |
| verify | 0s | – | 0 |
| **Total** | **24m 57s** | **$4.72** | **0** |
</details>
<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
edges)</summary>
```dot
digraph ImplementAndSimplify {
graph [
goal="Implement and simplify",
model_stylesheet="
* { backend: api; model: claude-opus-4-6;}
"
]
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 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
simplify [label="Simplify", prompt="@prompts/simplify.md"]
verify [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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 and test failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=success"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=success"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=success"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify -> verify
verify -> exit [condition="outcome=success"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Fabro Assistant <assistant@fabro.dev>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This PR adds a `fabro upgrade` command that downloads and installs new
releases from GitHub, along with a passive daily auto-check that
notifies users when a newer version is available. The upgrade flow
supports two download backends: the `gh` CLI (preferred, for auth and
rate-limit benefits) with an automatic fallback to plain HTTPS via
`reqwest` when `gh` is missing or not authenticated. The command
includes SHA256 checksum verification, atomic binary replacement with
rollback on failure, downgrade protection with interactive confirmation,
and `--dry-run`/`--force` flags.
A background upgrade check runs automatically on common commands (`run`,
`exec`, `init`, `install`), caching results in
`~/.fabro/last_upgrade_check.json` to avoid hitting GitHub more than
once per 24 hours. Users can disable this via `upgrade_check = false` in
`~/.fabro/cli.toml` or the `--no-upgrade-check` global flag. The check
is spawned as an async task and its notice prints to stderr after the
main command completes, ensuring it never blocks or breaks normal
operation—all errors are silently swallowed.
The implementation follows a test-first approach with unit tests
covering platform detection, version parsing, SHA256 verification,
upgrade check state serialization/staleness, and the new `upgrade_check`
config field. Dependencies `tempfile` (promoted from dev-dependencies)
and `sha2` are added to `fabro-cli`.
### Fabro Details
<details>
<summary>Ran 7 stages in 18m 39s for $5.61</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $2.92 | 0 |
| simplify | 0s | $2.68 | 0 |
| verify | 0s | – | 0 |
| **Total** | **18m 39s** | **$5.61** | **0** |
</details>
<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
edges)</summary>
```dot
digraph ImplementAndSimplify {
graph [
goal="Implement and simplify",
model_stylesheet="
* { backend: api; model: claude-opus-4-6;}
"
]
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 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
simplify [label="Simplify", prompt="@prompts/simplify.md"]
verify [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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 and test failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=success"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=success"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=success"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify -> verify
verify -> exit [condition="outcome=success"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
This PR adds a new `fabro fork` subcommand that creates a new run
branching from an existing run at a specific checkpoint, without
modifying the original run. This is a non-destructive alternative to
`fabro rewind` — instead of moving branch refs backward and losing later
checkpoint history, fork preserves the source run entirely and creates
fresh run and metadata branches for the new run.
The implementation heavily reuses existing infrastructure from
`rewind.rs` (timeline building, target resolution, parallel map loading,
prefix-based run ID lookup) and follows the same CLI patterns. The core
`execute_fork` function generates a new ULID, creates a run branch ref
pointing at the target checkpoint's commit, then builds a new metadata
branch containing an updated manifest (with new run ID and branch name),
the original graph, and the checkpoint state from the target commit. It
supports the same target syntax as rewind (`@N`, `node_name`,
`node_name@N`), defaults to the latest checkpoint when no target is
specified, and optionally pushes new branches to the remote.
The PR also makes `load_parallel_map` public in `rewind.rs` so fork can
reuse it, and includes five tests covering run branch creation, metadata
branch correctness, preservation of the original run, default-to-latest
behavior, and forking at a specific ordinal.
### Fabro Details
<details>
<summary>Ran 7 stages in 15m 15s for $4.39</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.94 | 0 |
| simplify | 0s | $2.44 | 0 |
| verify | 0s | – | 0 |
| **Total** | **15m 15s** | **$4.39** | **0** |
</details>
<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
edges)</summary>
```dot
digraph ImplementAndSimplify {
graph [
goal="Implement and simplify",
model_stylesheet="
* { backend: api; model: claude-opus-4-6;}
"
]
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 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
simplify [label="Simplify", prompt="@prompts/simplify.md"]
verify [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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 and test failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=success"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=success"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=success"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify -> verify
verify -> exit [condition="outcome=success"]
verify -> fixup
fixup -> verify
}
```
</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.6 (1M context) <noreply@anthropic.com>
The extern block declaring IOPMAssertionCreateWithName and
IOPMAssertionRelease was missing the #[link] attribute, causing
undefined symbol errors on macOS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This PR adds a `goal` field to the `WorkflowRunStarted` event so that
users can immediately see what a workflow is trying to accomplish when
reading logs. The field is an `Option<String>` with `serde(default,
skip_serializing_if)` to maintain backward compatibility with existing
JSONL logs that don't include it—mirroring the same pattern used by
`base_sha` and `run_branch`.
On the rendering side, `fabro logs --pretty` now displays the goal below
the workflow header line when present, using markdown rendering with
proper indentation and terminal-width wrapping. The markdown rendering
logic was extracted into a shared `render_indented_markdown` helper,
which is also now used by the existing `AssistantMessage` rendering to
eliminate duplication.
Tests cover round-trip serialization with a goal, backward-compatible
deserialization of old events without the field, verification that
`None` goals are omitted from JSON output, and pretty-formatting
behavior both with and without a goal present.
### Fabro Details
<details>
<summary>Ran 7 stages in 23m 38s for $3.02</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.76 | 0 |
| simplify | 0s | $1.26 | 0 |
| verify | 0s | – | 0 |
| **Total** | **23m 38s** | **$3.02** | **0** |
</details>
<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
edges)</summary>
```dot
digraph ImplementAndSimplify {
graph [
goal="Implement and simplify",
model_stylesheet="
* { backend: api; model: claude-opus-4-6;}
"
]
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 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
simplify [label="Simplify", prompt="@prompts/simplify.md"]
verify [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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 and test failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=success"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=success"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=success"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify -> verify
verify -> exit [condition="outcome=success"]
verify -> fixup
fixup -> verify
}
```
</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.6 (1M context) <noreply@anthropic.com>
This PR introduces `fabro-beastie`, a new cross-platform idle sleep
prevention crate (named after Beastie Boys — *No Sleep Till Brooklyn*),
and wires it into `fabro-cli` behind an opt-in `sleep_inhibitor` Cargo
feature. Long-running `fabro run` and `fabro exec` commands can be
killed by OS idle sleep, so when the feature is compiled in and
`prevent_idle_sleep = true` is set in `cli.toml`, an RAII guard keeps
the system awake for the duration of the command.
The `fabro-beastie` crate provides platform-specific backends: on macOS
it uses IOKit power assertions (`PreventUserIdleSystemSleep`), on Linux
it spawns `systemd-inhibit` (with `gnome-session-inhibit` as fallback)
and sets `PR_SET_PDEATHSIG` to prevent orphaned processes. Both fall
back to a no-op dummy backend if the platform backend is unavailable.
The public API is a single `guard(bool)` function returning an
`Option<SleepInhibitorGuard>` that releases on drop.
On the integration side, a `prevent_idle_sleep` boolean field is added
to `CliConfig` (defaulting to `false`), and `cfg`-guarded sleep guards
are placed at the entry points of both the `exec` and `run` command
paths in `fabro-cli`. Since the feature is off by default, there is zero
impact on normal builds — `fabro-beastie` is only pulled in when
explicitly enabled via `--features fabro-cli/sleep_inhibitor`.
### Fabro Details
<details>
<summary>Ran 7 stages in 20m 33s for $3.24</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $1.26 | 1 |
| simplify | 0s | $1.98 | 0 |
| verify | 0s | – | 0 |
| **Total** | **20m 33s** | **$3.24** | **1** |
</details>
<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
edges)</summary>
```dot
digraph ImplementAndSimplify {
graph [
goal="Implement and simplify",
model_stylesheet="
* { backend: api; model: claude-opus-4-6;}
"
]
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 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -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."]
simplify [label="Simplify", prompt="@prompts/simplify.md"]
verify [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 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 and test failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=success"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=success"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=success"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify -> verify
verify -> exit [condition="outcome=success"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Adds `fabro rm <RUN>...` to remove specific runs (the `docker rm` equivalent).
Refuses active runs unless `-f` is passed, writes Removing status, does
best-effort sandbox cleanup via reconnect, then deletes the run directory.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace duplicate `abbreviate_home` with existing `tilde_path`
- Include `Removing` in `is_active()` so removing runs aren't pruned
- Log warning on status.json write failure instead of silently discarding
- Extract `color_if` to cli/mod.rs, remove copies in runs.rs and rewind.rs
- Unify near-duplicate RunInfo construction in scan_runs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the 3-variant RunStatus enum (Concluded/Running/Unknown) with an
8-variant state machine (Submitted/Starting/Running/Paused/Removing/
Succeeded/Failed/Dead) persisted as status.json via RunStatusRecord.
Add StatusReason enum for fine-grained failure/success classification
(WorkflowError, Cancelled, SandboxInitFailed, Completed, etc.) and
validated state transitions via can_transition_to()/transition_to().
Map engine results to appropriate RunStatus+StatusReason at all write
sites: Submitted (detach), Starting+SandboxInitializing (run init),
Failed+SandboxInitFailed (scopeguard), Running (engine start), and
Succeeded/Failed with reason (engine completion).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Take first line before truncating to prevent multi-line goals from
breaking table layout
- Move goal field next to other manifest-sourced serialized fields
- Replace byte-slicing truncation in df_from (panics on multibyte chars)
with the char-safe truncate_goal helper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show each run's goal (from manifest) as the rightmost column, truncated
to 50 characters for readability. Adds a multibyte-safe truncate_goal
helper with tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add StatusInfo::simple() helper to eliminate repeated 4-field constructions
- Eliminate double read of status.txt in scan_runs() by calling read_status()
once and branching on Unknown vs non-Unknown
- Use write_status_file() consistently in engine.rs instead of raw fs::write
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Runs were invisible in `fabro ps` during sandbox initialization because
manifest.json isn't written until engine.run(). status.txt is written
immediately at run creation and updated at lifecycle transitions
(starting → running → concluded), replacing fragile inference logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate all 7 CLI tables to cli-table, which measures column widths
correctly in the presence of ANSI escape codes, fixing misaligned
columns in `fabro ps`. Also fix DIRECTORY to show ~/relative paths
instead of just the last component, and compute elapsed duration for
running jobs instead of showing "-".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Default to showing only running processes (use -a for all), remove row
limit, display oldest-first, truncate run IDs to 12 chars, add DIRECTORY
column from host_repo_path, and drop STARTED/COST/LABELS columns.
- Add host_repo_path to Manifest and populate from RunConfig
- Add StatusFilter enum (RunningOnly/All) to filter_runs
- Replace --limit with -a/--all flag (docker-ps semantics)
- Add host_repo_path to RunInfo, extract in scan_runs
- New column layout: RUN ID | WORKFLOW | STATUS | DIRECTORY | DURATION
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This PR enriches the `fabro ps` output with colored status indicators,
duration/cost columns, relative timestamps, and pagination controls.
Status values are now color-coded (green for success, red for fail, cyan
for running, dim for unknown), the header row is bolded, and
separators/labels are dimmed.
Duration and total cost are now extracted from `conclusion.json` and
displayed as new columns. Start times are shown as human-friendly
relative strings (e.g., "2m ago", "3h ago") instead of raw RFC 3339
timestamps, with full timestamps preserved in `--json` output.
New `--limit N` (default 10) and `--all` flags cap the displayed output,
with a footer indicating how many runs are shown out of the total.
PR: https://github.com/fabro-sh/fabro/pull/2
Move expand_tilde from fabro-config to fabro-util::path so it can be
shared without circular dependencies, and apply it to the goal file
path in resolve_cli_goal.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the identical CheckpointSaved hook block from both git and non-git
checkpoint branches to a single block after the if/else. Replace
bsha.to_string() with bsha.clone() for &String → String conversion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the separate CheckpointSaved event — CheckpointCompleted now fires in
both git and non-git paths. git_commit_sha is Optional (None when git is
disabled or for start nodes). The CheckpointSaved hook event is preserved
unchanged for backward compat with user hook configs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename GitCheckpoint/GitCheckpointFailed to CheckpointCompleted/CheckpointFailed
to separate checkpoint lifecycle from git operations. Add 7 new granular git
events: GitCommit, GitPush, GitBranch, GitWorktreeAdd, GitWorktreeRemove,
GitFetch, GitReset. Emit at all relevant call sites in engine.rs and parallel.rs.
Update push helpers to return bool for GitPush success tracking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace fake StageStarted/StageCompleted events with dedicated retro
variants so consumers can distinguish retro activity from normal stages.
The resume path now emits retro events instead of silently skipping them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix: empty-string status from old logs now defaults to "success"
instead of rendering as red/error
- Add From<&StageUsage> for fabro_llm::Usage to centralize conversion
- Simplify usage aggregation: replace collect+reduce+unwrap with
direct .reduce() on the iterator
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds aggregate `status` and `usage` fields to the WorkflowRunCompleted
event so `fabro logs --pretty` can render a complete end-of-run summary
(status, tokens, cache, reasoning) without scanning all StageCompleted
events. Also adds pretty handlers for PullRequestCreated/Failed events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Write id.txt and touch empty progress.jsonl in detach_run() before
spawning the child process so that `fabro logs -f ULID` can resolve
the run and tail the file immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The API server set RunConfig.dry_run but never called
engine.set_dry_run(), so command/script nodes executed for real
during API-served dry runs. Add the missing call.
Also extract EngineServices::test_default() to replace 11 identical
make_services() bodies and 8 inline struct constructions across
handler test modules (-254/+57 lines).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
--dry-run was not suppressing real git push operations in three places:
pre-run branch sync, post-run auto-PR creation, and engine checkpoint
pushes. Guard all three with dry_run checks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Command nodes were running for real during dry-run mode because the
dry_run flag only affected LLM-backed handlers. Propagate dry_run
through EngineServices so CommandHandler can skip execution and return
a simulated success.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Outputs run_id, run_dir, status, manifest, conclusion, checkpoint, and
sandbox as a JSON array (null for missing files). Resolves runs by ID
prefix or workflow name, matching existing `fabro logs` semantics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
URLs like https://x-access-token:TOKEN@github.com/owner/repo.git are
used by Daytona sandboxes. Strip the credentials before matching the
github.com prefix so pr_create and other callers work in those envs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
trycmd's Env struct requires env vars under [env.add], not directly
under [env]. Vars placed directly under [env] are silently ignored by
serde, so the subprocess ran with a fully cleared env. On CI this caused
dirs::home_dir() to fall back to passwd, loading the real cli.toml
(with app_id) but without GITHUB_APP_PRIVATE_KEY → partial config error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-generates a ULID in the parent, passes it to the child via hidden
`--run-id` arg, prints the ULID to stdout, and exits immediately.
Child stdout/stderr go to `{run_dir}/detach.log`. Uses `setsid()` on
unix to detach from the controlling terminal. Existing `fabro ps` and
`fabro logs` work with no changes since `run.pid` and `conclusion.json`
are written by the child as usual.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>