docs(skills): add nixpkgs review guidance
This commit is contained in:
@@ -5,6 +5,9 @@ Use this file to track mountpoints or directories that should be excluded from `
|
|||||||
## Known Ignores
|
## Known Ignores
|
||||||
|
|
||||||
- `$HOME/keybase`
|
- `$HOME/keybase`
|
||||||
|
- `$HOME/.cache/keybase`
|
||||||
|
- `$HOME/.local/share/keybase`
|
||||||
|
- `$HOME/.config/keybase`
|
||||||
- `/keybase`
|
- `/keybase`
|
||||||
- `/var/lib/railbird`
|
- `/var/lib/railbird`
|
||||||
- `/run/user/*/doc` (FUSE portal mount; machine-specific example observed: `/run/user/1004/doc`)
|
- `/run/user/*/doc` (FUSE portal mount; machine-specific example observed: `/run/user/1004/doc`)
|
||||||
|
|||||||
77
dotfiles/agents/skills/nixpkgs-review/SKILL.md
Normal file
77
dotfiles/agents/skills/nixpkgs-review/SKILL.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
name: nixpkgs-review
|
||||||
|
description: Review or prepare nixpkgs package changes and PRs using a checklist distilled from review feedback on Ivan Malison's own NixOS/nixpkgs pull requests. Use when working in nixpkgs on package inits, updates, packaging fixes, or before opening or reviewing a nixpkgs PR.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nixpkgs Review
|
||||||
|
|
||||||
|
Use this skill when the task is specifically about reviewing or tightening a change in `NixOS/nixpkgs`.
|
||||||
|
|
||||||
|
The goal is not generic style review. The goal is to catch the kinds of issues that repeatedly came up in real nixpkgs feedback on Ivan's PRs: derivation structure, builder choice, metadata, PR hygiene, and JS packaging details.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Read the scope first.
|
||||||
|
Open the changed `package.nix` files, related metadata, and the PR title/body if there is one.
|
||||||
|
|
||||||
|
2. Run the historical checklist below.
|
||||||
|
Bias toward concrete review findings and actionable edits, not abstract style commentary.
|
||||||
|
|
||||||
|
3. Validate the package path.
|
||||||
|
Use the narrowest reasonable validation for the task: targeted build, package eval, or `nixpkgs-review` when appropriate.
|
||||||
|
|
||||||
|
4. If you are writing a review:
|
||||||
|
Lead with findings ordered by severity, include file references, and tie each point to a nixpkgs expectation.
|
||||||
|
|
||||||
|
5. If you are preparing a PR:
|
||||||
|
Fix the checklist items before opening it, then confirm title/body/commit hygiene.
|
||||||
|
|
||||||
|
## Historical Checklist
|
||||||
|
|
||||||
|
### Derivation structure
|
||||||
|
|
||||||
|
- Prefer `finalAttrs` over `rec` for derivations and nested derivations when self-references matter.
|
||||||
|
- Prefer `tag = "v${...}"` over `rev` when fetching a tagged upstream release.
|
||||||
|
- Check whether `strictDeps = true;` should be enabled.
|
||||||
|
- Use the narrowest builder/stdenv that matches the package. If no compiler is needed, consider `stdenvNoCC`.
|
||||||
|
- Put source modifications in `postPatch` or another appropriate hook, not inside `buildPhase`.
|
||||||
|
- Prefer `makeBinaryWrapper` over `makeWrapper` when a compiled wrapper is sufficient.
|
||||||
|
- Keep wrappers aligned with `meta.mainProgram` so overrides remain clean.
|
||||||
|
- Avoid `with lib;` in package expressions; prefer explicit `lib.*` references.
|
||||||
|
|
||||||
|
### Metadata and platform expectations
|
||||||
|
|
||||||
|
- For new packages, ensure maintainers are present and include the submitter when appropriate.
|
||||||
|
- Check whether platform restrictions are justified. Do not mark packages Linux-only or broken without evidence.
|
||||||
|
- If a package is only workable through patch accumulation and has no maintainer, call that out directly.
|
||||||
|
|
||||||
|
### JS, Bun, Electron, and wrapper-heavy packages
|
||||||
|
|
||||||
|
- Separate runtime deps from build-only deps. Large closures attract review attention.
|
||||||
|
- Remove redundant env vars and duplicated configuration if build hooks already cover them.
|
||||||
|
- Check bundled tool/runtime version alignment, especially browser/runtime pairs.
|
||||||
|
- Install completions, desktop files, or icons when upstream clearly ships them and the package already exposes the feature.
|
||||||
|
- Be careful with wrappers that hardcode env vars users may want to override.
|
||||||
|
|
||||||
|
### PR hygiene
|
||||||
|
|
||||||
|
- PR title should match nixpkgs naming and the package version.
|
||||||
|
- Keep the PR template intact unless there is a strong reason not to.
|
||||||
|
- Avoid unrelated commits in the PR branch.
|
||||||
|
- Watch for duplicate or overlapping PRs before investing in deeper review.
|
||||||
|
- If asked, squash fixup history before merge.
|
||||||
|
|
||||||
|
## Review Output
|
||||||
|
|
||||||
|
When producing a review, prefer this shape:
|
||||||
|
|
||||||
|
- Finding: what is wrong or risky.
|
||||||
|
- Why it matters in nixpkgs terms.
|
||||||
|
- Concrete fix, ideally with the exact attr/hook/builder to use.
|
||||||
|
|
||||||
|
If there are no findings, say so explicitly and mention remaining validation gaps.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- Read [references/review-patterns.md](references/review-patterns.md) for the curated list of recurring review themes and concrete PR examples.
|
||||||
|
- Run `scripts/mine_pr_feedback.py --repo NixOS/nixpkgs --author colonelpanic8 --limit 20 --format markdown` to refresh the source material from newer PRs.
|
||||||
4
dotfiles/agents/skills/nixpkgs-review/agents/openai.yaml
Normal file
4
dotfiles/agents/skills/nixpkgs-review/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Nixpkgs Review"
|
||||||
|
short_description: "Review nixpkgs changes with historical guidance"
|
||||||
|
default_prompt: "Use $nixpkgs-review to review this nixpkgs package change before I open the PR."
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# Nixpkgs Review Patterns
|
||||||
|
|
||||||
|
This reference is a curated summary of recurring feedback from Ivan Malison's `NixOS/nixpkgs` PRs. Use it to ground reviews in patterns that have already come up from nixpkgs reviewers.
|
||||||
|
|
||||||
|
## Most Repeated Themes
|
||||||
|
|
||||||
|
### 1. Prefer `finalAttrs` over `rec`
|
||||||
|
|
||||||
|
This came up repeatedly on both package init and update PRs.
|
||||||
|
|
||||||
|
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`: reviewer asked for `buildNpmPackage (finalAttrs: { ... })` instead of `rec`.
|
||||||
|
- [PR #490033](https://github.com/NixOS/nixpkgs/pull/490033) `rumno`: same feedback for `rustPlatform.buildRustPackage`.
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- If the derivation self-references `version`, `src`, `pname`, `meta.mainProgram`, or nested outputs, default to `finalAttrs`.
|
||||||
|
|
||||||
|
### 2. Prefer `tag` when upstream release is a tag
|
||||||
|
|
||||||
|
This also repeated across multiple PRs.
|
||||||
|
|
||||||
|
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`
|
||||||
|
- [PR #490033](https://github.com/NixOS/nixpkgs/pull/490033) `rumno`
|
||||||
|
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- If upstream publishes a named release tag, prefer `tag = "v${finalAttrs.version}";` or the exact tag format instead of a raw `rev`.
|
||||||
|
|
||||||
|
### 3. Use the right hook and builder
|
||||||
|
|
||||||
|
Reviewers often push on hook placement and builder/stdenv choice.
|
||||||
|
|
||||||
|
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: feedback to move work from `buildPhase` into `postPatch`.
|
||||||
|
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: feedback to consider `stdenvNoCC`.
|
||||||
|
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`: prefer `makeBinaryWrapper` for a simple wrapper.
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- Check whether each mutation belongs in `postPatch`, `preConfigure`, `buildPhase`, or `installPhase`.
|
||||||
|
- Check whether the package genuinely needs a compiler toolchain.
|
||||||
|
- For simple env/arg wrappers, prefer `makeBinaryWrapper`.
|
||||||
|
|
||||||
|
### 4. Enable `strictDeps` unless there is a reason not to
|
||||||
|
|
||||||
|
This was called out explicitly on [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465).
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- For new derivations, ask whether `strictDeps = true;` should be present.
|
||||||
|
- If not, be ready to justify why the builder or package layout makes it unnecessary.
|
||||||
|
|
||||||
|
### 5. Keep metadata explicit and override-friendly
|
||||||
|
|
||||||
|
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`: reviewer asked to avoid `with lib;`.
|
||||||
|
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: reviewer suggested deriving wrapper executable name from `finalAttrs.meta.mainProgram`.
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- Prefer `lib.licenses.mit` over `with lib;`.
|
||||||
|
- Keep `meta.mainProgram` authoritative and have wrappers follow it when practical.
|
||||||
|
|
||||||
|
### 6. Maintainers matter for new packages
|
||||||
|
|
||||||
|
- [PR #496806](https://github.com/NixOS/nixpkgs/pull/496806) `gws`: reviewer would not merge until the submitter appeared in maintainers.
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- For package inits, check maintainers early rather than waiting for review feedback.
|
||||||
|
|
||||||
|
### 7. PR title and template hygiene are review targets
|
||||||
|
|
||||||
|
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: asked to fix the PR title to match the version.
|
||||||
|
- [PR #490033](https://github.com/NixOS/nixpkgs/pull/490033) `rumno`: reviewer asked what happened to the PR template.
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- Before opening or updating a PR, verify the title, template, and branch scope.
|
||||||
|
|
||||||
|
### 8. Duplicate or overlapping PRs get noticed quickly
|
||||||
|
|
||||||
|
- [PR #490227](https://github.com/NixOS/nixpkgs/pull/490227) was replaced by [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230).
|
||||||
|
- [PR #490053](https://github.com/NixOS/nixpkgs/pull/490053) overlapped with [PR #490033](https://github.com/NixOS/nixpkgs/pull/490033).
|
||||||
|
- [PR #488606](https://github.com/NixOS/nixpkgs/pull/488606), [PR #488602](https://github.com/NixOS/nixpkgs/pull/488602), and [PR #488603](https://github.com/NixOS/nixpkgs/pull/488603) were closed after reviewers pointed to existing work.
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- Search for existing PRs on the package before spending time polishing a review.
|
||||||
|
- If a branch contains unrelated commits, fix that before asking for review.
|
||||||
|
|
||||||
|
### 9. JS/Bun/Electron packages draw runtime-layout scrutiny
|
||||||
|
|
||||||
|
This came up heavily on `t3code` and `playwright-cli`.
|
||||||
|
|
||||||
|
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) `t3code`: reviewers proposed trimming the runtime closure, removing unnecessary env vars, and adding shell completions and desktop integration.
|
||||||
|
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) `playwright-cli`: reviewers called out mismatched bundled `playwright-core` and browser binaries, and wrapper behavior that prevented user overrides.
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- For JS-heavy packages, inspect closure size, runtime vs build-only deps, wrapper env vars, and version alignment between bundled libraries and external binaries.
|
||||||
|
|
||||||
|
### 10. Cross-platform evidence helps
|
||||||
|
|
||||||
|
- [PR #490230](https://github.com/NixOS/nixpkgs/pull/490230) received an approval explicitly noting Darwin success.
|
||||||
|
- [PR #497465](https://github.com/NixOS/nixpkgs/pull/497465) got feedback questioning platform restrictions and build behavior.
|
||||||
|
|
||||||
|
Practical rule:
|
||||||
|
- If the package plausibly supports Darwin, avoid premature Linux-only restrictions and mention what was or was not tested.
|
||||||
|
|
||||||
|
## How To Use This Reference
|
||||||
|
|
||||||
|
- Use these patterns as a focused checklist before submitting or reviewing nixpkgs changes.
|
||||||
|
- Do not blindly apply every point. Check whether the builder, language ecosystem, and upstream release model actually match.
|
||||||
|
- When in doubt, prefer concrete evidence from the current package diff over generic convention.
|
||||||
2
dotfiles/agents/skills/nixpkgs-review/scripts/.gitignore
vendored
Normal file
2
dotfiles/agents/skills/nixpkgs-review/scripts/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
208
dotfiles/agents/skills/nixpkgs-review/scripts/mine_pr_feedback.py
Executable file
208
dotfiles/agents/skills/nixpkgs-review/scripts/mine_pr_feedback.py
Executable file
@@ -0,0 +1,208 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Mine external feedback from recent GitHub PRs.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
python scripts/mine_pr_feedback.py --repo NixOS/nixpkgs --author colonelpanic8
|
||||||
|
python scripts/mine_pr_feedback.py --repo NixOS/nixpkgs --author colonelpanic8 --limit 30 --format json
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd: list[str]) -> str:
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise RuntimeError(proc.stderr.strip() or f"command failed: {' '.join(cmd)}")
|
||||||
|
return proc.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def gh_json(args: list[str]) -> object:
|
||||||
|
return json.loads(run(["gh", *args]))
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_prs(repo: str, author: str, limit: int) -> list[dict]:
|
||||||
|
prs: dict[int, dict] = {}
|
||||||
|
for state in ("open", "closed"):
|
||||||
|
data = gh_json(
|
||||||
|
[
|
||||||
|
"search",
|
||||||
|
"prs",
|
||||||
|
"--repo",
|
||||||
|
repo,
|
||||||
|
"--author",
|
||||||
|
author,
|
||||||
|
"--limit",
|
||||||
|
str(max(limit, 30)),
|
||||||
|
"--state",
|
||||||
|
state,
|
||||||
|
"--json",
|
||||||
|
"number,title,state,closedAt,updatedAt,url",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for pr in data:
|
||||||
|
prs[pr["number"]] = pr
|
||||||
|
return sorted(
|
||||||
|
prs.values(),
|
||||||
|
key=lambda pr: (pr["updatedAt"], pr["number"]),
|
||||||
|
reverse=True,
|
||||||
|
)[:limit]
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_feedback(repo: str, author: str, pr: dict) -> dict:
|
||||||
|
owner, name = repo.split("/", 1)
|
||||||
|
number = pr["number"]
|
||||||
|
|
||||||
|
def api(path: str) -> list[dict]:
|
||||||
|
return gh_json(["api", f"repos/{owner}/{name}/{path}", "--paginate"])
|
||||||
|
|
||||||
|
issue_comments = api(f"issues/{number}/comments")
|
||||||
|
review_comments = api(f"pulls/{number}/comments")
|
||||||
|
reviews = api(f"pulls/{number}/reviews")
|
||||||
|
|
||||||
|
comments = []
|
||||||
|
for comment in issue_comments:
|
||||||
|
login = comment["user"]["login"]
|
||||||
|
body = (comment.get("body") or "").strip()
|
||||||
|
if login != author and body:
|
||||||
|
comments.append({"kind": "issue", "user": login, "body": body})
|
||||||
|
for comment in review_comments:
|
||||||
|
login = comment["user"]["login"]
|
||||||
|
body = (comment.get("body") or "").strip()
|
||||||
|
if login != author and body:
|
||||||
|
comments.append(
|
||||||
|
{
|
||||||
|
"kind": "review_comment",
|
||||||
|
"user": login,
|
||||||
|
"body": body,
|
||||||
|
"path": comment.get("path"),
|
||||||
|
"line": comment.get("line"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for review in reviews:
|
||||||
|
login = review["user"]["login"]
|
||||||
|
body = (review.get("body") or "").strip()
|
||||||
|
if login != author and body:
|
||||||
|
comments.append(
|
||||||
|
{
|
||||||
|
"kind": "review",
|
||||||
|
"user": login,
|
||||||
|
"body": body,
|
||||||
|
"state": review.get("state"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {**pr, "comments": comments}
|
||||||
|
|
||||||
|
|
||||||
|
def is_bot(login: str) -> bool:
|
||||||
|
return login.endswith("[bot]") or login in {"github-actions", "app/dependabot"}
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(results: list[dict], include_bots: bool) -> str:
|
||||||
|
commenters = Counter()
|
||||||
|
kept = []
|
||||||
|
for pr in results:
|
||||||
|
comments = [
|
||||||
|
comment
|
||||||
|
for comment in pr["comments"]
|
||||||
|
if include_bots or not is_bot(comment["user"])
|
||||||
|
]
|
||||||
|
if comments:
|
||||||
|
kept.append({**pr, "comments": comments})
|
||||||
|
commenters.update(comment["user"] for comment in comments)
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"# PR Feedback Summary",
|
||||||
|
"",
|
||||||
|
f"- PRs scanned: {len(results)}",
|
||||||
|
f"- PRs with external feedback: {len(kept)}",
|
||||||
|
"",
|
||||||
|
"## Top commenters",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
for user, count in commenters.most_common(10):
|
||||||
|
lines.append(f"- `{user}`: {count}")
|
||||||
|
|
||||||
|
for pr in kept:
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
f"## PR #{pr['number']}: {pr['title']}",
|
||||||
|
"",
|
||||||
|
f"- URL: {pr['url']}",
|
||||||
|
f"- State: {pr['state']}",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for comment in pr["comments"]:
|
||||||
|
body = comment["body"].replace("\r", " ").replace("\n", " ").strip()
|
||||||
|
snippet = body[:280] + ("..." if len(body) > 280 else "")
|
||||||
|
lines.append(f"- `{comment['user']}` `{comment['kind']}`: {snippet}")
|
||||||
|
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Collect review feedback from recent GitHub PRs.")
|
||||||
|
parser.add_argument("--repo", required=True, help="GitHub repo in owner/name form")
|
||||||
|
parser.add_argument("--author", required=True, help="PR author to inspect")
|
||||||
|
parser.add_argument("--limit", type=int, default=20, help="How many recent PRs to inspect")
|
||||||
|
parser.add_argument(
|
||||||
|
"--format",
|
||||||
|
choices=("markdown", "json"),
|
||||||
|
default="markdown",
|
||||||
|
help="Output format",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--include-bots",
|
||||||
|
action="store_true",
|
||||||
|
help="Keep bot comments in the output",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--workers",
|
||||||
|
type=int,
|
||||||
|
default=6,
|
||||||
|
help="Maximum concurrent GitHub API workers",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
run(["gh", "auth", "status"])
|
||||||
|
except RuntimeError as err:
|
||||||
|
print(err, file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
prs = fetch_prs(args.repo, args.author, args.limit)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
with ThreadPoolExecutor(max_workers=args.workers) as pool:
|
||||||
|
futures = [pool.submit(fetch_feedback, args.repo, args.author, pr) for pr in prs]
|
||||||
|
for future in as_completed(futures):
|
||||||
|
results.append(future.result())
|
||||||
|
|
||||||
|
results.sort(key=lambda pr: (pr["updatedAt"], pr["number"]), reverse=True)
|
||||||
|
|
||||||
|
if args.format == "json":
|
||||||
|
if not args.include_bots:
|
||||||
|
for pr in results:
|
||||||
|
pr["comments"] = [
|
||||||
|
comment for comment in pr["comments"] if not is_bot(comment["user"])
|
||||||
|
]
|
||||||
|
json.dump(results, sys.stdout, indent=2)
|
||||||
|
sys.stdout.write("\n")
|
||||||
|
else:
|
||||||
|
sys.stdout.write(render_markdown(results, args.include_bots))
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user