Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3381781
internal: Give Claude Code the same agent rules, skills and hooks as …
claude Oct 5, 2026
8db4197
internal: Run eslint --fix before push instead of per edit
claude Oct 5, 2026
b5e5ad2
internal: Rerun worktree setup until it succeeds
claude Oct 5, 2026
38c4a6a
internal: Skip lint-fixing files with uncommitted edits on push
claude Oct 5, 2026
dca38d4
internal: Simplify pre-push lint file selection and cache eslint
claude Oct 5, 2026
c2682f4
internal: Lint uncommitted JS/TS at the end of each agent turn
claude Oct 5, 2026
aced1f1
docs: Describe end-of-turn lint hook in AGENTS.md
claude Oct 5, 2026
eed7844
internal: Skip end-of-turn eslint when no uncommitted JS/TS changed; …
claude Oct 5, 2026
99b457f
internal: Track end-of-turn lint with its own marker instead of .esli…
claude Oct 5, 2026
e8d9cce
internal: Hand unfixable end-of-turn lint errors back to the agent
claude Oct 5, 2026
0b4edca
internal: Scope lint follow-up to the agent's own files; don't wait o…
claude Oct 5, 2026
0d9c4d0
internal: Simplify agent hooks: share git helper, use eslint's fix re…
cursoragent Oct 5, 2026
915f627
Merge remote-tracking branch 'origin/master' into claude/project-thre…
cursoragent Oct 5, 2026
4a8204d
internal: Regenerate Claude Code rules from master's Cursor rule changes
cursoragent Oct 5, 2026
43bac4a
internal: Don't record edits made during end-of-turn eslint as alread…
cursoragent Oct 5, 2026
7034567
internal: Format the end-of-turn eslint hook test
cursoragent Oct 5, 2026
c39e937
internal: Run the agent hook tests in the agent-rules CI check
cursoragent Oct 5, 2026
29874fd
internal: Run agent hook tests by glob so they run on any Node version
claude Oct 5, 2026
87c24ab
internal: Pre-push lints only uncommitted files the same command's co…
claude Oct 5, 2026
ccdbbfc
Merge remote-tracking branch 'origin/master' into claude/project-thre…
claude Oct 5, 2026
01b9972
internal: Regenerate Claude Code rules from master's ci-config rule
claude Oct 5, 2026
d1cfe5e
internal: Never cancel agent-rules runs on master
claude Oct 5, 2026
7084649
internal: Hold pushes until hook lint fixes are committed; don't mist…
claude Oct 5, 2026
8812fc9
internal: Stop holding pushes once a commit has taken the hook's lint…
claude Oct 5, 2026
f8e9542
internal: Hold earlier lint fixes unless this command's git add stage…
claude Oct 5, 2026
e6bcdba
internal: Always hold earlier lint fixes until a commit has them
claude Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Interface design principles for package APIs — where configuration, behavior, and state belong across schema, endpoint, and hook layers
alwaysApply: false
name: interface-design
description: Interface design principles for package APIs — where configuration, behavior, and state belong across schema, endpoint, and hook layers. Use when adding or changing options, behavior, or state in packages/*.
---
# Interface Design

Expand Down
37 changes: 37 additions & 0 deletions .claude/hooks/worktree-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* global require */
// Claude Code twin of Cursor's `.cursor/worktrees.json`: when a session starts
// in a fresh git worktree (`claude --worktree`), runs its `setup-worktree`
// commands. Anywhere else this is one stat per session start.
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
// written only once every command succeeds, so a failed setup reruns
const done = path.join(projectDir, 'node_modules/.worktree-setup-done');
// a linked worktree has a `.git` file; the main checkout has a directory
const isWorktree = fs
.statSync(path.join(projectDir, '.git'), {
throwIfNoEntry: false,
})
?.isFile();
if (!isWorktree || fs.existsSync(done)) process.exit(0);

const commands =
JSON.parse(
fs.readFileSync(path.join(projectDir, '.cursor/worktrees.json'), 'utf8'),
)['setup-worktree'] ?? [];
for (const command of commands) {
try {
execSync(command, { cwd: projectDir, stdio: ['ignore', 'ignore', 'pipe'] });
} catch (err) {
// stdout becomes session context, so the agent knows setup is incomplete
console.log(
`Worktree setup failed at \`${command}\`:\n${String(err.stderr).slice(-2000)}`,
);
process.exit(0);
}
}
fs.mkdirSync(path.dirname(done), { recursive: true });
fs.writeFileSync(done, '');
console.log(`Worktree set up: ${commands.join(' && ')}`);
40 changes: 40 additions & 0 deletions .claude/rules/agents-md-authoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
paths:
- "**/AGENTS.md"
---
<!-- Generated from .cursor/rules/agents-md-authoring.mdc by `yarn build:agent-rules`. Edit the source. -->


