# AGENTS.md Guidance for AI coding agents working in this repository. ## What this is `sb-builder`: a PyQt6 desktop GUI that drives the SlackBuild tooling on a Slackware -current build VM (slackrepo, mkhint, sbpull, sbshadow) over ssh. Two programs: the GUI (`sb_builder/`, PyQt6 incl. QtDBus, stdlib otherwise) and `remote/sbb-remote`, a single-file stdlib-only Python helper deployed on the VM. Do not add third-party dependencies to either. Design and phasing: `docs/superpowers/specs/`. Phase 1 (core) is `2026-10-19-sb-builder-phase1-design.md`. ## How it works - Every GUI action is `ssh sbb-remote [args]`; the helper answers with JSON. `sb_builder/remote.py` is the only code that runs ssh (and local `virsh`). `sb_builder/upstream.py` runs the local `nvchecker` over the repo's `nvchecker.toml`; the VM never sees the GitHub token. - Trust boundary: the helper runs only a fixed table of job kinds and builds argv itself from validated args. Never pass shell strings or paths from the GUI to the helper. - Jobs run detached on the VM (`setsid`, `stdin=/dev/null`), one at a time under a flock, logging to `/jobs//`. The GUI can disconnect and reattach with `job-follow `. - Exception, by design: `gpg-unlock` reads the signing key passphrase from stdin (`remote.call(..., stdin=...)`) and feeds it to `gpg-preset-passphrase`. Every other subprocess in the helper runs with `stdin=DEVNULL`, so nothing can read the passphrase off the ssh channel. Never put the passphrase in argv, env, logs or files. - Hints are parsed (`shlex`), never sourced. `sb_builder/hints.py` mirrors slackrepo's own rules (`../GITHUB/slackrepo/libexec/functions.d/parsefunctions.sh`); check that file when changing it. - Hint editing lives in the helper (`hint-update`, `hint-new`, `fix-current` jobs run the helper's own `_hint` command): text edits, `.bak` before each write, a final `SBB-RESULT {json}` log line read back with `job-result`. The phantom deps list is the helper's `phantom_deps_file`; sbshadow runs `sbb-remote fix-current-now` after each sync. mkhint stays on the VM only for neovim (bundled deps, `manifest_packages`), until phase 4b part 2. - Jobs never get input. Any tool run as a job must be non-interactive (`--yes`/`--no` style flags). If a tool needs a flag it lacks, add it in that tool's own repo (e.g. `slackrepo_setup` for sbshadow) rather than working around it here. ## UI guidelines Every tab follows these (design: `docs/superpowers/specs/2026-10-20-sb-builder-ui-design.md`). 1. Tabs are permanent and load their data lazily on first show (`loaded`, `reload()`); the ribbon never opens or closes tabs. 2. Main ribbon: the tab's actions (`tab.actions`) in workflow order. Small ribbon: view controls (`tab.view_actions`). Build them with `sb_builder/ui.py` `action()`. Nothing actionable lives inside a view except navigation links. 3. Change actions act on ticked rows, view actions on the highlighted row. A row click toggles its tick; Ctrl+A / Ctrl+Shift+A select visible / clear. 4. An inapplicable action is disabled with `ui.enable(action, reason)`, the reason in its tooltip; never hidden. 5. When a job this session started ends, its next step opens as a non-modal dialog where the user is (`MainWindow.mine`, `NextStep`, `HintResultDialog`); starting a job never switches tab; the Jobs tab shows activity; a dismissed step can be taken again from the ribbon. 6. A modal confirm only for destructive or outward steps (delete hint, publish, apply shadowing). ## Conventions - GUI config: `$XDG_CONFIG_HOME/sb-builder/config.toml`. Helper config: `/etc/sb-builder/config.toml` on the VM. Both read with `tomllib`, both have shipped `config.example.toml` files, defaults live in code. - One file per GUI tab. - Deploying to the VM is outward-facing: confirm before `install.sh --remote`. - Placeholders only in code, docs, tests and commits. ## Testing python3 -m unittest discover -s tests `test_gui.py` forces `QT_QPA_PLATFORM=offscreen` (the desktop session sets its own, and a modal dialog on the real display blocks the run). A fake remote must answer every call a test triggers: an unanswered call returns "ssh: connection failed", which opens a warning dialog that blocks forever offscreen. Headless (`test_gui.py` sets `QT_QPA_PLATFORM=offscreen`), no VM needed. `test_remote.py` loads `remote/sbb-remote` with `SourceFileLoader` (no `.py` extension) and points it at a temp state dir through `SBB_CONFIG`. Two `ResourceWarning: subprocess ... is still running` lines are expected: the detached `_run` is never waited for. To try the helper on the VM without installing it, copy it to `/tmp` there and run it with `SBB_CONFIG` pointing at a config whose `state_dir` is under `/tmp`. ## Repo facts - License: GPLv2 only (`LICENSE`, header in each source file). - Work directly on master. - `.superpowers/` is brainstorming scratch, ignored.