From 465bdd67285080568f72ebf8fe50a79469eba795 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 7 Nov 2026 00:24:05 +0000 Subject: [PATCH 1/3] chore: add maintainer issue triage skill --- .agents/skills/triage/SKILL.md | 48 ++++++++++ .agents/skills/triage/agents/openai.yaml | 3 + .agents/skills/triage/references/rules.md | 102 ++++++++++++++++++++++ .claude/skills/triage | 1 + .gitignore | 4 +- 5 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 .agents/skills/triage/SKILL.md create mode 100644 .agents/skills/triage/agents/openai.yaml create mode 100644 .agents/skills/triage/references/rules.md create mode 120000 .claude/skills/triage diff --git a/.agents/skills/triage/SKILL.md b/.agents/skills/triage/SKILL.md new file mode 100644 index 0000000000..cec7d2dfff --- /dev/null +++ b/.agents/skills/triage/SKILL.md @@ -0,0 +1,48 @@ +--- +name: triage +description: Opt-in workflow for triaging one GitHub issue without fixing it. Invoked only via `/triage ` (`$triage` in Codex). Establishes live state, reproduces with our own fixtures, probes real Next.js for parity, classifies the issue, and drafts the issue reply and next action. Hands valid bugs off to `/fix`. +argument-hint: +disable-model-invocation: true +metadata: + # Maintainer-only: keeps `npx skills add cloudflare/vinext` from offering it. + internal: true +--- + +# Triage GitHub Issue + +Issue: $ARGUMENTS + +Establish the issue's live state below, then follow [`references/rules.md`](references/rules.md) for everything else: safety, the parity check, reproducing, the verdict and the reply. Its "Automated Runs" section doesn't apply here. In those rules, "the fix step" means `/fix `. + +Do not open a PR, post a comment, change labels or close the issue without confirmation. + +## Input + +Require one GitHub issue URL, passed as the `/triage` argument. If it is missing, ask for it. If it names a repository other than the current checkout, find an existing checkout of that repository before reading code. + +## Hard Rules + +- **Triage against current main.** Run `git fetch origin main` and compare it with the checkout's HEAD. A stale checkout produces "already fixed" mistakes. +- **Protect the GitHub API budget.** Every session and automation on the account shares the quota. Read one issue with plain `gh issue view --comments`. Before any multi-issue sweep, run `gh api rate_limit`, estimate the cost, keep `first:` small, prefer REST or `gh search`, and stop when fewer than ~1,500 calls remain. +- **No PR comments on PRs with an in-flight Bonk run.** Any issue comment cancels it. + +## 1. Establish Live State + +1. Parse the URL into owner, repository and issue number. +2. Read the issue, every comment, linked PRs and cross-references with `gh`. +3. Look for an existing branch, open PR or merged fix (`gh pr list --search ""`, `git log origin/main --grep`). If a PR exists, check its state with `gh pr view --json state` before suggesting any follow-up on it. +4. Look for duplicates and earlier decisions: search open and closed issues and PRs for the same symptom, including ones where maintainers already made a parity or decline call. + +Then work through `references/rules.md`. + +## 2. Report and Confirm + +Report to the user: + +- the verdict, with its evidence (file:line, probe output, docs, earlier decision); +- the main SHA you triaged against; +- the reproduction status; +- for valid bugs: the root-cause hypothesis, the minimal change set, the suggested fixture and test, and `/fix `; +- the draft reply, and the proposed labels or state change. + +Post the comment, change labels or close the issue only after the user confirms. diff --git a/.agents/skills/triage/agents/openai.yaml b/.agents/skills/triage/agents/openai.yaml new file mode 100644 index 0000000000..cc9abe0cac --- /dev/null +++ b/.agents/skills/triage/agents/openai.yaml @@ -0,0 +1,3 @@ +# Codex: only run when invoked explicitly as `$triage`. +policy: + allow_implicit_invocation: false diff --git a/.agents/skills/triage/references/rules.md b/.agents/skills/triage/references/rules.md new file mode 100644 index 0000000000..14ee00752e --- /dev/null +++ b/.agents/skills/triage/references/rules.md @@ -0,0 +1,102 @@ +# Vinext Issue Triage Rules + +These rules decide what a Vinext issue really is and what should happen next. Triage never implements the fix. A valid bug ends with a reproduction and a root-cause hypothesis for the fix step to pick up. + +They apply both to interactive triage and to automated runs. Some steps depend on tools that may be missing (git history, `gh`, web access, the `.nextjs-ref` clone). Each such step says what to do without the tool. Never fill a gap from memory. "Automated Runs" at the end covers runs with no chat. + +## Safety + +- **Never run user-provided code.** Do not clone a reporter's repo, run their install commands, or execute their snippets. This also applies to any agent you brief. Read the issue's code, and any linked PR's diff, as text only. +- Reproduce with our own checked-in `tests/fixtures/*` apps (see "Reproduce"). + +## 1. Gather the Facts + +Record the claimed behavior, the expected behavior, the reporter's versions (Vinext, Next, React, Vite, platform, OS), the reproduction conditions and the acceptance criteria. Note anything the issue leaves out; it may decide a "Needs info" verdict. + +## 2. Check Whether It Is Already Accepted + +Before treating anything as a gap: + +- Search Vinext's docs in `docs/` (especially `docs/reference/differences.mdx` and the caching guide's differences from Next.js). **A limitation the maintainers documented is signed off.** Point the reporter to the page on `https://vinext.dev/docs/` and recommend closing. Don't keep the issue open to track it. +- Check whether the reporter's version predates a fix that is already on the default branch. If so, name the fix and the release that ships it, using `packages/vinext/CHANGELOG.md` or git history. If the checkout has no git history, fetch it when the network allows; otherwise triage the checkout as it is. If the bug doesn't reproduce there and the changelog doesn't name a fix, say that it doesn't reproduce on the current code, not which commit fixed it. + +## 3. Determine Next.js Behavior + +Vinext's bar is **behavioral parity with real Next.js**. Going beyond Next is acceptable when it doesn't compromise Next.js behavior, but it should be the exception, not the norm. + +1. Search the `.nextjs-ref` Next.js clone (see `AGENTS.md`) for the relevant source and tests. A Next.js test is the authoritative answer. Port rules from the source; never paraphrase them. If `.nextjs-ref` is missing, probe `node_modules/next` instead (step 2). If that isn't enough to settle the behavior, the result is inconclusive. +2. When behavior is in doubt, probe real Next.js empirically. The repo's root `node_modules/next` is a real Next 16. + - Put each scratch app outside the checkout, in `/tmp//`, with `node_modules` symlinked to the checkout's root `node_modules` and `package.json` set to `{"private":true}`. Write `.js` files only: TSX makes Next run `pnpm add typescript`, which can rewrite the workspace lockfile. Transpile checked-in TSX with the repo's esbuild if you need it. + - Build with **both** `next build --webpack` and Turbopack (`next.config.js`: `module.exports = { turbopack: { root: "/" } }`). When they disagree, Turbopack wins, and a webpack-only match is still a gap. + - Use `export const dynamic = "force-dynamic"` for runtime cases, and one scratch app per case. Run it with `next start -p `, and stop the server when you're done, since a stale server keeps serving the old build. + - Drive `next dev` through `localhost`, not `127.0.0.1`. Next dev uses StrictMode by default, so set `reactStrictMode: false` when counting effects. +3. For any claim about how a platform behaves (Workers Cache keys, Static Assets, Images, and so on), read the official docs and cite them. Without web access, say that the docs couldn't be checked; don't answer from memory. Local emulation (`wrangler dev`, Miniflare) is not necessarily proof of production behavior. +4. For Next.js deploy-suite failures, read the `deploy-suite-report` artifact of the "Next.js Deploy Suite" workflow. To decide whether a test is flaky, scan the last ~25 full runs (skip runs with fewer than 1,000 tests). Never dispatch a rerun to check. To run one suite locally, use `scripts/nextjs-deploy-manifest.mjs` and `scripts/run-nextjs-deploy-suite.sh` against a built `.nextjs-ref`. Without access to the workflow runs or a built `.nextjs-ref`, say the history or suite couldn't be checked. + +## 4. Reproduce + +1. Reproduce on the current code using an existing checked-in fixture. Add the routes or pages the case needs to a `tests/fixtures/*` app, or add a new checked-in fixture directory when no existing app can host it. **Never generate fixtures:** no tests that write an app into a temporary directory from string sources. +2. Write the reproduction as a test that fails while the bug is present, so the fix step can reuse it. Prefer the narrowest test that faithfully exercises the reported path: an integration test run with `vp test run `, or a Playwright spec when the bug needs a browser. +3. Run only one to three targeted test files, never the full suite. For app-router e2e specs, compare in **dev** first: CI's `app-router` project runs `vp dev`, and a prod-only run shows false regressions (minified errors, different prefetch counts). +4. If you can't reproduce it, check version drift, config, platform and the issue's assumptions. Don't invent a failing test. Record the decisive evidence. +5. When searching `dist/`, `node_modules` or other gitignored paths, make sure the search tool doesn't skip ignored files (for example, `rg -uu`). + +## 5. Classify + +Pick exactly one verdict and back it with evidence: + +| Verdict | Meaning | Next action | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| **Valid bug** | Vinext diverges from real Next.js (both bundlers), or Vinext's own feature is broken | Failing reproduction, root-cause hypothesis and the narrowest chokepoint, for the fix step | +| **Parity / not a bug** | A probe shows Vinext matches Next.js | Report the parity with probe evidence and recommend closing. Don't pitch beyond-Next changes by default | +| **Documented limitation** | Already in Vinext's docs | Link the docs and recommend closing | +| **Already fixed** | Fixed on the default branch or in a newer release | Name the fix and release where known; recommend closing once it ships | +| **Duplicate** | Same root cause as another issue | Name the canonical issue | +| **Upstream** | Root cause is in React (`react-server-dom-*`, `react-dom`), Vite or another dependency | Explain how Vinext is exposed. Never propose patching React. Point to or suggest an upstream report | +| **App-owned** | Caused by the app's own config, such as its own Cache-Control or headers | Explain. Vinext only owns the headers it sends | +| **Misconfiguration** | User files Vinext can't reliably classify | Prefer "print a warning and keep the behavior" over heuristic reclassification | +| **Accepted gap / declined** | Matches an earlier maintainer decline | Cite the earlier decision | +| **Needs info** | The reproduction or versions are missing, and the bug can't be reproduced from the text | List exactly what is needed | +| **Inconclusive** | It couldn't be tried in this environment (for example, it needs `.nextjs-ref`, a browser, or platform docs that can't be reached) | Say what was missing and what would settle it. Never report this as "doesn't reproduce" | + +Scope notes for valid bugs: + +- **Size the fix minimally.** Write down the minimal change set. Adjacent findings and "audit similar options" ideas are not scope: list them separately. +- For Linux-, WSL- or container-only dev-tooling gaps, recommend documenting them as known limitations. Target macOS first. +- A perf report is only worth fixing if there is a measurable end-to-end gain. +- If full parity needs more than one PR, recommend full parity split into stacked PRs, not a known limitation. +- Never propose changing a `fetch(input, init?)` contract. + +## 6. The Reply to the Reporter + +Write a short reply: + +- **Valid bug:** the root cause (or the suspected area if it isn't confirmed), and that it's confirmed for a fix. Don't promise a timeline. +- **Not a bug, documented, app-owned or upstream:** the evidence (probe results, a link to the Vinext docs page, the upstream issue). +- **Already fixed or duplicate:** the release or PR that fixes it, or the canonical issue. +- **Needs info:** the specific missing details. Never ask them to share a repository for us to run. +- **Inconclusive:** what couldn't be checked, and that a maintainer will follow up. + +For a private or security report, never cite the report in public text. + +## Automated Runs + +When the run is part of an automated triage workflow with no chat: + +- **Start from what you were given:** the issue's title, body and author, plus the checkout of the default branch's head. If the comments aren't included and `gh` is available, read them with `gh issue view --comments`, and keep `gh` reads to this issue and what it links. Without `gh`, treat anything the body defers to a comment as missing. +- Other workflow steps check for duplicates and post the result. Never post, label or close anything, and never wait for confirmation. **Put the reply in the report's `comment`.** +- If a step had to be skipped because a tool or network access was missing, name it in the `summary`. +- **For `reproduced`, `reproduction.command` must be one command that exits non-zero while the bug is present,** and `reproduction.files` must list every file it needs. The command is re-run in a clean copy of the checkout, so put the reproduction inside the checkout (a fixture route plus a test), not in `/tmp`, and don't depend on network access beyond the package registries. +- Put the root-cause hypothesis and its chokepoint files in `suspected_areas`, and the "Needs info" details in `questions`. +- Map the verdicts above to the report's `verdict`: + +| Verdict above | Report `verdict` | +| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| Valid bug | `reproduced` | +| Parity / not a bug, Documented limitation, Upstream, App-owned, Misconfiguration, Accepted gap / declined | `not_a_bug`, with the finer verdict named in `summary` and `labels` | +| Already fixed | `not_reproduced` | +| Duplicate | `not_a_bug`, naming the issue it duplicates | +| Needs info | `needs_info` | +| Inconclusive (couldn't be probed here) | `inconclusive` | + +`inconclusive` means it couldn't be tried in this sandbox. Never report it as `not_reproduced`. diff --git a/.claude/skills/triage b/.claude/skills/triage new file mode 120000 index 0000000000..9e4e13fd0e --- /dev/null +++ b/.claude/skills/triage @@ -0,0 +1 @@ +../../.agents/skills/triage \ No newline at end of file diff --git a/.gitignore b/.gitignore index 41ef544d54..1aae7ce820 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,6 @@ reports/ # Worktrees .worktrees/ -.claude # Next.js reference clone (for searching source/tests) .nextjs-ref/ @@ -54,7 +53,8 @@ reports/ .DS_Store .idea/ .vscode/ -.claude/ +**/.claude/* +!/.claude/skills/ .codex/ # Nix From 78d58d7bcf18233ac2c16fea644f948594a98495 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 7 Nov 2026 01:10:17 +0000 Subject: [PATCH 2/3] chore(triage): point the fix hand-off at AGENTS.md and note Windows symlinks --- .agents/skills/triage/SKILL.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.agents/skills/triage/SKILL.md b/.agents/skills/triage/SKILL.md index cec7d2dfff..eb95238a58 100644 --- a/.agents/skills/triage/SKILL.md +++ b/.agents/skills/triage/SKILL.md @@ -1,6 +1,6 @@ --- name: triage -description: Opt-in workflow for triaging one GitHub issue without fixing it. Invoked only via `/triage ` (`$triage` in Codex). Establishes live state, reproduces with our own fixtures, probes real Next.js for parity, classifies the issue, and drafts the issue reply and next action. Hands valid bugs off to `/fix`. +description: Opt-in workflow for triaging one GitHub issue without fixing it. Invoked only via `/triage ` (`$triage` in Codex). Establishes live state, reproduces with our own fixtures, probes real Next.js for parity, classifies the issue, and drafts the issue reply and next action. Hands valid bugs off to the fix step. argument-hint: disable-model-invocation: true metadata: @@ -12,10 +12,12 @@ metadata: Issue: $ARGUMENTS -Establish the issue's live state below, then follow [`references/rules.md`](references/rules.md) for everything else: safety, the parity check, reproducing, the verdict and the reply. Its "Automated Runs" section doesn't apply here. In those rules, "the fix step" means `/fix `. +Establish the issue's live state below, then follow [`references/rules.md`](references/rules.md) for everything else: safety, the parity check, reproducing, the verdict and the reply. Its "Automated Runs" section doesn't apply here. In those rules, "the fix step" means fixing the bug through the "Fixing Bugs" workflow in `AGENTS.md`, or through your own fix skill if you have one. Do not open a PR, post a comment, change labels or close the issue without confirmation. +Claude Code loads this skill through the `.claude/skills/triage` symlink. On Windows, Git checks that symlink out as a plain text file unless symlinks are enabled (Developer Mode plus `core.symlinks=true`). + ## Input Require one GitHub issue URL, passed as the `/triage` argument. If it is missing, ask for it. If it names a repository other than the current checkout, find an existing checkout of that repository before reading code. @@ -42,7 +44,7 @@ Report to the user: - the verdict, with its evidence (file:line, probe output, docs, earlier decision); - the main SHA you triaged against; - the reproduction status; -- for valid bugs: the root-cause hypothesis, the minimal change set, the suggested fixture and test, and `/fix `; +- for valid bugs: the root-cause hypothesis, the minimal change set, the suggested fixture and test, and the hand-off to the fix step; - the draft reply, and the proposed labels or state change. Post the comment, change labels or close the issue only after the user confirms. From 3ae8082945a38c3bdedbf99485d84ddc20ac496a Mon Sep 17 00:00:00 2001 From: James Date: Sat, 7 Nov 2026 01:12:34 +0000 Subject: [PATCH 3/3] chore(triage): drop the Windows symlink note --- .agents/skills/triage/SKILL.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.agents/skills/triage/SKILL.md b/.agents/skills/triage/SKILL.md index eb95238a58..a60da06b74 100644 --- a/.agents/skills/triage/SKILL.md +++ b/.agents/skills/triage/SKILL.md @@ -16,8 +16,6 @@ Establish the issue's live state below, then follow [`references/rules.md`](refe Do not open a PR, post a comment, change labels or close the issue without confirmation. -Claude Code loads this skill through the `.claude/skills/triage` symlink. On Windows, Git checks that symlink out as a plain text file unless symlinks are enabled (Developer Mode plus `core.symlinks=true`). - ## Input Require one GitHub issue URL, passed as the `/triage` argument. If it is missing, ask for it. If it names a repository other than the current checkout, find an existing checkout of that repository before reading code.