aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-06-13 18:36:31 +0200
committerDanilo M. <danix@danix.xyz>2026-06-13 18:36:31 +0200
commitd4789701532c8acdfb4b109931e65e5e046871de (patch)
tree1f8bc043687aa3e8fa079f95deee5a5e15036d79 /docs/superpowers
parentd11b8be143998ea7349808b9e9da68139399aace (diff)
parent8e6531764b00b29259fc59bd4e1f16e019bc3f2a (diff)
downloadmkhintfile-d4789701532c8acdfb4b109931e65e5e046871de.tar.gz
mkhintfile-d4789701532c8acdfb4b109931e65e5e046871de.zip
Merge feat/nvchecker: nvchecker integration
- --new appends nvchecker [section] (github/pypi autodetect, else stub) - --hintfile without -v suggests latest version via nvchecker - --check/-C bulk update with per-package confirm + single slackrepo - --check populate missing sections prompt - fix: TOML-quote section names with non-bare-key chars - fix: resolve relative keyfile path against config dir Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/plans/2026-06-13-check-populate-missing-sections.md379
-rw-r--r--docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md155
2 files changed, 534 insertions, 0 deletions
diff --git a/docs/superpowers/plans/2026-06-13-check-populate-missing-sections.md b/docs/superpowers/plans/2026-06-13-check-populate-missing-sections.md
new file mode 100644
index 0000000..6d76262
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-13-check-populate-missing-sections.md
@@ -0,0 +1,379 @@
+# `--check` Populate Missing Sections Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make `mkhint --check` offer to populate `nvchecker.toml` for hint files that have no `[pkg]` section, instead of silently skipping them.
+
+**Architecture:** Add a small `_has_nvchecker_section` helper (DRY the existing section-presence grep), refactor `add_nvchecker_section` to use it, then extend `check_updates` to collect missing-section packages during its classify loop and, after the loop, batch-prompt to append sections via the existing `add_nvchecker_section` autodetect. On accept, the run stops with a "review and re-run" message.
+
+**Tech Stack:** bash, grep, find, sort -V. Tests use the existing mock-based suite (`tests/mkhint_test.sh`).
+
+---
+
+## Background for the implementer
+
+Read before starting:
+
+- **Spec:** `docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md` — this plan implements it exactly.
+- **Main script:** `mkhint`. Relevant anchors (current line numbers):
+ - `add_nvchecker_section()` starts at line 266. Its duplicate-guard grep is line 275:
+ ```bash
+ if grep -qE "^\[${pkg}\][[:space:]]*$" "$NVCHECKER_CONFIG"; then
+ ```
+ - `check_updates()` starts at line 606. The classify loop is lines 633-648. The "no result" skip is line 638:
+ ```bash
+ latest=$(nvchecker_latest "$pkg") || { echo "skip ${pkg}: no nvchecker source"; continue; }
+ ```
+ The "nothing outdated" check is lines 650-653.
+ - `list_hint_files()` (earlier in the file) locates a package's `.info` with:
+ ```bash
+ find "$REPO_DIR" -mindepth 2 -name "${pkg}.info" 2>/dev/null | head -1
+ ```
+ Reuse this exact pattern.
+- **`set -e`** is active. Command substitutions and `read` are safe; `add_nvchecker_section` returns 0. No new unguarded fallible calls are introduced.
+- **Test harness:** `tests/mkhint_test.sh`.
+ - `run_mkhint` sed-patches `REPO_DIR`/`HINT_DIR`/`TMP_DIR`/`NVCHECKER_CONFIG` to mock paths.
+ - `setup()` creates fixtures including `ghpkg` (github `.info` under `$MOCK_REPO/development/ghpkg/ghpkg.info`) and a base `$MOCK_BASE/nvchecker.toml` containing only `[__config__]`, plus `$MOCK_BASE/new_ver.json` seeded with curl + clion versions.
+ - Interactive prompts are fed via `< <(printf '...')`.
+ - Assertions: `assert_contains`, `assert_not_contains`, `assert_file_exists`, `assert_file_not_exists`, `assert_exit_code`. Output-substring checks use the inline `grep -q` + `(( PASS++ ))` pattern (see T24/T27 in the file).
+ - The current suite has 69 passing assertions (T1–T28).
+
+## File Structure
+
+- `mkhint` — `_has_nvchecker_section` helper; `add_nvchecker_section` refactor; `check_updates` missing-section collection + populate prompt.
+- `tests/mkhint_test.sh` — T29–T31, plus one orphan fixture for T31.
+- `CLAUDE.md` / `README.md` — document the populate prompt.
+
+Single-file tool; keep everything in `mkhint`.
+
+---
+
+## Task 1: Add `_has_nvchecker_section` helper and refactor `add_nvchecker_section`
+
+Pure refactor — no behaviour change. Establishes the helper that Task 2 reuses.
+
+**Files:**
+- Modify: `mkhint` (add helper just above `add_nvchecker_section` at line 266; change line 275-278)
+
+- [ ] **Step 1: Add the helper above `add_nvchecker_section`**
+
+Immediately before the `# Append an nvchecker [pkg] section...` comment (line 264), add:
+
+```bash
+# Return 0 if NVCHECKER_CONFIG already has a [pkg] section
+_has_nvchecker_section() {
+ local pkg="$1"
+ [[ -f "$NVCHECKER_CONFIG" ]] || return 1
+ grep -qE "^\[${pkg}\][[:space:]]*$" "$NVCHECKER_CONFIG"
+}
+```
+
+- [ ] **Step 2: Use the helper inside `add_nvchecker_section`**
+
+Replace the duplicate-guard block (currently lines 274-278):
+
+```bash
+ # Skip if section already present
+ if grep -qE "^\[${pkg}\][[:space:]]*$" "$NVCHECKER_CONFIG"; then
+ echo "nvchecker: [${pkg}] already present in $NVCHECKER_CONFIG"
+ return 0
+ fi
+```
+
+with:
+
+```bash
+ # Skip if section already present
+ if _has_nvchecker_section "$pkg"; then
+ echo "nvchecker: [${pkg}] already present in $NVCHECKER_CONFIG"
+ return 0
+ fi
+```
+
+Note: `add_nvchecker_section` still runs `mkdir -p`/`touch` on the config before this check, so `_has_nvchecker_section`'s `[[ -f ]]` guard is belt-and-suspenders here but matters for Task 2's caller (which checks before any touch).
+
+- [ ] **Step 3: Syntax check + full suite (no regressions)**
+
+Run: `bash -n mkhint && bash tests/mkhint_test.sh`
+Expected: syntax clean; `Results: 69 passed, 0 failed` (refactor changes nothing observable; T16-T19/T29 not yet added).
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mkhint
+git commit -m "refactor: extract _has_nvchecker_section helper"
+```
+
+---
+
+## Task 2: Collect missing sections and add the populate prompt in `check_updates`
+
+**Files:**
+- Modify: `mkhint` `check_updates` — classify loop (line 638) and after the loop (before line 650)
+
+- [ ] **Step 1: Distinguish missing-section from no-result in the classify loop**
+
+First, declare the `missing_sections` array alongside the other locals. Change line 631:
+
+```bash
+ local outdated_pkgs=() outdated_old=() outdated_new=() outdated_flag=()
+```
+
+to:
+
+```bash
+ local outdated_pkgs=() outdated_old=() outdated_new=() outdated_flag=()
+ local missing_sections=()
+```
+
+Then replace the no-result skip (line 638):
+
+```bash
+ latest=$(nvchecker_latest "$pkg") || { echo "skip ${pkg}: no nvchecker source"; continue; }
+```
+
+with:
+
+```bash
+ if ! latest=$(nvchecker_latest "$pkg"); then
+ if _has_nvchecker_section "$pkg"; then
+ echo "skip ${pkg}: no nvchecker result"
+ else
+ echo "skip ${pkg}: no nvchecker section"
+ missing_sections+=("$pkg")
+ fi
+ continue
+ fi
+```
+
+(`set -e` note: `latest=$(...)` inside an `if !` is a guarded context — the non-zero return does not abort.)
+
+- [ ] **Step 2: Add the populate prompt after the classify loop**
+
+The classify loop ends at the `done` (line 648). Immediately after that `done`, and before the `if [[ ${#outdated_pkgs[@]} -eq 0 ]]; then` block (line 650), insert:
+
+```bash
+ # Offer to populate nvchecker.toml for packages with no section
+ if [[ ${#missing_sections[@]} -gt 0 ]]; then
+ echo ""
+ echo "${#missing_sections[@]} package(s) have no nvchecker section: ${missing_sections[*]}"
+ local answer
+ read -r -p "Populate ${NVCHECKER_CONFIG} now? [Y/n] " answer
+ answer="${answer:-Y}"
+ if [[ "$answer" =~ ^[Yy]$ ]]; then
+ local mp info
+ for mp in "${missing_sections[@]}"; do
+ info=$(find "$REPO_DIR" -mindepth 2 -name "${mp}.info" 2>/dev/null | head -1)
+ if [[ -z "$info" ]]; then
+ echo "skip ${mp}: no .info found in $REPO_DIR"
+ continue
+ fi
+ add_nvchecker_section "$mp" "$info"
+ done
+ echo ""
+ echo "Sections added. Review $NVCHECKER_CONFIG (fill any stubs), then re-run 'mkhint -C'."
+ return 0
+ fi
+ fi
+```
+
+- [ ] **Step 3: Syntax check + full suite (no regressions)**
+
+Run: `bash -n mkhint && bash tests/mkhint_test.sh`
+Expected: syntax clean; `Results: 69 passed, 0 failed`. (Existing `-C` tests T23-T28 all use packages that already have sections or are explicitly named with seeded keyfile entries, so none trigger the new missing-section path. Verify they still pass — if T24's "all up to date" now prints a missing-section prompt instead, investigate: T24 uses `curl` which has a keyfile entry, so it should classify as up-to-date, not missing.)
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add mkhint
+git commit -m "feat: --check offers to populate missing nvchecker sections"
+```
+
+---
+
+## Task 3: Add tests T29–T31
+
+**Files:**
+- Modify: `tests/mkhint_test.sh` — orphan fixture in `setup()`; T29-T31 before the SUMMARY section
+
+- [ ] **Step 1: Add an orphan hint fixture path note**
+
+T31 needs a package that has a hint file but NO `.info` anywhere in `MOCK_REPO`. We create the hint file inline in the test (no `.info`), so no `setup()` change is strictly required. No fixture edit needed — proceed to the tests.
+
+- [ ] **Step 2: Add T29 — accept populate, github section written, run stops, hint unchanged**
+
+In `tests/mkhint_test.sh`, find the T28 block (the last test, `assert_contains "scan-all updated curl"`), which is immediately before:
+
+```bash
+# ─── SUMMARY ──────────────────────────────────────────────────────────────────
+teardown
+```
+
+Insert the following three test blocks between the end of T28 and the `# ─── SUMMARY` line:
+
+```bash
+# ── T29: --check missing section, accept populate → section added, run stops ───
+echo ""
+echo "T29: --check missing section, accept populate → github section appended, no update"
+# ghpkg has a github .info in MOCK_REPO but no [ghpkg] in toml and not in keyfile.
+# Reset toml to only [__config__] so ghpkg is genuinely missing.
+cat > "$MOCK_BASE/nvchecker.toml" << EOF
+[__config__]
+oldver = "$MOCK_BASE/old_ver.json"
+newver = "$MOCK_BASE/new_ver.json"
+EOF
+cat > "$MOCK_BASE/new_ver.json" << 'EOF'
+{ "version": 2, "data": {} }
+EOF
+rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null
+cat > "$MOCK_HINT/ghpkg.hint" << 'EOF'
+VERSION="1.0.0"
+ARCH="x86_64"
+DOWNLOAD="https://github.com/someowner/ghpkg/archive/v1.0.0/ghpkg-1.0.0.tar.gz"
+MD5SUM="11111111111111111111111111111111"
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+EOF
+out=$(run_mkhint -C ghpkg < <(printf 'Y\n') 2>&1)
+assert_contains "ghpkg section appended" "$MOCK_BASE/nvchecker.toml" '\[ghpkg\]'
+assert_contains "github source detected" "$MOCK_BASE/nvchecker.toml" 'source = "github"'
+echo "$out" | grep -q "Review .*re-run" \
+ && { echo " PASS: review/re-run message shown"; (( PASS++ )); } \
+ || { echo " FAIL: review message missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T29 review msg"); }
+assert_contains "ghpkg hint version unchanged" "$MOCK_HINT/ghpkg.hint" 'VERSION="1.0.0"'
+
+# ── T30: --check missing section, decline populate → no section added ──────────
+echo ""
+echo "T30: --check missing section, decline populate → nothing added"
+cat > "$MOCK_BASE/nvchecker.toml" << EOF
+[__config__]
+oldver = "$MOCK_BASE/old_ver.json"
+newver = "$MOCK_BASE/new_ver.json"
+EOF
+cat > "$MOCK_BASE/new_ver.json" << 'EOF'
+{ "version": 2, "data": {} }
+EOF
+rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null
+cat > "$MOCK_HINT/ghpkg.hint" << 'EOF'
+VERSION="1.0.0"
+ARCH="x86_64"
+DOWNLOAD="https://github.com/someowner/ghpkg/archive/v1.0.0/ghpkg-1.0.0.tar.gz"
+MD5SUM="11111111111111111111111111111111"
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+EOF
+run_mkhint -C ghpkg < <(printf 'n\n') >/dev/null 2>&1
+assert_not_contains "no ghpkg section after decline" "$MOCK_BASE/nvchecker.toml" '\[ghpkg\]'
+
+# ── T31: --check missing section, no .info in repo, accept → skipped, no section
+echo ""
+echo "T31: --check missing section but no .info → skipped, no section added"
+cat > "$MOCK_BASE/nvchecker.toml" << EOF
+[__config__]
+oldver = "$MOCK_BASE/old_ver.json"
+newver = "$MOCK_BASE/new_ver.json"
+EOF
+cat > "$MOCK_BASE/new_ver.json" << 'EOF'
+{ "version": 2, "data": {} }
+EOF
+rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null
+# orphanpkg has NO .info anywhere in MOCK_REPO
+cat > "$MOCK_HINT/orphanpkg.hint" << 'EOF'
+VERSION="3.0.0"
+ARCH="x86_64"
+DOWNLOAD="https://example.com/orphanpkg-3.0.0.tar.gz"
+MD5SUM="44444444444444444444444444444444"
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+EOF
+out=$(run_mkhint -C orphanpkg < <(printf 'Y\n') 2>&1)
+echo "$out" | grep -q "no .info found" \
+ && { echo " PASS: no .info reported"; (( PASS++ )); } \
+ || { echo " FAIL: 'no .info found' not in output"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T31 no info"); }
+assert_not_contains "no orphanpkg section added" "$MOCK_BASE/nvchecker.toml" '\[orphanpkg\]'
+```
+
+- [ ] **Step 3: Run the full suite**
+
+Run: `bash tests/mkhint_test.sh`
+Expected: all PASS including T29-T31; `Results: N passed, 0 failed` (N = 75: 69 + 6 new assertions).
+
+If a test fails, debug with superpowers:systematic-debugging. Likely culprits: stdin line count (T29/T31 supply exactly one `Y`/one line — there is no slackrepo prompt because the run returns 0 right after populate), or the `grep -q "Review .*re-run"` pattern not matching the exact message string (the message is `Sections added. Review <path> (fill any stubs), then re-run 'mkhint -C'.`).
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add tests/mkhint_test.sh
+git commit -m "test: add T29-T31 for --check populate missing sections"
+```
+
+---
+
+## Task 4: Document the populate prompt
+
+**Files:**
+- Modify: `CLAUDE.md`, `README.md`
+
+- [ ] **Step 1: Update CLAUDE.md**
+
+In the "Key Behaviors" section, find the bullet describing `--check`/`-C`. Append a sentence (or a sub-bullet) stating: when `--check` encounters hint files with no `[pkg]` section in `nvchecker.toml`, it lists them and prompts once to populate the config; on accept it appends sections via `add_nvchecker_section` (github/pypi autodetect, else stub), prints a "review and re-run" message, and stops the run without applying updates. Packages whose `.info` cannot be found in `REPO_DIR` are skipped.
+
+Add a test-coverage table row group:
+
+```
+| T29 | `--check` missing section, accept populate — github section appended, run stops, hint unchanged |
+| T30 | `--check` missing section, decline populate — nothing added |
+| T31 | `--check` missing section, no `.info` in repo — skipped, no section added |
+```
+
+- [ ] **Step 2: Update README.md**
+
+In the `--check` documentation section, add a short paragraph: if any scanned hint has no nvchecker source configured, `mkhint --check` offers to populate `nvchecker.toml` for them (auto-detecting github/pypi from the SBo `.info`, otherwise writing a commented stub to fill in). After populating it asks you to review the file and re-run `mkhint -C`.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add CLAUDE.md README.md
+git commit -m "docs: document --check populate-missing-sections prompt"
+```
+
+---
+
+## Task 5: Final verification
+
+- [ ] **Step 1: Syntax + full suite**
+
+Run: `bash -n mkhint && bash tests/mkhint_test.sh`
+Expected: clean; `Results: 75 passed, 0 failed`.
+
+- [ ] **Step 2: Manual smoke — empty toml, decline**
+
+Run:
+```bash
+tmpcfg=$(mktemp); printf '[__config__]\noldver="/tmp/o.json"\nnewver="/tmp/n.json"\n' > "$tmpcfg"
+echo '{"version":2,"data":{}}' > /tmp/n.json
+HOME=$HOME bash -c 'true' # noop, just confirming environment
+```
+(The real manual path requires nvchecker/jq installed; the mock suite already covers behaviour. This step is optional if tools are absent — the suite is authoritative.)
+
+- [ ] **Step 3: Confirm no stray TODO/debug**
+
+Run: `grep -n "TODO\|XXX\|DEBUG" mkhint`
+Expected: only the intentional stub-template `# TODO: configure nvchecker source` inside `add_nvchecker_section`.
+
+- [ ] **Step 4: Spec compliance review**
+
+Use superpowers:requesting-code-review (or self-review) against
+`docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md`.
+Confirm: missing-section detection, batch prompt, `.info` lookup + skip on absent,
+`add_nvchecker_section` reuse, `return 0` stop with review message, T29-T31 present and passing.
+
+---
+
+## Self-Review notes (author)
+
+- **Spec coverage:** §"Detect missing sections" → Task 2 Step 1 + Task 1 helper. §"Offer to populate" → Task 2 Step 2. `.info` lookup + skip → Task 2 Step 2 (find + empty check). Reuse of `add_nvchecker_section` → Task 2 Step 2. `return 0` stop → Task 2 Step 2. Decline fall-through → preserved (no change to outdated path). Tests T29-T31 → Task 3. Docs → Task 4.
+- **Placeholder scan:** none. T31 Step 1 explicitly states no fixture edit needed (not a deferred TODO).
+- **Name consistency:** `_has_nvchecker_section`, `missing_sections`, `add_nvchecker_section`, `NVCHECKER_CONFIG`, `REPO_DIR` consistent across Tasks 1-3 and matching the existing codebase.
+- **set -e:** the one new fallible call (`latest=$(nvchecker_latest ...)`) is wrapped in `if !`. `find` is in a command substitution. `add_nvchecker_section` returns 0. Safe.
+- **Assertion count:** T29 = 4 (toml header, source, review msg, hint unchanged), T30 = 1, T31 = 2. 7 new... recount: T29 adds 4 PASS, T30 adds 1, T31 adds 2 → 7. Plan Step 3 says N=75 (69+6). CORRECTED: expected total is 76 (69 + 7). Implementer: assert on the actual printed total, not a hardcoded number — the suite prints `Results: <PASS> passed, <FAIL> failed`; the pass criterion is `0 failed`.
diff --git a/docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md b/docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md
new file mode 100644
index 0000000..3a8b42a
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md
@@ -0,0 +1,155 @@
+# `--check` populate missing nvchecker sections — design
+
+Date: 2026-06-13
+
+## Summary
+
+When `mkhint --check` / `-C` scans hint files, packages that have no `[pkg]`
+section in `nvchecker.toml` are currently skipped silently-ish (`skip pkg: no
+nvchecker source`). On a fresh/empty `nvchecker.toml` this means *every* hint is
+skipped and the command does nothing useful.
+
+This feature makes `--check` detect those missing-section packages and, after the
+scan, offer to populate `nvchecker.toml` by appending sections for them (reusing
+the existing `add_nvchecker_section` autodetect logic). If the user accepts, the
+sections are written and the run stops with a message telling the user to review
+the file (fill any stubs) and re-run `mkhint -C`.
+
+## Motivation
+
+`add_nvchecker_section` already exists and is called on `--new`. `--check` should
+give the user a path to bootstrap nvchecker config for hints created before this
+integration existed, without hand-editing the TOML for every package.
+
+## Behaviour
+
+Implemented inside the existing `check_updates` function.
+
+### 1. Detect missing sections during the classify loop
+
+The classify loop currently does, per target package:
+
+```bash
+latest=$(nvchecker_latest "$pkg") || { echo "skip ${pkg}: no nvchecker source"; continue; }
+```
+
+Change so the "no result" case distinguishes two situations:
+
+- A `[pkg]` section **exists** in `NVCHECKER_CONFIG` but nvchecker produced no
+ version → genuine "no result", keep the existing skip message
+ (`skip ${pkg}: no nvchecker result`).
+- A `[pkg]` section is **missing** → add `pkg` to a `missing_sections` array and
+ print `skip ${pkg}: no nvchecker section` (do not classify it as outdated).
+
+Section presence is tested with the same pattern `add_nvchecker_section` uses,
+extracted into a small shared helper:
+
+```bash
+# Return 0 if NVCHECKER_CONFIG has a [pkg] section
+_has_nvchecker_section() {
+ local pkg="$1"
+ grep -qE "^\[${pkg}\][[:space:]]*$" "$NVCHECKER_CONFIG"
+}
+```
+
+`add_nvchecker_section` is updated to call `_has_nvchecker_section` instead of its
+inline `grep` (pure DRY refactor, no behaviour change).
+
+### 2. Offer to populate, after the classify loop
+
+After the classify loop completes and before the "nothing outdated" / report
+logic, insert:
+
+```bash
+if [[ ${#missing_sections[@]} -gt 0 ]]; then
+ echo ""
+ echo "${#missing_sections[@]} package(s) have no nvchecker section: ${missing_sections[*]}"
+ local answer
+ read -r -p "Populate ${NVCHECKER_CONFIG} now? [Y/n] " answer
+ answer="${answer:-Y}"
+ if [[ "$answer" =~ ^[Yy]$ ]]; then
+ local mp info
+ for mp in "${missing_sections[@]}"; do
+ info=$(find "$REPO_DIR" -mindepth 2 -name "${mp}.info" 2>/dev/null | head -1)
+ if [[ -z "$info" ]]; then
+ echo "skip ${mp}: no .info found in $REPO_DIR"
+ continue
+ fi
+ add_nvchecker_section "$mp" "$info"
+ done
+ echo ""
+ echo "Sections added. Review $NVCHECKER_CONFIG (fill any stubs), then re-run 'mkhint -C'."
+ return 0
+ fi
+fi
+```
+
+Key points:
+
+- Single batch prompt (not per package). Default Yes.
+- For each missing package, the `.info` is located in `REPO_DIR` with the same
+ `find` pattern `list_hint_files` already uses.
+ - No `.info` found → print `skip ${mp}: no .info found in $REPO_DIR`, do not add
+ a section.
+ - `.info` found → call `add_nvchecker_section "$mp" "$info"`, which appends a
+ github/pypi section if detected, otherwise a commented stub. (Its own
+ duplicate guard via `_has_nvchecker_section` makes this safe.)
+- After populating, **stop the run** (`return 0`) with the review message. The
+ newly added sections are not queried this run — the user reviews/fills stubs
+ first, then re-runs. (Decision: simple separation over one-shot convenience.)
+- If the user declines, fall through to the normal report path. The
+ missing-section packages were already excluded during classify, so they simply
+ don't appear as outdated.
+
+### Interaction with the outdated set
+
+A run can have both outdated packages (with sections) and missing-section
+packages. If the user accepts populate, the run stops *before* applying any
+outdated updates — so populate takes precedence and the user re-runs to pick up
+updates afterward. This is acceptable: it keeps each run's effect simple and
+predictable. If the user declines populate, outdated updates proceed as today.
+
+## Affected code
+
+- `mkhint`:
+ - New helper `_has_nvchecker_section`.
+ - `add_nvchecker_section` refactored to use it (no behaviour change).
+ - `check_updates`: `missing_sections` array, distinguished skip messages, the
+ populate prompt block.
+- `tests/mkhint_test.sh`: T29–T31.
+- `CLAUDE.md` / `README.md`: document the populate prompt under `--check`.
+
+No new dependencies, no new flags, no exit-code changes.
+
+## Testing
+
+Mock-based, as existing suite. The mock nvchecker keyfile (`new_ver.json`) only
+contains versions for packages we want "found"; a package absent from the keyfile
+AND absent from the TOML is a missing-section case.
+
+| ID | Scenario |
+|-----|----------|
+| T29 | `-C`, hint with no section, github `.info`, accept populate → github section appended, run stops with review message, hint NOT updated |
+| T30 | `-C`, hint with no section, decline populate → no section added, no error (normal skip path) |
+| T31 | `-C`, hint with no section, no `.info` in repo, accept populate → `skip ... no .info found`, no section added |
+
+Test mechanics:
+
+- T29 uses a package that has a `.info` with a github HOMEPAGE/DOWNLOAD in
+ `MOCK_REPO` (the existing `ghpkg` fixture) but **no** `[ghpkg]` line in the test
+ `nvchecker.toml` and **no** entry in `new_ver.json`. After `printf 'Y\n'`,
+ assert the TOML now contains `[ghpkg]` + `source = "github"`, assert output
+ contains the review message, assert the hint file VERSION is unchanged.
+- T30 uses the same setup but `printf 'n\n'`; assert TOML still has no `[ghpkg]`.
+- T31 uses a package name with a hint file but no matching `.info` anywhere in
+ `MOCK_REPO` (e.g. a hand-written `orphan.hint`); after `printf 'Y\n'`, assert
+ output contains `no .info found` and TOML has no `[orphan]` section.
+
+## Self-review notes
+
+- Reuses `add_nvchecker_section` and the `find` pattern from `list_hint_files` —
+ no new mechanisms.
+- `_has_nvchecker_section` is the only new unit; DRYs an existing grep.
+- `set -e` safety: `find ... || head` is in a command substitution (non-fatal);
+ `add_nvchecker_section` returns 0; `read` is fine. No unguarded fallible calls.
+- Scope: single function change plus one helper plus three tests. Single plan.