aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/superpowers/specs/2026-06-13-nvchecker-integration-design.md206
1 files changed, 206 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-06-13-nvchecker-integration-design.md b/docs/superpowers/specs/2026-06-13-nvchecker-integration-design.md
new file mode 100644
index 0000000..ad273e3
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-13-nvchecker-integration-design.md
@@ -0,0 +1,206 @@
+# nvchecker integration for mkhint — design
+
+Date: 2026-06-13
+
+## Summary
+
+Integrate [nvchecker](https://github.com/lilydjwg/nvchecker) into `mkhint` so the
+tool can discover the latest upstream version of a package and use it when
+updating hint files. Three features:
+
+1. `--new` writes an nvchecker `[section]` for the package (auto-detected from the
+ `.info` file where possible).
+2. `--hintfile pkg` with no `-v` queries nvchecker, suggests the latest version,
+ and lets the user accept or override it.
+3. `--check` / `-C` runs nvchecker for every hint file, reports outdated packages,
+ asks per-package whether to update, applies the updates, then runs a single
+ `slackrepo update` for all updated packages.
+
+## Dependencies (new)
+
+- `nvchecker` — fetches the latest upstream version.
+- `nvtake` — ships with nvchecker; syncs the keyfile after an update is taken.
+- `jq` — parses the nvchecker newver JSON keyfile.
+
+A new `check_nvchecker` function validates that `nvchecker`, `nvtake`, and `jq`
+are on `PATH`, modelled on the existing `check_wget`. It is called at the start of
+any code path that needs nvchecker (Features 2 and 3; Feature 1 needs none of the
+binaries, only writes config).
+
+## Configuration
+
+- New constant near the top of `mkhint`, alongside `REPO_DIR` / `HINT_DIR`:
+
+ ```bash
+ NVCHECKER_CONFIG="$HOME/.config/nvchecker/nvchecker.toml"
+ ```
+
+- The file holds a single `[__config__]` section (oldver/newver keyfile paths)
+ plus one `[pkg]` section per package. mkhint never creates `[__config__]` — the
+ user sets that up once when first configuring nvchecker. mkhint only appends
+ `[pkg]` sections (Feature 1) and reads the newver keyfile (Features 2 and 3).
+- The newver keyfile path is read from `[__config__]` (key `newver`) via a small
+ TOML-grep, so mkhint knows where nvchecker wrote results. If `[__config__]` or
+ the `newver` key is missing, nvchecker-dependent features error out with a
+ message telling the user to configure `__config__`.
+
+## Source of truth
+
+The hint file's `VERSION="..."` field is the authoritative current version.
+nvchecker's newver keyfile provides the latest upstream version. mkhint compares
+the two itself. After a hint file is successfully updated to the new version,
+mkhint runs `nvtake pkg` so nvchecker's oldver is brought in sync and the package
+is not re-reported as outdated on the next run.
+
+## Version comparison
+
+Comparison uses `sort -V` to determine direction, not plain string inequality:
+
+```bash
+# newest of the two, by version sort
+newest=$(printf '%s\n%s\n' "$current" "$latest" | sort -V | tail -1)
+```
+
+- `current == latest` (exact string match) → up to date, skip silently.
+- `current != latest` and `newest == latest` → update available (`current → latest`).
+- `current != latest` and `newest == current` → upstream appears older; show as
+ `current → latest (?downgrade)`.
+
+The user confirms every package individually before any change, so the human is
+the final gate; `sort -V` only classifies and orders the report.
+
+## Feature 1 — `--new` writes an nvchecker `[section]`
+
+New function `add_nvchecker_section pkg info_file`, called from the `--new` path
+after the hint file is created.
+
+Behaviour:
+
+1. If `nvchecker.toml` already contains a `[pkg]` section, do nothing (no
+ duplicate, no overwrite).
+2. Read `DOWNLOAD` and `HOMEPAGE` from the `.info` file.
+3. Detect the source:
+ - URL matches `github.com/OWNER/REPO` →
+ ```toml
+ [pkg]
+ source = "github"
+ github = "OWNER/REPO"
+ use_max_tag = true
+ ```
+ - URL matches `pypi.org` or `files.pythonhosted.org` →
+ ```toml
+ [pkg]
+ source = "pypi"
+ pypi = "NAME"
+ ```
+ - Otherwise write a commented stub with a TODO and examples of the common
+ source types (`regex` on a URL, `git` tags), so the user can fill it in:
+ ```toml
+ [pkg]
+ # TODO: configure nvchecker source for "pkg"
+ # source = "regex"
+ # url = "..."
+ # regex = "..."
+ # see https://nvchecker.readthedocs.io/en/latest/usage.html
+ ```
+4. Append the section to `NVCHECKER_CONFIG` (create the file/dir if absent).
+5. Print a notice regardless of detection outcome:
+ `nvchecker: review/fill [pkg] section in ~/.config/nvchecker/nvchecker.toml`.
+
+If `NVCHECKER_CONFIG` does not exist, the file is created containing just the new
+`[pkg]` section; the user is still responsible for adding `[__config__]` before
+the query features work. The notice covers this.
+
+## Feature 2 — `--hintfile pkg` with no `-v` suggests the latest version
+
+Today `--hintfile` without `-v` is an error (version required). New behaviour:
+
+When `--hintfile pkg` is given and `VERSION` is empty:
+
+1. `check_nvchecker`.
+2. Run `nvchecker -c "$NVCHECKER_CONFIG"` (refreshes the keyfile).
+3. Read newver for `pkg` from the keyfile via `jq` → `latest`. New helper
+ `nvchecker_latest pkg` echoes the newver or returns non-zero on failure.
+4. If there is no `[pkg]` section, nvchecker fails, or no newver is produced →
+ error telling the user to add/fix the section; exit non-zero.
+5. Read the hint file's current `VERSION` → `current`.
+6. Prompt (new helper `suggest_version pkg`, echoes the chosen version):
+ `current X, latest Y. Use Y? [Y/n] (or type a version)`
+ - Enter or `y` → `VERSION=Y`.
+ - A typed version string → use that.
+ - `n` → abort with exit 0 (user declined).
+7. Continue on the normal update path: `update_hint_file` → checksums →
+ `nvtake pkg` → `prompt_slackrepo pkg`.
+
+Explicit `-v VERSION` keeps working exactly as today and makes no nvchecker call.
+
+## Feature 3 — `--check` / `-C` bulk
+
+`-c` is already `--clean`, so the bulk flag is `--check` / `-C` (capital C, no
+clash). `--check` takes an optional list of package names; with no names it scans
+every hint file in `HINT_DIR`.
+
+New function `check_updates [pkgs...]`:
+
+1. `check_nvchecker`.
+2. Run `nvchecker -c "$NVCHECKER_CONFIG"` once (refreshes the keyfile for all).
+3. For each target hint file:
+ - `current` = hint `VERSION`; `latest` = newver from keyfile via `jq`.
+ - No `[pkg]` section or no newver → skip, note `pkg: no nvchecker source` in
+ the report.
+ - `current == latest` → skip silently (up to date).
+ - Otherwise classify with `sort -V` and add to the report list as
+ `pkg current → latest` (or `(?downgrade)`).
+4. If nothing is outdated → print `all up to date`, exit 0.
+5. Print the report, then prompt **per package**:
+ `pkg current → latest. Update? [Y/n]`
+ - `y`/Enter → queue the package for update.
+ - `n` → skip it.
+6. For each queued package: `update_hint_file pkg latest` → checksums →
+ `nvtake pkg`. Collect the packages that updated successfully.
+7. After all updates: a single `prompt_slackrepo` call with all updated package
+ names → `slackrepo update pkg1 pkg2 ...`.
+
+### Argument-parsing conflicts
+
+`--check` / `-C` is mutually exclusive with `-v`, `-f`/`--hintfile`, and
+`-n`/`--new`. Combining them is an error (exit 1).
+
+## Affected files
+
+- `mkhint` — new constant, `check_nvchecker`, `add_nvchecker_section`,
+ `nvchecker_latest`, `suggest_version`, `check_updates`; arg parsing for
+ `--check`/`-C`; `--new` and `--hintfile` paths hooked; `nvtake` after updates.
+- `mkhint.bash-completion` — add `--check -C` to `all_flags`; complete package
+ names for `--check` like `--hintfile`/`--delete`.
+- `README.md` — document the three features and the nvchecker/jq dependencies.
+- `tests/mkhint_test.sh` — new test cases (see below).
+
+## Testing
+
+Extend the existing mock-based suite. Mock `nvchecker`, `nvtake`, and `jq`
+behaviour the way `wget` is already faked — no network, no real keyfile required.
+The mock nvchecker writes a controllable newver JSON; the mock nvtake is a no-op
+that records it was called.
+
+New cases:
+
+| ID | Scenario |
+|-----|----------|
+| T16 | `--new` GitHub `.info` — `[pkg]` with `source="github"` appended, notice printed |
+| T17 | `--new` PyPI `.info` — `[pkg]` with `source="pypi"` appended |
+| T18 | `--new` unrecognised URL — commented stub `[pkg]` appended, notice printed |
+| T19 | `--new` when `[pkg]` already in toml — no duplicate appended |
+| T20 | `--hintfile` no `-v`, accept suggestion — VERSION set to latest, nvtake called |
+| T21 | `--hintfile` no `-v`, type override — VERSION set to typed value |
+| T22 | `--hintfile` no `-v`, no `[pkg]` section — error, non-zero exit |
+| T23 | `--check` one outdated, confirm — hint updated, nvtake called, slackrepo prompted |
+| T24 | `--check` all current — `all up to date`, exit 0, no slackrepo |
+| T25 | `--check` mixed, decline one / accept one — only accepted updated |
+| T26 | `--check` with `-v` — mutually-exclusive error, exit 1 |
+
+## Exit codes
+
+No new codes. Reuse: 1 (invalid args / mutually-exclusive flags), 2 (file/section
+not found), existing 0/4. A missing nvchecker/jq/nvtake binary reports via
+`check_nvchecker` and exits non-zero (reuse 4 — "required tool not available").