fix(bling): ship working ujust completions for bash/zsh/fish - #1129
Conversation
Read justfile & flags file, register it as completion with recipes for all three shells. Also declare 'local justfile' override for testing with bats https://opncd.ai/share/HiYO5BRh Assisted-by: Muse Spark 1.3 in OpenCode
Meant for guarding against regression to filename completion. Important, since vendored completions (from common) take priorities over image-defined ones https://opncd.ai/share/HiYO5BRh Assisted-by: Muse Spark 1.3 in OpenCode
https://opncd.ai/share/HiYO5BRh Assisted-by: Muse Spark 1.3 in OpenCode
Replace previous ujust completion generation / text-replacement method in build-stages reference with central ujust completions from common https://opncd.ai/share/HiYO5BRh Assisted-by: Muse Spark 1.3 in OpenCode
|
Note Muse Spark 1.3 typing on behalf of KiKaraage Summary
Root cause: the This PR replaces generation with tailored completions that express what What changedPer-file captions
Verification
|
Instructions for consumer imagesOne shared completion serves every variant, because recipes resolve from each image's own
Per image:
Follow-ups kept out of this PR
|
…ctbluefin#1131) ## What does this change? - Comment out `kcl` tap & formula install due to broken tap (upstream GoReleaser emitted duplicate `kcl@0.9.rb` / `kcl-lsp@0.9.rb` with wrong class (`KclAT090`)), blocking Validate Brewfiles actions - Remove `aurora-wallpapers` from artwork brewfile (& its bats test) after ublue-os/homebrew-tap#656 - Use `kpt` from homebrew core repo now that it's been upstreamed 🎉 ## Why? Closes projectbluefin#1130 Unblock projectbluefin#1129 ## PR pipeline ``` opened ──▶ 4-review ──▶ approved ──▶ merged ``` > A maintainer reviews and approves; merge goes through the merge queue. > Select `blocked` or `hold` to pause the work. ## Checklist - [x] PR title follows Conventional Commits (`fix:`, `feat:`, `docs:`, `ci:`, `refactor:`, etc.) - [x] `just check` passes - [x] `pre-commit run --all-files` passes - [ ] Skill doc updated if the change affects agent-facing conventions or behavior (see `docs/skills/skill-improvement.md`) - [x] `AGENTS.md` / `docs/SKILL.md` / `docs/skills/` links remain valid - [ ] CI is green after push: `gh run list --repo projectbluefin/common --limit 5`
hanthor
left a comment
There was a problem hiding this comment.
This is the right fix in the right repo, and the build gate is the part that makes it stick.
Fixing it here rather than downstream
The PR body lists five prior attempts — bluefin#1236, #1262, #1270, dakota#358, #364 — and that history is the argument for this change. toggle-devmode and friends live in common's system.just, so a fix applied in each consumer is a fix that has to be applied again for every consumer, forever. Ending the sed -E 's/([\(_" ])just/\1ujust/g' rewrite and shipping tailored completions is the version that can actually be finished.
The regex was never going to be right, either. It rewrites just → ujust on a (, _, " or space boundary across a generated completion script — so it depends on the exact shape of whatever just --completions emits, and silently produces something subtly wrong when upstream changes that shape. That's the class of bug where a green build ships broken completions.
The Containerfile gate is the best part
grep -qF 'complete -F _ujust ujust' /tmp/ujust-gate/ujust
grep -qF '#compdef ujust' /tmp/ujust-gate/_ujust
grep -qF 'complete -c ujust' /tmp/ujust-gate/ujust.fish
if grep -qF 'JUST_COMPLETE' ...; then echo "ujust completion is a just dynamic-loader shim" >&2; exit 1; fi
for f in ...; do if [ -e "/out/shared/${f}" ]; then echo "ujust completion shadowed by /out/shared/${f}" >&2; exit 1; fi; doneTwo things it gets right that a lesser version wouldn't. The JUST_COMPLETE check catches a regression to a dynamic-loader shim — I appended JUST_COMPLETE=1 to the bash file and confirmed the gate fires. And the /out/shared loop catches re-introduction of the shadowing that caused #1126 in the first place, rather than only checking the checked-in file is correct. That second one is the failure mode that would otherwise come back.
Verification
bats tests/test_ujust_completion.bats → 26/26, 0 failures.
Mutation-tested the bash completion rather than trusting the green run — all three caught:
| mutation | result |
|---|---|
complete -F _ujust ujust → … just (break the binding) |
1 failed |
recipe lister returns nothing (COMPREPLY=() at :36) |
2 failed |
flags branch disabled (COMPREPLY=() at :28) |
2 failed |
Reading --version etc. from a shipped ujust-flags data file rather than hardcoding a list in three scripts is the right call — one place to update, and the gate asserts --version is present in it.
One caveat worth knowing
7 of the 26 tests skip when zsh and fish aren't installed — all the zsh and fish cases:
zsh completion lists recipes from the entry justfile # skip zsh not installed
zsh completion offers flags for a dash-prefixed word # skip zsh not installed
zsh completion falls back to recipes when flags missing # skip zsh not installed
fish completion offers matching recipes for a prefix # skip fish not installed
... (4 fish cases total)
In my environment only bash has real coverage, and the run still reports 26/26 with nothing red. So the two shells this PR exists to fix beyond bash are only actually exercised if the CI runner ships zsh and fish — worth confirming, because if it doesn't, the zsh/fish completions are shipped untested while the suite looks fully green.
Two options, either fine: install zsh/fish in the test job, or assert the expected skip count so a silent drop to bash-only fails. The same pattern bit projectbluefin/finpilot#365, where three security-relevant cases skipped under root and the mutation survived unnoticed.
Approving — the gate means a regression here fails the build rather than shipping quietly, which is more than the previous arrangement could say.
Generated by Claude Code
hanthor
left a comment
There was a problem hiding this comment.
This is a good change and the diagnosis behind it is right. just --completions bash | sed -E 's/([\(_" ])just/\1ujust/g' was never going to survive contact with reality — it rewrote whatever just happened to emit, including internal identifiers, and it silently produced a different result every time upstream touched its completion templates. Replacing it with three tailored, readable files plus a shared ujust-flags data list is the right shape, and factoring the flags into a data file so the three shells stay in sync is a genuinely nice touch.
I want to single out the build-time gate, because it's the part I was most skeptical of and it mostly earned its keep. I extracted the RUN block verbatim and ran it against deliberately broken inputs rather than reading it and assuming. Eleven of twelve mutants were caught:
--- bash binds 'just' not 'ujust' rc=1
--- zsh compdef just rc=1
--- fish complete -c just rc=1
--- --version dropped from flags rc=1
--- bash regressed to JUST_COMPLETE shim rc=1 ujust completion is a just dynamic-loader shim
--- zsh regressed to JUST_COMPLETE shim rc=1 ujust completion is a just dynamic-loader shim
--- fish regressed to JUST_COMPLETE shim rc=1 ujust completion is a just dynamic-loader shim
--- generator resurrected: /out/shared bash rc=1 ujust completion shadowed by /out/shared/usr/share/bash-completion/completions/ujust
--- generator resurrected: /out/shared zsh rc=1 ujust completion shadowed by /out/shared/usr/share/zsh/site-functions/_ujust
--- generator resurrected: /out/shared fish rc=1 ujust completion shadowed by /out/shared/usr/share/fish/vendor_completions.d/ujust.fish
--- bash file emptied rc=1
The shadowing loop in particular is aimed at exactly the right thing — COPY --from=build /out/shared /system_files/shared at the end of the ctx stage really would overwrite the checked-in files, and the gate placement after every /out/shared write is correct. Build runs on pull_request, so this is a live PR gate, not decoration. Good work.
The blocking problem is that neither the gate nor the 26 new tests notice if the completions stop reading the flags file they ship.
ujust-flags is installed at /usr/share/ublue-os/just/ujust-flags, and each completion computes that path as a default beside the entry justfile. I broke that default in the bash completion only:
- local flags_file="${UJUST_FLAGS_FILE:-${justfile%/*}/ujust-flags}"
+ local flags_file="${UJUST_FLAGS_FILE:-${justfile%/*}/just-flags}"That ships a bash completion that will never find its flag list, so ujust -<TAB> offers nothing. Both defences stay green:
$ bats tests/test_ujust_completion.bats | grep -c '^not ok'
0
$ ./gate.sh # the Containerfile RUN block, verbatim
GATE-PASS
The test that should have caught it is completions read flags from the data file instead of embedding lists, which does grep -qF "ujust-flags" "${BASH_COMPLETION}". After the mutation, here is every line in that file matching it:
$ grep -nF 'ujust-flags' system_files/shared/usr/share/bash-completion/completions/ujust
3:# Flags come from the ujust-flags data file shipped next to the entry
The assertion is satisfied by the header comment. The prose survived; the code didn't. That's the same failure mode the old sed pipeline had — something that looks like it's checking the thing is actually checking a description of the thing — and it's worth fixing here precisely because this PR's whole purpose is to make the completions verifiably correct.
Two concrete fixes, both cheap:
- Anchor the greps to code rather than any occurrence. For bash, something like
grep -qE '^[[:space:]]*local flags_file=.*ujust-flags' "${BASH_COMPLETION}", and the equivalents for_ujust(line 12) andujust.fish(line 21). The same applies to the entry-justfile assertion incompletions default to the image entry justfile, which is a baregrep -qFtoday. - Add a gate line tying the computed default to the installed path, so a rename on either side fails the build. The gate already has the install path in hand from the
COPY; asserting that the stringujust-flagsappears in an assignment (not a comment) in all three files would have killed this mutant.
While I was in there, the gate greps are also comment-insensitive in the other direction — commenting out the binding passes:
--- bash binding COMMENTED OUT rc=0 GATE-PASS
grep -qE '^complete -F _ujust ujust' instead of grep -qF closes that, and likewise for ^#compdef ujust and ^complete -c ujust.
Three non-blocking things worth knowing before you merge.
The new test file never runs in CI. You added it to the Justfile test recipe, but .github/workflows/unit-tests.yml enumerates bats files individually and the workflow is untouched by this PR:
$ git diff fbbb193..HEAD -- .github/workflows/
(empty)
$ grep -n "ujust" .github/workflows/unit-tests.yml
(no matches)
$ grep -o "tests/test_[a-z0-9_]*\.bats" .github/workflows/unit-tests.yml | sort -u | wc -l
21
$ ls tests/*.bats | wc -l
34
You're in company — thirteen files including test_ujust.bats are already unwired — but the effect is that only the Containerfile gate defends this in CI today, which makes the gate gap above matter more than it otherwise would. One step in unit-tests.yml fixes it.
Seven of the 26 tests skip without zsh and fish, so the zsh and fish behavioural cases reduce to static greps on any runner that lacks those shells. unit-tests.yml runs ubuntu-latest and installs only bats, so if you do wire the file in, add zsh fish to that apt-get install line or the shells you're actually fixing get the weakest coverage of the three.
The static flag list has already drifted from just. This is the cost of dropping generation, and it deserves to be a conscious trade rather than a surprise. Against just 1.58.0, seventeen real flags are missing from ujust-flags:
--alias-style --allow-missing --ceiling --chooser --cygpath --default-list
--dotenv-command --dotenv-filename --dotenv-path --group --indentation --json
--justfile-name --no-cache --tempdir --time --timestamp-format
I verified a sample are genuinely accepted by the installed just rather than parser artefacts, e.g. just --json emits the JSON dump and just --group errors with a value is required for '--group <GROUP>'. Nothing shipped is invalid — --format is a real alias for --fmt — the list is just stale. The gate only asserts --version is present, so drift is unbounded. A cheap guard would be a test that every line in ujust-flags is accepted by the installed just, which catches removals upstream even if it can't catch additions.
Also flagging that the validate check is currently red on this head (mergeable_state: blocked) — I didn't dig into it, but it'll need to go green for the merge queue.
The bash behavioural tests themselves are solid, for what it's worth. Renaming the entry justfile default, narrowing the dash glob so -V stops completing, dropping the flags-file existence guard, swapping --summary for --list, unbinding complete, and hardcoding the flag list all produced failures. It's specifically the flags-file path that slipped through.
Generated by Claude Code
Assisted-by: GPT-5.6 Terra via GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Correctness review (head dbfd2e4) — no blockers. Two low-severity observations, both verified against real just binaries (1.43.0 and 1.58.0):
-
ujust-flagsis a version snapshot that doesn't match any singlejustrelease (system_files/shared/usr/share/ublue-os/just/ujust-flags:1-67). It includes--jobs,--usage,--evaluate-format,--clean,--complete-aliases, and-F, which are rejected by just 1.43.0 (error: unexpected argument) but valid in 1.58.0 — so on an image shipping an older just, TAB completes flags that then error. Conversely it omits ~17 flags that exist in 1.58.0 (--chooser,--dotenv-path,--dotenv-filename,--timestamp-format,--tempdir,--json,--alias-style,--allow-missing, …). Worth pinning the list to the just version actually shipped in the image, or noting which version it tracks in the file header. -
Bash completes recipes at every argument position (
system_files/shared/usr/share/bash-completion/completions/ujust:29-35has no cword check), soujust update <TAB>offers the recipe list again, while zsh gates on(( CURRENT == 2 ))(system_files/shared/usr/share/zsh/site-functions/_ujust:7). Cosmetic inconsistency, not a regression versus the broken generator this replaces.
Verified: entry justfile path matches the ujust wrapper (exec just --justfile /usr/share/ublue-os/just/00-entry.just), Containerfile gate regexes match the checked-in files, and the bats suite exercises all three shells in CI (fish/zsh added to the apt install in .github/workflows/unit-tests.yml:40).
— hive: agent=reviewer backend=copilot model=claude-fable-5
Danathar
left a comment
There was a problem hiding this comment.
Ran the suite at dbfd2e4 with real zsh and fish installed: 30/30 pass. Also pointed the bash completion at this machine's actual /usr/share/ublue-os/just/00-entry.just: ujust tog<TAB> offers toggle-devmode toggle-tpm2 toggle-updates toggle-user-motd, and ujust --li<TAB> offers the four --list* flags. Common's build (with the Containerfile gate) is green on the PR.
@hanthor's blocking point looks addressed in dbfd2e4: the test and gate greps are now anchored to the assignment lines (^[[:space:]]*local flags_file=.*ujust-flags, ^[[:space:]]*echo .*ujust-flags for fish) and the binding checks use ^complete -F _ujust ujust$ / ^#compdef ujust$ / ^complete -c ujust , so the "comment survives, code doesn't" and "binding commented out" mutants both fail now.
Two small follow-ups, neither blocking: ujust-flags is a snapshot — against just 1.57.0 here it's missing 17 flags (--alias-style --allow-missing --ceiling --chooser --cygpath --default-list --dotenv-command --dotenv-filename --dotenv-path --group --indentation --json --justfile-name --no-cache --tempdir --time --timestamp-format) and lists --format, which isn't a just flag (--dump-format / --evaluate-format are). Worth a one-off refresh from just --help on the image's just version, but it doesn't change the fix.
|
@hanthor your changes-requested review from 09-17 is still the standing decision on this PR, but |
There was a problem hiding this comment.
docs-currency
system_files/shared/usr/share/ublue-os/just/ujust-flagsis missing a number of flags thatjustaccepts and theujustwrapper forwards verbatim (system_files/shared/usr/bin/ujust:38—exec just --justfile ... "${@}"): e.g.--json,--dotenv-path,--dotenv-filename,--alias-style,--allow-missing,--group,--tempdir,--timestamp-format(checked against just 1.58.0). The file's own header (completions/ujust:3-5) says it is the single source of truth updated whenjust --helpgains flags, so it should start complete. Low severity — completion coverage only.docs/skills/containerfile/SKILL.md:60— the new bullet sits in the Red Flags list (anti-patterns like "checkout tags/...", "acurlblock without…"), but it is worded as a statement of the correct design, and the grammar is broken ("guarded by aRUNgate …, which never reintroduce a … generator"). Consider phrasing it as the red flag itself, e.g. "Ajust --completions | sedgenerator step — completions are tailored files undersystem_files/shared/guarded by the build-stage gate."
No findings from: security, intent-alignment, style.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
- ujust-flags now mirrors every flag just 1.58.0 accepts (ujust execs
just with "${@}"), adding --alias-style, --allow-missing, --ceiling,
--chooser, --cygpath, --default-list, --dotenv-command,
--dotenv-filename, --dotenv-path, --group, --indentation, --json,
--justfile-name, --no-cache, --tempdir, --time, --timestamp-format,
and dropping --format, which just does not accept.
- Reword the containerfile SKILL.md Red Flags bullet so it names the
anti-pattern instead of restating the correct design.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Reviewed at head 055a887. Ran bats tests/test_ujust_completion.bats at that SHA: 30/30 pass (zsh/fish cases skipped locally; CI installs both per .github/workflows/unit-tests.yml:40 and the test job is green).
correctness
- (low) zsh completion only offers flags/recipes when
CURRENT == 2(system_files/shared/usr/share/zsh/site-functions/_ujust:7). At later word positions it relies on a_justfallback (_ujust:31-35) that this repo does not ship, soujust --color <TAB>or a second flag completes nothing in zsh, while the bash completion offers flags at any position (.../bash-completion/completions/ujust:23). Inconsistent but non-blocking.
style
- (low) The gate assertions now live in three literal copies: the Containerfile
RUNgate (Containerfile:88-100), the checked-in completion files, andtests/test_ujust_completion.bats:143-150, which greps the Containerfile for the exact gate strings including trailing semicolons. Any innocuous reformat of the Containerfile gate breaks the bats suite. Consider grepping for the semantic pattern rather than the exact source line.
No findings from: security, intent-alignment, docs-currency.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78
What does this change?
Change ujust completions from auto-generated
sed -E 's/([\(_" ])just/\1ujust/g'to tailored files for both bash, zsh, and fish; plus add the ujust flag list & updatecontainerfileskill.Why?
Closes #1126
Related issues/PRs:
PR pipeline
Checklist
fix:,feat:,docs:,ci:,refactor:, etc.)just checkpasses (clean locally)pre-commit run --all-filespasses (passes locally)docs/skills/skill-improvement.md):docs/skills/containerfile/updated, generation section rewrittenAGENTS.md/docs/SKILL.md/docs/skills/links remain validgh run list --repo projectbluefin/common --limit 5Assisted-by: Muse Spark 1.3 via OpenCode
https://opncd.ai/share/HiYO5BRh