Files
self-healing-autopoietic-shell/contrib/fx/README.md
T

6.0 KiB

fx — automated, selectable fix sources for f()

fx automates the [LEARN] ... Enter a fix: prompt in f() by gathering candidate fixes from a directory of small, pluggable scripts and letting you pick one (or auto-picking) before it's piped into f's stdin.

Zero changes to ../../src/f.sh. f()'s read -r n already doesn't care whether that line comes from a human or a program — fx just decides what to type there.

Usage

. ../../src/f.sh
. fx.sh
fx some-command --that --might --fail

Set FX_AUTO=1 to skip the picker and take the top-ranked candidate automatically. Without it, fx uses fzf if installed, or a plain numbered prompt otherwise.

Default sources (sources.d/, run in filename order)

File What it offers
10-known.sh Fixes already taught for a command that starts the same way, since f()'s own lookup only matches the exact string.
20-history.sh The closest-looking command you've actually run before, read from $HISTFILE.
30-selfdiag.sh Re-runs the command once, and if the tool's own error output suggests a corrected invocation (git's --set-upstream hint, etc.), emits a function that re-derives the suggestion live on every future call — not a frozen snapshot. This distinction matters: an earlier version of this source cached the literal suggested line and it silently broke on the second branch it saw (git ls-remote showed the second branch never got pushed). Re-diagnosing on every call is what makes one taught fix generalize correctly.
40-pathfuzzy.sh If the first word isn't a real command, offers a function (not an alias — aliases don't expand in non-interactive shells without shopt -s expand_aliases) wrapping the closest-spelled real command, using transposition-aware edit distance so gti scores closer to git than to unrelated same-length commands like ftp.
50-thefuck.sh Bridges to thefuck --yes, if installed, as one more opinion. Note --yes executes its own suggestion, so this candidate is often already-applied by the time you see it. Wrapped in timeout 5 — fuzz-testing found it hanging past 3s on a majority of unmatched inputs with no fast-fail path of its own.
70-local-llm.sh Off by default (no-ops unless a local Ollama or LM Studio server responds on its usual port). The one source with real reasoning capability, for failures none of the deterministic sources above can characterize — see "Known gaps" below for a concrete real-world case.
80-remote-api.sh Off by default (no-ops unless FX_REMOTE_API_URL/KEY/MODEL are all set). Same reasoning capability as 70-local-llm.sh, over a paid remote API instead — the last-resort tier, both in cost and in filename order.
90-team-shared.sh Off by default (no-ops unless FX_TEAM_URL is set) — see below, this one is the extension example.

Adding a custom source

Drop a new executable file into sources.d/. Nothing else to register.

Contract:

  • invoked as sourcefile "$CMD" where $CMD is the whole failing command line
  • may read $FX_OUTPUT — captured stdout+stderr of one real attempt (empty if FX_NO_PROBE is set)
  • prints zero or more candidate fix lines to stdout, one per line
  • non-zero exit or no output = "no opinion," silently skipped
  • filename prefix (NN-name) sets consideration/display order — lower runs first

90-team-shared.sh is a real, working example of this contract: it pulls a shared p-format corrections file from $FX_TEAM_URL and offers exact-key matches. It's a template, not a dependency — treat a URL you point it at the same way you'd treat an rc file you source: a match becomes eval'd code the moment you select it.

Known gaps

Every deterministic/heuristic source (10-50) fixes one of two shapes: the command name is wrong (40-pathfuzzy.sh), or the tool itself already said what the right invocation is (30-selfdiag.sh). Neither covers the shape where the command name and structure are entirely correct, but one argument value is wrong for reasons that require outside knowledge to diagnose.

Concrete, real case, not hypothetical: testing against a live third-party system (an independently-built local service stack) from WSL, curl http://127.0.0.1:7778/... failed — not because curl or the URL syntax was wrong, but because WSL2's NAT networking means 127.0.0.1 from inside WSL doesn't reach the Windows host; the actual gateway IP (cat /etc/resolv.conf's nameserver line) does. No source here fixes this: pathfuzzy only touches the command name, selfdiag needs the tool's own output to suggest the correction (a bare "Connection refused" doesn't), and known/history need this exact case taught before it can help. This is precisely what 70-local-llm.sh/80-remote-api.sh exist for — handed the command plus $FX_OUTPUT, a model that knows WSL2's networking quirks could plausibly reason its way to the fix. Neither tier was configured during that test, so fx correctly stayed silent instead of guessing.

A related, categorically different case from the same session: three of that stack's five services turned out to be bound to 127.0.0.1 only (reachable from the Windows host itself, confirmed working there; unreachable from WSL at any IP, connection times out rather than refuses). No source in this directory — including the LLM tiers — can fix that, because the packet never reaches the process at all. That's a server-side bind-address decision, not a client-side command to correct. Worth naming as its own category: not every "why did this fail" question is a fx question. Some are infrastructure decisions to leave alone (this one was, deliberately) and document, not to work around.

Known cost

fx runs the command once itself before consulting sources (so 30-selfdiag.sh has real output to read). That's one extra execution beyond what plain f() does. Set FX_NO_PROBE=1 to skip it for commands with side effects you don't want repeated — the output-aware source just has no opinion in that case, and the others still work.