# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this is Two deliverables: `nvchecker.toml` (a merged nvchecker config) and `nvtable` (a Bash script that joins nvchecker's upstream results against the version installed on this host and the version shipped by the SlackBuild repo that packages it). No build step, no test framework, no dependencies to install. ## Verifying changes There is no test suite. These are the checks that actually catch regressions: ```bash bash -n nvtable # syntax only ./nvtable -n -C # default view, no state writes, no color ./nvtable -n -C -a # every tracked package ``` **Always pass `-n` while developing.** Without it `nvtable` runs `nvtake`, which rewrites `~/.config/nvchecker/old_ver.json` as a side effect of merely displaying the table. To validate the config without touching live state, rewrite `[__config__]` to point at scratch paths and run there: ```bash sed 's|^oldver.*|oldver = "/tmp/o.json"|; s|^newver.*|newver = "/tmp/n.json"|' \ nvchecker.toml > /tmp/run.toml echo '{"version":2,"data":{}}' > /tmp/o.json nvchecker -c /tmp/run.toml 2>&1 | grep -oE '\] [^:]+: no-result' ``` A stanza that resolves prints `updated to `; a broken one prints `no-result`. The strongest check on a stanza is that the version it returns equals the `VERSION=` in that package's `.info` — that caught a stanza pointing at an entirely wrong upstream project (`mutagen-io/mutagen` vs `quodlibet/mutagen`) that still resolved cleanly. `nvtable` reads `new_ver.json` and never refreshes it. After renaming stanzas the table reports the old names as untracked until `nvchecker` runs again. ## Live state this touches The repo is self-contained, but the installed tool reaches outside it: | Path | Role | |------|------| | `~/.config/nvchecker/nvchecker.toml` | the deployed copy of this config | | `~/.config/nvchecker/{old,new}_ver.json` | nvchecker state; `nvtake` writes `old_ver` | | `~/bin/nvtable` | symlink to the script in this repo | | `~/.profile.d/qar.sh` | login script; calls `nvtable` where `nvcmp` used to run | Because `qar.sh` runs on every interactive shell, a bug in `nvtable` shows up in every new terminal. Test with `-n -C` before touching the deployed copy. `NVTABLE_CONF` and `NVTABLE_NEWVER` override the config and state paths, which is how to exercise the real script against scratch data. ## Architecture `nvtable` is one pass with three joins. The subtlety is entirely in the joins, not the control flow. **Name resolution.** The design rule is that a stanza name equals the Slackware package name, so most rows join with no mapping at all. Two alias maps cover the rest, and they are separate because the mappings differ per direction: `ALIAS` maps a stanza to installed package name(s) (`kvantum` → `kvantum-qt5 kvantum-qt6`, one-to-many), while `REPO_ALIAS` maps a stanza to a SlackBuild directory (`kitty` → `kitty-bin`). Adding one map for both breaks the other direction. **Installed version** resolves through four sources in order: the `OPT_JSON` map (apps unpacked under `/opt` that are none of the below, currently Typora, declared as `:`), `/var/log/packages`, the appimage-updater state file, then `flatpak list`. **Repo version** is the `VERSION=` line from `///.info`. Note the category level: `.info` files sit at depth 3, not 2. The repositories scanned are the `REPOS` array at the top of the script. ### Traps that produced real bugs here These are non-obvious and each one silently produced wrong output: - **A Slackware package filename cannot be parsed by counting dashes.** It is `PRGNAM-VERSION-ARCH-BUILD`, but VERSION may itself contain a dash (`solvespace-3.1-659215d`). Lookups anchor on the package name being searched for, and require the following character to be a digit so `kvantum` does not match `kvantum-qt5`. - **Installed versions carry build metadata upstream never has** (`r8125` is `9.018.00_6.18.41`, version plus kernel). `vers_equal` treats installed as equal when it merely extends upstream at a component boundary; strict equality marks such packages permanently behind. - **`read` with `IFS=$'\t'` collapses consecutive tabs**, so an empty field shifts every later field left. Row records are packed with `|` for this reason. Do not "simplify" it back to tabs. - **Installed-behind and repo-behind are independent conditions.** A row can be both (🔴🟡). Collapsing the marker to a single worst-case state hides the packaging work. - **Column padding must happen before color codes are added**, or `printf` counts escape bytes as width. ### nvtake When a package is caught up, its name is collected and one `nvtake` call records them all, moving `new_ver` → `old_ver` so nvchecker's state tracks this system rather than only upstream history. `-n` disables it. The call is wrapped in `flock -n` because `nvtake` does read-modify-write on `old_ver.json` with no locking of its own, and `qar.sh` runs on every shell — so several terminals opening at once means concurrent writers. On lock contention the take is skipped rather than queued; the next run recomputes the same set. ## nvchecker.toml conventions - **Stanza name = Slackware package name** wherever a package exists. Renaming a stanza silently orphans its history in `old_ver.json` (the old key stays, nothing matches the new one) and breaks any external consumer that looks entries up by name — `appimage-updater` reads this config and requires the entries `Feishin` and `cherrytree` verbatim. - **`# repo=` comments** mark which repository ships each package (`sbo`, `my`, `sps`); entries with none are tracked-only. - **A name containing a dot must be quoted.** `[llama.cpp-vulkan]` parses as a nested `llama` table and fails with `no source specified`. - **`use_latest_release` on GitHub needs `prefix = "v"`** when the tags carry one, otherwise the version comes back as `v8.30.1` and never matches a `.info` VERSION. - Prefer `use_latest_release` over `use_max_tag`: `use_max_tag` hits a tightly rate-limited endpoint. - Per-repo `.extras/nvchecker.toml` files are deliberately kept in their own repositories for standalone sweeps. This config is the superset; they drift. The GitHub token lives in `~/.config/nvchecker/keys.toml`, which `.gitignore` excludes so it cannot be committed alongside the config.