# Authoring AGENTS.md

AGENTS.md is read into context every time an agent works in the package/directory. Every line costs tokens and competes with the conversation. Treat it like a SKILL.md.

## Default assumption

The agent is smart and will read the source. Only include what it cannot derive in seconds from `ls`, `Read`, or running tests.

## Include

- **Hard correctness constraints** consumers depend on (referential equality, identity-keyed caches, storage-shape-as-API). One line of statement + one line of consequence.
- **Non-obvious gotchas** with concrete numbers (e.g. "adding an optional field to `EntityPath` regressed `getSmallResponse` 5–10%").
- **APIs the agent must call** that aren't discoverable from types alone — pointer to function + one-line contract.
- **Workflow specifics** the agent will get wrong without help (test methodology, benchmark thermal-noise rules, build-artifact ordering).

## Exclude

- **Architecture overviews / file maps** — `Glob` and `Read` cover this.
- **Anything in a parent AGENTS.md** — root `AGENTS.md` is always loaded; do not restate Jest project names, build commands, file naming conventions, monorepo structure, etc. Reference the parent only when adding a *consequence* not in the parent.
- **Verbose motivation paragraphs** — state the constraint, not why constraints exist in general.
- **Code blocks that the agent can write itself** from a one-line rule.
- **Time-sensitive notes** ("as of 2024…", "the new design will…").

## Format

- Telegraphic bullets and short sentences. No preamble.
- Group by concern (Correctness / Performance / Workflow), not by file.
- Backtick file paths and API names.
- Target ≤100 lines for most packages; larger only with strong justification.

## Verification

Before saving, scan the parent (and root) `AGENTS.md` and remove any duplicate facts. If a section reads like a tutorial or general explanation, cut it.
318 changes: 318 additions & 0 deletions .claude/rules/benchmarking.md

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions .claude/rules/breaking-changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
paths:
- "packages/**"
- ".changeset/**"
- "plans/next-breaking-release.md"
---
<!-- Generated from .cursor/rules/breaking-changes.mdc by `yarn build:agent-rules`. Edit the source. -->


# Breaking Change Strategy

Get users the fix now with a clean upgrade path; batch the actual break into a later release.

## Before shipping a change that could break

Check both:
- **User code**: subclasses, overrides, explicit annotations, and classes implementing exported interfaces.
- **Mixed versions**: `@data-client/rest`/`endpoint`/`graphql` aren't dependencies of `react`/`vue`/`core`, so users can pair a newer client with an older endpoint (and vice versa). Type checks in core against endpoint types must still accept older endpoint versions.

Requiring a matching version for a feature that is new in the same release is not breaking.

## When it would break

1. **Ship a compatible version**: a shim that keeps old code compiling and working (e.g. method-syntax declarations for parameter bivariance, a loose structural type instead of the strict interface, a deprecated alias).
2. **Prepare users**: in the changeset and the release's draft blog post, recommend the future-proof form now with a code example, so the later break is a no-op for them.
3. **Track the cleanup**: add an entry to [plans/next-breaking-release.md](../../plans/next-breaking-release.md) naming the shim to remove, what it breaks, and the migration.

Mark a change `BREAKING` (minor bump while under 1.0) only when no compatible version is reasonable. When a release is already breaking, work through `plans/next-breaking-release.md`, move each finished item into that release's blog migration guide, and delete it from the plan.
51 changes: 51 additions & 0 deletions .claude/rules/ci-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
paths:
- ".circleci/**"
- ".github/workflows/*.yml"
---
<!-- Generated from .cursor/rules/ci-config.mdc by `yarn build:agent-rules`. Edit the source. -->


# CI configuration

## CircleCI (`.circleci/config.yml`)

