Commit graph

48 commits

Author SHA1 Message Date
Bryan Helmkamp
31ef250e2c
Fix asset capture robustness and retry filtering 2026-03-29 22:23:33 -04:00
Bryan Helmkamp
4268d04052
Add per-asset metadata (mime, md5, sha256) to asset capture
Replace the batch AssetsCaptured event with per-file AssetCaptured events
that include content hashes and MIME type. The asset collection manifest
now stores a captured_assets array with full metadata instead of bare
path strings, enabling downstream integrity verification and content
type awareness.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:10:09 -04:00
Bryan Helmkamp
57abdcaeba
Rename cli.toml to user.toml 2026-03-29 21:24:10 -04:00
Bryan Helmkamp
d7e32f1380
Centralize global CLI override application 2026-03-29 21:08:16 -04:00
Bryan Helmkamp
79a366022f
Infer CLI mode from storage and server flags 2026-03-29 20:58:46 -04:00
Bryan Helmkamp
f5e4b4bb4e
Make metadata rebuild recovery robust 2026-03-29 13:47:09 -04:00
Bryan Helmkamp
48ea1cc4de
Consolidate integration tests under tests/it 2026-03-29 13:47:09 -04:00
Bryan Helmkamp
e1e433ea17
Remove legacy runtime IPC fallbacks 2026-03-29 13:47:09 -04:00
Bryan Helmkamp
5c215033c1
Apply rustfmt 2024 style edition across workspace
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:08 -04:00
Bryan Helmkamp
88cab7b8f9
Clean up runtime path docs and test comment 2026-03-29 13:47:08 -04:00
Bryan Helmkamp
9e7814d062
Create RuntimeState for run runtime and cache paths 2026-03-29 13:47:08 -04:00
Bryan Helmkamp
6a0f571571
Refactor workflow operations and config resolution 2026-03-29 13:47:07 -04:00
Bryan Helmkamp
25df708760
Thin CLI run commands and move execution into workflows operations 2026-03-29 13:47:07 -04:00
Bryan Helmkamp
33f4da33c1
Clean up resolved settings interfaces 2026-03-29 13:47:07 -04:00
Bryan Helmkamp
68ca9e892a
Fix workflow-relative config resolution 2026-03-29 13:47:07 -04:00
Bryan Helmkamp
8a256bb68e
Refactor config layering into combine plus settings 2026-03-29 13:47:07 -04:00
Bryan Helmkamp
126ece3d71
Rename --run-dir to --storage-dir, unify with data_dir
Replace the per-run `--run-dir` CLI flag with `--storage-dir` which sets
the base storage directory (default ~/.fabro). Runs are now created under
`<storage-dir>/runs/` automatically. This unifies the server's `data_dir`
config with the CLI by renaming `FabroConfig.data_dir` to `storage_dir`
and adding a `storage_dir()` convenience method.

Key changes:
- FabroConfig: `data_dir` → `storage_dir` (serde alias preserves compat)
- CLI: `--run-dir` → `--storage-dir` on `fabro run`
- `__detached`: now takes `--storage-dir` + `--run-id` instead of `--run-dir`
- All ~20 CLI commands derive runs base from config instead of hardcoded default
- Added parameterized `runs_base(storage_dir)` and `make_run_dir()` helpers
- Updated OpenAPI spec, docs, and all tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:07 -04:00
Bryan Helmkamp
960ae3b3ac
Add fabro config show command 2026-03-29 13:47:07 -04:00
Bryan Helmkamp
c871ecb274
Refactor fabro-cli run command layout 2026-03-29 13:47:07 -04:00
Bryan Helmkamp
241f0192f2
refactor(fabro-cli): slim down main 2026-03-29 13:47:07 -04:00
Bryan Helmkamp
b748c7e6ea
Guard resume against completed runs 2026-03-29 13:47:07 -04:00
Bryan Helmkamp
d45b4516ab
refactor: clean CREATE/START/RESUME separation
Resume now follows the same subprocess pattern as run: look up run
directory by ID prefix, validate checkpoint exists, clean stale
artifacts, reset status to Submitted, spawn _run_engine --resume, and
attach. This eliminates ~1600 lines of duplicated env/sandbox setup
from resume.rs.

