Compare commits

..

2 Commits

Author SHA1 Message Date
Daveswo 3c4947ee65 Fix CRLF corruption in fx.sh, add .gitattributes to prevent recurrence
The previous commit's fx.sh was silently checked out with CRLF line
endings because this clone's core.autocrlf was true -- git converted
LF to CRLF on checkout, and every subsequent edit/copy carried that
corruption forward. Undetected by `sh` on the dev host (tolerant of
CRLF), but busybox ash on the actual AdaptiveOS target is not: booting
the resulting apkovl produced real syntax errors and ": not found" on
every corrupted line, confirmed by testing the rebuilt ISO in QEMU.

Fix: stripped all CR bytes, set core.autocrlf=false locally, and added
.gitattributes (* text=auto eol=lf) so this can't recur for any future
clone regardless of the cloning user's global git config.
2026-08-22 02:16:47 -04:00
Daveswo e9f51827d6 fx: preserve fix-source provenance in a separate p.trace file
Previously fx() stripped the "[source-label]" prefix before handing a
chosen fix to f(), so once a fix landed in p, a manually-typed fix, a
locally-known fix, and an LLM-guessed fix (70-local-llm.sh,
80-remote-api.sh) were byte-for-byte indistinguishable. That silently
undermines 80-remote-api.sh's own documented trust boundary, which
requires LLM-sourced fixes to stay visually distinguishable from
verified ones -- and the distinction was permanently lost once p is
disk-persisted across a reboot (adaptive-loop.sh).

fx() now records "cmd=source-file" to FX_TRACE_FILE (default ./p.trace)
alongside every fix it hands to f(), using the same "record intent, not
confirmed success" timing f() already uses for p itself. Verified
end-to-end against a stub 80-remote-api.sh-style source: p and p.trace
both populate correctly, and the retried command actually succeeds.
2026-08-22 02:16:47 -04:00
2 changed files with 39 additions and 4 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+38 -4
View File
@@ -26,6 +26,24 @@
# FX_NO_PROBE if set, skip fx's own diagnostic run of the command
# (cheaper, but the 30-selfdiag.sh source gets no
# output to read and will have no opinion)
# FX_TRACE_FILE where chosen fixes' source labels are recorded, one
# "cmd=source-file" line per accepted fix. Defaults to
# ./p.trace, alongside f()'s own ./p. Written whenever
# a non-blank candidate is handed to f() -- same
# "wrote intent, not confirmed success" semantics as
# p itself (see f.sh). This exists because f() alone
# has no notion of *where* a fix came from: once a
# fix is in p, a manually-typed fix, a locally-known
# fix (10-known.sh), and an LLM-guessed fix
# (70/80-*.sh) are byte-for-byte indistinguishable.
# 80-remote-api.sh's own docstring is explicit that
# LLM-sourced fixes must stay visually distinguishable
# from verified ones -- without this file, that
# distinction is silently lost the moment a fix is
# accepted, and permanently lost if p is disk-persisted
# across a reboot (see adaptive-loop.sh). p.trace is
# what a caller checks before trusting an auto-applied
# fix it didn't personally review.
#
# Source contract (see sources.d/*.sh):
# - any executable file in $FX_SOURCES_DIR
@@ -44,6 +62,7 @@
# active later when fx() is actually called, which is almost never the
# repo root in practice.
FX_SOURCES_DIR="${FX_SOURCES_DIR:-$(pwd)/contrib/fx/sources.d}"
FX_TRACE_FILE="${FX_TRACE_FILE:-p.trace}"
fx() {
if [ ! -d "$FX_SOURCES_DIR" ]; then
@@ -92,12 +111,17 @@ fx() {
'
)
chosen=""
# chosen_line keeps the "[source-label] fix text" form intact so
# provenance can be recovered after picking -- stripping the label
# too early (as this used to do in each branch below) throws away
# the one piece of information that lets a later reader tell an
# LLM-guessed fix apart from a locally-verified one.
chosen_line=""
if [ -n "$candidates" ]; then
if [ -n "$FX_AUTO" ]; then
chosen=$(printf '%s\n' "$candidates" | head -1 | sed 's/^\[[^]]*\] //')
chosen_line=$(printf '%s\n' "$candidates" | head -1)
elif command -v fzf >/dev/null 2>&1; then
chosen=$(printf '%s\n' "$candidates" | fzf --prompt="fix for: $cmd > " --height=40% | sed 's/^\[[^]]*\] //')
chosen_line=$(printf '%s\n' "$candidates" | fzf --prompt="fix for: $cmd > " --height=40%)
else
i=0
printf '%s\n' "$candidates" | while IFS= read -r line; do
@@ -110,9 +134,19 @@ fx() {
# f(), same as when zero candidates were found at all.
printf 'pick a number (blank to give up): '
read -r n
[ -n "$n" ] && chosen=$(printf '%s\n' "$candidates" | sed -n "${n}p" | sed 's/^\[[^]]*\] //')
[ -n "$n" ] && chosen_line=$(printf '%s\n' "$candidates" | sed -n "${n}p")
fi
fi
chosen=$(printf '%s\n' "$chosen_line" | sed 's/^\[[^]]*\] //')
chosen_source=$(printf '%s\n' "$chosen_line" | sed -n 's/^\[\([^]]*\)\].*/\1/p')
# Same "record intent, not confirmed success" timing as f()'s own
# write to p: logged as soon as a non-blank fix is handed off, not
# gated on the retry inside f() actually succeeding.
if [ -n "$chosen" ] && [ -n "$chosen_source" ]; then
printf '%s=%s\n' "$cmd" "$chosen_source" >> "$FX_TRACE_FILE"
fi
printf '%s\n' "$chosen" | f "$@"
}