Closes the review findings on the spec #16 integration branch (Voculinux, the Void Linux fork of Vocalinux). Each area below maps to a finding from the review.
Model recommendation (ticket 20).get_recommended_model() keys to the backend the installed whisper.cpp build has, read from detect_pywhispercpp_gpu_backend, and to free memory measured at recommendation time: free VRAM on a discrete GPU for Vulkan/CUDA builds, free RAM otherwise. The result is a ladder rung (default large-v3-turbo-q8_0, q5_0, small/small.en floor), so an RTX 2050 reaches the ladder. Manual selections are untouched. The threshold numbers are marked provisional: they cite ticket 19's measurement, which is still open and not recorded in the repo.
Injection (tickets 31, spec c3/c4). Explicit niri guard: autodetection never probes or selects the RemoteDesktop portal on niri, and the ibus-wayland bridge exception does not reopen IBus there. Key-event tool resolution now uses wtype before ydotool, matching _check_dependencies. Explicit user pins remain escape hatches. conftest.py scrubs NIRI_SOCKET so tests that model other desktops are not affected inside a niri session.
Void packaging (tickets 27, 25 to 28). The app template now sources the fork's release sdist, with homepage and maintainer on git.bongbetic.com. The checksum is an explicit PENDING_FORK_RELEASE_SHA256 marker until the fork's v0.18.5 release exists, which fails closed in xbps-src. It never pins the upstream digest. depends adds pipewire, wtype, wl-clipboard, xdotool and pulseaudio-utils, and drops ibus. post_install installs the fork's own names with no sed or mv. INSTALL.msg states the niri first run (no prompt; typing goes direct) and never points at the portal on niri. The container smoke queries the fork's distribution name.
Signed xbps release (ticket 29). New .gitea/workflows/xbps-release.yml: tag-only trigger, exact tag-to-version.py gate, hard failure without the signing secret, signed full repo data, publish to the tag release and the rolling latest release. The GitHub build-xbps job is removed. The repository key is the existing Bongbetic RSA-4096 key, committed as its public half in packaging/void/repository-key.plist. Fingerprint fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25 is in the README, and a test checks the README against the committed key.
Naming sweep (ticket 18, spec c5).tests/test_rename_sweep.py allowlist rules are scoped to retained surfaces (upstream slugs and labelled upstream prose, the retained package's modules and imports, legacy-import files, tooling paths only). Tests check that user-facing old names are denied. The web install page no longer shows the bare old name.
Standards. Identity constants derive from one slug (values unchanged, checked against origin/main). Legacy import tidied. Perf-baseline host binaries use host_env(), and the AST guard covers that script. Docstrings corrected. AGENTS.md layout updated.
Web (tickets 24, spec c7). Fork source links, AUR claims corrected, privacy and capability wording aligned to web/PRODUCT.md. The upstream star count is removed rather than repointed.
Docs. USER_GUIDE and NIRI describe the niri first run and the corrected rung order. Release process documents the xbps release and the template bump as a pull request.
Refs xavierk/voculinux#16 (spec) and the child tickets above. Deliberately not "Fixes": several child tickets remain open (hardware baseline, tray probe evidence, hardware acceptance).
Type of change
Bug fix
New feature
Breaking change
Documentation
Refactor
Tests
Packaging / CI
Checklist
Code follows project style (Black, isort, flake8). Black, isort and critical flake8 pass on all 19 changed Python files. mypy src/ passes (88 files).
Documentation updated when user-facing behavior or install steps change
Tests added or updated for the change
Local tests pass for every touched test file, run alone
Conventional commit message style
Full suite: the branch adds no failing test against origin/main (full-run diff: new on branch: none, and the rename-sweep failure is fixed). About 145 failures are pre-existing order-dependent pollution from GTK and numpy mocks, and they also occur on origin/main when the suite runs in one session. Files pass alone.
just is not installed here, so the recipes were run as their underlying commands: uv lock --check, generate_distro_package_map.py --check and check_exports.py all pass.
Notes for reviewers
Decisions and open items for a human:
Checksum pending. The Void template cannot build until the fork's v0.18.5 release asset exists and its sha256 is set in a pull request. That is intentional.
Release ordering gap. The template fetches the sdist from the tag's release asset, but the Gitea workflow creates that release only after building. The first tag needs the sdist attached first. Worth fixing before the first tag.
Signer label.repo.sh signs as "Voculinux xbps@voculinux.com", while the committed key's label reads "Bongbetic soubarna@live.in". The fingerprint is the same either way.
Secrets and tag. The XBPS_SIGNING_KEY secret must be created on Gitea. The private key is not in this PR. The first tag push needs explicit approval.
Thresholds. The ladder cutoffs are provisional until ticket 19 is recorded.
Stored legacy dictionary path. A migrated config.json can keep pointing at ~/.config/vocalinux/dictionary.txt, which the app then reads and writes. The spec says to load the copy unchanged, so I did not change this. It needs a decision.
Update checker. It still queries upstream's GitHub releases (__url__ is VocaHQ/vocalinux), so fork users can see upstream versions. The packaged-install path is handled by the marker in utils/packaging.py.
Web. The Google Analytics property G-7NBBNJNNQ7 looks like upstream's, and the footer links point at VocaHQ channels. Both need a decision. The Source link to git.bongbetic.com appears to 404 anonymously, so the repo may need to be public before the site ships.
Not merged. Squash-merge is a human step.
## Description
Closes the review findings on the spec #16 integration branch (Voculinux, the Void Linux fork of Vocalinux). Each area below maps to a finding from the review.
- **Model recommendation (ticket 20).** `get_recommended_model()` keys to the backend the installed whisper.cpp build has, read from `detect_pywhispercpp_gpu_backend`, and to free memory measured at recommendation time: free VRAM on a discrete GPU for Vulkan/CUDA builds, free RAM otherwise. The result is a ladder rung (default `large-v3-turbo-q8_0`, `q5_0`, `small`/`small.en` floor), so an RTX 2050 reaches the ladder. Manual selections are untouched. The threshold numbers are marked provisional: they cite ticket 19's measurement, which is still open and not recorded in the repo.
- **Injection (tickets 31, spec c3/c4).** Explicit niri guard: autodetection never probes or selects the RemoteDesktop portal on niri, and the ibus-wayland bridge exception does not reopen IBus there. Key-event tool resolution now uses wtype before ydotool, matching `_check_dependencies`. Explicit user pins remain escape hatches. `conftest.py` scrubs `NIRI_SOCKET` so tests that model other desktops are not affected inside a niri session.
- **Void packaging (tickets 27, 25 to 28).** The app template now sources the fork's release sdist, with homepage and maintainer on git.bongbetic.com. The checksum is an explicit `PENDING_FORK_RELEASE_SHA256` marker until the fork's v0.18.5 release exists, which fails closed in xbps-src. It never pins the upstream digest. `depends` adds pipewire, wtype, wl-clipboard, xdotool and pulseaudio-utils, and drops ibus. `post_install` installs the fork's own names with no sed or mv. INSTALL.msg states the niri first run (no prompt; typing goes direct) and never points at the portal on niri. The container smoke queries the fork's distribution name.
- **Signed xbps release (ticket 29).** New `.gitea/workflows/xbps-release.yml`: tag-only trigger, exact tag-to-`version.py` gate, hard failure without the signing secret, signed full repo data, publish to the tag release and the rolling `latest` release. The GitHub `build-xbps` job is removed. The repository key is the existing Bongbetic RSA-4096 key, committed as its public half in `packaging/void/repository-key.plist`. Fingerprint `fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25` is in the README, and a test checks the README against the committed key.
- **Naming sweep (ticket 18, spec c5).** `tests/test_rename_sweep.py` allowlist rules are scoped to retained surfaces (upstream slugs and labelled upstream prose, the retained package's modules and imports, legacy-import files, tooling paths only). Tests check that user-facing old names are denied. The web install page no longer shows the bare old name.
- **Standards.** Identity constants derive from one slug (values unchanged, checked against origin/main). Legacy import tidied. Perf-baseline host binaries use `host_env()`, and the AST guard covers that script. Docstrings corrected. AGENTS.md layout updated.
- **Web (tickets 24, spec c7).** Fork source links, AUR claims corrected, privacy and capability wording aligned to `web/PRODUCT.md`. The upstream star count is removed rather than repointed.
- **Docs.** USER_GUIDE and NIRI describe the niri first run and the corrected rung order. Release process documents the xbps release and the template bump as a pull request.
Refs #16, #18, #20, #25, #26, #27, #28, #29, #30, #31. Tickets #19, #22 and #32 are not touched.
## Related issue
Refs xavierk/voculinux#16 (spec) and the child tickets above. Deliberately not "Fixes": several child tickets remain open (hardware baseline, tray probe evidence, hardware acceptance).
## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Breaking change
- [x] Documentation
- [x] Refactor
- [x] Tests
- [x] Packaging / CI
## Checklist
- [x] Code follows project style (Black, isort, flake8). Black, isort and critical flake8 pass on all 19 changed Python files. mypy `src/` passes (88 files).
- [x] Documentation updated when user-facing behavior or install steps change
- [x] Tests added or updated for the change
- [x] Local tests pass for every touched test file, run alone
- [x] Conventional commit message style
Full suite: the branch adds no failing test against origin/main (full-run diff: `new on branch: none`, and the rename-sweep failure is fixed). About 145 failures are pre-existing order-dependent pollution from GTK and numpy mocks, and they also occur on origin/main when the suite runs in one session. Files pass alone.
`just` is not installed here, so the recipes were run as their underlying commands: `uv lock --check`, `generate_distro_package_map.py --check` and `check_exports.py` all pass.
## Notes for reviewers
Decisions and open items for a human:
1. **Checksum pending.** The Void template cannot build until the fork's v0.18.5 release asset exists and its sha256 is set in a pull request. That is intentional.
2. **Release ordering gap.** The template fetches the sdist from the tag's release asset, but the Gitea workflow creates that release only after building. The first tag needs the sdist attached first. Worth fixing before the first tag.
3. **Signer label.** `repo.sh` signs as "Voculinux <xbps@voculinux.com>", while the committed key's label reads "Bongbetic <soubarna@live.in>". The fingerprint is the same either way.
4. **Secrets and tag.** The `XBPS_SIGNING_KEY` secret must be created on Gitea. The private key is not in this PR. The first tag push needs explicit approval.
5. **Thresholds.** The ladder cutoffs are provisional until ticket 19 is recorded.
6. **Stored legacy dictionary path.** A migrated `config.json` can keep pointing at `~/.config/vocalinux/dictionary.txt`, which the app then reads and writes. The spec says to load the copy unchanged, so I did not change this. It needs a decision.
7. **Update checker.** It still queries upstream's GitHub releases (`__url__` is VocaHQ/vocalinux), so fork users can see upstream versions. The packaged-install path is handled by the marker in `utils/packaging.py`.
8. **Web.** The Google Analytics property `G-7NBBNJNNQ7` looks like upstream's, and the footer links point at VocaHQ channels. Both need a decision. The Source link to git.bongbetic.com appears to 404 anonymously, so the repo may need to be public before the site ships.
9. **Not merged.** Squash-merge is a human step.
Autodetection on niri now never probes or selects the RemoteDesktop
portal, and the ibus-wayland bridge exception no longer reopens IBus
there. Key events after startup resolve wtype before ydotool, matching
_check_dependencies and the decided ladder.
conftest scrubs NIRI_SOCKET so tests modelling other desktops are not
affected when run inside a niri session.
The install page spelled the old binary name on a line with no upstream
label, which fails the offline naming sweep. The launch note now leads
with voculinux and marks the old name as upstream on the same line.
The header badge fetched the star count of VocaHQ/vocalinux at build time
and labelled it as Voculinux's. The fetch and the count are gone; the
header, footer and social "source" links now go to the fork repository at
git.bongbetic.com/xavierk/voculinux, as do the issue links, the docs
anchors, the home page JSON-LD and the xbps release card.
Upstream GitHub stays only where it is the real artefact or provenance and
is labelled as such: the install script, the AppImage release card, the
upstream card and the changelog release links. The changelog now says its
entries are upstream history, the open-source page no longer claims
VocaHQ membership or an active GitHub community, and the organization
JSON-LD drops the upstream author as founder. DESIGN.md and PRODUCT.md
follow.
The Arch guide said Arch users can install Voculinux from the AUR while the
install and home pages say the fork AUR packages are not published yet.
The Arch description, intro, prerequisites and notes now say there is no
AUR install path today. The Snap and AUR cards on the install and home pages
no longer link to package pages that do not exist, and the nixos and flatpak
pages stop listing the AUR as an option.
PRODUCT.md promises no usage telemetry in the installed app, a guaranteed
whisper.cpp default, best-effort extras and audio leaving the machine only
through a user-configured remote API. Copy that went further is corrected:
"Zero Data Collection", "Truly Private" and the maintainer-cannot-see claim
are gone, the hero and ribbon say "no app telemetry" because the site itself
loads analytics, and the engine lists name whisper.cpp as the default with
the rest as extras. The privacy, FAQ, offline and home pages now say the app
uses the network for model downloads and the update check, without audio or
transcripts.
Guards the star-count removal, the fork source host constant, the Arch AUR
wording, the unlinked unpublished Snap and AUR cards, the privacy claims and
the install-page rule that the old name only appears with an upstream label.
A version tag push builds every Void template through packaging/void/repo.sh
(podman, pinned Void glibc container), signs the repository, and publishes
the complete glibc x86_64 set to the tag's release and to the rolling latest
release that the client registration URL serves.
Gates, each failing the job before anything publishes: the tag must equal
v plus __version__ exactly, XBPS_SIGNING_KEY must be set, the staged
repository must hold every class of file and nothing but glibc x86_64
data, every package must carry its signature, and the signing key must have
the pinned fingerprint. The key lives in a 0600 temp file that an
always-run step removes. Nothing commits, pushes, or bumps templates.
The offline tests run the workflow's own gate, verify, build and publish
scripts against a stateful stand-in for the Gitea release API.
get_recommended_model() now keys to the backend the installed whisper.cpp
build actually has (detect_pywhispercpp_gpu_backend) and to free memory
measured at recommendation time: free VRAM on a discrete GPU for a
Vulkan or CUDA build, free system RAM otherwise. The result is a rung of
the model ladder, so a Vulkan-capable Nvidia card reaches the ladder
instead of the total-memory tiers, and unquantized turbo, the large
family and medium are never a default.
Manual selections are untouched. The informational hardware probe no
longer decides the model. Dead VRAM-total parsing is removed.
free_memory_for_backend() holds the source choice; the floor-rung map is
shared by rung selection and step-down.
nvidia-smi (vram_mib) and wtype (inject probe) ran without env=host_env(),
so under an AppImage bundle they inherit the bundle's library paths. The
AST guard now covers the measurement script as well as src/vocalinux, and
the script gains the module logger the Style rules require.
Every lowercase identity constant is now derived from a single _SLUG, so
a rename is one edit. The values are unchanged (checked against
origin/main for all 33 constants and the user agent).
legacy_import: drop the unused subdirectory field from the payload
tuples, merge the four near-identical directory helpers, and use the
identity constants directly instead of aliases.
Docstrings: the retained package is vocalinux, not voculinux; the
one-time import is described as a legacy Vocalinux install.
The allowlist accepted any line that mentioned upstream, legacy, cmdline,
a dotted name or a path segment, so a user-facing old name could pass
by sharing a line with one of those words. Each rule is now scoped to a
retained surface: upstream repository slugs and labelled upstream prose,
dotted references to modules that exist under src/vocalinux, import and
-m forms, retained class names, legacy import sources in the files that
implement or test the import, quoted package names in tooling and test
paths only, and the pre-rename guard in the Void container harness.
The sweep skips the .git worktree pointer file, and the allowlist itself
has behaviour tests: user-facing names are denied and retained surfaces
are kept.
The app template fetched upstream's 0.18.5 archive, carried upstream's
homepage and maintainer, and renamed the launchers and desktop entry in
post_install. The fork's sdist already names them voculinux, so the
template now sources the fork's release asset and installs the names as
they are. The checksum is an explicit pending marker until the fork's
v0.18.5 release exists, which fails closed in xbps-src and never falls
back to the upstream digest.
depends now pulls in pipewire (capture), wtype (direct typing on niri),
wl-clipboard (clipboard), xdotool (X11 and XWayland) and pulseaudio-utils
(pactl), and drops ibus, which cannot inject on niri. Every name is
checked against the Void index.
INSTALL.msg states the niri first run plainly (no permission prompt;
typing goes direct), never points at the portal on niri, and drops the
IBus claim. The container smoke queries the fork's distribution name.
Tests assert the fork sources, the pending-or-pinned checksum, the
runtime helpers, the absence of ibus, and the niri wording.
AGENTS.md layout now lists app_identity, legacy_import, model_ladder,
packaging/void, the perf protocol script, the Gitea workflows and the
niri doc, as its own maintenance rule requires.
USER_GUIDE orders Wayland autodetection wtype before ydotool, matching
the decided ladder, and says the portal is never tried on niri.
docs/NIRI.md states the niri first dictation (no prompt; typing goes
direct through wtype; IBus excluded) and points to the GNOME and KDE
section.
The signed repository is now published by the tag-triggered Gitea workflow,
so release.yml drops the build-xbps job, its place in the checksum job's
needs list, the xbps-repo artifact pattern, and the checksum lines for
files that no longer arrive. The release notes point at the Gitea release
instead of promising attached repository files.
The policy tests now assert the new shape: release.yml carries no xbps
job, and the Gitea workflow is the one place that runs repo.sh.
Commit the public half of the repository signing key as
packaging/void/repository-key.plist and show its fingerprint beside the
registration lines in the root README and the Void packaging README. The
test derives the fingerprint from the committed key with the xbps
definition (MD5 of the ssh-rsa blob) instead of trusting typed text, checks
the key is 4096-bit public material only, and ties the README, the
registration URL and the release workflow to it.
Clients now register the fork's rolling latest release on Gitea instead of
a per-tag upstream GitHub URL, and the first sync no longer passes -y, so an
unknown key is never accepted silently. The Void README also documents key
generation, the XBPS_SIGNING_KEY secret, the tag workflow and the pending
app-template checksum.
Cover the one-time setup (the XBPS_SIGNING_KEY secret, the committed public
key and its fingerprint, the runner and token needs), the tag-push gates and
what the workflow publishes to the tag release and to latest, and the
checklist items for verifying the run. The template version and checksum
bump stays a normal pull request, never a bot commit to main, and now says
the checksum is the sha256 of the fork's own release asset.
The cherry-pick policy from ticket #30 was already in this file and is
enforced by tests/test_upstream_intake_policy.py. Correct what it names as
the retained internal module: it is vocalinux (src/vocalinux/), not
voculinux.
The rung cutoffs cite ticket 19's transcribe peaks, but ticket 19 is
still open and its measurement is not in this repository. The code and
test docstrings now say the numbers are provisional calibration to
replace, rather than evidence. Also formats the niri guard tests to the
project line length.
Semgrep's run-shell-injection rule flags ${{ github.ref_name }} and
${{ github.repository }} interpolated into run: blocks. The tag name is
attacker-influenced input (git allows $, ( and ; in ref names), so the
release upload steps now read both through step env and quote them.
Semgrep's missing-cooldown rules flag Dependabot and uv configs that
apply newly published releases immediately. Dependabot waits seven
days before proposing updates, and [tool.uv] sets exclude-newer to seven
days.
uv.lock was regenerated with uv 0.13.3 (the pinned version). The only
change is the [options] block that records the cutoff: the same 165
packages resolve, and all eight requirements/*.txt exports are
byte-identical to the committed files.
urllib.request.urlopen also opens file: and ftp: URLs, and the gateway
base URL and pairing targets are not guaranteed to be http(s). Add
require_http_url() to gateway_embed/urls.py and call it before each
urlopen. Health probes turn a rejected scheme into the existing soft
error; pairing raises ValueError.
The urlopen calls themselves stay flagged by the dynamic-urllib rule,
so each carries a one-line nosemgrep for that rule that points at the
check.
scripts/generate-model-checksums.py downloads the URLs that come from
remote model listings (the VOSK model-list.json and Hugging Face
responses), and urlopen would follow a file: URL there. _https() now
refuses anything but https before each request. The md5 digest is only
compared against the published listing, not used for security, so it is
marked usedforsecurity=False.
The remaining findings are false positives for this codebase, and each
gets a narrow nosemgrep on its own line with the reason:
- text_injector.py: argv lists without a shell; dictated text sits
after "--" so it cannot be parsed as an option.
- layout_key_map.py: the KDE layout_memory.xml in the user's own session
directory, not remote input.
- check_runtime.py: module names come from the literal tuples above.
- sync_flatpak_deps.py, verify_release.py: fixed https://pypi.org
endpoint.
- web tests: walks this repo's own src/app tree; loopback dev-server
probe in a test.
requires-python is >=3.11, so the ImportError branch that guarded
importlib.resources.files is dead code. Import it at module scope and
keep only the failures files() can really raise.
The python37-compatibility rule still matches the import itself even
though 3.11 has files(). That is a false positive for this floor, so
the import carries a one-line nosemgrep.
The medium and large entries carried the same 22-language table
verbatim. Hoist it into _BEST_AVAILABLE_LANGUAGES and give each entry a
dict() copy, so the two sizes keep independent mappings. Values are
unchanged (the old literals were asserted equal before the move).
requirements/*.txt are hash-pinned exports written by `just lock` and
checked byte-for-byte by scripts/check_exports.py. Duplication between
them is the lock's output, not hand-written code, so it is excluded
from the duplication gate. The threshold and minLines/minTokens are
unchanged.
xavierk
merged commit 180cd4d512 into main2026-11-06 14:13:50 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Description
Closes the review findings on the spec #16 integration branch (Voculinux, the Void Linux fork of Vocalinux). Each area below maps to a finding from the review.
get_recommended_model()keys to the backend the installed whisper.cpp build has, read fromdetect_pywhispercpp_gpu_backend, and to free memory measured at recommendation time: free VRAM on a discrete GPU for Vulkan/CUDA builds, free RAM otherwise. The result is a ladder rung (defaultlarge-v3-turbo-q8_0,q5_0,small/small.enfloor), so an RTX 2050 reaches the ladder. Manual selections are untouched. The threshold numbers are marked provisional: they cite ticket 19's measurement, which is still open and not recorded in the repo._check_dependencies. Explicit user pins remain escape hatches.conftest.pyscrubsNIRI_SOCKETso tests that model other desktops are not affected inside a niri session.PENDING_FORK_RELEASE_SHA256marker until the fork's v0.18.5 release exists, which fails closed in xbps-src. It never pins the upstream digest.dependsadds pipewire, wtype, wl-clipboard, xdotool and pulseaudio-utils, and drops ibus.post_installinstalls the fork's own names with no sed or mv. INSTALL.msg states the niri first run (no prompt; typing goes direct) and never points at the portal on niri. The container smoke queries the fork's distribution name..gitea/workflows/xbps-release.yml: tag-only trigger, exact tag-to-version.pygate, hard failure without the signing secret, signed full repo data, publish to the tag release and the rollinglatestrelease. The GitHubbuild-xbpsjob is removed. The repository key is the existing Bongbetic RSA-4096 key, committed as its public half inpackaging/void/repository-key.plist. Fingerprintfa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25is in the README, and a test checks the README against the committed key.tests/test_rename_sweep.pyallowlist rules are scoped to retained surfaces (upstream slugs and labelled upstream prose, the retained package's modules and imports, legacy-import files, tooling paths only). Tests check that user-facing old names are denied. The web install page no longer shows the bare old name.host_env(), and the AST guard covers that script. Docstrings corrected. AGENTS.md layout updated.web/PRODUCT.md. The upstream star count is removed rather than repointed.Refs #16, #18, #20, #25, #26, #27, #28, #29, #30, #31. Tickets #19, #22 and #32 are not touched.
Related issue
Refs xavierk/voculinux#16 (spec) and the child tickets above. Deliberately not "Fixes": several child tickets remain open (hardware baseline, tray probe evidence, hardware acceptance).
Type of change
Checklist
src/passes (88 files).Full suite: the branch adds no failing test against origin/main (full-run diff:
new on branch: none, and the rename-sweep failure is fixed). About 145 failures are pre-existing order-dependent pollution from GTK and numpy mocks, and they also occur on origin/main when the suite runs in one session. Files pass alone.justis not installed here, so the recipes were run as their underlying commands:uv lock --check,generate_distro_package_map.py --checkandcheck_exports.pyall pass.Notes for reviewers
Decisions and open items for a human:
repo.shsigns as "Voculinux xbps@voculinux.com", while the committed key's label reads "Bongbetic soubarna@live.in". The fingerprint is the same either way.XBPS_SIGNING_KEYsecret must be created on Gitea. The private key is not in this PR. The first tag push needs explicit approval.config.jsoncan keep pointing at~/.config/vocalinux/dictionary.txt, which the app then reads and writes. The spec says to load the copy unchanged, so I did not change this. It needs a decision.__url__is VocaHQ/vocalinux), so fork users can see upstream versions. The packaged-install path is handled by the marker inutils/packaging.py.G-7NBBNJNNQ7looks like upstream's, and the footer links point at VocaHQ channels. Both need a decision. The Source link to git.bongbetic.com appears to 404 anonymously, so the repo may need to be public before the site ships.Semgrep's run-shell-injection rule flags ${{ github.ref_name }} and ${{ github.repository }} interpolated into run: blocks. The tag name is attacker-influenced input (git allows $, ( and ; in ref names), so the release upload steps now read both through step env and quote them.