summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-07-10 18:50:12 +0200
committerDanilo M. <danix@danix.xyz>2026-07-10 18:50:12 +0200
commit0187a394d5c289d556d412a11bfcda2e665601c9 (patch)
tree5f47df16019873f9a55031905924835556fc1e3a
parentd69d5537e79cb53b5c91894cbccb3bbdd37c616e (diff)
downloadmkhintfile-0187a394d5c289d556d412a11bfcda2e665601c9.tar.gz
mkhintfile-0187a394d5c289d556d412a11bfcda2e665601c9.zip
release: v1.4.0 — RepoVer --list columnv1.4.0
Add a RepoVer column to --list showing the newest version built in PACKAGES_DIR (repo_version / repo_has_packages helpers). The column is auto opt-in: shown only when at least one package is built, hidden otherwise. It turns magenta when the built version lags the newer of the hint/.info version, a colour kept distinct from the green newer-source cell so the two comparisons don't blur. In a matched (yellow) row the RepoVer cell keeps its own colour. Tests T70-T74. Bump MKHINT_VERSION to 1.4.0 and cut the CHANGELOG section (also covering install.sh and the expanded dependency docs shipped since v1.3.1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--CHANGELOG.md12
-rw-r--r--CLAUDE.md7
-rw-r--r--README.md2
-rw-r--r--TODO.md2
-rwxr-xr-xmkhint81
-rw-r--r--mkhint.1.gzbin4162 -> 4256 bytes
-rw-r--r--mkhint.1.md7
-rwxr-xr-xtests/mkhint_test.sh92
8 files changed, 188 insertions, 15 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5acfdf..3a17d47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,18 @@ All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
[Semantic Versioning](https://semver.org/).
+## [1.4.0] - 2026-07-10
+
+### Added
+- `--list`/`-l`: new `RepoVer` column showing the newest version actually built
+ in `PACKAGES_DIR`. Auto opt-in: the column appears only when at least one
+ package is built, and is hidden entirely otherwise. It is coloured magenta
+ when the built version lags the newer of the hint/`.info` version, so you can
+ spot hints that have been bumped but not yet rebuilt.
+- `install.sh`: install or uninstall the binary, bash completion, and man page.
+ Runs system-wide as root or user-only (XDG `~/.local`) as a normal user;
+ detects the bash-completion dir and warns when the install dir is off `PATH`.
+
## [1.3.1] - 2026-07-10
### Added
diff --git a/CLAUDE.md b/CLAUDE.md
index a8e0b9e..81d1527 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -132,6 +132,11 @@ Test coverage:
| T62 | `-l` equal versions — whole row yellow, no green |
| T63 | `-l` hint with populated `DELREQUIRES` — `✓` in DelReq column |
| T64 | `-l` hint without `DELREQUIRES` — DelReq blank |
+| T70 | `repo_version` helper — extracts version from newest built `.txz` |
+| T71 | `-l` no packages — RepoVer column hidden entirely |
+| T72 | `-l` built pkg behind newest source — RepoVer magenta |
+| T73 | `-l` built pkg == newest source — RepoVer plain |
+| T74 | `-l` matched (yellow) row, built pkg behind — magenta RepoVer inside row |
| T68 | `-l` hint with `NODOWNLOAD=yes` — `✓` in NoDL column |
| T69 | `-l` hint without `NODOWNLOAD` — NoDL blank |
| T65 | `mkhint -v` prints `mkhint <version>`, exit 0 |
@@ -147,7 +152,7 @@ When adding new features, add a corresponding test case to `tests/mkhint_test.sh
- `add_nvchecker_section` autodetection: `_detect_nvchecker_source` recognizes github, gitlab, bitbucket, gitea, codeberg, and pagure owner/repo URLs plus the pypi, npm, gems, crates.io (`cratesio`), cpan, hackage, packagist, and cran registries, else leaves a commented stub. Github gets `use_latest_release = true` with commented `use_max_tag`/`prefix = "v"` fallbacks; the other forges get `use_max_tag = true` plus a commented `# prefix = "v"`; registries use the package name parsed from the URL via `_registry_name_from_url`, falling back to `PRGNAM` when no host-specific pattern matches. Either way, the managed stanza (freshly added or already present) is echoed fenced on stdout via `_extract_nvchecker_section`.
- `--new` when hint already exists: backs up old, creates empty skeleton.
- `--hintfile` with no `-V`: queries nvchecker for latest version, shows current vs. latest, prompts to accept/override/decline. After accepting, updates the hint, then dispatches slackrepo via `prompt_slackrepo`; `nvtake` (syncing nvchecker's oldver keyfile) fires from `run_slackrepo` only when you confirm the slackrepo question, not on version-accept. Decline slackrepo and the hint stays bumped but oldver lags, so nvchecker keeps reporting the update until it's built.
-- `--list` / `-l`: lists hints with `HintVer`/`SBOVer` plus a `DelReq` and a `NoDL` column (both before `Created`) showing `✓` when the hint has a populated `DELREQUIRES` (detected via `grep '^DELREQUIRES="..*"'`) resp. `NODOWNLOAD=yes` (via `grep -qi '^NODOWNLOAD=yes'`), each padded with `_pad_glyph` so the multibyte glyph stays aligned. Hints with no `VERSION` are skipped (and excluded from the total). Version columns are 22 wide to fit long version strings. Coloring: rows where the two versions are byte-equal get the whole row yellow (and populate `MATCHED_PKGS` for `-R`); when they differ, `sort -V` picks the newer side and only that version cell is green (version strings are pre-padded to 22 before the color wrap so the escape bytes don't break alignment); an absent/empty `SBOVer` leaves the row plain. Color only on a TTY (or `MKHINT_FORCE_COLOR`); plain when piped. Legend `(yellow row = versions match; green = newer side)` prints when any row matched. With package names (`-l foo bar`), the table is skipped and each named hint is shown side by side with its `.info` via `_show_hint_diff` (shared with `--review`); missing hint → exit 2.
+- `--list` / `-l`: lists hints with `HintVer`/`SBOVer` plus a `DelReq` and a `NoDL` column (both before `Created`) showing `✓` when the hint has a populated `DELREQUIRES` (detected via `grep '^DELREQUIRES="..*"'`) resp. `NODOWNLOAD=yes` (via `grep -qi '^NODOWNLOAD=yes'`), each padded with `_pad_glyph` so the multibyte glyph stays aligned. Hints with no `VERSION` are skipped (and excluded from the total). Version columns are 22 wide to fit long version strings. Coloring: rows where the two versions are byte-equal get the whole row yellow (and populate `MATCHED_PKGS` for `-R`); when they differ, `sort -V` picks the newer side and only that version cell is green (version strings are pre-padded to 22 before the color wrap so the escape bytes don't break alignment); an absent/empty `SBOVer` leaves the row plain. Color only on a TTY (or `MKHINT_FORCE_COLOR`); plain when piped. Legend `(yellow row = versions match; green = newer side)` prints when any row matched. A `RepoVer` column (before `Category`) is auto opt-in: shown only when `repo_has_packages` finds at least one `PACKAGES_DIR/*/*/*.txz` (else the whole column, header and cells, is omitted and the table renders exactly as before). Its value comes from `repo_version <pkg>` (newest match of `PACKAGES_DIR/*/<pkg>/<pkg>-*.txz` by `sort -V`, basename stripped of the `<pkg>-` prefix and `-<arch>-<build>.txz` suffix), and it is coloured **magenta** (`tput setaf 5` / `\033[35m`) when the built version lags the newer of `{HintVer, SBOVer}` (compared via `sort -V`), plain when level/ahead or when no package is built. In a matched (yellow) row the RepoVer cell carries its own colour: yellow is closed before it and reopened after (`c_on lead c_off rv c_on trail c_off`) so a magenta RepoVer is not swallowed by the row yellow. When the column is shown the legend gains `; magenta = built pkg behind`. With package names (`-l foo bar`), the table is skipped and each named hint is shown side by side with its `.info` via `_show_hint_diff` (shared with `--review`); missing hint → exit 2.
- `--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 (rewriting the hint on confirm), then prompts slackrepo for all updated packages. `nvtake` (syncing nvchecker's oldver) is NOT called at hint-rewrite time; it fires from `run_slackrepo`, per package, only after you answer Y to the slackrepo question and just before slackrepo runs. So answering Y acknowledges the versions; if slackrepo is then stopped or fails, just rerun it and nvchecker won't re-report versions already committed. Declining slackrepo (n) leaves the hints bumped but oldver behind, so those updates re-report next run. The slackrepo action is chosen per package by `pkg_in_repo` (glob `PACKAGES_DIR/*/<pkg>/<pkg>-*.txz`): a package already built in `PACKAGES_DIR` gets `slackrepo update`, an absent one gets `slackrepo build`. `--check` partitions its updated set into built vs. fresh and prompts `update` then `build` separately (each only if its list is non-empty), both dispatched through the shared `run_slackrepo` helper. 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.
diff --git a/README.md b/README.md
index a9f6ad9..5b30265 100644
--- a/README.md
+++ b/README.md
@@ -151,6 +151,8 @@ When updating a hint with multiline DOWNLOAD, mkhint:
Lists each hint file with its `HintVer` (version in the hint), `SBOVer` (version in the repository `.info`), a `DelReq` column showing `✓` when the hint carries a populated `DELREQUIRES`, and a `NoDL` column showing `✓` when the hint has `NODOWNLOAD=yes`. Hints with no `VERSION` set (e.g. pure `DELREQUIRES` hints) are skipped. Version columns are wide enough for long version strings. Rows where the two versions are equal are shown in yellow, so you can see at a glance which hints are now redundant with the upstream SBo version. When the versions differ, the newer of the two (decided with `sort -V`) is shown in green, so you can tell at a glance whether the hint or the repository is ahead. Color is used only on a TTY; piped output is plain. A legend is printed when any row matched.
+When `PACKAGES_DIR` holds at least one built package, an extra `RepoVer` column appears (auto opt-in; hidden when no packages are built). It shows the version of the newest built `*.txz` for the package and turns **magenta** when that built version is behind the newer of `HintVer`/`SBOVer`, so you can spot at a glance which hints have been bumped but not yet rebuilt. When the built package is level with the newest source version it is shown plain.
+
With one or more package names (`-l foo bar`), the table is skipped; instead each named hint is shown side by side with its `.info` (`git diff --no-index` if git is available, otherwise `diff -y`). A missing hint exits 2.
```bash
diff --git a/TODO.md b/TODO.md
index 99599d3..b545f02 100644
--- a/TODO.md
+++ b/TODO.md
@@ -4,7 +4,7 @@
- [ ] add a `--special` flag to add a package to the bundled list, asking for a link to it's upstream deps list file
- [~] split `mkhint` into multiple files — **deferred (2026-07-08).** 1568 lines / 46 functions with clear section markers is under the threshold where a split pays off, and bash has no module system: "split" means `source lib/*.sh`, which complicates the single-binary VM deploy (`scp mkhint` → copy a dir + fixed libdir, or a concatenate build step) and means auditing shared globals (`REPO_DIR`, `VERSION`, `FORCE`, config-source order) crossing file boundaries. Revisit trigger: file grows toward ~2500+ lines, OR a change to one area starts breaking an unrelated one, OR you can't find where something lives. When that happens, extract ONLY the self-contained bundled-dep block (`# ── bundled-dep manifest handling ──` … `# ── end`, already has begin/end markers and its own T-BM tests) as a single `lib/bundle.sh` — do not do a full teardown.
- [x] **(this session):** add a NoDL column to `--list` (✓ when `NODOWNLOAD=yes`), same `_pad_glyph` treatment as DelReq. Tests T68/T69.
- - [ ] add a `RepoVer` column to `--list`: the latest version we actually ship for the package in `PACKAGES_DIR` (parse from the built `*.txz` name, glob `PACKAGES_DIR/*/<pkg>/<pkg>-*.txz` like `pkg_in_repo`). Color: reuse the SBo-vs-hint `sort -V` green-the-newer-cell logic, but the comparison is between the **newest of {hint, SBo}** and the repo (built) version, so you see at a glance whether what's shipped is behind the newest source-side version. No repo package → blank/plain cell (same as absent SBOVer). Needs a helper to extract the version from the txz basename (strip `<pkg>-` prefix and the `-<arch>-<build>.txz` suffix).
+ - [x] add a `RepoVer` column to `--list`: newest version built in `PACKAGES_DIR` (glob `PACKAGES_DIR/*/<pkg>/<pkg>-*.txz`, `repo_version` helper strips the `<pkg>-` prefix and `-<arch>-<build>.txz` suffix). Auto opt-in via `repo_has_packages` (column hidden when no packages built). Coloured **magenta** when the built version lags the newer of {hint, SBo} (its own colour, kept distinct from the green newer-source cell to avoid ambiguity), plain when level. Tests T70–T74.
- [x] **v1.2.4:** suppress `prompt_continuation_urls` for manifest-listed packages (Phase 2 already corrects those lines; avoids the double-touch during the primary bump)
- [x] **v1.2.5: expand nvchecker source autodetection.** `_detect_nvchecker_source` maps github/gitlab/bitbucket/gitea/codeberg/pagure (owner/repo) plus pypi/npm/gems/cratesio/cpan/hackage/packagist/cran registries in `add_nvchecker_section` (single global emitter, used by `--new` and `--check` populate). github → `use_latest_release` + commented `use_max_tag`/`prefix` fallbacks; other forges → `use_max_tag` + commented `# prefix = "v"`; registries → name from URL via `_registry_name_from_url`, fall back to PRGNAM. Unrecognised → `# TODO` stub.
- [x] **v1.2.5:** `--new`/`-n` echoes the managed nvchecker stanza fenced on stdout (added or already-present, via `_extract_nvchecker_section`).
diff --git a/mkhint b/mkhint
index cff7dc5..38da649 100755
--- a/mkhint
+++ b/mkhint
@@ -45,7 +45,7 @@ if [[ ! -d $TMP_DIR ]]; then
mkdir $TMP_DIR
fi
-readonly MKHINT_VERSION="1.3.1"
+readonly MKHINT_VERSION="1.4.0"
# Variables
VERSION=""
@@ -109,18 +109,28 @@ list_hint_files() {
fi
# Color only on a TTY, or when forced for tests. tput optional.
- local c_on="" c_off="" g_on=""
+ # c_on=yellow (matched row), g_on=green (newer source side),
+ # m_on=magenta (built package behind newest source).
+ local c_on="" c_off="" g_on="" m_on=""
if [[ -n "$MKHINT_FORCE_COLOR" || -t 1 ]]; then
if command -v tput &>/dev/null && tput setaf 3 &>/dev/null; then
- c_on=$(tput setaf 3); g_on=$(tput setaf 2); c_off=$(tput sgr0)
+ c_on=$(tput setaf 3); g_on=$(tput setaf 2); m_on=$(tput setaf 5); c_off=$(tput sgr0)
else
- c_on=$'\033[33m'; g_on=$'\033[32m'; c_off=$'\033[0m'
+ c_on=$'\033[33m'; g_on=$'\033[32m'; m_on=$'\033[35m'; c_off=$'\033[0m'
fi
fi
+ # RepoVer column is auto opt-in: shown only when PACKAGES_DIR has packages.
+ local show_repo=""
+ repo_has_packages && show_repo=1
+
echo "Hint files in: $HINT_DIR"
echo "======================================================="
- printf "%-40s %22s %22s %-20s %-6s %-5s %s\n" "File" "HintVer" "SBOVer" "Category" "DelReq" "NoDL" "Created"
+ if [[ -n "$show_repo" ]]; then
+ printf "%-40s %22s %22s %22s %-20s %-6s %-5s %s\n" "File" "HintVer" "SBOVer" "RepoVer" "Category" "DelReq" "NoDL" "Created"
+ else
+ printf "%-40s %22s %22s %-20s %-6s %-5s %s\n" "File" "HintVer" "SBOVer" "Category" "DelReq" "NoDL" "Created"
+ fi
echo "-------------------------------------------------------"
MATCHED_PKGS=()
@@ -150,11 +160,36 @@ list_hint_files() {
local hv sv
hv=$(printf "%22s" "$VER")
sv=$(printf "%22s" "$SBO_VER")
+
+ # RepoVer cell (only when the column is on). Compare the newest of
+ # {hint, SBo} against the built version; magenta when the built
+ # package is behind, plain otherwise or when no package is built.
+ local rv="" rv_cell=""
+ if [[ -n "$show_repo" ]]; then
+ local REPO_VER; REPO_VER=$(repo_version "$pkg")
+ rv=$(printf "%22s" "$REPO_VER")
+ if [[ -n "$REPO_VER" ]]; then
+ local newest_src
+ newest_src=$(printf '%s\n%s\n' "$VER" "$SBO_VER" | sort -V | tail -1)
+ if [[ "$newest_src" != "$REPO_VER" && \
+ "$(printf '%s\n%s\n' "$newest_src" "$REPO_VER" | sort -V | tail -1)" == "$newest_src" ]]; then
+ rv="${m_on}${rv}${c_off}" # built pkg behind newest source
+ fi
+ fi
+ rv_cell=" $rv"
+ fi
+
if [[ -n "$SBO_VER" && "$VER" == "$SBO_VER" ]]; then
- # equal → whole row yellow
- local row
- row=$(printf "%-40s %s %s %-20s %s %s %s" "$name" "$hv" "$sv" "$category" "$dr" "$nd" "$date")
- printf "%s%s%s\n" "$c_on" "$row" "$c_off"
+ # equal → whole row yellow, but the RepoVer cell keeps its own
+ # color (close yellow before it, reopen after) so magenta shows.
+ local lead trail
+ lead=$(printf "%-40s %s %s" "$name" "$hv" "$sv")
+ trail=$(printf "%-20s %s %s %s" "$category" "$dr" "$nd" "$date")
+ if [[ -n "$show_repo" ]]; then
+ printf "%s%s%s %s %s%s%s\n" "$c_on" "$lead" "$c_off" "$rv" "$c_on" "$trail" "$c_off"
+ else
+ printf "%s%s %s%s\n" "$c_on" "$lead" "$trail" "$c_off"
+ fi
MATCHED_PKGS+=("$pkg")
matched=$((matched + 1))
else
@@ -168,7 +203,7 @@ list_hint_files() {
sv="${g_on}${sv}${c_off}"
fi
fi
- printf "%-40s %s %s %-20s %s %s %s\n" "$name" "$hv" "$sv" "$category" "$dr" "$nd" "$date"
+ printf "%-40s %s %s%s %-20s %s %s %s\n" "$name" "$hv" "$sv" "$rv_cell" "$category" "$dr" "$nd" "$date"
fi
count=$((count + 1))
fi
@@ -181,7 +216,11 @@ list_hint_files() {
echo "======================================================="
echo "Total: $count file(s)"
if [[ $matched -gt 0 && -n "$c_on" ]]; then
- echo "(yellow row = versions match; green = newer side)"
+ if [[ -n "$show_repo" ]]; then
+ echo "(yellow row = versions match; green = newer side; magenta = built pkg behind)"
+ else
+ echo "(yellow row = versions match; green = newer side)"
+ fi
fi
}
@@ -1548,6 +1587,26 @@ pkg_in_repo() {
compgen -G "${PACKAGES_DIR%/}/*/${pkg}/${pkg}-*.txz" >/dev/null 2>&1
}
+# Echo the version of the newest built package for <pkg> in PACKAGES_DIR, or
+# nothing if none is present. Parses the .txz basename: strips the "<pkg>-"
+# prefix and the "-<arch>-<build>.txz" suffix (Slackware naming).
+repo_version() {
+ local pkg="$1" newest base ver
+ newest=$(compgen -G "${PACKAGES_DIR%/}/*/${pkg}/${pkg}-*.txz" 2>/dev/null \
+ | sort -V | tail -1)
+ [[ -z "$newest" ]] && return 0
+ base=$(basename "$newest")
+ ver="${base#"${pkg}-"}" # drop leading "<pkg>-"
+ ver="${ver%-*-*.txz}" # drop trailing "-<arch>-<build>.txz"
+ printf '%s' "$ver"
+}
+
+# True when PACKAGES_DIR holds at least one built package. Used by --list to
+# decide whether to show the RepoVer column at all (auto opt-in).
+repo_has_packages() {
+ compgen -G "${PACKAGES_DIR%/}/*/*/*.txz" >/dev/null 2>&1
+}
+
# Prompt to run `slackrepo <action> <pkgs...>`; no-op on empty list.
run_slackrepo() {
local action="$1"; shift
diff --git a/mkhint.1.gz b/mkhint.1.gz
index 75c4e10..ce2c222 100644
--- a/mkhint.1.gz
+++ b/mkhint.1.gz
Binary files differ
diff --git a/mkhint.1.md b/mkhint.1.md
index a3bffe2..4be6ff4 100644
--- a/mkhint.1.md
+++ b/mkhint.1.md
@@ -84,8 +84,11 @@ Idempotent. Mutually exclusive with **-V**, **-f**, **-n**.
**\--list**, **-l** \[*FILE*...]
: List all hint files with their hint version, `.info` version, a
`DelReq` marker (populated `DELREQUIRES`), and a `NoDL` marker
-(`NODOWNLOAD=yes`). With package names, show each hint side by side with its
-`.info` instead of the table.
+(`NODOWNLOAD=yes`). When `PACKAGES_DIR` holds at least one built package, an
+extra `RepoVer` column is shown (auto opt-in) with the newest built version,
+coloured magenta when it lags the newer of the hint/`.info` version. With
+package names, show each hint side by side with its `.info` instead of the
+table.
**\--review**, **-R** \[*FILE*...]
: Review hints. With no arguments, iterate the hints whose version matches the
diff --git a/tests/mkhint_test.sh b/tests/mkhint_test.sh
index 4c74501..a21ff86 100755
--- a/tests/mkhint_test.sh
+++ b/tests/mkhint_test.sh
@@ -1470,6 +1470,98 @@ echo "$out" | grep curl.hint | grep -q "✓" \
&& { echo " FAIL: ✓ shown without NODOWNLOAD"; (( FAIL++ )); ERRORS+=("T69 check"); } \
|| { echo " PASS: no ✓ without NODOWNLOAD"; (( PASS++ )); }
+# ── T70: repo_version helper parses txz basename ─────────────────────────────
+echo ""
+echo "T70: repo_version extracts version from newest built .txz"
+rm -rf "$MOCK_PKGS"
+seed_pkg network curl 8.4.0
+seed_pkg network curl 8.6.0
+ver=$(run_mkhint_fn repo_version curl 2>/dev/null)
+[[ "$ver" == "8.6.0" ]] \
+ && { echo " PASS: repo_version = 8.6.0"; (( PASS++ )); } \
+ || { echo " FAIL: repo_version got '$ver'"; (( FAIL++ )); ERRORS+=("T70"); }
+rm -rf "$MOCK_PKGS"
+
+# ── T71: -l RepoVer column hidden when no packages ───────────────────────────
+echo ""
+echo "T71: -l hides RepoVer column when PACKAGES_DIR is empty"
+rm -rf "$MOCK_PKGS"
+rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null
+cat > "$MOCK_HINT/curl.hint" << 'EOF'
+VERSION="8.5.0"
+ARCH="x86_64"
+DOWNLOAD="https://curl.se/download/curl-8.5.0.tar.gz"
+MD5SUM="abc123def456abc123def456abc123de"
+EOF
+out=$(run_mkhint -l 2>&1)
+echo "$out" | grep -q "RepoVer" \
+ && { echo " FAIL: RepoVer header shown with no packages"; (( FAIL++ )); ERRORS+=("T71"); } \
+ || { echo " PASS: RepoVer column hidden"; (( PASS++ )); }
+
+# ── T72: -l RepoVer shown, built pkg behind → magenta ────────────────────────
+echo ""
+echo "T72: -l RepoVer magenta when built pkg behind newest source"
+rm -rf "$MOCK_PKGS"
+rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null
+# hint 8.6.0 (newest source), .info 8.5.0, built pkg 8.4.0 → repo behind
+cat > "$MOCK_HINT/curl.hint" << 'EOF'
+VERSION="8.6.0"
+ARCH="x86_64"
+DOWNLOAD="https://curl.se/download/curl-8.6.0.tar.gz"
+MD5SUM="abc123def456abc123def456abc123de"
+EOF
+seed_pkg network curl 8.4.0
+out=$(MKHINT_FORCE_COLOR=1 run_mkhint -l 2>&1)
+echo "$out" | grep -q "RepoVer" \
+ && { echo " PASS: RepoVer header shown"; (( PASS++ )); } \
+ || { echo " FAIL: RepoVer header missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T72 header"); }
+# magenta (ESC[35m) before the repo version 8.4.0
+echo "$out" | grep curl.hint | grep -q $'\033\[35m.*8\.4\.0' \
+ && { echo " PASS: RepoVer magenta when behind"; (( PASS++ )); } \
+ || { echo " FAIL: RepoVer not magenta"; echo "$out" | grep curl.hint | cat -v | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T72 magenta"); }
+rm -rf "$MOCK_PKGS"
+
+# ── T73: -l RepoVer plain when built pkg current ─────────────────────────────
+echo ""
+echo "T73: -l RepoVer plain when built pkg == newest source"
+rm -rf "$MOCK_PKGS"
+rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null
+# hint 8.6.0, built pkg 8.6.0 → repo current, no magenta
+cat > "$MOCK_HINT/curl.hint" << 'EOF'
+VERSION="8.6.0"
+ARCH="x86_64"
+DOWNLOAD="https://curl.se/download/curl-8.6.0.tar.gz"
+MD5SUM="abc123def456abc123def456abc123de"
+EOF
+seed_pkg network curl 8.6.0
+out=$(MKHINT_FORCE_COLOR=1 run_mkhint -l 2>&1)
+echo "$out" | grep curl.hint | grep -q $'\033\[35m' \
+ && { echo " FAIL: RepoVer magenta when current"; echo "$out" | grep curl.hint | cat -v | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T73 magenta"); } \
+ || { echo " PASS: RepoVer plain when current"; (( PASS++ )); }
+rm -rf "$MOCK_PKGS"
+
+# ── T74: -l yellow row, built pkg behind → magenta RepoVer inside row ─────────
+echo ""
+echo "T74: -l matched (yellow) row still shows magenta RepoVer when behind"
+rm -rf "$MOCK_PKGS"
+rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null
+# hint 8.5.0 == .info 8.5.0 (yellow row), built pkg 8.4.0 behind
+cat > "$MOCK_HINT/curl.hint" << 'EOF'
+VERSION="8.5.0"
+ARCH="x86_64"
+DOWNLOAD="https://curl.se/download/curl-8.5.0.tar.gz"
+MD5SUM="abc123def456abc123def456abc123de"
+EOF
+seed_pkg network curl 8.4.0
+out=$(MKHINT_FORCE_COLOR=1 run_mkhint -l 2>&1)
+echo "$out" | grep curl.hint | grep -q $'\033\[33m' \
+ && { echo " PASS: matched row yellow"; (( PASS++ )); } \
+ || { echo " FAIL: matched row not yellow"; echo "$out" | grep curl.hint | cat -v | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T74 yellow"); }
+echo "$out" | grep curl.hint | grep -q $'\033\[35m.*8\.4\.0' \
+ && { echo " PASS: RepoVer magenta inside yellow row"; (( PASS++ )); } \
+ || { echo " FAIL: RepoVer magenta lost in yellow row"; echo "$out" | grep curl.hint | cat -v | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T74 magenta"); }
+rm -rf "$MOCK_PKGS"
+
# ── T65: mkhint -v prints tool version ───────────────────────────────────────
echo ""
echo "T65: -v prints 'mkhint <version>' and exits 0"