Key changes:
- operations::start() and operations::resume() take run_dir instead
  of Persisted, loading state from disk internally
- run_engine() builds RunOptions from RunRecord on disk, so callers
  no longer extract record fields manually
- StartOptions flattened (no more nested InitOptions)
- FabroError::Precondition variant for start/resume guard checks
- _run_engine accepts --resume flag to dispatch to resume path
- operations::restore removed (no longer needed)
- Resume CLI stripped to just <RUN_ID> + --detach

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:07 -04:00
Bryan Helmkamp
e7a36a1feb
Remove RunSpec + Manifest types and all remaining references
Delete run_spec.rs and manifest.rs modules. Remove write_manifest()
from the engine, update DiskLifecycle and GitLifecycle to only write
StartRecord. Remove read_manifest() from MetadataStore. Update
run_fork to only handle run.json/start.json. Convert resume.rs to
use RunRecord/StartRecord from the metadata branch. Update all tests
and integration tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:06 -04:00
Bryan Helmkamp
08dbcee01e
Add RunRecord + StartRecord alongside RunSpec + Manifest
Introduce two new persistence types aligned to the CREATE/START lifecycle:
- RunRecord (run.json): written at CREATE with merged FabroConfig, fully
  transformed Graph, and run metadata
- StartRecord (start.json): written at START with start_time, run_branch,
  and base_sha

All readers (run_lookup, inspect, diff, pr, attach, detached_support,
start, run_fork, pull_request, run_rewind, resume) now read from the
new types first. Legacy manifest.json + spec.json are still written
for backward compatibility (removal in follow-up).