- Jest `--maxWorkers` is pinned per job to the `resource_class` vCPU count (large = 4, medium = 2) because docker containers report the host's CPUs via `os.cpus()`. Exception: the ReactNative `unit_tests` run is deliberately uncapped — its suites are fake-timer-wait dominated and capping workers flakes 5s test timeouts.
- Jobs halt via the `halt-unless-relevant-change` command based on flags computed once in `setup` (`.ci-esmodule-relevant`, `.ci-tests-relevant`) and transported via `save_cache`/`restore_cache` (keyed on `CIRCLE_SHA1`) so jobs can halt before paying `attach_workspace`. Missing/unreadable flags fail open (jobs run). On the default branch both flags are always true (a push may carry several commits). The diff uses `--no-renames` so moving a file out of a relevant dir still counts.
- `esmodule` (validate-esmodule-browser-build, esmodule-types*): a denylist, so new paths fail open. False only when every changed path is provably outside the esmodule jobs' inputs: the shared `DOCS_ONLY` paths (also the `tests` denylist) plus `.vscode/`, `plans/`, root `__tests__/` (excluded by every `tsconfig.compile.json`), `eslint.config.mjs`, `jest.config.js`, `examples/*.md`, and examples the jobs never build (`benchmark`, `benchmark-react`, `coin-app`, `nextjs`, `normalizr-github`, `normalizr-redux`, `test-bundlesize`, `vue-todo-app`). Only add a path if no esmodule job (or the `setup` builds feeding them) reads it.
- `tests` (lint, typecheck, unit_tests, node_matrix): false only when every changed path is docs/website/tooling (`website/`, `docs/`, `.changeset/`, `.cursor/`, `.agents/`, `.claude/`, `.github/`, root `*.md`), except `website/src/components/Playground/` (has unit tests). When both flags are false, `setup` halts before install.
- Legacy TS types (`ci:build:legacy-types`, consumed by `esmodule-types`):
- Built inside `setup` (`ci:build:setup:esmodule`) only when the esmodule flag is set; there is no separate job, to keep a job hop off the critical path.
- CI builds the endpoint, normalizr and rest legacy outputs, all for TS >= 4.0 (the minimum supported TS, and the oldest in the `esmodule-types` matrix). `use-enhanced-reducer` still ships a `ts3.4` build in release builds (`build:types`).
- `scripts/build-legacy-types.sh` builds each TS version concurrently; each `ts<version>/` gets the downleveled `lib` (with `abstract new` rewritten to `new` below 4.2, which `downlevel-dts` misses), then every newer version's `src-*-types` overlay, then its own. Keep overlays to small single-purpose modules (like `NoInfer.ts`, `tupleTypes.ts`) so whole-file copies can't go stale.
- `esmodule-types` also runs `examples/todo-app/tsconfig.typetest-libcheck.json` (`skipLibCheck: false`, `types: []`) so errors inside the legacy outputs fail CI, and `esmodule-types-latest` runs it with `--moduleResolution bundler` (TS 7 removed `node`) to cover `lib/`; the other typetests use `skipLibCheck: true`.
- Any change to legacy types building must leave `ts*/` output byte-identical to master (diff it) unless it intentionally changes published types (then add a changeset).
- `typecheck` also runs `yarn check:typeperf` ([scripts/typeperf](../../scripts/typeperf/README.md)), which reads the `ci:build:types` output from `setup`'s workspace.
- Never `git fetch --depth` the base branch in the relevance check: a shallow fetch severs the merge base and the three-dot diff fails.
- Changing root `package.json` `workspaces` requires updating the `setup` job's workspace trimming step.
- `setup`'s workspace leaves out the yarn cache (`.yarn/cache`) to keep its upload short. Jobs that re-resolve dependencies (`yarn up`/`add`) run `restore-yarn-cache`, keyed on `.ci-deps-key`, a hash of the manifests as committed (taken before trimming and `yarn up` rewrite them).

## GitHub Actions (`.github/workflows/`)

