Commit graph

1190 commits

Author SHA1 Message Date
Bryan Helmkamp
76d863ca10
Bump version to 0.5.0 2026-03-15 18:33:39 -04:00
Bryan Helmkamp
7292ab4079
Update docs for new CLI commands, GitHub token injection, and events
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 18:33:18 -04:00
Bryan Helmkamp
ec0a612ea5
Add changelog entries for March 14-15
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 18:28:28 -04:00
Bryan Helmkamp
8b948a2d68
Fix fabro-beastie linker error by linking IOKit framework
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>
2026-03-15 18:27:42 -04:00
arc-1e68f1[bot]
43f5fb0edb
Inject GitHub App IAT into Sandbox as GITHUB_TOKEN (#7)
This PR adds GitHub App Installation Access Token (IAT) injection into
sandboxes, allowing `gh` CLI and other GitHub-authenticated tools to
work seamlessly inside workflow sandboxes. Workflow authors can declare
required GitHub permissions in `workflow.toml` under a `[github]`
section (e.g., `permissions = { contents = "write", pull_requests =
"read" }`), with project-wide defaults available in `fabro.toml`.
Workflow-level config fully replaces project-level defaults, consistent
with existing `[pull_request]` behavior.

The implementation introduces a `GitHubConfig` struct wired through
`WorkflowRunConfig`, `RunDefaults`, and `ProjectConfig`, with proper
`apply_defaults` (inherit if unset) and `merge_overlay` (replace if
present) semantics. At runtime, a new `mint_github_token()` helper signs
a JWT, resolves the repo's owner/repo from the origin URL, and requests
a scoped IAT which is injected as `GITHUB_TOKEN` into the sandbox
environment. The previously private
`create_installation_access_token_with_permissions` in `fabro-github` is
made public to support this. A preflight check also mints a token during
validation to surface credential or permission issues early.

Comprehensive tests cover TOML parsing with and without `[github]`,
default inheritance, workflow-over-default precedence, and overlay merge
semantics for `RunDefaults`.

### Fabro Details

<details>
<summary>Ran 7 stages in 27m 15s for $5.88</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $3.79 | 0 |
| simplify | 0s | $2.09 | 0 |
| verify | 0s | – | 0 |
| **Total** | **27m 15s** | **$5.88** | **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: Claude <claude@anthropic.com>
2026-03-15 18:24:30 -04:00
arc-1e68f1[bot]
527b6252ef
Add goal to WorkflowRunStarted and render in fabro logs --pretty (#6)
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>
2026-03-15 17:46:17 -04:00
dependabot[bot]
0929eaac87
Bump quinn-proto from 0.11.13 to 0.11.14 (#1)
Bumps [quinn-proto](https://github.com/quinn-rs/quinn) from 0.11.13 to
0.11.14.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/quinn-rs/quinn/releases">quinn-proto's
releases</a>.</em></p>
<blockquote>
<h2>quinn-proto 0.11.14</h2>
<p><a href="https://github.com/jxs"><code>@​jxs</code></a> reported a
denial of service issue in quinn-proto 5 days ago:</p>
<ul>
<li><a
href="https://github.com/quinn-rs/quinn/security/advisories/GHSA-6xvm-j4wr-6v98">https://github.com/quinn-rs/quinn/security/advisories/GHSA-6xvm-j4wr-6v98</a></li>
</ul>
<p>We coordinated with them to release this version to patch the issue.
Unfortunately the maintainers missed these issues during code review and
we did not have enough fuzzing coverage -- we regret the oversight and
have added an additional fuzzing target.</p>
<p>Organizations that want to participate in coordinated disclosure can
contact us privately to discuss terms.</p>
<h2>What's Changed</h2>
<ul>
<li>Fix over-permissive proto dependency edge by <a
href="https://github.com/Ralith"><code>@​Ralith</code></a> in <a
href="https://redirect.github.com/quinn-rs/quinn/pull/2385">quinn-rs/quinn#2385</a></li>
<li>0.11.x: avoid unwrapping VarInt decoding during parameter parsing by
<a href="https://github.com/djc"><code>@​djc</code></a> in <a
href="https://redirect.github.com/quinn-rs/quinn/pull/2559">quinn-rs/quinn#2559</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2c315aa7f9"><code>2c315aa</code></a>
proto: bump version to 0.11.14</li>
<li><a
href="8ad47f431e"><code>8ad47f4</code></a>
Use newer rustls-pki-types PEM parser API</li>
<li><a
href="c81c0289ab"><code>c81c028</code></a>
ci: fix workflow syntax</li>
<li><a
href="0050172969"><code>0050172</code></a>
ci: pin wasm-bindgen-cli version</li>
<li><a
href="8a6f82c58d"><code>8a6f82c</code></a>
Take semver-compatible dependency updates</li>
<li><a
href="e52db4ad8d"><code>e52db4a</code></a>
Apply suggestions from clippy 1.91</li>
<li><a
href="6df7275c58"><code>6df7275</code></a>
chore: Fix <code>unnecessary_unwrap</code> clippy</li>
<li><a
href="c8eefa07e0"><code>c8eefa0</code></a>
proto: avoid unwrapping varint decoding during parameters parsing</li>
<li><a
href="9723a97775"><code>9723a97</code></a>
fuzz: add fuzzing target for parsing transport parameters</li>
<li><a
href="eaf0ef3025"><code>eaf0ef3</code></a>
Fix over-permissive proto dependency edge (<a
href="https://redirect.github.com/quinn-rs/quinn/issues/2385">#2385</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/quinn-rs/quinn/compare/quinn-proto-0.11.13...quinn-proto-0.11.14">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=quinn-proto&package-manager=cargo&previous-version=0.11.13&new-version=0.11.14)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/fabro-sh/fabro/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-15 17:41:32 -04:00
arc-1e68f1[bot]
f24e410fb3
Plan: Add prevent_idle_sleep to fabro via fabro-beastie (#5)
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>
2026-03-15 17:41:20 -04:00
arc-1e68f1[bot]
b61786ff30
Rename metadata branch from refs/fabro/{run_id} to fabro/meta/{run_id} (#3)
* fabro(01KKS6PG9929P116A2RRKXC738): toolchain (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 2
Fabro-Checkpoint: a274eab045

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): preflight_compile (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 3
Fabro-Checkpoint: 8ce709ecdb

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): preflight_lint (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 4
Fabro-Checkpoint: 4f4c237214

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): implement (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 5
Fabro-Checkpoint: baf4d48a0f

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): simplify (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 6
Fabro-Checkpoint: ddc0967c0b

⚒️ Generated with [Fabro](https://fabro.sh)

* fabro(01KKS6PG9929P116A2RRKXC738): verify (success)

Fabro-Run: 01KKS6PG9929P116A2RRKXC738
Fabro-Completed: 7
Fabro-Checkpoint: 2249924bb2

⚒️ 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>
2026-03-15 17:40:51 -04:00
Bryan Helmkamp
a4abd68686
Use temp dir for dry-run instead of ~/.fabro/runs to avoid clutter in fabro ps -a
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
4c067f123c
Add fabro rm command to remove runs by ID with sandbox cleanup
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>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
50dcc04707
Simplify run status code: dedup color_if/abbreviate_home, fix Removing active state
- 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>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
a40494fd65
Replace CLI RunStatus with proper state machine (status.json)
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>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
193a18285a
Fix truncate_goal: handle newlines, reuse for byte-unsafe inline truncation
- 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>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
26f5603a13
Add GOAL column to fabro ps table output
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>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
c2c351c472
Simplify status.txt implementation: reduce boilerplate and fix double read
- 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>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
047c2c4fef
Add status.txt for explicit run lifecycle tracking
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>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
c8b9436678
Adopt cli-table for ANSI-aware table rendering and fix fabro ps bugs
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>
2026-03-15 17:27:10 -04:00
Bryan Helmkamp
2b2c37b82b
Extract short_run_id helper to deduplicate run ID truncation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:27:09 -04:00
Bryan Helmkamp
ceca61e952
Make fabro ps behave like docker ps
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>
2026-03-15 17:27:09 -04:00
Bryan Helmkamp
38cfb4286d
chore: bump snapshot to fabro-v5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:27:09 -04:00
Bryan Helmkamp
4e75174a6d
Disable debug info for dependencies in dev builds
[profile.dev.package."*"]
debug = false

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:27:09 -04:00
Bryan Helmkamp
cf112ea195
chore: disable incremental compilation in sandbox image
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:27:09 -04:00
Bryan Helmkamp
0bb0e6e3cf Improve fabro ps output
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
2026-03-15 17:27:09 -04:00
Bryan Helmkamp
0a760e5d53
chore: add gh cli to image 2026-03-15 15:02:58 -04:00
Bryan Helmkamp
be88061479
Fix --goal-file not expanding ~ to home directory
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>
2026-03-15 14:57:38 -04:00
Bryan Helmkamp
7249fbaf54
Deduplicate CheckpointSaved hook, use idiomatic bsha.clone()
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>
2026-03-15 14:50:42 -04:00
Bryan Helmkamp
a8f6dbb7fd
Fold CheckpointSaved into CheckpointCompleted
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>
2026-03-15 14:43:09 -04:00
Bryan Helmkamp
36c1bd3794
Add granular git events, rename GitCheckpoint → CheckpointCompleted
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>
2026-03-15 14:35:17 -04:00
Bryan Helmkamp
510272fa69
Add RetroStarted, RetroCompleted, RetroFailed events
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>
2026-03-15 14:21:43 -04:00
Bryan Helmkamp
ecec4c507c
Fix empty-status backward-compat bug, add From<&StageUsage> for Usage
- 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>
2026-03-15 14:14:17 -04:00
Bryan Helmkamp
70beb76c41
Add status + usage to WorkflowRunCompleted, enrich fabro logs --pretty summary
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>
2026-03-15 14:04:02 -04:00
Bryan Helmkamp
d97c1f4a09
Strip "Plan:" prefix from PR titles
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:47:20 -04:00
Bryan Helmkamp
ea6aa93c22
Fix race condition between fabro run --detach and fabro logs -f
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>
2026-03-15 13:37:48 -04:00
Bryan Helmkamp
0bd012acb6
Add -p short alias for --pretty in fabro logs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:29:34 -04:00
Bryan Helmkamp
891a6db29b
commas 2026-03-15 12:31:08 -04:00
Bryan Helmkamp
d25fc56b10
Fix dry-run bug in API server and deduplicate test helpers
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>
2026-03-15 12:28:54 -04:00
Bryan Helmkamp
ef21be5a6d
Fix --dry-run pushing branches to remote
--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>
2026-03-15 12:12:35 -04:00
Bryan Helmkamp
938f3a7c74
docs 2026-03-15 12:11:54 -04:00
Bryan Helmkamp
bc3c5be59e
Fix --dry-run executing command/script nodes instead of simulating them
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>
2026-03-15 12:07:41 -04:00
Bryan Helmkamp
0c80d1f616
Add progress spinner to run --preflight
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 11:44:22 -04:00
Bryan Helmkamp
514686787e
cleanup workflows 2026-03-15 11:04:04 -04:00
Bryan Helmkamp
5428702bc5
files-internal -> docs-internal 2026-03-15 10:58:01 -04:00
Bryan Helmkamp
bc0cb34d5e
rm prompts 2026-03-15 10:57:26 -04:00
Bryan Helmkamp
70d58fc7ba
Add fabro inspect to show detailed JSON data for a workflow run
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>
2026-03-14 18:19:21 -04:00
Bryan Helmkamp
8dd860ea81
Handle credential-embedded GitHub URLs in parse_github_owner_repo
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>
2026-03-14 17:47:55 -04:00
Bryan Helmkamp
d5236dcda2
Fix doctor trycmd test: move env vars to [env.add] section
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>
2026-03-14 17:28:44 -04:00
Bryan Helmkamp
1e7fd59c69
Add fabro run --detach to fork workflows as background processes
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>
2026-03-14 17:18:50 -04:00
Bryan Helmkamp
84932eb3a8
Fix doctor dry-run trycmd test on CI by setting HOME to nonexistent path
`inherit = false` clears HOME but `dirs::home_dir()` falls back to the
passwd database, picking up the runner's ~/.fabro/cli.toml. The loaded
app_id without GITHUB_APP_PRIVATE_KEY triggers a partial-config error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 17:06:35 -04:00
Bryan Helmkamp
312bef9b87
Add routing context to EdgeSelected event: reason, status, hints
Emit reason (condition/preferred_label/suggested_next/unconditional/
jump/fallback), stage_status, preferred_label, suggested_next_ids,
and is_jump so logs explain why an edge was chosen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 16:47:28 -04:00