Repo rules for AI agents working on qui.
- Stay inside requested scope. Do not implement review-suggested/extra changes without explicit user approval.
- Treat other agent/Codex/CodeRabbit feedback as input to discuss, not automatic action.
- A review suggestion that changes a branch lands only after you show the input that branch guarded, in a test or a trace. A simplification that reads cleaner can still drop a case the old guard handled.
- qui is single-user self-hosted software. Prefer readable, maintainable code over paranoid guards for impossible states.
- Backend:
cmd/qui,internal/, sharedpkg/ - Frontend:
web/src, assetsweb/public, bundle outputinternal/web/dist - User docs:
documentation/docs/; internal notes:docs/(gitignored except the paths.gitignoreallows; add a!docs/<file>line to commit a new note) - Docker/compose/release files: repo root. Releases build with
.goreleaser.release.yml, local builds with.goreleaser.yml; edit both.
Keep README.md concise; put feature deep-dives in documentation/docs/.
Before changing cross-module data flow, service boundaries, API routing, or long-lived architecture, read docs/architecture.md.
- Build:
make build(frontend bundle + Go binary) - Backend only:
make backend - Frontend only:
make frontend - Dev:
make dev,make dev-backend,make dev-frontend - Required before final for code changes:
make precommit, targeted tests for touched packages,make build - Go tests: always use
-race -count=1 - Full Go suite:
make test(go test -race -count=1 -v ./...) - OpenAPI changes under
internal/web/swagger: runmake test-openapi
CI runs make test on every push. Run the full suite locally only when asked, or when one change crosses many packages.
Before opening or updating a PR, complete these steps for the full PR diff:
- Review changed code and its callers for backend and frontend performance risks. Include shared helpers, dependencies, and configuration. Consider call frequency and data size when assessing allocations, nested scans, queries, concurrency, rendering, and requests. If no performance risk applies, explain why in the PR's Performance section.
- If a change can affect performance, you must measure before and after. If the risk is unclear, measure it. Use the merge-base with the PR's target branch as the baseline. Compare it against the latest PR code under the same workload and environment. Use representative synthetic data and local stubs for external services. Repeat runs to distinguish regressions from measurement noise.
- Use existing benchmarks, profilers, or repeatable browser measurements. Temporary measurement code is sufficient. Committing benchmark files is optional. For Go benchmarks, measure without
-race. For rendering and interaction changes, measure the browser with a production build. - Record the affected paths, revisions, workload size, environment, and measurement method in the PR's Performance section. Include before/after numbers, the measured differences, and your conclusion. Choose relevant metrics, such as time, memory, allocations, request counts, or bundle size. CI results qualify only when they provide this comparison. Otherwise, measure locally, even when CI covers the tests.
- Investigate regressions beyond measurement noise. Fix them or obtain explicit maintainer acceptance of the measured cost before declaring the PR ready. After further code changes, repeat the affected measurements. If measurements are blocked, report the blocker and keep this step incomplete.
make precommit= fmt + gofix on changed files, thenmake lint.make lint= golangci-lint on Go issues that are new since thedevelopmerge-base, then the fullpnpm lint.make lint-jsonwriteslint-report.json.make fmt= gofmt + frontend eslint fix on changed files.- Avoid repo-wide
pnpm format/eslint --fixsweeps unless explicitly requested. - If lint/check output reveals a real issue, fix the smallest relevant scope or report why blocked.
- If lint output is unclear or requires policy judgment, read
docs/linting.md; otherwise treat tool output and config as source of truth.
- Keep Go
gofmtclean. - Exports: PascalCase. Locals: camelCase.
- Group package interfaces by domain under
internal/<area>. - Prefer explicit error handling.
- Keep interfaces small (<=5 methods).
- Avoid
map[string]interface{}; use structs. - No backward compatibility shims unless requested.
- Go 1.22+: do not add
tt := ttin parallel subtests. - Tests live beside code as
*_test.go; prefer table-driven tests and existing fixtures. - Test file writes should use
os.WriteFile(..., 0o600)unless broader mode is required.
- Prefer behavior-bearing branches only.
- If multiple
switchcases equaldefault, collapse them. - Boolean classifiers should list exceptional
true/error cases; letdefaulthandle common path. - Do not add documentation-only branches unless compiler/linter/tests enforce value.
- A row that shows the bug or the new behavior must fail against the code before the change. A row that expects no output can pass for the wrong reason, so the table also needs a case that does produce output.
A comment caches what the code cannot show: why this shape, the bug a guard prevents, a coupling to another file. Caching what the line does buys nothing and rots first. One line is the norm.
- Change a line, change its comment, in the same diff. A stale-comment finding from a review bot is right; a docstring coverage percentage is not.
- An invariant a future change must hold is a test, not a sentence with "must not" in it.
- Doc-comment an exported identifier when its name leaves the contract unclear.
qui must work on Windows and Unix-like hosts.
- Host-only paths (data dir, backups):
filepath.Join,filepath.Clean,filepath.Rel,filepath.Separator. - Paths to or from an
fsops.Backend, including save paths from qBittorrent: usebackend.Paths(). A remote backend uses slash paths, and hostfilepathchanges them on a Windows host. - Slash-delimited formats only:
pathfor torrent-internal file names, URLs, API payloads. - At torrent/API -> local FS boundaries: validate slash paths, then convert with
filepath.FromSlash. - Traversal checks must reject POSIX + Windows escaping on every OS: leading
/, leading\, drive letters, UNC,... - Cross-platform tests: avoid raw
"/foo/"local path assertions; usefilepath.ToSlashorfilepath.Join. - Path traversal tests should include POSIX and Windows cases.
Frontend-specific rules live in web/AGENTS.md. Read that file before you edit, spec, or review a change to web/, i18n, React components, or frontend tests.
- DB schema changes need SQLite + Postgres migrations, matching model/store updates, same PR.
- Open PRs: consolidate schema work to at most one new SQLite migration and one new Postgres migration; edit draft migrations before merge.
- API contract changes must update
internal/web/swaggerand passmake test-openapi. - New
string_poolFK columns need a leading index in BOTH the SQLite and Postgres migrations, plus an entry inreferencedStringsInsertQuery. Neither engine auto-indexes FK child columns and the daily string_pool GC full-scans unindexed ones (discussion #2048).TestStringPoolFKColumnsAreIndexedenforces this on SQLite; the Postgres index is on you. - Keep diffs minimal in high-churn areas:
internal/services/crossseed,internal/qbittorrent,internal/models.
- Keep Superpowers workflow files local and untracked; never add or commit
docs/superpowers/. - Before you open a PR or add commits to one, review the complete PR diff for documentation needs. If the diff needs Docusaurus documentation, update
documentation/docs/in the same PR. State in the final report whether you updated the documentation or why no update was needed. - When available, use the
simple-english,unslop, andstop-slopskills for documentation prose. - Conventional commits:
feat(scope):,fix(scope):, etc. - Before each commit, review the diff for over-engineering. If the ponytail plugin (https://github.com/DietrichGebert/ponytail) is installed, use its
ponytail:ponytail-reviewskill. If it is not, do a trim pass: remove speculative config, unused states, single-caller layers, and duplicate helpers. - Update PR branches by merging develop into them, never rebase/force-push. PRs are squash-merged, so rebase gains nothing and force-pushes break review history and contributors' local branches.
- Never add AI advertising/attribution/co-author lines.
- Fill
.github/pull_request_template.mdinto the PR body;gh pr create --bodydoes not auto-fill it. - Never publish private tracker links or torrent names taken from a client. This covers PR and issue titles, bodies, and comments, commit messages, and
documentation/.- No tracker URL that carries a path, query, or key: torrent pages, announce URLs, passkeys,
.torrentlinks. Bare hostnames and tracker names stay allowed; the code and docs use them. - No release name copied word for word from a user report or a torrent client. Build an equivalent name: keep each token that matters, change the title and the group. Make sure the new name still causes the bug before you publish it.
- Naming a work in prose, or building a name from a real title and group tag, is allowed. The rule is about strings copied from someone's client, not about which words you use.
- Scrub reports from Discord or DMs the same way before you quote them. Keep the real string in notes outside the repo so the repro stays runnable; tracked files under
docs/count as published. - New test fixtures and code comments use names built by the rule above. Do not sweep the existing ones.
- Screenshots: capture from an instance you fill with synthetic torrents. If the bug shows only on a real library, blur the name, tracker, and save path columns.
- No tracker URL that carries a path, query, or key: torrent pages, announce URLs, passkeys,
Before you report a code change complete, run it live: build and start the app (make build then the binary, or make dev) and exercise the behavior the change touches. Report the command and the output you observed. If the change needs human judgment (UI look and feel, real tracker behavior), ask the user to test it and say what remains for them. If a live run is not possible, say so and name the closest check you did run.
State required checks run, skipped/deferred checks with reason, and unresolved failures. Do not claim complete while a required repo check is known failing unless user accepts the risk.
- Issue tracker: bug reports and feature requests are GitHub Discussions;
ready-for-agentwork becomes a linked issue. Seedocs/agents/issue-tracker.md. - Triage: labels equal the five role names (
needs-triage,needs-info,ready-for-agent,ready-for-human,wontfix). Both the workflow and a local/triagesession obeydocs/agents/triage.md; its outcomes override the skill's own outcomes.ready-for-agent(bugonly) creates the linked issue and closes the discussion. Do not post the brief on the discussion. - Domain docs:
GLOSSARY.mdat the root, ADRs indocs/adr/. Seedocs/agents/domain.md.
These rules are for AI PR reviewers. The agent workflow rules in this file (precommit, field test, commit gate, PR body format) are for coding agents. Do not apply them to PR authors.
- Report a defect only when the change causes a concrete wrong behavior. Name the trigger and the result for the user. If you cannot name both, omit the finding.
- Check the merge base. If
developalready has the problem, still report it, but label it "already on develop" and do not call it a regression. - When the PR body, a linked issue, an ADR in
docs/adr/, or a code comment calls a behavior deliberate, respond to that reason. Report a design flaw only when you can say why the stated reason does not hold. - Do not report what gofmt, golangci-lint, ESLint, tsc, or
pnpm check:i18nalready report. Do not ask for docstrings. - Read earlier review threads. Do not repeat a finding that was resolved or refuted, unless you have new evidence.
- Treat a change to the SQL of a migration that already exists on
develop, or a rename of one, as P1. Migrations are tracked by file name only: installs that ran it never run the new SQL, and a renamed file runs again. Safe path: put the change in a new migration.