aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-07-04 08:51:37 +0200
committerDanilo M. <danix@danix.xyz>2026-07-04 08:51:37 +0200
commit64f419d7485cefee854c04c5b49e8adfc5e80531 (patch)
treeb55729a7164f33595ec856faef561e9ce4a8c3f5
parent84039594800902c30f3dfed8092b01f1a8620d68 (diff)
downloadmkhintfile-64f419d7485cefee854c04c5b49e8adfc5e80531.tar.gz
mkhintfile-64f419d7485cefee854c04c5b49e8adfc5e80531.zip
Add phantom-dep stripping for slackware-current
SBo SlackBuilds target stable; some build deps (rust-opt, google-go-lang) are unneeded on -current. Auto-add DELREQUIRES for these instead of writing a hintfile per dependent by hand. - Read the phantom-dep list from ~/.config/mkhint/phantom-deps (one per line, # comments; missing file = no-op). - --fix-current / -F: sweep the repo, ensure every package whose REQUIRES contains a phantom dep carries the matching DELREQUIRES. Create a minimal hint if absent, back up and merge into an existing hint otherwise. Idempotent, no prompts, mutually exclusive with -v/-f/-n. - --new: auto-append DELREQUIRES for phantom deps found in the .info. - Tests T50-T56; docs and bash-completion updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--CLAUDE.md20
-rw-r--r--README.md41
-rwxr-xr-xmkhint115
-rw-r--r--mkhint.bash-completion2
-rwxr-xr-xtests/mkhint_test.sh123
5 files changed, 297 insertions, 4 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 6fa8bac..c83af91 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -23,7 +23,16 @@ nvchecker config path is read from `$HOME/.config/nvchecker/nvchecker.toml` (not
NVCHECKER_CONFIG="$HOME/.config/nvchecker/nvchecker.toml"
```
-User must set up the `[__config__]` section once before version-checking features work. Bash completion now also knows `--check`/`-C`.
+User must set up the `[__config__]` section once before version-checking features work. Bash completion now also knows `--check`/`-C` and `--fix-current`/`-F`.
+
+Phantom-dep list (deps needed on Slackware stable but not on -current, e.g. `rust-opt`,
+`google-go-lang`) is read at runtime from a plain file, one dep per line, `#` comments allowed:
+
+```bash
+PHANTOM_DEPS_FILE="$HOME/.config/mkhint/phantom-deps"
+```
+
+Missing file = empty list = phantom-dep handling is a no-op. Consumed by `--fix-current` and `--new`.
## Running / Testing
@@ -103,6 +112,13 @@ Test coverage:
| T47 | `-R <missing>` — exit 2 |
| T48 | `--check` upstream `2026-06-02` vs hint `2026_06_02` — treated as current |
| T49 | `--check` accept dashed upstream — hint VERSION stored as underscore form |
+| T50 | `--new` on .info requiring a phantom dep — `DELREQUIRES` added |
+| T51 | `--new` on .info with no phantom dep — no `DELREQUIRES` |
+| T52 | `--fix-current` dependent with no hint — minimal hint created, only phantom dep stripped |
+| T53 | `--fix-current` existing manual hint — `.bak` made, `DELREQUIRES` merged, other vars intact |
+| T54 | `--fix-current` re-run — idempotent, no duplicate dep, no `.bak` churn |
+| T55 | missing phantom-deps file — `--fix-current` and `--new` are no-ops |
+| T56 | `--fix-current` with `-v` — mutually-exclusive error, exit 1 |
When adding new features, add a corresponding test case to `tests/mkhint_test.sh`.
@@ -116,6 +132,8 @@ When adding new features, add a corresponding test case to `tests/mkhint_test.sh
- `--review` / `-R`: with no package args, iterates only the matched (highlighted) hints. With explicit package names (`-R foo bar`), reviews each named hint regardless of version match (existence required: missing hint → exit 2). For each, shows the hint side-by-side with its `.info` (`git diff --no-index` if git present, else `diff -y`), then prompts `[K]eep / [D]elete / [S]kip` (default Keep). Delete removes the hint and its `.bak` via `_remove_hint`. Prints a deleted/kept summary counting hints actually reviewed. `-l` and `-R` combine: `-lR` shows the table first, then reviews. Bare `-R` with no matches → "nothing to review", exit 0. Per-hint logic lives in `_review_one_hint`; `review_hint_files` drives it from either the named list or `MATCHED_PKGS`.
- `--check` / `-C`: runs nvchecker for all (or named) hints. With exactly one explicit package it uses `nvchecker -e <pkg>` to skip scanning the whole config; with two+ packages or no args it does one full scan. (`--hintfile` with no `-v` also queries via `nvchecker -e <pkg>`.) reports outdated packages with current → latest versions, prompts per-package to update, applies updates with `nvtake`, then prompts single `slackrepo update` for all updated packages. Hints with no `[pkg]` section in `nvchecker.toml` are collected and, after the scan, a single prompt offers to populate the config via `add_nvchecker_section` (github/pypi autodetect, else stub); on accept it prints a "review and re-run" message and stops the run without applying updates. Packages whose `.info` is not found in `REPO_DIR` are skipped. Upstream versions are normalized via `_normalize_version` (`-` → `_`) before comparing and before storing, so a dashed upstream version like `2026-06-02` matches the packaged `2026_06_02` (SlackBuild versions cannot contain `-`) and updates are written in the underscore form. The same normalization applies to `--hintfile` with no `-v`. `nvtake` still uses nvchecker's own raw keyfile value.
- `--no-dl` / `-N`: downloads and recalculates checksums as normal, then appends `NODOWNLOAD=yes` after `MD5SUM_x86_64=`. Works with `--hintfile` or `--new`. Error if used alone.
+- `--fix-current` / `-F`: bulk sweep. Loads `PHANTOM_DEPS_FILE`, scans every `.info` in `REPO_DIR`, and for each package whose REQUIRES contains a phantom dep, ensures its hint carries the matching `DELREQUIRES`. No existing hint → create a minimal `DELREQUIRES="..."` file. Existing hint → back up to `.bak` and union the phantom deps into its `DELREQUIRES` line (dedup), leaving all other content untouched. Idempotent: if the deps are already present, the file is left alone and no `.bak` is written. No per-package prompts, safe under `set -e`. Mutually exclusive with `-v`/`-f`/`-n` (exit 1). Empty/missing list → "Nothing to do", exit 0. Helpers: `load_phantom_deps`, `phantom_deps_in_info`, `merge_delrequires`, `fix_current`.
+- `--new` phantom-dep hook: after commenting out REQUIRES, `create_new_hint_file` appends `DELREQUIRES="..."` for any phantom dep found in the `.info` REQUIRES. Same list as `--fix-current`.
- `--delete` / `-d`: removes hint file and `.bak` if present. Accepts multiple package names. Exits 2 on first missing file.
- Downloads go to `/tmp/mkhint/download` (single shared temp file, deleted after md5 calculation).
- Multiline `DOWNLOAD`/`DOWNLOAD_x86_64`: parsed via `parse_multiline_var` (awk). First URL always re-downloaded. Continuation URLs (2+) prompt user interactively — changed URLs re-downloaded, unchanged URLs keep existing md5. Written back via `perl -i` with `\` continuation format preserved. Shared logic in `update_checksums` → `_process_download_var`.
diff --git a/README.md b/README.md
index 386a456..1c6903a 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,8 @@ newver = "/var/lib/nvchecker/newver"
mkhint only appends package-specific `[section]` entries to this file; the `[__config__]` section must be created manually once.
+The `--fix-current` / `--new` phantom-dependency list is read from `~/.config/mkhint/phantom-deps` (one dep per line, `#` comments allowed). See "Strip -current phantom dependencies" below. A missing file makes those features no-ops.
+
## Usage
### Update an existing hint file
@@ -168,6 +170,44 @@ If any scanned hint file has no nvchecker source configured, `--check` lists tho
Because a SlackBuild version string cannot contain `-` (it would break `PRGNAM` parsing), an upstream version such as `2026-06-02` is packaged as `2026_06_02`. mkhint normalizes upstream versions (`-` becomes `_`) before comparing them, so these are treated as the same version and no spurious upgrade or downgrade is offered. When you accept an update, the version is written to the hint file in the underscore form. The same normalization applies to `--hintfile` without `-v`. `nvtake` still records nvchecker's own raw upstream value.
+### Strip -current phantom dependencies
+
+SBo SlackBuilds target Slackware stable. Some of their build dependencies are unneeded on
+slackware-current because it already ships them as system packages or newer versions, for example
+`google-go-lang` (a system package on -current) and `rust-opt` (only needed on stable, where the
+system rust is old). slackrepo strips such a dep from a package by putting `DELREQUIRES="dep"` in
+that package's hint file.
+
+List these "phantom" deps once, one per line (`#` comments allowed), in:
+
+```
+~/.config/mkhint/phantom-deps
+```
+
+Example:
+
+```
+# deps needed on stable but not on -current
+rust-opt
+google-go-lang
+```
+
+`--fix-current` (`-F`) then sweeps the whole repository: for every package whose `REQUIRES` contains
+a listed dep, it ensures the hint carries the matching `DELREQUIRES`. Packages with no hint get a
+minimal one; packages with an existing hint are backed up to `.bak` and have the dep merged into
+their `DELREQUIRES` line, leaving everything else untouched. It is idempotent, so run it after each
+weekly repository regeneration:
+
+```bash
+mkhint --fix-current
+mkhint -F
+```
+
+`--new` also applies the list automatically: when it creates a hint from an `.info` whose `REQUIRES`
+contains a phantom dep, it adds the matching `DELREQUIRES` for you.
+
+If the list file is missing or empty, both features are no-ops.
+
### Help
```bash
@@ -196,6 +236,7 @@ mkhint -h
| DOWNLOAD_x86_64 | Download URL for x86_64-specific build |
| MD5SUM_x86_64 | MD5 checksum of the x86_64 archive |
| NODOWNLOAD | Set to `yes` to skip download/checksum verification in slackrepo |
+| DELREQUIRES | Space-separated deps to strip from REQUIRES (e.g. -current phantom deps) |
## Notes
diff --git a/mkhint b/mkhint
index b18cbc3..a70e590 100755
--- a/mkhint
+++ b/mkhint
@@ -21,6 +21,10 @@ REPO_DIR="/var/lib/sbopkg/SBo-danix/"
HINT_DIR="/etc/slackrepo/SBo-danix/hintfiles/"
TMP_DIR="/tmp/mkhint"
NVCHECKER_CONFIG="$HOME/.config/nvchecker/nvchecker.toml"
+# Deps that exist on Slackware stable but are unneeded on -current (system
+# package or newer already present). Listed one per line, '#' comments allowed.
+# Missing file = empty list = phantom-dep handling is a no-op.
+PHANTOM_DEPS_FILE="$HOME/.config/mkhint/phantom-deps"
# create the temp dir if not existing
if [[ ! -d $TMP_DIR ]]; then
@@ -50,6 +54,7 @@ Usage:
./mkhint --new FILE Create new hint file (no version)
./mkhint --hintfile FILE Update hint, suggest latest version via nvchecker
./mkhint --check [FILE...] Check all (or named) hints for upstream updates
+ ./mkhint --fix-current Sweep repo, add DELREQUIRES for -current phantom deps
./mkhint --list List hint files
./mkhint --review Review hints matching SBo version, keep/delete each
./mkhint --review FILE... Review named hints (any version), keep/delete each
@@ -66,12 +71,14 @@ Options:
--review, -R [FILE...] Review hints; no args = matched only, FILE... = named hints (any version)
--clean, -c Remove all .bak files from HINT_DIR
--check, -C [FILE...] Check hints for upstream updates via nvchecker, update interactively
+ --fix-current, -F Add/merge DELREQUIRES for -current phantom deps across the whole repo
--delete, -d FILE Delete a hint file (and .bak if present)
--no-dl, -N Skip downloads; add NODOWNLOAD=yes to hint file (use with -f or -n)
--help, -h Show this help message
Hint files are stored in: $HINT_DIR
Temporary files are stored in: $TMP_DIR
+Phantom-dep list (for --fix-current / --new): $PHANTOM_DEPS_FILE
Variables order in hint files:
VERSION, ARCH, DOWNLOAD, MD5SUM, DOWNLOAD_x86_64, MD5SUM_x86_64
@@ -302,6 +309,90 @@ download_file() {
echo "$md5"
}
+# ── phantom-dep handling (deps unneeded on -current) ──────────────────────────
+
+# Load PHANTOM_DEPS_FILE into the PHANTOM_DEPS array (one dep per line, '#'
+# comments and blank lines ignored). Missing file => empty array.
+PHANTOM_DEPS=()
+load_phantom_deps() {
+ PHANTOM_DEPS=()
+ [[ -f "$PHANTOM_DEPS_FILE" ]] || return 0
+ local line
+ while IFS= read -r line; do
+ line="${line%%#*}" # strip comment
+ line="${line//[[:space:]]/}" # strip whitespace
+ [[ -n "$line" ]] && PHANTOM_DEPS+=("$line")
+ done < "$PHANTOM_DEPS_FILE"
+}
+
+# Read REQUIRES="..." from an .info file and echo the phantom deps it contains,
+# space-separated. Empty output if none.
+phantom_deps_in_info() {
+ local info="$1" requires dep
+ [[ -f "$info" ]] || return 0
+ requires=$(. "$info" >/dev/null 2>&1; echo "${REQUIRES:-}")
+ local hits=()
+ for dep in "${PHANTOM_DEPS[@]}"; do
+ [[ " $requires " == *" $dep "* ]] && hits+=("$dep")
+ done
+ echo "${hits[*]}"
+}
+
+# Merge a set of deps into a hint file's DELREQUIRES (union, dedup), preserving
+# all other content. Creates a minimal hint if the file is absent. Backs up an
+# existing file to .bak first (mkhint convention). Args: hintpath dep...
+merge_delrequires() {
+ local hint="$1"; shift
+ local new_deps=("$@")
+ [[ ${#new_deps[@]} -eq 0 ]] && return 0
+
+ local existing="" combined
+ if [[ -f "$hint" ]]; then
+ existing=$(. "$hint" >/dev/null 2>&1; echo "${DELREQUIRES:-}")
+ fi
+ # union existing + new, dedup, first-seen order
+ combined=$(printf '%s\n' $existing "${new_deps[@]}" | awk 'NF && !seen[$0]++' | tr '\n' ' ')
+ combined="${combined% }"
+
+ if [[ -f "$hint" ]]; then
+ # no change needed? (all new deps already present) → skip, don't churn .bak
+ local cur_norm new_norm
+ cur_norm=$(printf '%s\n' $existing | awk 'NF && !seen[$0]++' | sort | tr '\n' ' ')
+ new_norm=$(printf '%s\n' $existing "${new_deps[@]}" | awk 'NF && !seen[$0]++' | sort | tr '\n' ' ')
+ [[ "$cur_norm" == "$new_norm" ]] && return 0
+
+ cp "$hint" "${hint}.bak"
+ if grep -q '^DELREQUIRES=' "$hint"; then
+ sed -i "s#^DELREQUIRES=.*#DELREQUIRES=\"$combined\"#" "$hint"
+ else
+ printf 'DELREQUIRES="%s"\n' "$combined" >> "$hint"
+ fi
+ else
+ printf 'DELREQUIRES="%s"\n' "$combined" > "$hint"
+ fi
+}
+
+# Bulk sweep: for every package in REPO_DIR whose REQUIRES contains a phantom
+# dep, ensure its hint carries the matching DELREQUIRES. Idempotent.
+fix_current() {
+ load_phantom_deps
+ if [[ ${#PHANTOM_DEPS[@]} -eq 0 ]]; then
+ echo "No phantom deps configured ($PHANTOM_DEPS_FILE). Nothing to do."
+ return 0
+ fi
+ echo "Sweeping for phantom deps: ${PHANTOM_DEPS[*]}"
+ local info prgnam deps count=0
+ while IFS= read -r info; do
+ prgnam=$(basename "$info" .info)
+ deps=$(phantom_deps_in_info "$info")
+ [[ -z "$deps" ]] && continue
+ merge_delrequires "$HINT_DIR/$prgnam.hint" $deps
+ echo " $prgnam <- DELREQUIRES: $deps"
+ (( count++ )) || true
+ done < <(find "$REPO_DIR" -mindepth 2 -maxdepth 3 -name '*.info' | sort)
+ echo "Done. $count package(s) with phantom deps."
+}
+
# Create new hint file
create_new_hint_file() {
cd "$HINT_DIR"
@@ -335,6 +426,14 @@ create_new_hint_file() {
echo 'ARCH="x86_64"' >> $normalized_file
fi
+ # auto-strip -current phantom deps present in the .info REQUIRES
+ load_phantom_deps
+ local phantom; phantom=$(phantom_deps_in_info "$info")
+ if [[ -n "$phantom" ]]; then
+ printf 'DELREQUIRES="%s"\n' "$phantom" >> "$normalized_file"
+ echo "added DELREQUIRES=\"$phantom\" (unneeded on -current)."
+ fi
+
if [[ -n "$VERSION" ]]; then
local old_version
old_version=$(grep '^VERSION=' "$normalized_file" | sed 's/VERSION="//;s/"$//')
@@ -872,8 +971,8 @@ check_updates() {
# Main function
main() {
local parsed
- parsed=$(getopt -o v:f:n:lcCdNhR \
- --long version:,hintfile:,new:,list,clean,check,delete,no-dl,help,review \
+ parsed=$(getopt -o v:f:n:lcCdNhRF \
+ --long version:,hintfile:,new:,list,clean,check,delete,no-dl,help,review,fix-current \
-n 'mkhint' -- "$@") || { show_help; exit 1; }
eval set -- "$parsed"
@@ -907,6 +1006,10 @@ main() {
COMMAND="check"
shift
;;
+ --fix-current|-F)
+ COMMAND="fix-current"
+ shift
+ ;;
--delete|-d)
COMMAND="delete"
shift
@@ -964,6 +1067,11 @@ main() {
exit 1
fi
+ if [[ "$COMMAND" == "fix-current" && ( -n "$VERSION" || -n "$HINT_FILE" || -n "$NEW_HINT_FILE" ) ]]; then
+ echo "Error: --fix-current cannot be combined with --version/--hintfile/--new" >&2
+ exit 1
+ fi
+
if [[ -n "$SHOW_LIST" || -n "$RUN_REVIEW" ]]; then
[[ -n "$SHOW_LIST" ]] && list_hint_files
if [[ -n "$RUN_REVIEW" ]]; then
@@ -988,6 +1096,9 @@ main() {
check)
check_updates "${DELETE_HINT_FILES[@]}"
;;
+ fix-current)
+ fix_current
+ ;;
update)
check_wget
if [[ -z "$VERSION" ]]; then
diff --git a/mkhint.bash-completion b/mkhint.bash-completion
index 896e9a9..6d05ea3 100644
--- a/mkhint.bash-completion
+++ b/mkhint.bash-completion
@@ -8,7 +8,7 @@ _mkhintfile_completions() {
repo_dir="/var/lib/sbopkg/SBo-danix"
hint_dir="/etc/slackrepo/SBo-danix/hintfiles"
- local all_flags="--version -v --hintfile -f --new -n --list -l --review -R --clean -c --check -C --delete -d --no-dl -N --help -h"
+ local all_flags="--version -v --hintfile -f --new -n --list -l --review -R --clean -c --check -C --fix-current -F --delete -d --no-dl -N --help -h"
# -R/--review takes any number of hint names; complete them repeatedly.
local w in_review=""
diff --git a/tests/mkhint_test.sh b/tests/mkhint_test.sh
index 4aa0c2d..421d7b6 100755
--- a/tests/mkhint_test.sh
+++ b/tests/mkhint_test.sh
@@ -18,6 +18,9 @@ setup() {
"$MOCK_REPO/development/ghpkg" \
"$MOCK_REPO/python/pypkg" \
"$MOCK_REPO/multimedia/yt-dlp" \
+ "$MOCK_REPO/development/gopkg" \
+ "$MOCK_REPO/development/rustpkg" \
+ "$MOCK_REPO/development/mixedpkg" \
"$MOCK_HINT" \
"$MOCK_TMP"
@@ -105,6 +108,54 @@ MAINTAINER="Test"
EMAIL="test@test.com"
EOF
+ # phantom-dep fixtures: packages requiring -current phantom deps
+ cat > "$MOCK_REPO/development/gopkg/gopkg.info" << 'EOF'
+PRGNAM="gopkg"
+VERSION="1.0.0"
+HOMEPAGE="https://example.com/gopkg"
+DOWNLOAD="https://example.com/gopkg-1.0.0.tar.gz"
+MD5SUM="33333333333333333333333333333333"
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+REQUIRES="google-go-lang"
+MAINTAINER="Test"
+EMAIL="test@test.com"
+EOF
+
+ cat > "$MOCK_REPO/development/rustpkg/rustpkg.info" << 'EOF'
+PRGNAM="rustpkg"
+VERSION="1.0.0"
+HOMEPAGE="https://example.com/rustpkg"
+DOWNLOAD="https://example.com/rustpkg-1.0.0.tar.gz"
+MD5SUM="44444444444444444444444444444444"
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+REQUIRES="rust-opt other-dep"
+MAINTAINER="Test"
+EMAIL="test@test.com"
+EOF
+
+ # mixedpkg: real deps + a phantom dep, gets a manual hint in some tests
+ cat > "$MOCK_REPO/development/mixedpkg/mixedpkg.info" << 'EOF'
+PRGNAM="mixedpkg"
+VERSION="3.2.1"
+HOMEPAGE="https://example.com/mixedpkg"
+DOWNLOAD="https://example.com/mixedpkg-3.2.1.tar.gz"
+MD5SUM="66666666666666666666666666666666"
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+REQUIRES="libfoo google-go-lang libbar"
+MAINTAINER="Test"
+EMAIL="test@test.com"
+EOF
+
+ # phantom-dep list consumed by --fix-current and --new
+ cat > "$MOCK_BASE/phantom-deps" << 'EOF'
+# deps unneeded on -current
+rust-opt
+google-go-lang
+EOF
+
# nvchecker config + keyfile for tests
cat > "$MOCK_BASE/nvchecker.toml" << EOF
[__config__]
@@ -130,6 +181,7 @@ run_mkhint() {
-e "s|HINT_DIR=\".*\"|HINT_DIR=\"$MOCK_HINT\"|" \
-e "s|TMP_DIR=\".*\"|TMP_DIR=\"$MOCK_TMP\"|" \
-e "s|NVCHECKER_CONFIG=\".*\"|NVCHECKER_CONFIG=\"$MOCK_BASE/nvchecker.toml\"|" \
+ -e "s|PHANTOM_DEPS_FILE=\".*\"|PHANTOM_DEPS_FILE=\"$MOCK_BASE/phantom-deps\"|" \
"$SCRIPT" > "$tmp_script"
bash "$tmp_script" "$@"
local rc=$?
@@ -975,6 +1027,77 @@ EOF
run_mkhint -C curl < <(printf 'Y\nn\n')
assert_contains "hint stores normalized version" "$MOCK_HINT/curl.hint" 'VERSION="2026_07_01"'
+# ── T50: --new on .info requiring a phantom dep → DELREQUIRES added ───────────
+echo ""
+echo "T50: --new phantom dep → DELREQUIRES added"
+rm -f "$MOCK_HINT/gopkg.hint"
+run_mkhint -n gopkg
+assert_contains "DELREQUIRES added" "$MOCK_HINT/gopkg.hint" 'DELREQUIRES="google-go-lang"'
+assert_contains "REQUIRES still commented" "$MOCK_HINT/gopkg.hint" '#REQUIRES='
+
+# ── T51: --new on .info with no phantom dep → no DELREQUIRES ──────────────────
+echo ""
+echo "T51: --new no phantom dep → no DELREQUIRES"
+rm -f "$MOCK_HINT/curl.hint" "$MOCK_HINT/curl.hint.bak"
+run_mkhint -n curl
+assert_not_contains "no DELREQUIRES" "$MOCK_HINT/curl.hint" 'DELREQUIRES'
+
+# ── T52: -F, dependent with no hint → minimal hint created ────────────────────
+echo ""
+echo "T52: --fix-current, no existing hint → minimal hint created"
+rm -f "$MOCK_HINT/rustpkg.hint" "$MOCK_HINT/rustpkg.hint.bak"
+run_mkhint -F
+assert_file_exists "rustpkg hint created" "$MOCK_HINT/rustpkg.hint"
+assert_contains "rustpkg DELREQUIRES" "$MOCK_HINT/rustpkg.hint" 'DELREQUIRES="rust-opt"'
+assert_not_contains "only rust-opt stripped" "$MOCK_HINT/rustpkg.hint" 'other-dep'
+
+# ── T53: -F, existing manual hint → .bak made, DELREQUIRES merged, rest intact ─
+echo ""
+echo "T53: --fix-current merges into existing hint, preserves content"
+cat > "$MOCK_HINT/mixedpkg.hint" << 'EOF'
+VERSION="3.2.1"
+ARCH="x86_64"
+DOWNLOAD="https://example.com/mixedpkg-3.2.1.tar.gz"
+MD5SUM="66666666666666666666666666666666"
+EOF
+rm -f "$MOCK_HINT/mixedpkg.hint.bak"
+run_mkhint -F
+assert_file_exists "backup made" "$MOCK_HINT/mixedpkg.hint.bak"
+assert_contains "DELREQUIRES merged" "$MOCK_HINT/mixedpkg.hint" 'DELREQUIRES="google-go-lang"'
+assert_contains "VERSION preserved" "$MOCK_HINT/mixedpkg.hint" 'VERSION="3.2.1"'
+assert_contains "DOWNLOAD preserved" "$MOCK_HINT/mixedpkg.hint" 'mixedpkg-3.2.1.tar.gz'
+
+# ── T54: -F re-run → idempotent, no duplicate deps, no new .bak churn ─────────
+echo ""
+echo "T54: --fix-current idempotent on second run"
+rm -f "$MOCK_HINT/mixedpkg.hint.bak"
+run_mkhint -F
+assert_not_contains "no duplicate dep" "$MOCK_HINT/mixedpkg.hint" 'google-go-lang google-go-lang'
+assert_file_not_exists "no churn .bak" "$MOCK_HINT/mixedpkg.hint.bak"
+
+# ── T55: empty/missing phantom-deps file → -F and --new are no-ops ────────────
+echo ""
+echo "T55: no phantom-deps file → --fix-current no-op"
+mv "$MOCK_BASE/phantom-deps" "$MOCK_BASE/phantom-deps.off"
+rm -f "$MOCK_HINT/gopkg.hint"
+out=$(run_mkhint -F)
+echo "$out" | grep -q "Nothing to do" \
+ && { echo " PASS: reports nothing to do"; (( PASS++ )); } \
+ || { echo " FAIL: reports nothing to do"; (( FAIL++ )); ERRORS+=("T55 message"); }
+assert_file_not_exists "no hint created" "$MOCK_HINT/gopkg.hint"
+run_mkhint -n gopkg
+assert_not_contains "new: no DELREQUIRES" "$MOCK_HINT/gopkg.hint" 'DELREQUIRES'
+mv "$MOCK_BASE/phantom-deps.off" "$MOCK_BASE/phantom-deps"
+
+# ── T56: -F combined with -v → exit 1 ─────────────────────────────────────────
+echo ""
+echo "T56: --fix-current with -v → exit 1"
+set +e
+run_mkhint -F -v 1.0.0 2>/dev/null
+code=$?
+set -e
+assert_exit_code "mutually exclusive" 1 "$code"
+
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
teardown