Also adds dry_run, auto_approve, no_retro fields to FabroConfig, derives
Default on LlmConfig and Graph, and updates docs + tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:06 -04:00
Bryan Helmkamp
0466f94239
Fix CLI integration test timeout by skipping upgrade check
The dry_run_writes_jsonl_and_live_json test was timing out at 4s because
the arc() helper didn't pass --no-upgrade-check, causing every test run
to await a background GitHub API call before process exit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:47:06 -04:00
Bryan Helmkamp
50b1776607
Remove dead selected_options field from Answer struct
The field was populated in constructors but never read by any code.
Selected keys are already carried by AnswerValue::MultiSelected(Vec<String>),
making this field redundant. Also removes the unused options parameter from
Answer::multi_selected().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:07:26 -04:00
Bryan Helmkamp
f5d363237a
Fix workflow slug lookup for split and resumed runs 2026-03-23 10:23:11 -04:00
Bryan Helmkamp
b5c014443e
Preserve split run metadata across restarts 2026-03-23 10:04:49 -04:00
Bryan Helmkamp
6f0023698b
Fix split run lifecycle lookup 2026-03-23 08:44:46 -04:00
Bryan Helmkamp
3db2c237e2
Unify fabro run foreground to use create + start + attach (#141)
## Summary

- **Unify foreground and detach code paths**: Both `fabro run` modes now
go through the same `create_run() + start_run()` pipeline, with
foreground adding `attach_run()`. Only `--preflight` remains as a
special case.
- **Fix three bugs in create→start→attach path**: (1) `_run_engine`
crashed for `.fabro` workflows by hardcoding `run.toml` — now falls back
to `graph.fabro`; (2) `attach_run` couldn't detect crashed engines due
to zombie processes — `start_run` now returns the `Child` handle; (3)
`create_run` ignored `--run-id`.
- **Configure nextest slow-timeout profiles**: Tighten unit test timeout
to 2s slow / 4s kill, add `e2e` profile with 10s/30s. Switch CI and docs
to `cargo nextest run`.

## Test plan

- [ ] `cargo nextest run --workspace` passes with new timeout profiles
- [ ] `fabro run <workflow>` works in foreground mode (create + start +
attach)
- [ ] `fabro run --detach <workflow>` prints run ID and exits
- [ ] `fabro attach <run>` works standalone (without child handle)
- [ ] `fabro resume <run>` works for both `.toml` and `.fabro` workflows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:48:09 -04:00
brynary-fabro[bot]
ac355d58c7
Extract fabro resume subcommand (#137)
This PR extracts the `fabro resume` subcommand from `fabro run`,
replacing the `--resume` and `--run-branch` flags with a dedicated, more
ergonomic interface. Users can now run `fabro resume <RUN_ID>` instead
of constructing `fabro run --run-branch fabro/run/<RUN_ID>` manually,
and the command also accepts run ID prefixes (matching the pattern
established by `fabro rewind` and `fabro fork`). Checkpoint-file-based
resumption is also supported via `fabro resume --checkpoint
path/to/checkpoint.json --workflow workflow.fabro`.

The implementation moves the ~315-line `run_from_branch()` function out
of `run.rs` and into a new `commands/resume.rs` module, splitting it
into two preparation paths (`prepare_from_checkpoint` and
`prepare_from_branch`) that converge on a shared `run_resumed()` tail.
Several previously private helpers in `run.rs` are widened to
`pub(crate)` to allow sharing: `local_sandbox_with_callback`,
`resolve_ssh_config`, `resolve_ssh_clone_params`,
`resolve_preserve_sandbox`, `generate_retro`, `write_finalize_commit`,
`print_final_output`, `print_assets`, and the new `default_run_dir`
helper extracted from duplicated inline logic. The `RunArgs` struct
loses its `resume` and `run_branch` fields along with their
`conflicts_with` annotations, and `RunSpec` drops the corresponding
fields with `#[serde(default)]` for backward compatibility.

Documentation across `docs/reference/cli.mdx`,
`docs/execution/checkpoints.mdx`, and
`docs/core-concepts/how-fabro-works.mdx` is updated to reflect the new
interface, and the `rewind`/`fork` commands now hint `fabro resume
<short-prefix>` instead of the full branch name.

### Fabro Details

<details>
<summary>Ran 9 stages in 30m 25s for $6.70</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 10s | – | 0 |
| preflight_lint | 13s | – | 0 |
| implement | 18m 30s | $3.95 | 0 |
| simplify_opus | 9m 38s | $2.75 | 0 |
| simplify_gpt | 0s | – | 0 |
| verify | 19s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **30m 25s** | **$6.70** | **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-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 -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -q --workspace -- -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-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 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]
    fmt               [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]

    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_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=success"]
    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.6 (1M context) <noreply@anthropic.com>
2026-03-22 13:45:06 -04:00
brynary-fabro[bot]
3680a57a25
Decompose fabro run into create / start / attach (#116)
This PR decomposes `fabro run` into three composable primitives —
`create`, `start`, and `attach` — following the Docker-style lifecycle
model. Previously, `fabro run` performed everything in a single
monolithic function, and `--detach` was implemented by reconstructing
CLI argv to spawn a child process, which was brittle and hard to extend.
The new architecture cleanly separates concerns: `fabro create`
allocates the run directory and persists a `RunSpec` struct to
`spec.json`; `fabro start` spawns a detached `_run_engine` process (a
hidden internal command that reads `spec.json`) via `setsid`; and `fabro
attach` tails `progress.jsonl` with live rendering and handles
file-based interview IPC. `fabro run` is now a composition of these
three primitives, and `fabro run --detach` simply skips the attach step.

The main rendering work lives in a new `handle_json_line()` method on
`ProgressUI` that parses JSONL envelopes and dispatches to the same
internal rendering methods already used by the in-process event handler.
This preserves 100% rendering fidelity without duplicating
spinner/stage/tool-call logic — the attach loop just feeds file lines
into the same code paths. File-based interview IPC is handled in the
attach loop itself: it watches for `interview_request.json`, prompts the
user via `ConsoleInterviewer`, and writes `interview_response.json` back
for the engine to consume. The `hide_bars`/`show_bars` methods
previously private to `ProgressAwareInterviewer` are promoted to public
methods on `ProgressUI` and reused in both the attach loop and the
existing in-process interviewer.

The old `detach_run()` function in `main.rs`, which reconstructed argv
by string-scanning `std::env::args()`, is deleted entirely and replaced
by the `create` + `start` composition. New tests cover the
`handle_json_line` dispatch paths (stage started/completed, tool calls,
retro events, invalid input) and the CLI argument parsing for the new
command variants.

### Fabro Details

<details>
<summary>Ran 9 stages in 30m 55s for $8.55</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 11s | – | 0 |
| preflight_lint | 12s | – | 0 |
| implement | 18m 15s | $5.28 | 0 |
| simplify_opus | 10m 31s | $3.27 | 0 |
| simplify_gpt | 0s | – | 0 |
| verify | 17s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **30m 55s** | **$8.55** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (12 nodes and 15
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 -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -q --workspace -- -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-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 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]
    fmt               [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]

    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_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=success"]
    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.6 (1M context) <noreply@anthropic.com>
2026-03-20 14:08:54 -04:00
brynary-fabro[bot]
69ef57cd4a
Add node-level model validation + missing catalog aliases (#110)
This PR addresses two related issues that caused `fabro run` to fail at
runtime with an unhelpful LLM error when using model names like
`gpt-54`: the model catalog was missing hyphenated aliases, and the
validator had no rule to catch unknown model names on workflow nodes.

The catalog changes add `gpt-54`, `gpt-54-pro`, and `gpt-54-mini` as
recognized aliases alongside the existing `gpt54`-style variants for the
`gpt-5.4` model family. This makes the hyphenated form a valid input
that resolves correctly at both validation and runtime, and the updated
`fabro model list` output and insta snapshots reflect the expanded alias
columns. Three new alias-resolution unit tests confirm each mapping.

On the validation side, a new `NodeModelKnownRule` mirrors the existing
`StylesheetModelKnownRule` but iterates over graph nodes instead of
stylesheet declarations, emitting `Severity::Warning` diagnostics with
the relevant `node_id` when a node's `model` or `provider` attribute
doesn't match anything in the catalog. To avoid duplication, the inline
validation logic in `StylesheetModelKnownRule` was refactored into
shared `check_model_known` and `check_provider_known` helpers that both
rules now call. Five unit tests cover the valid-model, unknown-model,
alias, unknown-provider, and no-attribute cases. An unrelated dead-code
helper in `git.rs` was also removed as part of the cleanup.

### Fabro Details

<details>
<summary>Ran 11 stages in 12m 36s for $2.85</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 58s | – | 0 |
| preflight_lint | 10s | – | 0 |
| implement | 2m 56s | $1.13 | 0 |
| simplify_opus | 4m 12s | $1.13 | 0 |
| simplify_gpt | 0s | – | 0 |
| verify | 10s | – | 0 |
| fixup | 2m 27s | $0.59 | 0 |
| verify | 10s | – | 0 |
| fmt | 0s | – | 0 |
| **Total** | **12m 36s** | **$2.85** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (12 nodes and 15
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 -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo clippy -q --workspace -- -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-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 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]
    fmt               [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]

    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_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=success"]
    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.6 (1M context) <noreply@anthropic.com>
2026-03-19 22:48:08 -04:00
Bryan Helmkamp
d6fdeb8135
Add fabro secret CLI subcommands for managing ~/.fabro/.env
Provides get/list/rm/set subcommands to manage secrets without manually
editing the .env file. Extracts shared dotenv utilities into
fabro-config::dotenv and refactors install.rs to use them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 18:02:38 -04:00
Bryan Helmkamp
3a4ff8a282
Add hidden --skill flag to fabro repo init
Allows skill installation during project setup via `fabro repo init --skill`,
which installs the fabro-create-workflow skill to .claude/skills/. The flag is
hidden from help output since `fabro skill install` is being deprecated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:54:30 -04:00
Bryan Helmkamp
7c16f3b3c9
Add fabro repo deinit command to reverse project initialization
Removes fabro.toml and the fabro/ directory from the git repo root.
Fails with a clear error when the project is not initialized.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:51:45 -04:00
Bryan Helmkamp
076430cddc
Rename init to repo init with backwards-compat shim
Move `fabro init` under `fabro repo init` subcommand group.
The old `fabro init` still works but is hidden from help and
prints a deprecation warning before executing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:41:49 -04:00
Bryan Helmkamp
f7e07cb827
Expand scenario tests to cover more CLI subcommands
Add coverage for validate, model list, workflow list, doctor, exec,
ps, inspect, logs, rm, system df, asset list, asset cp, and cp.
Uses HOME isolation for run lifecycle tests and synthetic assets
for asset/cp testing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:05:13 -04:00
Bryan Helmkamp
bf19a992fa
Update model list snapshots for gpt-5.4-mini
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 15:50: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
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
008fdb03dc
Add inherit = false to all trycmd test envs to prevent CI flakiness
The doctor dry-run test was failing in CI because it inherited the host
environment. With no LLM API keys set, the doctor reported errors and
exited non-zero. Adding `inherit = false` to all 18 .toml test files
ensures deterministic behavior regardless of the host environment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 16:43:23 -04:00
Bryan Helmkamp
8cb6c4f26f
Rename .dot files to .fabro and update all references
Rename 79 workflow files from .dot to .fabro extension across
fabro/workflows/, test/, test/docs/, and files-internal/demo/.
Update TOML configs, Rust production code, test code, and shell
scripts. Backward compat tests in test/attractor/ are unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 22:27:19 -04:00
Bryan Helmkamp
858bdc3788 Split trycmd into per-subcommand tests and remove live-probe doctor fixtures
Split the single cli_tests trycmd function into 14 per-subcommand test
functions so nextest can parallelize them. Delete header.toml and
verbose.toml doctor fixtures that ran live network probes — the existing
dry-run-flag.toml and help.trycmd already cover the same output assertion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 18:03:23 -04:00
Bryan Helmkamp
cb21dc8096 Fix slow unit tests (>5s) by removing unnecessary production delays
- doctor_no_color test: add --dry-run flag to skip live network probes (11s -> <1s)
- retry tests: set retry_policy=linear for 500ms delay instead of 5s default backoff
- cli_backend tests: make poll_interval configurable on AgentCliBackend, use 10ms in tests
- doctor TOML fixtures: remove status.code=1 now that dry-run doctor exits 0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 17:59:43 -04:00
Bryan Helmkamp
28884ae093 rename Arc to Fabro in all Rust crates, symbols, env vars, and supporting files
- Rename 20 crate directories lib/crates/arc-* → fabro-*
- Update all Cargo.toml: crate names, dep paths, feature flags, bin name
- Rename arc_server module → fabro_server in fabro-llm
- ArcError → FabroError across 30+ files
- ARC_VERSION/ARC_GIT_SHA/ARC_BUILD_DATE → FABRO_* constants
- All use/qualified paths: arc_agent:: → fabro_agent::, etc. (~1500 occurrences)
- Env vars ARC_* → FABRO_* in string literals and shell scripts
- String literals: X-Arc-Demo, arc-bot, arc@local, arc-web, arc-mcp, etc.
- Path strings: .arc/ → .fabro/, arc.toml → fabro.toml, refs/arc/ → refs/fabro/
- arc-api.yaml → fabro-api.yaml (OpenAPI spec)
- skills/arc-create-workflow → fabro-create-workflow
- trycmd fixtures: $ arc → $ fabro
- Inline snapshots (insta) updated
- CI, Docker, install.sh, scripts, CLAUDE.md, AGENTS.md
- TypeScript app: env vars, headers, JWT issuer
- Docs: page slugs, git refs, config paths, sandbox names, repo URLs
- Repo references: brynary/arc → fabro-sh/fabro

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:25:58 -04:00