- Workflows install only needed workspaces via `./scripts/ci-install.sh [extra-workspace ...]`.
- `skills.yml` `paths` must cover every input of `website/framework-docs/skillReferences.mjs` (docs, skill manifests, the generator and its deps).
- `agent-rules.yml` runs `scripts/agent-rules.mjs --check` and the agent hook tests (`node --test '.cursor/hooks/*.test.js'`; a bare directory runs nothing on Node 22) with no install (node and git only). Its `paths` must cover every input and output of that script, and `.cursor/hooks/`.
- `editor-types.yml` reruns `yarn copy:websitetypes` and fails if `website/src/components/Playground/editor-types` changes. It needs the `website` workspace (for deps like `bignumber.js`), which CircleCI's `setup` drops. Its `paths` must cover every input of `scripts/copywebsitetypes.sh`.
- `site-preview.yml` runs one `build` job (one install for the typecheck and the build). It builds the site directly (no Vercel CLI, only the packages it imports via `ci:build:website`, `VERCEL_ENV=preview` to include drafts), restores Docusaurus' webpack cache (only master pushes save it, so PRs share one entry), and fails on any `[WARNING]`/`[ERROR]` line. Broken links are `warn` in `docusaurus.config.ts` so this check catches them without failing Vercel deploys.
- Production docs deploys come from Vercel's Git integration only (gated by `vercel-ignore.sh`); there is no Actions deploy workflow. Vercel clones ~10 commits deep, so `website/scripts/deepenGitHistory.cjs` (called from `docusaurus.config.ts`) fetches 800 more for the "Last updated" dates.
- `site-preview.yml` `paths` (`website/**`, `docs/{core,rest,graphql}/**`) must match `SITE_PATHS` in `website/scripts/vercel-ignore.sh`.
- `benchmark-react.yml` caches Playwright browsers keyed on the resolved `playwright` version from `examples/benchmark-react`; bumping playwright invalidates the cache automatically.
- Benchmark workflows (`benchmark.yml`, `benchmark-react.yml`) tune the host (CPU governor, swapoff) and pin CPUs with `taskset` — they must run directly on the runner, not in a `container:`.
- Never cancel a run on master: a cancelled check marks the commit red, which hurts npm search scoring. PR runs cancel superseded ones (`group: <name>-${{ github.head_ref || github.run_id }}`, `cancel-in-progress: true`); push runs get a unique group. Runs that must not overlap (`release.yml`, `beta-release.yml`) use a shared group with `queue: max`, which keeps up to 100 pending runs in order instead of cancelling all but one (GitHub rejects it alongside `cancel-in-progress: true`; actionlint 1.7.12 doesn't know the key yet). The benchmark push groups still use the default single pending slot.
- Report-style workflows (`benchmark*.yml`, `bundle_size.yml`, `codeql-analysis.yml`) skip draft PRs with a job-level `if: ${{ !github.event.pull_request.draft }}` and list `ready_for_review` in `pull_request.types`, so they run once a PR is marked ready. Correctness checks (`editor-types`, `skills`, `website`) still run on drafts.
- `paths` leave out what a workflow never reads, so test- or docs-only edits under `packages/` don't fan out: `__tests__/`, `typescript-tests/`, `src-*-types/` (legacy types) and `*.md`. `bundle_size.yml` also skips packages `examples/test-bundlesize` doesn't bundle (graphql, test, vue).
- CodeQL triggers only on shipped source (`packages/*/src/**`, `packages/*/node.mjs`). `bundle_size.yml` is PR-only: on push the action measures but has nowhere to report.
- Bundle Size and the benchmarks list `yarn.lock` in `paths` but gate their main job on the reusable `dependency-gate.yml` (`scripts/ci-deps-relevant.mjs`). It runs the job when a non-manifest file in the workflow's paths changed, a manifest of the measured workspaces (or a workspace package they ship with) changed, or the yarn.lock resolutions reachable from their dependencies or the babel/browserslist/core-js build tooling changed. Bumps of test, lint, React Native or website tooling skip it; anything it can't classify runs. The gate reads the caller's `on.<event>.paths` itself (via `github.workflow_ref` and `yq`), so there's one list. Root `devDependencies` aren't walked: add a new root build dependency to `BUILD_TOOLS`.
- Renovate (`.github/renovate.json`) uses `rebaseWhen: conflicted`: master's branch protection requires up-to-date branches, which makes the default rebase every Renovate PR (and rerun all of its CI) on each master push. Update the branch before merging a Renovate PR; if that update isn't merged, Renovate treats the branch as edited and stops rebasing it until its PR's rebase checkbox is ticked.

## Vercel docs site (`website/scripts/vercel-ignore.sh`)

- Vercel's ignore step: exit 0 skips, anything else builds, so fail open. Never diff only `HEAD^`: a master merge or multi-commit push makes it wrong. Run `vercel-ignore.test.sh` after changes.
- `git.deploymentEnabled: false` doesn't stick (dashboard overrides it); skipped pushes still show as canceled deployments.
- Vercel's clone can have a `master`/`origin/master` ref at the commit being built, so always fetch master and never use a ref equal to `HEAD` as the base (it makes every preview skip).
- Previews on `renovate/*` skip when the only site changes are website `package.json` or lockfiles; production is unchanged.
14 changes: 14 additions & 0 deletions .claude/rules/library-goals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
paths:
- "packages/**"
---
<!-- Generated from .cursor/rules/library-goals.mdc by `yarn build:agent-rules`. Edit the source. -->


# Library Goals Alignment

When editing library code in `packages/*`, read [GOALS.md](../../GOALS.md) and weigh changes against it. It is the source of truth for project priorities — do not rely on a summary of it.

- Evaluate design decisions (new APIs, abstractions, dependencies) against the goals before implementing.
- When goals conflict for a given change (e.g. bundle size vs. performance), resolve the trade-off using the priorities expressed in GOALS.md, and note the reasoning.
- If a requested change works against the goals, say so and propose an alternative that stays aligned.
28 changes: 28 additions & 0 deletions .claude/rules/markdown-formatting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
paths:
- "**/*.md"
- "**/*.mdc"
- "**/*.mdx"
---
<!-- Generated from .cursor/rules/markdown-formatting.mdc by `yarn build:agent-rules`. Edit the source. -->


# Markdown Formatting

## Documentation Links

Link API concepts that have corresponding doc pages:

- **External doc links**: `[Union](https://dataclient.io/rest/api/Union)`
- **Internal doc links**: Use site paths like `[Controller](/docs/api/Controller)` or `[Entity](/rest/api/Entity)`
- **Package links**: `[@data-client/rest](www.npmjs.com/package/@data-client/rest)`

## Repository Links

- **PR links**: `[#1234](https://github.com/reactive/data-client/pull/1234)`
- **Commit links**: ``[`abc123`](https://github.com/reactive/data-client/commit/abc123)``

## Code References

- Use backticks for inline code: function names, class names, file paths, package names
- Use fenced code blocks with language tags for multi-line code examples
20 changes: 20 additions & 0 deletions .claude/rules/skills-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
paths:
- "docs/**"
- ".agents/skills/**"
- "packages/*/src/index.ts"
---
<!-- Generated from .cursor/rules/skills-sync.mdc by `yarn build:agent-rules`. Edit the source. -->


# Skills sync

Skill `references/*.md` files listed in a skill's `references.json` are generated from `docs/` by `yarn build:skills` (an agent hook runs it before `git push`; the `skills` CI check fails on drift and on dead `references/` links in `SKILL.md`). Everything else in a skill (`SKILL.md`, references without the generated header) is hand-written and only changes when you change it.

- **Editing a doc**: never edit the generated reference. Edit the doc; references regenerate.
- **Adding a doc**: if a skill covers that API or topic (match by skill `description`), add the page to its `references.json` and link it from the skill's reference list in `SKILL.md`. New partials (`_foo.mdx`) need nothing; they're inlined.
- **Renaming, moving or deleting a doc**: update every `references.json` entry and `SKILL.md` link to it (`grep -rn '<old path or name>' .agents/skills`). The generator fails on a missing source.
- **Changing a public API** (rename, signature, new option, deprecation): grep `.agents/skills` for the old name and update `SKILL.md` examples and hand-written references in the same PR. Generated references only follow the docs.
- **Adding a hand-written reference**: `.gitattributes` marks `references/**` as `linguist-generated` (collapsed in GitHub diffs); add a `-linguist-generated` line for it. `yarn build:skills` fails until you do.
- **Framework-specific pages**: a `frameworks: [react]` page is skipped for Vue; a skill whose `frameworks` lists `vue` must not rely on it. Skills covering both frameworks link `name.md`; `name.vue.md` exists only where Vue differs.
- App-level examples in skills import from `@data-client/react` or `@data-client/vue` (and `/test` subpaths), never `@data-client/core`.
25 changes: 24 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,30 @@
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.cursor/hooks/build-skills.js\""
"if": "Bash(git *)",
"command": "node \"$CLAUDE_PROJECT_DIR/.cursor/hooks/pre-push.js\""
}
]
}
],
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/worktree-setup.js\"",
"timeout": 1800
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.cursor/hooks/eslint-fix.js\""
}
]
}
Expand Down
1 change: 1 addition & 0 deletions .claude/skills
8 changes: 4 additions & 4 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"version": 1,
"hooks": {
"afterFileEdit": [
"beforeShellExecution": [
{
"command": "node .cursor/hooks/eslint-fix.js"
"command": "node .cursor/hooks/pre-push.js"
}
],
"beforeShellExecution": [
"stop": [
{
"command": "node .cursor/hooks/build-skills.js"
"command": "node .cursor/hooks/eslint-fix.js"
}
]
}
Expand Down
Loading
Loading