refactor(capabilities): unify framework gating into one capability vocabulary (#976)#978
Open
rayhanadev wants to merge 3 commits into
Open
refactor(capabilities): unify framework gating into one capability vocabulary (#976)#978rayhanadev wants to merge 3 commits into
rayhanadev wants to merge 3 commits into
Conversation
Next.js `output: "export"` apps have no request-time server, so rules that recommend server `redirect()`, middleware, or Server Actions emitted impossible advice. Detect static export and surface it through a general framework-capability vocabulary so the relevant rules adapt: server-fetch-without-revalidate gates off, nextjs-no-client-side-redirect keeps firing but drops its server clause, and no-prevent-default falls back to the framework-neutral <form> message. Refactors buildCapabilities into one declarative capability table (the single source of truth across all frameworks) consumed via a runtime `capabilities` channel, replacing rules' hardcoded framework Sets. Consolidates project-info/ from 47 tiny files into 15 cohesive modules. Behavior-neutral; the public export surface is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commit: |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit dbb9b86. Configure here.
Shorter name for the runtime capability accessor. Its doc comment now defines the whole vocabulary in one place: build-time gating (`requires` / `disabledBy` via `shouldEnableRule`) versus the runtime read (`hasCapability`). The `disabledBy` -> `disabledWhen` rename is left as a follow-up; it touches ~80 rule files plus the registry codegen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consistent vocabulary with `hasCapability`: `disabledWhen: ["react-compiler"]` reads "disabled when react-compiler is present." Pure rename of the rule-metadata field and the `shouldEnableRule` parameter across 21 files; behavior is unchanged (net 0 LOC). Historical CHANGELOG entries keep the old name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
A Next.js app built with
output: "export"is fully static. No server runs when someone loads a page. But several rules still suggested server-only fixes: a serverredirect(), middleware, or a Server Action. A static export can't do any of those, so the advice was impossible to follow (#976).The deeper problem: each rule had its own hardcoded list of frameworks, and at runtime it could only see the framework name. So a rule couldn't tell that this
nextjswas a static export with no server. Now every rule reads one shared list of capabilities instead.Before: every rule guesses on its own, from just the framework name.
After: one shared list, and one question decides what each rule does.
flowchart TD PI["ProjectInfo: the facts about a project"] BC["buildCapabilities: one shared list of what<br/>the project can do (nextjs, server-actions,<br/>nextjs:static-export, ...)"] Q{"Does the rule still have<br/>something true to say here?"} OFF["No: turn the rule off<br/>(requires / disabledWhen)"] ON["Yes, but the advice changes:<br/>keep it on, pick the right message<br/>(hasCapability)"] SF["server-fetch-without-revalidate:<br/>no server means nothing to warn about,<br/>so it stays off"] NPD["no-prevent-default:<br/>the form still breaks without JS, so it warns,<br/>but doesn't suggest a Server Action"] PI --> BC --> Q Q -->|no| OFF --> SF Q -->|yes| ON --> NPDWhat changed
buildCapabilitiesis now a short list of rules instead of a 115-line if/else block. It's the single place that says what each framework supports.hasCapability(...).no-prevent-defaultnow checks forserver-actionsinstead of keeping its own framework list.nextjs:static-exportand losesserver-actions. Soserver-fetch-without-revalidateturns off,nextjs-no-client-side-redirectstill warns but without the server advice, andno-prevent-defaultshows the plain<form>message.project-info/dropped from 47 files to 15. The merges are renames, so behavior and the publicindex.tsexports stay the same. The change is 11 lines shorter overall.Test plan
pnpm typecheck && pnpm test && pnpm lint && pnpm smoke:json-reportall pass. The full suite is 2,022 tests, run fresh with no cache.For later, out of scope here: mirror the
no-prevent-defaultchange in the ESLint plugin, and teach the static-export and React Compiler detectors to look in workspace folders (the Bugbot note).🤖 Generated with Claude Code
Note
Medium Risk
Touches project discovery, rule enablement, and diagnostic text for all Next.js projects; wrong static-export detection could hide valid server rules or show bad advice, though defaults favor keeping server-aware behavior when config is missing.
Overview
Fixes #976 for Next.js apps with
output: "export": discovery setsisStaticExportfromnext.config.*, and capability building addsnextjs:static-exportwhile withholdingserver-actions. Rules no longer push impossible server fixes—server-fetch-without-revalidateis off, redirect advice is softened in core, andno-prevent-defaultuseshasCapability("server-actions")instead of a hardcoded framework list.Capability model:
buildCapabilitiesis refactored to a declarativeFRAMEWORK_CAPABILITIEStable; the sorted capability set is passed into oxlint settings for runtime checks. Rule metadatadisabledByis renamed todisabledWhenacross lint and security-scan gating.project-infocleanup: many small modules are consolidated intodependencies.ts,detectors.ts,workspaces.ts,version.ts,fs-utils.ts, andrn-metadata.tswith import path updates only (behavior preserved). Version helpers unify underisMajorMinorAtLeastwith tests split accordingly.Reviewed by Cursor Bugbot for commit 81c9bfe. Bugbot is set up for automated code reviews on this repo. Configure here.