From d20082e106a004b7e7f323fa3a6f5590ace489d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 19:00:47 +0000 Subject: [PATCH 1/8] Add Claude Code worker bootstrap hook and context Add a SessionStart hook and settings so Claude Code on the web workers install and build the repo automatically, plus a CLAUDE.md that points at the existing shared instructions and documents environment specifics. - .claude/hooks/session-start.sh: selects Node 24 via nvm (required by devEngines), runs npm install and npm run build; remote-only and idempotent - .claude/settings.json: registers the SessionStart hook - CLAUDE.md: imports .github/copilot-instructions.md and documents bootstrap, common commands, and that native iOS/Android builds need mobile SDKs Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde --- .claude/hooks/session-start.sh | 59 ++++++++++++++++++++++++++++++++++ .claude/settings.json | 14 ++++++++ CLAUDE.md | 58 +++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100755 .claude/hooks/session-start.sh create mode 100644 .claude/settings.json create mode 100644 CLAUDE.md diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100755 index 00000000..f11119e1 --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# +# SessionStart hook: bootstraps the repo so a fresh Claude Code worker can build, +# lint and test the Node.js tooling packages out of the box. +# +# It ensures Node.js 24 (required by "devEngines" in package.json), installs the +# npm workspace dependencies and builds the TypeScript project references. +# +# Native iOS/Android artifacts are intentionally NOT built here: they require the +# Android NDK / Apple toolchains which are not present on a generic Linux worker. +# See CLAUDE.md for how to build those when you actually need them. +set -euo pipefail + +# Only bootstrap in remote (Claude Code on the web) workers. Local machines +# usually already have their own Node setup, so we don't want to interfere. +if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then + exit 0 +fi + +cd "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}" + +# --- Ensure Node.js 24 via nvm ----------------------------------------------- +# package.json's devEngines pins Node ^24 (and npm ^11); npm refuses to install +# with an older runtime. nvm ships on the remote workers, so use it to select 24. +NVM_DIR="${NVM_DIR:-/opt/nvm}" +if [ ! -s "$NVM_DIR/nvm.sh" ] && [ -s "$HOME/.nvm/nvm.sh" ]; then + NVM_DIR="$HOME/.nvm" +fi + +if [ -s "$NVM_DIR/nvm.sh" ]; then + export NVM_DIR + # nvm.sh dereferences unset variables, so relax `set -u` while it is active. + set +u + # shellcheck disable=SC1091 + . "$NVM_DIR/nvm.sh" + nvm install 24 >/dev/null + nvm use 24 >/dev/null + set -u + + NODE_BIN="$(dirname "$(nvm which 24)")" + export PATH="$NODE_BIN:$PATH" + + # Persist the Node 24 toolchain on PATH for the rest of the session. + if [ -n "${CLAUDE_ENV_FILE:-}" ]; then + echo "export PATH=\"$NODE_BIN:\$PATH\"" >> "$CLAUDE_ENV_FILE" + fi +else + echo "warning: nvm not found; using system Node $(node -v). package.json requires Node ^24." >&2 +fi + +echo "Using Node $(node -v) / npm $(npm -v)" + +# --- Install dependencies and build the TypeScript packages ------------------- +# `npm install` (not `ci`) so the resolved node_modules can be cached between +# sessions; it stays deterministic thanks to the committed package-lock.json. +npm install +npm run build + +echo "Bootstrap complete: dependencies installed and TypeScript packages built." diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..e06b0338 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh" + } + ] + } + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..44d78e14 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,58 @@ +# CLAUDE.md + +Guidance for Claude Code (and other agents) working in this repository. + +The architecture overview, package layout, and coding patterns are shared with +other AI tools and documented once in the general instructions file below — +prefer editing that file over duplicating content here: + +@.github/copilot-instructions.md + +## Environment & bootstrap + +On Claude Code on the web, the `SessionStart` hook +(`.claude/hooks/session-start.sh`) bootstraps the worker automatically, so a +fresh session is ready to build, lint and test the Node.js tooling packages. It: + +1. Selects **Node.js 24** via `nvm` — `package.json`'s `devEngines` pins Node + `^24` and npm `^11`, and npm refuses to install with an older runtime. +2. Runs `npm install` (workspace dependencies). +3. Runs `npm run build` (`tsc --build` across the TypeScript project references). + +If you ever need to reproduce this by hand (e.g. after switching Node versions): + +```bash +nvm use 24 # or otherwise ensure Node >= 24 +npm install +npm run build +``` + +## Common commands + +```bash +npm run build # Incremental TypeScript build (tsc --build) +npm run lint # ESLint over the repo +npm run prettier:check # Formatting check (prettier:write to fix) +npm run depcheck # Dependency usage check +npm test # Cross-package tests (host, cmake-rn, gyp-to-cmake, node-addon-examples) + +# Focused iteration on a single workspace: +npm test --workspace cmake-rn +npm test --workspace gyp-to-cmake +``` + +## Native (iOS/Android) builds are not bootstrapped + +`npm run bootstrap` (and the `bootstrap` scripts in `packages/weak-node-api` and +`packages/ferric-example`) compile native artifacts and require the **Android +NDK / Apple toolchains**, which are not present on a generic Linux worker. +Without them, `ferric build` produces "0 targets" and fails, and a full +`npm run lint` reports errors in `apps/test-app/App.tsx` because the generated +native binding types are missing. + +Focus agent work on the Node.js tooling packages (`packages/cmake-rn`, +`packages/gyp-to-cmake`, `packages/cmake-file-api`, `packages/host`, etc.). When +you do need native builds, pass an explicit target, e.g. `npx ferric --apple` or +`npm run prebuild:build:android --workspace weak-node-api`, and ensure the +corresponding SDK/toolchain is installed first. Ask the maintainer to run +mobile-integration tests when they are needed. From fcca15b0fcdc03352fe5ca6d219c0ebaa6da831b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 19:03:10 +0000 Subject: [PATCH 2/8] Move shared agent instructions to top-level AGENTS.md Promote the tool-agnostic instructions to a top-level AGENTS.md (the general cross-agent standard) and add environment/bootstrap notes. CLAUDE.md now imports AGENTS.md, and .github/copilot-instructions.md redirects to it so the content has a single source of truth. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde --- .github/copilot-instructions.md | 79 ++---------------------------- AGENTS.md | 85 +++++++++++++++++++++++++++++++++ CLAUDE.md | 64 +++++-------------------- 3 files changed, 103 insertions(+), 125 deletions(-) create mode 100644 AGENTS.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 579a640e..c68dc755 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,77 +1,8 @@ # Copilot Instructions for React Native Node-API -This is a **monorepo** that brings Node-API support to React Native, enabling native addons written in C/C++/Rust to run on React Native across iOS and Android. +The shared, tool-agnostic agent instructions for this repository now live in the +top-level [`AGENTS.md`](../AGENTS.md). Please read that file. -## Package-Specific Instructions - -**IMPORTANT**: Before working on any package, always check for and read package-specific `copilot-instructions.md` files in the package directory. These contain critical preferences and patterns for that specific package. - -## Architecture Overview - -**Core Flow**: JS `require("./addon.node")` → Babel transform → `requireNodeAddon()` TurboModule call → native library loading → Node-API module initialization - -### Package Architecture - -See the [README.md](../README.md#packages) for detailed descriptions of each package and their roles in the system. Key packages include: - -- `packages/host` - Core Node-API runtime and Babel plugin -- `packages/cmake-rn` - CMake wrapper for native builds -- `packages/cmake-file-api` - TypeScript wrapper for CMake File API with Zod validation -- `packages/ferric` - Rust/Cargo wrapper with napi-rs integration -- `packages/gyp-to-cmake` - Legacy binding.gyp compatibility -- `apps/test-app` - Integration testing harness - -## Critical Build Dependencies - -- **Custom Hermes**: Currently depends on a patched Hermes with Node-API support (see [facebook/hermes#1377](https://github.com/facebook/hermes/pull/1377)) -- **Prebuilt Binary Spec**: All tools must output to the exact naming scheme: - - Android: `*.android.node/` with jniLibs structure + `react-native-node-api-module` marker file - - iOS: `*.apple.node` (XCFramework renamed) + marker file - -## Essential Workflows - -### Development Setup - -```bash -pnpm install && pnpm run build # Install deps and build all packages -pnpm run bootstrap # Build native components (weak-node-api, examples) -``` - -### Package Development - -- **TypeScript project references**: Use `tsc --build` for incremental compilation -- **Workspace scripts**: Most build/test commands use pnpm workspaces (`--filter` flag), run in topological (dependency) order and fail fast -- **Focus on Node.js packages**: AI development primarily targets the Node.js tooling packages rather than native mobile code -- **No TypeScript type asserts**: You have to ask explicitly and justify if you want to add `as` type assertions. - -## Key Patterns - -### Babel Transformation - -The core magic happens in `packages/host/src/node/babel-plugin/plugin.ts`: - -```js -// Input: require("./addon.node") -// Output: require("react-native-node-api").requireNodeAddon("pkg-name--addon") -``` - -### CMake Integration - -For linking against Node-API in CMakeLists.txt: - -```cmake -include(${WEAK_NODE_API_CONFIG}) -target_link_libraries(addon PRIVATE weak-node-api) -``` - -### Cross-Platform Naming - -Library names use double-dash separation: `package-name--path-component--addon-name` - -### Testing - -- **Individual packages**: Some packages have VS Code test tasks and others have their own test scripts for focused iteration (e.g., `pnpm --filter cmake-rn run test`). Use the latter only if the former is missing. -- **Cross-package**: Use root-level `pnpm test` for cross-package testing once individual package tests pass -- **Mobile integration**: Available but not the primary AI development focus - ask the developer to run those tests as needed - -**Documentation**: Integration details, platform setup, and toolchain configuration are covered in existing repo documentation files. +**IMPORTANT**: Before working on any package, also check for and read +package-specific instruction files (`AGENTS.md` or `copilot-instructions.md`) in +the package directory. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..57c7b40d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,85 @@ +# AGENTS.md + +Instructions for AI coding agents working in the React Native Node-API repo. + +This is a **monorepo** that brings Node-API support to React Native, enabling native addons written in C/C++/Rust to run on React Native across iOS and Android. + +## Package-Specific Instructions + +**IMPORTANT**: Before working on any package, always check for and read package-specific instruction files (`AGENTS.md` or `copilot-instructions.md`) in the package directory. These contain critical preferences and patterns for that specific package. + +## Architecture Overview + +**Core Flow**: JS `require("./addon.node")` → Babel transform → `requireNodeAddon()` TurboModule call → native library loading → Node-API module initialization + +### Package Architecture + +See the [README.md](README.md#packages) for detailed descriptions of each package and their roles in the system. Key packages include: + +- `packages/host` - Core Node-API runtime and Babel plugin +- `packages/cmake-rn` - CMake wrapper for native builds +- `packages/cmake-file-api` - TypeScript wrapper for CMake File API with Zod validation +- `packages/ferric` - Rust/Cargo wrapper with napi-rs integration +- `packages/gyp-to-cmake` - Legacy binding.gyp compatibility +- `apps/test-app` - Integration testing harness + +## Environment & Bootstrap + +- **Node.js 24 is required** — `package.json`'s `devEngines` pins Node `^24` (and npm `^11`), and npm refuses to install with an older runtime. +- Standard setup for the Node.js tooling packages: + + ```bash + npm install # Install workspace dependencies + npm run build # Incremental TypeScript build (tsc --build) + ``` + +- On Claude Code on the web, `.claude/hooks/session-start.sh` performs the above automatically (selecting Node 24 via nvm) at session start. +- **Native (iOS/Android) builds are not part of the default bootstrap.** `npm run bootstrap` and the native `bootstrap` scripts compile artifacts that require the Android NDK / Apple toolchains, which are absent on a generic Linux worker. Focus on the Node.js tooling packages; pass an explicit target (e.g. `npx ferric --apple`) only when the corresponding SDK is installed. + +## Critical Build Dependencies + +- **Custom Hermes**: Currently depends on a patched Hermes with Node-API support (see [facebook/hermes#1377](https://github.com/facebook/hermes/pull/1377)) +- **Prebuilt Binary Spec**: All tools must output to the exact naming scheme: + - Android: `*.android.node/` with jniLibs structure + `react-native-node-api-module` marker file + - iOS: `*.apple.node` (XCFramework renamed) + marker file + +## Essential Workflows + +### Package Development + +- **TypeScript project references**: Use `tsc --build` for incremental compilation +- **Workspace scripts**: Most build/test commands use npm workspaces (`--workspace` flag) +- **Focus on Node.js packages**: AI development primarily targets the Node.js tooling packages rather than native mobile code +- **No TypeScript type asserts**: You have to ask explicitly and justify if you want to add `as` type assertions. + +## Key Patterns + +### Babel Transformation + +The core magic happens in `packages/host/src/node/babel-plugin/plugin.ts`: + +```js +// Input: require("./addon.node") +// Output: require("react-native-node-api").requireNodeAddon("pkg-name--addon") +``` + +### CMake Integration + +For linking against Node-API in CMakeLists.txt: + +```cmake +include(${WEAK_NODE_API_CONFIG}) +target_link_libraries(addon PRIVATE weak-node-api) +``` + +### Cross-Platform Naming + +Library names use double-dash separation: `package-name--path-component--addon-name` + +### Testing + +- **Individual packages**: Some packages have VS Code test tasks and others have their own `npm test` scripts for focused iteration (e.g., `npm test --workspace cmake-rn`). Use the latter only if the former is missing. +- **Cross-package**: Use root-level `npm test` for cross-package testing once individual package tests pass +- **Mobile integration**: Available but not the primary AI development focus - ask the developer to run those tests as needed + +**Documentation**: Integration details, platform setup, and toolchain configuration are covered in existing repo documentation files. diff --git a/CLAUDE.md b/CLAUDE.md index 44d78e14..256ce4ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,58 +1,20 @@ # CLAUDE.md -Guidance for Claude Code (and other agents) working in this repository. +Guidance for Claude Code working in this repository. -The architecture overview, package layout, and coding patterns are shared with -other AI tools and documented once in the general instructions file below — -prefer editing that file over duplicating content here: +The architecture overview, package layout, coding patterns, and +environment/bootstrap notes are shared with other AI tools and live in the +top-level, tool-agnostic instructions file — prefer editing that over +duplicating content here: -@.github/copilot-instructions.md +@AGENTS.md -## Environment & bootstrap +## Claude Code on the web -On Claude Code on the web, the `SessionStart` hook -(`.claude/hooks/session-start.sh`) bootstraps the worker automatically, so a -fresh session is ready to build, lint and test the Node.js tooling packages. It: +A `SessionStart` hook (`.claude/hooks/session-start.sh`) bootstraps remote +workers automatically: it selects Node.js 24 via `nvm` (required by +`package.json`'s `devEngines`), then runs `npm install` and `npm run build`, so a +fresh session is ready to build, lint, and test the Node.js tooling packages. -1. Selects **Node.js 24** via `nvm` — `package.json`'s `devEngines` pins Node - `^24` and npm `^11`, and npm refuses to install with an older runtime. -2. Runs `npm install` (workspace dependencies). -3. Runs `npm run build` (`tsc --build` across the TypeScript project references). - -If you ever need to reproduce this by hand (e.g. after switching Node versions): - -```bash -nvm use 24 # or otherwise ensure Node >= 24 -npm install -npm run build -``` - -## Common commands - -```bash -npm run build # Incremental TypeScript build (tsc --build) -npm run lint # ESLint over the repo -npm run prettier:check # Formatting check (prettier:write to fix) -npm run depcheck # Dependency usage check -npm test # Cross-package tests (host, cmake-rn, gyp-to-cmake, node-addon-examples) - -# Focused iteration on a single workspace: -npm test --workspace cmake-rn -npm test --workspace gyp-to-cmake -``` - -## Native (iOS/Android) builds are not bootstrapped - -`npm run bootstrap` (and the `bootstrap` scripts in `packages/weak-node-api` and -`packages/ferric-example`) compile native artifacts and require the **Android -NDK / Apple toolchains**, which are not present on a generic Linux worker. -Without them, `ferric build` produces "0 targets" and fails, and a full -`npm run lint` reports errors in `apps/test-app/App.tsx` because the generated -native binding types are missing. - -Focus agent work on the Node.js tooling packages (`packages/cmake-rn`, -`packages/gyp-to-cmake`, `packages/cmake-file-api`, `packages/host`, etc.). When -you do need native builds, pass an explicit target, e.g. `npx ferric --apple` or -`npm run prebuild:build:android --workspace weak-node-api`, and ensure the -corresponding SDK/toolchain is installed first. Ask the maintainer to run -mobile-integration tests when they are needed. +See the "Environment & Bootstrap" section of `AGENTS.md` for the manual steps and +for why native iOS/Android builds are not part of the default bootstrap. From cc8195cc3ebc68f6b5dd3764584e47fedb974324 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 19:04:39 +0000 Subject: [PATCH 3/8] Rename cmake-file-api package instructions to AGENTS.md Agents pick up nested AGENTS.md files in subdirectories, so align the package-level instructions with the same convention as the root. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde --- packages/cmake-file-api/{copilot-instructions.md => AGENTS.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/cmake-file-api/{copilot-instructions.md => AGENTS.md} (99%) diff --git a/packages/cmake-file-api/copilot-instructions.md b/packages/cmake-file-api/AGENTS.md similarity index 99% rename from packages/cmake-file-api/copilot-instructions.md rename to packages/cmake-file-api/AGENTS.md index c6def984..7fc2a41e 100644 --- a/packages/cmake-file-api/copilot-instructions.md +++ b/packages/cmake-file-api/AGENTS.md @@ -1,4 +1,4 @@ -# Copilot Instructions for cmake-file-api Package +# AGENTS.md — cmake-file-api Package This package provides a TypeScript wrapper around the CMake File API using Zod schemas for validation. From 6c7c1e1111c5f2d3d8aafd62e43483602414266f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 19:11:00 +0000 Subject: [PATCH 4/8] Pin Node version in .nvmrc instead of hardcoding it in the hook Add a .nvmrc set to lts/krypton (matching CI's setup-node) and have the SessionStart hook run `nvm install`/`nvm use` without a version argument so the pinned version is the single source of truth. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde --- .claude/hooks/session-start.sh | 20 +++++++++++--------- .nvmrc | 1 + AGENTS.md | 4 ++-- CLAUDE.md | 7 ++++--- 4 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 .nvmrc diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh index f11119e1..2212de73 100755 --- a/.claude/hooks/session-start.sh +++ b/.claude/hooks/session-start.sh @@ -3,8 +3,8 @@ # SessionStart hook: bootstraps the repo so a fresh Claude Code worker can build, # lint and test the Node.js tooling packages out of the box. # -# It ensures Node.js 24 (required by "devEngines" in package.json), installs the -# npm workspace dependencies and builds the TypeScript project references. +# It ensures the Node.js version pinned in .nvmrc (which matches CI), installs +# the npm workspace dependencies and builds the TypeScript project references. # # Native iOS/Android artifacts are intentionally NOT built here: they require the # Android NDK / Apple toolchains which are not present on a generic Linux worker. @@ -19,9 +19,10 @@ fi cd "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}" -# --- Ensure Node.js 24 via nvm ----------------------------------------------- -# package.json's devEngines pins Node ^24 (and npm ^11); npm refuses to install -# with an older runtime. nvm ships on the remote workers, so use it to select 24. +# --- Ensure the Node.js version from .nvmrc via nvm --------------------------- +# .nvmrc pins the Node version (matching CI). package.json's devEngines requires +# Node ^24 / npm ^11, and npm refuses to install with an older runtime. nvm ships +# on the remote workers, so use it to install and select the pinned version. NVM_DIR="${NVM_DIR:-/opt/nvm}" if [ ! -s "$NVM_DIR/nvm.sh" ] && [ -s "$HOME/.nvm/nvm.sh" ]; then NVM_DIR="$HOME/.nvm" @@ -33,14 +34,15 @@ if [ -s "$NVM_DIR/nvm.sh" ]; then set +u # shellcheck disable=SC1091 . "$NVM_DIR/nvm.sh" - nvm install 24 >/dev/null - nvm use 24 >/dev/null + # No version argument: nvm reads .nvmrc from the current directory. + nvm install >/dev/null + nvm use >/dev/null + NODE_BIN="$(dirname "$(nvm which current)")" set -u - NODE_BIN="$(dirname "$(nvm which 24)")" export PATH="$NODE_BIN:$PATH" - # Persist the Node 24 toolchain on PATH for the rest of the session. + # Persist the resolved Node toolchain on PATH for the rest of the session. if [ -n "${CLAUDE_ENV_FILE:-}" ]; then echo "export PATH=\"$NODE_BIN:\$PATH\"" >> "$CLAUDE_ENV_FILE" fi diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..b03f4086 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +lts/krypton diff --git a/AGENTS.md b/AGENTS.md index 57c7b40d..cade84fc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ See the [README.md](README.md#packages) for detailed descriptions of each packag ## Environment & Bootstrap -- **Node.js 24 is required** — `package.json`'s `devEngines` pins Node `^24` (and npm `^11`), and npm refuses to install with an older runtime. +- **Node.js 24 is required** — pinned in `.nvmrc` as `lts/krypton` (matching CI); `package.json`'s `devEngines` requires Node `^24` and npm `^11`, and npm refuses to install with an older runtime. With nvm: `nvm install && nvm use`. - Standard setup for the Node.js tooling packages: ```bash @@ -33,7 +33,7 @@ See the [README.md](README.md#packages) for detailed descriptions of each packag npm run build # Incremental TypeScript build (tsc --build) ``` -- On Claude Code on the web, `.claude/hooks/session-start.sh` performs the above automatically (selecting Node 24 via nvm) at session start. +- On Claude Code on the web, `.claude/hooks/session-start.sh` performs the above automatically (selecting the `.nvmrc` version via nvm) at session start. - **Native (iOS/Android) builds are not part of the default bootstrap.** `npm run bootstrap` and the native `bootstrap` scripts compile artifacts that require the Android NDK / Apple toolchains, which are absent on a generic Linux worker. Focus on the Node.js tooling packages; pass an explicit target (e.g. `npx ferric --apple`) only when the corresponding SDK is installed. ## Critical Build Dependencies diff --git a/CLAUDE.md b/CLAUDE.md index 256ce4ed..bd3bdb81 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,9 +12,10 @@ duplicating content here: ## Claude Code on the web A `SessionStart` hook (`.claude/hooks/session-start.sh`) bootstraps remote -workers automatically: it selects Node.js 24 via `nvm` (required by -`package.json`'s `devEngines`), then runs `npm install` and `npm run build`, so a -fresh session is ready to build, lint, and test the Node.js tooling packages. +workers automatically: it selects the Node.js version pinned in `.nvmrc` via +`nvm` (Node 24, as required by `package.json`'s `devEngines`), then runs +`npm install` and `npm run build`, so a fresh session is ready to build, lint, +and test the Node.js tooling packages. See the "Environment & Bootstrap" section of `AGENTS.md` for the manual steps and for why native iOS/Android builds are not part of the default bootstrap. From b1cb04125f8532d6884d29d1522c41102ec7a6cc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 20:13:38 +0000 Subject: [PATCH 5/8] Document preferring an upstream fix over a local workaround Add guidance to AGENTS.md for handling failures that look like known upstream bugs: verify the fix at the source, prefer the smallest installable version bump over a workaround, treat upgrades as revertible hypotheses, and comment any unavoidable workaround with its removal condition. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde --- AGENTS.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index cade84fc..121a7bcc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,6 +36,27 @@ See the [README.md](README.md#packages) for detailed descriptions of each packag - On Claude Code on the web, `.claude/hooks/session-start.sh` performs the above automatically (selecting the `.nvmrc` version via nvm) at session start. - **Native (iOS/Android) builds are not part of the default bootstrap.** `npm run bootstrap` and the native `bootstrap` scripts compile artifacts that require the Android NDK / Apple toolchains, which are absent on a generic Linux worker. Focus on the Node.js tooling packages; pass an explicit target (e.g. `npx ferric --apple`) only when the corresponding SDK is installed. +## Prefer an upstream fix over a local workaround + +When a build/runtime failure looks like a known upstream bug, before writing a +patch or workaround: + +1. Find where the fix actually landed and **verify at the source** — the + changelog, lockfile, or the dependency's own manifest/podspec for a *specific + installable version*, not the version list and not a related package's + timeline (a fork or platform variant may carry a fix on a line its upstream + never did). +2. If an installable version within our constraints contains the fix, prefer the + **smallest** bump that includes it (patch > minor > major) over a workaround. +3. Treat the upgrade as a hypothesis under test: say so, and be ready to revert — + every upgrade adds new unknown-bug surface. If it doesn't fix the issue, throw + it away rather than stacking a workaround on top of it. +4. If the bump is more than a patch, or widens scope/risk, check with me before + committing to it. +5. If no fixed version is reachable, a workaround is fine — but comment it with + the exact condition that makes it removable (e.g. "remove once dep ships + fmt ≥ 12.1"), and if you write that condition, verify it isn't already met. + ## Critical Build Dependencies - **Custom Hermes**: Currently depends on a patched Hermes with Node-API support (see [facebook/hermes#1377](https://github.com/facebook/hermes/pull/1377)) From 11c9ca13313f82f70152be6520b5adabf94b2f2a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 19:38:31 +0000 Subject: [PATCH 6/8] Adopt pnpm in worker bootstrap and docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main migrated from npm workspaces to pnpm (#381). Update the SessionStart hook to install via pnpm (provisioned through Corepack from the pinned packageManager field) and refresh the npm→pnpm command references in AGENTS.md and CLAUDE.md. Also fix the hook's Node selection: resolve the bin dir for the .nvmrc version by name instead of `nvm which current`, which reported the worker's default Node 22 when it sat earlier on PATH — the hook was persisting Node 22 into the session env. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde --- .claude/hooks/session-start.sh | 31 ++++++++++++++++++++++--------- AGENTS.md | 17 +++++++++-------- CLAUDE.md | 6 +++--- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh index 2212de73..1ad57df4 100755 --- a/.claude/hooks/session-start.sh +++ b/.claude/hooks/session-start.sh @@ -4,7 +4,7 @@ # lint and test the Node.js tooling packages out of the box. # # It ensures the Node.js version pinned in .nvmrc (which matches CI), installs -# the npm workspace dependencies and builds the TypeScript project references. +# the pnpm workspace dependencies and builds the TypeScript project references. # # Native iOS/Android artifacts are intentionally NOT built here: they require the # Android NDK / Apple toolchains which are not present on a generic Linux worker. @@ -21,8 +21,9 @@ cd "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}" # --- Ensure the Node.js version from .nvmrc via nvm --------------------------- # .nvmrc pins the Node version (matching CI). package.json's devEngines requires -# Node ^24 / npm ^11, and npm refuses to install with an older runtime. nvm ships -# on the remote workers, so use it to install and select the pinned version. +# Node ^24 / pnpm ^10, and the package manager refuses to install with an older +# runtime. nvm ships on the remote workers, so use it to install and select the +# pinned version. NVM_DIR="${NVM_DIR:-/opt/nvm}" if [ ! -s "$NVM_DIR/nvm.sh" ] && [ -s "$HOME/.nvm/nvm.sh" ]; then NVM_DIR="$HOME/.nvm" @@ -37,7 +38,11 @@ if [ -s "$NVM_DIR/nvm.sh" ]; then # No version argument: nvm reads .nvmrc from the current directory. nvm install >/dev/null nvm use >/dev/null - NODE_BIN="$(dirname "$(nvm which current)")" + # Resolve the bin dir for the pinned version explicitly. `nvm which current` + # can report the worker's default Node when it sits earlier on PATH, so ask + # for the .nvmrc version by name instead. + NVMRC_VERSION="$(cat "$PWD/.nvmrc" 2>/dev/null || echo default)" + NODE_BIN="$(dirname "$(nvm which "$NVMRC_VERSION")")" set -u export PATH="$NODE_BIN:$PATH" @@ -50,12 +55,20 @@ else echo "warning: nvm not found; using system Node $(node -v). package.json requires Node ^24." >&2 fi -echo "Using Node $(node -v) / npm $(npm -v)" +# --- Select pnpm via Corepack ------------------------------------------------ +# The repo uses pnpm, pinned in package.json's "packageManager" field. Corepack +# ships with Node and provisions that exact pnpm version on demand, so we don't +# depend on whatever pnpm (if any) the worker image happens to have. +export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 +corepack enable >/dev/null 2>&1 || true + +echo "Using Node $(node -v) / pnpm $(pnpm -v)" # --- Install dependencies and build the TypeScript packages ------------------- -# `npm install` (not `ci`) so the resolved node_modules can be cached between -# sessions; it stays deterministic thanks to the committed package-lock.json. -npm install -npm run build +# Plain `pnpm install` (not `--frozen-lockfile`) so the resolved node_modules can +# be cached between sessions; it stays deterministic thanks to the committed +# pnpm-lock.yaml. +pnpm install +pnpm run build echo "Bootstrap complete: dependencies installed and TypeScript packages built." diff --git a/AGENTS.md b/AGENTS.md index 121a7bcc..95ffb2cb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,16 +25,17 @@ See the [README.md](README.md#packages) for detailed descriptions of each packag ## Environment & Bootstrap -- **Node.js 24 is required** — pinned in `.nvmrc` as `lts/krypton` (matching CI); `package.json`'s `devEngines` requires Node `^24` and npm `^11`, and npm refuses to install with an older runtime. With nvm: `nvm install && nvm use`. +- **Node.js 24 is required** — pinned in `.nvmrc` as `lts/krypton` (matching CI); `package.json`'s `devEngines` requires Node `^24` and pnpm `^10`, and the package manager refuses to install with an older runtime. With nvm: `nvm install && nvm use`. +- **pnpm is the package manager** — pinned in `package.json`'s `packageManager` field. Let Corepack (bundled with Node) provide it: `corepack enable`, then use `pnpm`. - Standard setup for the Node.js tooling packages: ```bash - npm install # Install workspace dependencies - npm run build # Incremental TypeScript build (tsc --build) + pnpm install # Install workspace dependencies + pnpm run build # Incremental TypeScript build (tsc --build) ``` -- On Claude Code on the web, `.claude/hooks/session-start.sh` performs the above automatically (selecting the `.nvmrc` version via nvm) at session start. -- **Native (iOS/Android) builds are not part of the default bootstrap.** `npm run bootstrap` and the native `bootstrap` scripts compile artifacts that require the Android NDK / Apple toolchains, which are absent on a generic Linux worker. Focus on the Node.js tooling packages; pass an explicit target (e.g. `npx ferric --apple`) only when the corresponding SDK is installed. +- On Claude Code on the web, `.claude/hooks/session-start.sh` performs the above automatically (selecting the `.nvmrc` version via nvm and pnpm via Corepack) at session start. +- **Native (iOS/Android) builds are not part of the default bootstrap.** `pnpm run bootstrap` and the native `bootstrap` scripts compile artifacts that require the Android NDK / Apple toolchains, which are absent on a generic Linux worker. Focus on the Node.js tooling packages; pass an explicit target (e.g. `pnpm exec ferric --apple`) only when the corresponding SDK is installed. ## Prefer an upstream fix over a local workaround @@ -69,7 +70,7 @@ patch or workaround: ### Package Development - **TypeScript project references**: Use `tsc --build` for incremental compilation -- **Workspace scripts**: Most build/test commands use npm workspaces (`--workspace` flag) +- **Workspace scripts**: Most build/test commands use pnpm workspaces (`--filter` flag), run in topological (dependency) order and fail fast - **Focus on Node.js packages**: AI development primarily targets the Node.js tooling packages rather than native mobile code - **No TypeScript type asserts**: You have to ask explicitly and justify if you want to add `as` type assertions. @@ -99,8 +100,8 @@ Library names use double-dash separation: `package-name--path-component--addon-n ### Testing -- **Individual packages**: Some packages have VS Code test tasks and others have their own `npm test` scripts for focused iteration (e.g., `npm test --workspace cmake-rn`). Use the latter only if the former is missing. -- **Cross-package**: Use root-level `npm test` for cross-package testing once individual package tests pass +- **Individual packages**: Some packages have VS Code test tasks and others have their own `test` scripts for focused iteration (e.g., `pnpm --filter cmake-rn run test`). Use the latter only if the former is missing. +- **Cross-package**: Use root-level `pnpm test` for cross-package testing once individual package tests pass - **Mobile integration**: Available but not the primary AI development focus - ask the developer to run those tests as needed **Documentation**: Integration details, platform setup, and toolchain configuration are covered in existing repo documentation files. diff --git a/CLAUDE.md b/CLAUDE.md index bd3bdb81..6580242e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,9 +13,9 @@ duplicating content here: A `SessionStart` hook (`.claude/hooks/session-start.sh`) bootstraps remote workers automatically: it selects the Node.js version pinned in `.nvmrc` via -`nvm` (Node 24, as required by `package.json`'s `devEngines`), then runs -`npm install` and `npm run build`, so a fresh session is ready to build, lint, -and test the Node.js tooling packages. +`nvm` (Node 24, as required by `package.json`'s `devEngines`) and pnpm via +Corepack, then runs `pnpm install` and `pnpm run build`, so a fresh session is +ready to build, lint, and test the Node.js tooling packages. See the "Environment & Bootstrap" section of `AGENTS.md` for the manual steps and for why native iOS/Android builds are not part of the default bootstrap. From 960f89b74a1bdb590895fc152d1c6e3e0e2a202d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 19:44:45 +0000 Subject: [PATCH 7/8] Format AGENTS.md with Prettier The workaround section used *...* emphasis; the repo's Prettier config normalizes emphasis to _..._. Apply it so prettier:check passes. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde --- AGENTS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 95ffb2cb..5cb3f1c4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,8 +43,8 @@ When a build/runtime failure looks like a known upstream bug, before writing a patch or workaround: 1. Find where the fix actually landed and **verify at the source** — the - changelog, lockfile, or the dependency's own manifest/podspec for a *specific - installable version*, not the version list and not a related package's + changelog, lockfile, or the dependency's own manifest/podspec for a _specific + installable version_, not the version list and not a related package's timeline (a fork or platform variant may carry a fix on a line its upstream never did). 2. If an installable version within our constraints contains the fix, prefer the From a83dcacb67659cf7a33efb0794ee5e922788bf86 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 20:01:00 +0000 Subject: [PATCH 8/8] Add PostToolUse hook to format files with Prettier on write Runs the workspace Prettier on each file Claude Code writes or edits inside the repo, using --ignore-unknown so it's a no-op on unsupported files and honors .prettierignore. Keeps the tree formatted without a separate pass; CI's prettier:check stays the source of truth. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde --- .claude/hooks/format-file.sh | 32 ++++++++++++++++++++++++++++++++ .claude/settings.json | 11 +++++++++++ CLAUDE.md | 8 ++++++++ 3 files changed, 51 insertions(+) create mode 100755 .claude/hooks/format-file.sh diff --git a/.claude/hooks/format-file.sh b/.claude/hooks/format-file.sh new file mode 100755 index 00000000..114a58be --- /dev/null +++ b/.claude/hooks/format-file.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# +# PostToolUse hook: formats a file with Prettier right after Claude Code writes +# or edits it, so formatting never has to be fixed up later (or caught by CI). +# +# It runs the workspace's pinned Prettier with `--ignore-unknown`, so it is a +# no-op on files Prettier can't format and it honors .prettierignore — meaning +# it is safe to run on every write regardless of file type. +set -euo pipefail + +ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}" + +# The hook payload arrives as JSON on stdin; the edited path is tool_input.file_path. +FILE_PATH="$(node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const j=JSON.parse(s);process.stdout.write(String((j.tool_input&&j.tool_input.file_path)||""))}catch{process.stdout.write("")}})' 2>/dev/null || true)" + +# Nothing to do without a real, existing file. +[ -n "$FILE_PATH" ] && [ -f "$FILE_PATH" ] || exit 0 + +# Only format files inside the repo (skip scratchpad/other locations). +ABS_FILE="$(cd "$(dirname "$FILE_PATH")" 2>/dev/null && pwd)/$(basename "$FILE_PATH")" || exit 0 +case "$ABS_FILE" in + "$ROOT"/*) ;; + *) exit 0 ;; +esac + +# Use the workspace Prettier if dependencies are installed; otherwise skip. +PRETTIER="$ROOT/node_modules/.bin/prettier" +[ -x "$PRETTIER" ] || exit 0 + +# Never fail the tool call over formatting. +"$PRETTIER" --ignore-unknown --write "$ABS_FILE" >/dev/null 2>&1 || true +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json index e06b0338..42e29588 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -9,6 +9,17 @@ } ] } + ], + "PostToolUse": [ + { + "matcher": "Write|Edit|MultiEdit", + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-file.sh" + } + ] + } ] } } diff --git a/CLAUDE.md b/CLAUDE.md index 6580242e..7b60afb9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,3 +19,11 @@ ready to build, lint, and test the Node.js tooling packages. See the "Environment & Bootstrap" section of `AGENTS.md` for the manual steps and for why native iOS/Android builds are not part of the default bootstrap. + +## Formatting + +A `PostToolUse` hook (`.claude/hooks/format-file.sh`) runs the workspace's +Prettier on every file Claude Code writes or edits inside the repo. It uses +`--ignore-unknown`, so it is a no-op on files Prettier can't format and it honors +`.prettierignore` — keeping the tree formatted without a separate `prettier:write` +pass. It's a convenience only; CI's `prettier:check` remains the source of truth.