1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
|
# sb-builder phase 4: hints
Date: 2026-10-20
Status: approved
Builds on: `2026-10-19-sb-builder-phase1-design.md` (helper, job model, GUI shell),
`2026-10-19-sb-builder-phase2-design.md` (inventory, hint view, slackrepo jobs)
## Goal
Manage slackrepo hint files from sb-builder: check upstream versions, update
version hints, create and delete hints, and see a diff of every change
with a way to revert it.
Direction set by the user: the repo is run from the desktop, the VM shell is
for upgrading Slackware only. The VM-side tools were built for a terminal; a
tool may lose its in-VM usability if that makes sb-builder simpler. So the
hint logic of mkhint moves into `sbb-remote` (Python, JSON answers) and the
upstream check moves to the desktop. mkhint is not extended.
## Facts this design rests on
Verified on the VM, in the mkhint source (`~/Programming/GIT/mkhintfile/mkhint`,
1.4.0, 2291 lines) and in slackrepo (`../GITHUB/slackrepo`):
- The VM has 607 hints, all in `hintdir`, none in subdirectories and none in
the SBo tree. 594 carry only `DELREQUIRES`, 13 carry
`VERSION` + `ARCH` + `DOWNLOAD` + `MD5SUM`, one carries `OPTIONS`. No hint
carries `NODOWNLOAD`. Pinned hints carry `DOWNLOAD_x86_64`/`MD5SUM_x86_64`
lines, mostly empty (`claude-code`: `DOWNLOAD="UNSUPPORTED"` with the URL in
`DOWNLOAD_x86_64`), and `ARCH="x86_64"`.
- slackrepo reads `DOWNLOAD="no"` as "no download" and ignores `NODOWNLOAD`.
`DOWNLOAD_$ARCH` replaces `DOWNLOAD` when set
(`libexec/functions.d/parsefunctions.sh:457`), so `DOWNLOAD="no"` next to a
`DOWNLOAD_x86_64` line has no effect.
- mkhint's version update: back up to `.bak`, replace the old version by the
new one everywhere in the file, also the `_` -> `-` form when either version
contains `_`, also the dotless form of a dotted version inside URLs as a
whole number (`_swap_dotless_url_version`), then for `DOWNLOAD`/`MD5SUM` and
`DOWNLOAD_x86_64`/`MD5SUM_x86_64`: download the first URL and recompute its
md5, keep the md5 of continuation URLs unless the user typed a new URL.
`UNSUPPORTED`/`UNTESTED` values are skipped.
- mkhint's new hint from `.info`: copy the `.info`, drop `PRGNAM`, `HOMEPAGE`,
`MAINTAINER`, `EMAIL`, comment out `REQUIRES`, set `ARCH="x86_64"`, append
`DELREQUIRES` for phantom deps in `REQUIRES`, then apply a version as above
when one is given.
- mkhint's `-F`: for every `.info` in the repo whose `REQUIRES` contains a
phantom dep, merge those deps into the hint's `DELREQUIRES` (union, first
seen order, `.bak` only when something changes, minimal hint created when
absent). It edits only the `DELREQUIRES` line. sbshadow
(slackrepo_setup) runs it after every sync: that is where the 594
DELREQUIRES-only hints come from, and why they exist (slackrepo must not
build deps -current does not need). Until now sbshadow ran it on `--no`
previews too.
- The VM's nvchecker config has 64 stanzas named after the packages, plus a
`[__config__]` with oldver, newver and keyfile. nvchecker v2.22 is
installed on the desktop, with a GitHub token in
`~/.config/nvchecker/keys.toml`. With `--logger json` it prints one JSON
object per line on stdout: `{"name", "version", "event": "updated"}` per
result, `{"name", "error", "level": "error"}` per failure; it exits 0 when
entries fail and needs no oldver or newver to run.
- A DELREQUIRES-only hint has no `VERSION`; mkhint's update cannot bump it.
Updating one pins it from the `.info` first; the next `mkhint -F` keeps the
pin. A package with a phantom dep always has such a hint, so `hint-new`
never sees one.
- slackrepo `update <item>` builds an item that exists, built or not
(`update_command` calls `build_command`).
- The phantom deps list on the VM (`/root/.config/mkhint/phantom-deps`) has
18 entries.
- Two packages use bundled-dep manifests: neovim (url mode) and openvino
(sha mode, GitHub submodules). That code is about 470 lines of mkhint.
## Scope
In: upstream check, version update, new hint, delete hint,
`--no-dl` as `DOWNLOAD="no"`, diff and revert.
Out, for good (the Hints screen covers them): mkhint `--list`, `--versions`,
`--review`, `--strip-version`, `--info`, `--clean`.
Out, later (phase 4b, which then removes mkhint and nvchecker from the VM):
bundled-dep reconcile for neovim and openvino, and a helper `fix-current`
that sbshadow calls in place of `mkhint -F`, with the phantom deps list
moved into the helper config as its single source. Also out: generating nvchecker stanzas
for new packages (add them to `nvchecker.toml` by hand), free text hint
editing, migration of `NODOWNLOAD=yes` lines (none exist).
## Part 1: upstream check, on the desktop
- `nvchecker.toml` in the repo root: the VM's 64 stanzas, without
`[__config__]`. Stanza name = package name. Git-tracked, edited by hand.
- `sb_builder/upstream.py` runs nvchecker through `QProcess` (as `remote.py`
runs `virsh`) with a fixed argv: `nvchecker -c <tmp>/nvchecker.toml
--logger json`. The temp dir holds a generated config: a `[__config__]`
with `keyfile` (from the GUI config, when the file exists), followed by the
repo file's text. No oldver, newver or `nvtake`: the comparison is against
the hints.
- Result: `{pkg: version}` and `{pkg: error}` from the JSON log. A package
without a result, or a failed run (exit status, stderr), shows as
`nvchecker error` with the reason in the tooltip.
- Upstream versions are stored with `-` as `_` (mkhint's `_normalize_version`):
`-` is illegal in a SlackBuild version, so upstream `2026-10-14` is
packaged `2026_10_14`. The comparison and `hint-update` use that form.
- A package is outdated when its upstream version differs from the hint
`VERSION`, or from the `.info` `VERSION` when the hint has none. Versions
are compared as strings; no ordering is attempted.
GUI config gains `[hints] nvchecker_keyfile`, default
`~/.config/nvchecker/keys.toml` (expanded). A missing keyfile is not an
error: nvchecker runs without a token.
## Part 2: helper (`sbb-remote` 0.5.0)
### Config
New key `manifest_packages` (below). `remote/config.example.toml` shows it.
### Arguments
- package: `PKGNAME` and present in the repo (as for slackrepo jobs).
- version: `^[A-Za-z0-9._+-]+$`, at most 64 characters.
- hint path: always `<hintdir>/<pkg>.hint`. If `find_hint` returns a
different file for the package (a hint earlier in slackrepo's search
order), every hint command refuses: "hint lives at <path>, not managed".
- packages with a bundled-dep manifest are named in config key
`manifest_packages`, default `["neovim", "openvino"]`. `hint-update` and
`hint-new` with a version refuse them: "bundled deps: use mkhint until
phase 4b".
### Job kinds
- `hint-update <pkg>=<ver>... [--no-dl]`: per package, in order: compute the
new text (apply the version, below), then back the hint up to `.bak` and
write atomically. A failure (download, missing `.info` VERSION) leaves the
hint and its old `.bak` untouched, logs `<pkg>: failed: <why>` and
continues with the next package. A package without a hint gets a new one
built as `hint-new` does. The last log line is
`SBB-RESULT {"ok": [pkg...], "failed": [[pkg, why]...]}`. Exit status 1
when any package failed.
- `hint-new <pkg> [<ver>] [--no-dl]`: refuses when the hint exists. Builds
the hint from the `.info` as mkhint does, without phantom deps (see the
facts), then applies the version when given. No `.bak` (a stale
one is removed); revert means delete.
These jobs never sign, so they do not check the signing key. Their argv is
the helper itself: `python3 sbb-remote _hint <kind> <args>`, validated again
there. Job ids now allow a hyphenated kind (`...-hint-update`).
`job-result <id>`: the last `SBB-RESULT` object of a job's log.
### Applying a version
1. A hint without `VERSION` is first pinned from the `.info`: its
`VERSION`, `DOWNLOAD*` and `MD5SUM*` lines, plus `ARCH="x86_64"` when the
hint has no `ARCH`, are put in front of the hint's text. Old version = the
hint's `VERSION`. With no version given (`hint-new <pkg>`), steps 2 and 3
are skipped.
2. Replace old by new everywhere in the file except inside `MD5SUM*`
values. When either contains `_`, also the `-` forms. When old contains
`.`, also its dotless form inside URLs, as a whole number (no digit before
or after).
3. Without `--no-dl`: for `DOWNLOAD`/`MD5SUM` and
`DOWNLOAD_x86_64`/`MD5SUM_x86_64`, split the values on whitespace (hint
values may continue over lines with `\`). Skip a pair whose value is empty,
`UNSUPPORTED` or `UNTESTED`. Download the first URL with `urllib`
(timeout 60 s, streamed into `hashlib.md5`, nothing kept on disk) and
replace its md5; keep the md5s of the other URLs. Write values back one
URL per line with ` \` continuation, as mkhint does.
4. With `--no-dl`: nothing is downloaded. Every `DOWNLOAD*` and `MD5SUM*`
assignment is removed (whole multi-line values) and `DOWNLOAD="no"` is
written, because `DOWNLOAD_$ARCH` would override it.
Hint text is edited as text, not regenerated, so comments, order and other
keys stay as they are. An assignment is `KEY="..."` (the value may span
lines inside the quotes) or `KEY=word`, at line start.
### Commands (answer at once, not jobs)
- `hint-diff <pkg>`: `{"bak": text|null, "current": text|null}`. Both null
is an error ("no backup").
- `hint-revert <pkg>`: when a `.bak` exists, move it over the hint (also
recreating a deleted hint); when there is no `.bak` and the hint exists,
delete the hint (reverting a `hint-new`). Returns `{"ok": true}`.
- `hint-delete <pkg>`: move the hint to `.bak`. Errors when there is no hint.
`.bak` is one level deep and overwritten by each change, the mkhint
convention.
### Version
`VERSION` 0.5.0 in `remote/sbb-remote` and `sb_builder/__init__.py`.
## Part 3: GUI
### Hints screen (`sb_builder/hints_tab.py`, ribbon entry and `Open > Hints`)
- Rows: every package with a stanza in `nvchecker.toml` or a hint in
`hintdir`. Columns: checkbox, package, hint version, `.info` version,
upstream, state. Versions come from the existing `inventory` call.
- States: `outdated`, `current`, `no stanza`, `not checked`,
`nvchecker error` (stderr in the tooltip). Filter chips per state and a
text filter on the name, as in the Packages tab.
- Buttons:
- **Check upstream**: runs Part 1 over the whole file, fills the upstream
column, checks the outdated rows. Results live for the session.
- **Update selected**: starts `hint-update pkg=ver ...` for the checked
rows that have an upstream version; a **No download** checkbox adds
`--no-dl`.
- **Delete hint**: confirm dialog, then `hint-delete` for the current row.
- Jobs go through the Jobs tab like every other job.
### After a hint job
When a `hint-update` or `hint-new` job ends, the GUI reads
its `SBB-RESULT` line and opens a dialog with one entry per changed package:
a unified diff (`difflib`) of `hint-diff`, and a **Revert** button per
package. Failed packages are listed with their reason. The dialog ends with
**Build these**, which starts `slackrepo update --preview` for the changed
packages that were not reverted (update builds unbuilt items too), and
**Close**. Revert is offered once per package: a second one would delete
the restored hint. After Revert or Delete the inventory is reloaded.
Each diff is fetched when its package is selected.
### Packages detail pane
- No hint: **New hint**, with an optional version field and a **No
download** checkbox, starts `hint-new`.
- Hint present: **Open in Hints** selects the package in the Hints screen.
## Error handling
- Bad arguments, unmanaged hint paths and manifest packages are refused by
the helper before a job starts; the GUI shows the message.
- A download failure (HTTP status, timeout, DNS) fails that package only and
leaves its hint as it was.
- Writes are atomic (temp file in the same directory, `os.replace`).
- nvchecker missing or failing on the desktop affects only the upstream
column; the rest of the screen works.
- A job without an `SBB-RESULT` line (killed, lost) opens no dialog; the
log shows what happened and `hint-diff` still works per package.
## Testing
- `test_remote.py`: an `http.server` thread serves fixture files for md5.
Version swap forms (plain, `_`/`-`, dotless in URL only, md5 hex not
touched), multi-line `DOWNLOAD`/`MD5SUM` with continuation md5s kept,
`UNSUPPORTED` skipped, `--no-dl` removing `DOWNLOAD_x86_64` and writing
`DOWNLOAD="no"`, a download failure restoring the hint while the next
package still updates, `SBB-RESULT` contents, `hint-new` from `.info`,
a DELREQUIRES-only hint pinned from the `.info`, new -> revert, update -> diff ->
revert, delete -> revert, manifest package refusal, unmanaged hint path
refusal, bad version and package rejection.
- `test_upstream.py`: generated config, JSON log parsing, states (hint
version, `.info` fallback), the repo file has no `[__config__]`, `Checker`
against a fake `nvchecker` on `PATH` and a missing one. No network.
- `test_gui.py`: Hints screen with a fake remote and injected upstream
results (states, checked rows, Update selected argv), diff dialog Revert
call.
## Deploy (asks first)
1. `./install.sh --remote` with helper 0.5.0.
2. slackrepo_setup's sbshadow, which no longer runs `mkhint -F` on `--no`.
3. mkhint, nvchecker and `/root/.config/{mkhint,nvchecker}` stay on the VM
until phase 4b.
|