fabro/docs/superpowers/plans/2026-05-06-cli-input-overrides.md
Bryan Helmkamp f07bb4aaba
feat(cli): support sparse input overrides (#222)
## Summary
- Add repeatable `-I` / `--input KEY=VALUE` CLI overrides for workflow
run inputs on `fabro run`, `fabro create`, and `fabro preflight`. CLI
inputs are sparse per-key overrides that merge over the resolved config
inputs (preserving unrelated inherited values), unlike TOML
`[run.inputs]` which still replaces wholesale.
- Manifest bundling and graph-level goal resolution render workflow
source with the effective inputs before structural scanning, so
input-driven `@prompt`, `import`, and `stack.child_workflow` paths get
bundled correctly.
- Persist raw `KEY=VALUE` strings on `ManifestArgs.input` so server-side
replay applies the same sparse overrides on top of merged config.
- Review-driven cleanups: shared `TemplateContext::for_input_scan`
helper for the recurring "render inputs but defer goal" idiom (replaces
4 sites), `#[derive(Default)]` on `ManifestBuildInput` to drop
boilerplate, inline trivial `apply_input_overrides` wrapper, drop a
redundant clone, and tighten the parser/test helpers.

## Test plan
- [ ] `cargo nextest run -p fabro-cli -p fabro-config -p fabro-server -p
fabro-template -p fabro-workflow`
- [ ] `cargo +nightly-2026-04-14 fmt --check --all`
- [ ] `cargo +nightly-2026-04-14 clippy -p fabro-cli -p fabro-config -p
fabro-server -p fabro-template -p fabro-workflow --all-targets -- -D
warnings`
- [ ] Smoke: `fabro run <workflow> -I key=value --input other=42`
overrides those keys while preserving unrelated inherited inputs
- [ ] Smoke: `-I` accepts strings, integers, floats, booleans, empty
values; rejects arrays, inline tables, datetimes; rejects missing `=`
and empty key
- [ ] Smoke: input-driven `@prompts/{{ inputs.foo }}` and
`stack.child_workflow="{{ inputs.bar }}/workflow.fabro"` paths bundle
correctly when overridden via `-I`

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:00:35 -04:00

7.6 KiB

CLI Input Overrides Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add repeatable -I/--input key=value CLI overrides for workflow run inputs.

Architecture: Keep config-file [run.inputs] whole-map replacement semantics unchanged. Parse CLI input flags as a separate sparse override layer, then apply those key-level overrides after effective workflow settings are resolved. Manifest bundling must use the same effective inputs when scanning structural DOT references so input-driven @prompt, import, and stack.child_workflow paths are included in created run manifests.

Tech Stack: Rust, clap, Axum server manifest flow, OpenAPI/progenitor-generated fabro-api types, generated TypeScript Axios client, TOML value parsing, nextest.


Summary

Add repeatable CLI input overrides for fabro run, fabro create, and fabro preflight so documented -I key=value usage works. CLI inputs override individual keys in the effective run.inputs map while preserving other inherited inputs.

Behavior Decisions

Input Result
foo= accepted as empty string
foo=bar accepted as string "bar" via fallback
foo="bar" accepted as TOML string "bar"
foo=false accepted as boolean false
foo=3 accepted as integer 3
foo=0.75 accepted as float 0.75
foo=2026-05-06 rejected; datetimes are not input scalars for this CLI flag
foo=[1] rejected; arrays are not supported
foo={a=1} rejected; inline tables are not supported
foo rejected; missing =
=bar rejected; empty key
duplicate keys accepted; last value wins

Implementation Tasks

  • Add a shared input-override parser in fabro-config.

    • Parse raw KEY=VALUE strings into HashMap<String, toml::Value>.
    • Split only on the first =.
    • Apply the behavior table above exactly.
    • Return errors that include the key when one is available and explain the failure reason.
    • Do not echo full raw KEY=VALUE strings in error messages unless the input has no parseable key and the structure itself is the error.
    • Add unit tests for every row in the behavior table.
  • Add -I, --input <KEY=VALUE> to run-like CLI args.

    • Add a shared clap args struct in lib/crates/fabro-cli/src/args.rs.
    • Flatten it into RunArgs and PreflightArgs; create inherits RunArgs.
    • Add parser tests for fabro run workflow.toml -I foo=bar, fabro create workflow.toml --input foo=bar, and fabro preflight workflow.toml -I foo=bar.
    • Add a regression test that top-level fabro -V still parses as version.
  • Apply CLI inputs as sparse settings overrides.

    • Do not put parsed CLI inputs directly in RunLayer.inputs, because that would trigger whole-map replacement semantics.
    • After WorkflowSettingsBuilder::build(), extend settings.run.inputs with parsed CLI overrides.
    • Make fabro run/preflight/create preserve inherited inputs when only one key is overridden by -I.
    • Keep the existing TOML [run.inputs] replacement test unchanged and passing.
  • Make manifest bundling input-aware before structural scanning.

    • Ensure build_run_manifest() computes effective settings with CLI input overrides before calling workflow collection.
    • Render each DOT source used only for manifest scanning with TemplateContext::new().with_goal("{{ goal }}").with_inputs(effective_inputs.clone()) before parsing for structural references.
    • Keep the manifest's stored workflow and file sources as original source text; rendering is only for discovery.
    • Apply this to root workflows and imported workflow files before scanning goal, node prompt, node import, and stack.child_workflow / stack.child_dotfile.
    • Add manifest-builder tests where -I supplies a dynamic @prompt path, import path, and stack.child_workflow path, and assert the referenced files/workflows are bundled.
  • Make graph-level manifest goal resolution input-aware.

    • Update resolve_manifest_goal() precedence 3 so graph-level goal attributes are parsed from the same rendered root DOT source used for manifest structural scanning.
    • Keep precedence 1 (--goal / --goal-file) and precedence 2 (run.goal) unchanged.
    • Preserve the manifest's stored root workflow source as original source text.
    • Add a manifest-builder test for graph [goal="@prompts/{{ inputs.goal_file }}"] with -I goal_file=goal.md and assert the manifest goal has path == "prompts/goal.md" and text == <contents of prompts/goal.md>.
  • Persist and replay input overrides through run manifests.

    • Add input: string[] to ManifestArgs in docs/public/api-reference/fabro-api.yaml.
    • Update run_manifest_args() and preflight_manifest_args() to include raw repeated input args.
    • Update every types::ManifestArgs construction site and default/fixture in Rust.
    • Update manifest_args_is_empty() so input-only manifests are not dropped.
    • Add a test where the only CLI override is -I foo=bar and assert manifest.args.input == ["foo=bar"].
  • Apply manifest input overrides on the server.

    • In prepare_manifest(), parse manifest.args.input with the shared parser.
    • Apply parsed inputs after WorkflowSettingsBuilder::build() and before any prepared settings are used.
    • Add a server manifest replay test where project/workflow config has multiple inputs, manifest args override one key, and the unrelated inherited key remains.
  • Regenerate API clients.

    • Run cargo build -p fabro-api so progenitor regenerates Rust API types.
    • Run cd lib/packages/fabro-api-client && bun run generate so the TypeScript Axios client includes ManifestArgs.input.
    • Include both Rust and TypeScript generated diffs in the implementation change set.
  • Update docs.

    • Update docs/public/workflows/variables.mdx to document -I/--input key=value.
    • Update docs/public/execution/run-configuration.mdx and docs/public/administration/server-configuration.mdx so all [run.inputs] precedence and merge-semantics docs distinguish TOML whole-map replacement from CLI per-key overrides.
    • Refresh generated CLI docs after adding the clap flag.
    • State that CLI input flags are highest precedence and merge per key.
    • Keep the existing warning that TOML [run.inputs] layers replace the whole inherited map.

Test Plan

  • Parser tests in fabro-config cover the full behavior table.
  • CLI parse tests cover run, create, preflight, --input, -I, and top-level version parsing.
  • Settings tests prove CLI input overrides preserve unrelated inherited inputs and TOML [run.inputs] replacement behavior remains unchanged.
  • Manifest builder tests cover input-driven @prompt, import, and stack.child_workflow bundling.
  • Manifest goal tests cover input-driven graph-level goal="@..." resolution.
  • Manifest replay tests cover input-only manifest args and server-side application of input overrides.
  • Verification commands:
    • cargo build -p fabro-api
    • cd lib/packages/fabro-api-client && bun run generate
    • cargo dev docs refresh
    • cargo dev docs check
    • targeted CLI/config/server tests
    • cargo nextest run -p fabro-cli -p fabro-config -p fabro-server

Assumptions

  • CLI input flags are sparse per-key overrides, not whole-map replacement.
  • --input is the long flag; --var is not added.
  • Input keys remain flat strings.
  • TOML datetimes, arrays, and inline tables are rejected for CLI input overrides.