Skip to content

Commit 3ea96e4

Browse files
kraenhansenclaude
andauthored
Add agent bootstrap hook and consolidate instructions into AGENTS.md (#373)
* 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde * 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde * 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde * 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde * 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde * Adopt pnpm in worker bootstrap and docs 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde * 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde * 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvxEaPhyEq2HCZ6XFjEAde --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4e5b831 commit 3ea96e4

8 files changed

Lines changed: 274 additions & 75 deletions

File tree

.claude/hooks/format-file.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env bash
2+
#
3+
# PostToolUse hook: formats a file with Prettier right after Claude Code writes
4+
# or edits it, so formatting never has to be fixed up later (or caught by CI).
5+
#
6+
# It runs the workspace's pinned Prettier with `--ignore-unknown`, so it is a
7+
# no-op on files Prettier can't format and it honors .prettierignore — meaning
8+
# it is safe to run on every write regardless of file type.
9+
set -euo pipefail
10+
11+
ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
12+
13+
# The hook payload arrives as JSON on stdin; the edited path is tool_input.file_path.
14+
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)"
15+
16+
# Nothing to do without a real, existing file.
17+
[ -n "$FILE_PATH" ] && [ -f "$FILE_PATH" ] || exit 0
18+
19+
# Only format files inside the repo (skip scratchpad/other locations).
20+
ABS_FILE="$(cd "$(dirname "$FILE_PATH")" 2>/dev/null && pwd)/$(basename "$FILE_PATH")" || exit 0
21+
case "$ABS_FILE" in
22+
"$ROOT"/*) ;;
23+
*) exit 0 ;;
24+
esac
25+
26+
# Use the workspace Prettier if dependencies are installed; otherwise skip.
27+
PRETTIER="$ROOT/node_modules/.bin/prettier"
28+
[ -x "$PRETTIER" ] || exit 0
29+
30+
# Never fail the tool call over formatting.
31+
"$PRETTIER" --ignore-unknown --write "$ABS_FILE" >/dev/null 2>&1 || true
32+
exit 0

.claude/hooks/session-start.sh

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env bash
2+
#
3+
# SessionStart hook: bootstraps the repo so a fresh Claude Code worker can build,
4+
# lint and test the Node.js tooling packages out of the box.
5+
#
6+
# It ensures the Node.js version pinned in .nvmrc (which matches CI), installs
7+
# the pnpm workspace dependencies and builds the TypeScript project references.
8+
#
9+
# Native iOS/Android artifacts are intentionally NOT built here: they require the
10+
# Android NDK / Apple toolchains which are not present on a generic Linux worker.
11+
# See CLAUDE.md for how to build those when you actually need them.
12+
set -euo pipefail
13+
14+
# Only bootstrap in remote (Claude Code on the web) workers. Local machines
15+
# usually already have their own Node setup, so we don't want to interfere.
16+
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
17+
exit 0
18+
fi
19+
20+
cd "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}"
21+
22+
# --- Ensure the Node.js version from .nvmrc via nvm ---------------------------
23+
# .nvmrc pins the Node version (matching CI). package.json's devEngines requires
24+
# Node ^24 / pnpm ^10, and the package manager refuses to install with an older
25+
# runtime. nvm ships on the remote workers, so use it to install and select the
26+
# pinned version.
27+
NVM_DIR="${NVM_DIR:-/opt/nvm}"
28+
if [ ! -s "$NVM_DIR/nvm.sh" ] && [ -s "$HOME/.nvm/nvm.sh" ]; then
29+
NVM_DIR="$HOME/.nvm"
30+
fi
31+
32+
if [ -s "$NVM_DIR/nvm.sh" ]; then
33+
export NVM_DIR
34+
# nvm.sh dereferences unset variables, so relax `set -u` while it is active.
35+
set +u
36+
# shellcheck disable=SC1091
37+
. "$NVM_DIR/nvm.sh"
38+
# No version argument: nvm reads .nvmrc from the current directory.
39+
nvm install >/dev/null
40+
nvm use >/dev/null
41+
# Resolve the bin dir for the pinned version explicitly. `nvm which current`
42+
# can report the worker's default Node when it sits earlier on PATH, so ask
43+
# for the .nvmrc version by name instead.
44+
NVMRC_VERSION="$(cat "$PWD/.nvmrc" 2>/dev/null || echo default)"
45+
NODE_BIN="$(dirname "$(nvm which "$NVMRC_VERSION")")"
46+
set -u
47+
48+
export PATH="$NODE_BIN:$PATH"
49+
50+
# Persist the resolved Node toolchain on PATH for the rest of the session.
51+
if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
52+
echo "export PATH=\"$NODE_BIN:\$PATH\"" >> "$CLAUDE_ENV_FILE"
53+
fi
54+
else
55+
echo "warning: nvm not found; using system Node $(node -v). package.json requires Node ^24." >&2
56+
fi
57+
58+
# --- Select pnpm via Corepack ------------------------------------------------
59+
# The repo uses pnpm, pinned in package.json's "packageManager" field. Corepack
60+
# ships with Node and provisions that exact pnpm version on demand, so we don't
61+
# depend on whatever pnpm (if any) the worker image happens to have.
62+
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
63+
corepack enable >/dev/null 2>&1 || true
64+
65+
echo "Using Node $(node -v) / pnpm $(pnpm -v)"
66+
67+
# --- Install dependencies and build the TypeScript packages -------------------
68+
# Plain `pnpm install` (not `--frozen-lockfile`) so the resolved node_modules can
69+
# be cached between sessions; it stays deterministic thanks to the committed
70+
# pnpm-lock.yaml.
71+
pnpm install
72+
pnpm run build
73+
74+
echo "Bootstrap complete: dependencies installed and TypeScript packages built."

.claude/settings.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"hooks": [
6+
{
7+
"type": "command",
8+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh"
9+
}
10+
]
11+
}
12+
],
13+
"PostToolUse": [
14+
{
15+
"matcher": "Write|Edit|MultiEdit",
16+
"hooks": [
17+
{
18+
"type": "command",
19+
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-file.sh"
20+
}
21+
]
22+
}
23+
]
24+
}
25+
}

.github/copilot-instructions.md

Lines changed: 5 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,8 @@
11
# Copilot Instructions for React Native Node-API
22

3-
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.
3+
The shared, tool-agnostic agent instructions for this repository now live in the
4+
top-level [`AGENTS.md`](../AGENTS.md). Please read that file.
45

5-
## Package-Specific Instructions
6-
7-
**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.
8-
9-
## Architecture Overview
10-
11-
**Core Flow**: JS `require("./addon.node")` → Babel transform → `requireNodeAddon()` TurboModule call → native library loading → Node-API module initialization
12-
13-
### Package Architecture
14-
15-
See the [README.md](../README.md#packages) for detailed descriptions of each package and their roles in the system. Key packages include:
16-
17-
- `packages/host` - Core Node-API runtime and Babel plugin
18-
- `packages/cmake-rn` - CMake wrapper for native builds
19-
- `packages/cmake-file-api` - TypeScript wrapper for CMake File API with Zod validation
20-
- `packages/ferric` - Rust/Cargo wrapper with napi-rs integration
21-
- `packages/gyp-to-cmake` - Legacy binding.gyp compatibility
22-
- `apps/test-app` - Integration testing harness
23-
24-
## Critical Build Dependencies
25-
26-
- **Custom Hermes**: Currently depends on a patched Hermes with Node-API support (see [facebook/hermes#1377](https://github.com/facebook/hermes/pull/1377))
27-
- **Prebuilt Binary Spec**: All tools must output to the exact naming scheme:
28-
- Android: `*.android.node/` with jniLibs structure + `react-native-node-api-module` marker file
29-
- iOS: `*.apple.node` (XCFramework renamed) + marker file
30-
31-
## Essential Workflows
32-
33-
### Development Setup
34-
35-
```bash
36-
pnpm install && pnpm run build # Install deps and build all packages
37-
pnpm run bootstrap # Build native components (weak-node-api, examples)
38-
```
39-
40-
### Package Development
41-
42-
- **TypeScript project references**: Use `tsc --build` for incremental compilation
43-
- **Workspace scripts**: Most build/test commands use pnpm workspaces (`--filter` flag), run in topological (dependency) order and fail fast
44-
- **Focus on Node.js packages**: AI development primarily targets the Node.js tooling packages rather than native mobile code
45-
- **No TypeScript type asserts**: You have to ask explicitly and justify if you want to add `as` type assertions.
46-
47-
## Key Patterns
48-
49-
### Babel Transformation
50-
51-
The core magic happens in `packages/host/src/node/babel-plugin/plugin.ts`:
52-
53-
```js
54-
// Input: require("./addon.node")
55-
// Output: require("react-native-node-api").requireNodeAddon("pkg-name--addon")
56-
```
57-
58-
### CMake Integration
59-
60-
For linking against Node-API in CMakeLists.txt:
61-
62-
```cmake
63-
include(${WEAK_NODE_API_CONFIG})
64-
target_link_libraries(addon PRIVATE weak-node-api)
65-
```
66-
67-
### Cross-Platform Naming
68-
69-
Library names use double-dash separation: `package-name--path-component--addon-name`
70-
71-
### Testing
72-
73-
- **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.
74-
- **Cross-package**: Use root-level `pnpm test` for cross-package testing once individual package tests pass
75-
- **Mobile integration**: Available but not the primary AI development focus - ask the developer to run those tests as needed
76-
77-
**Documentation**: Integration details, platform setup, and toolchain configuration are covered in existing repo documentation files.
6+
**IMPORTANT**: Before working on any package, also check for and read
7+
package-specific instruction files (`AGENTS.md` or `copilot-instructions.md`) in
8+
the package directory.

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lts/krypton

AGENTS.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# AGENTS.md
2+
3+
Instructions for AI coding agents working in the React Native Node-API repo.
4+
5+
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.
6+
7+
## Package-Specific Instructions
8+
9+
**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.
10+
11+
## Architecture Overview
12+
13+
**Core Flow**: JS `require("./addon.node")` → Babel transform → `requireNodeAddon()` TurboModule call → native library loading → Node-API module initialization
14+
15+
### Package Architecture
16+
17+
See the [README.md](README.md#packages) for detailed descriptions of each package and their roles in the system. Key packages include:
18+
19+
- `packages/host` - Core Node-API runtime and Babel plugin
20+
- `packages/cmake-rn` - CMake wrapper for native builds
21+
- `packages/cmake-file-api` - TypeScript wrapper for CMake File API with Zod validation
22+
- `packages/ferric` - Rust/Cargo wrapper with napi-rs integration
23+
- `packages/gyp-to-cmake` - Legacy binding.gyp compatibility
24+
- `apps/test-app` - Integration testing harness
25+
26+
## Environment & Bootstrap
27+
28+
- **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`.
29+
- **pnpm is the package manager** — pinned in `package.json`'s `packageManager` field. Let Corepack (bundled with Node) provide it: `corepack enable`, then use `pnpm`.
30+
- Standard setup for the Node.js tooling packages:
31+
32+
```bash
33+
pnpm install # Install workspace dependencies
34+
pnpm run build # Incremental TypeScript build (tsc --build)
35+
```
36+
37+
- 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.
38+
- **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.
39+
40+
## Prefer an upstream fix over a local workaround
41+
42+
When a build/runtime failure looks like a known upstream bug, before writing a
43+
patch or workaround:
44+
45+
1. Find where the fix actually landed and **verify at the source** — the
46+
changelog, lockfile, or the dependency's own manifest/podspec for a _specific
47+
installable version_, not the version list and not a related package's
48+
timeline (a fork or platform variant may carry a fix on a line its upstream
49+
never did).
50+
2. If an installable version within our constraints contains the fix, prefer the
51+
**smallest** bump that includes it (patch > minor > major) over a workaround.
52+
3. Treat the upgrade as a hypothesis under test: say so, and be ready to revert —
53+
every upgrade adds new unknown-bug surface. If it doesn't fix the issue, throw
54+
it away rather than stacking a workaround on top of it.
55+
4. If the bump is more than a patch, or widens scope/risk, check with me before
56+
committing to it.
57+
5. If no fixed version is reachable, a workaround is fine — but comment it with
58+
the exact condition that makes it removable (e.g. "remove once dep ships
59+
fmt ≥ 12.1"), and if you write that condition, verify it isn't already met.
60+
61+
## Critical Build Dependencies
62+
63+
- **Custom Hermes**: Currently depends on a patched Hermes with Node-API support (see [facebook/hermes#1377](https://github.com/facebook/hermes/pull/1377))
64+
- **Prebuilt Binary Spec**: All tools must output to the exact naming scheme:
65+
- Android: `*.android.node/` with jniLibs structure + `react-native-node-api-module` marker file
66+
- iOS: `*.apple.node` (XCFramework renamed) + marker file
67+
68+
## Essential Workflows
69+
70+
### Package Development
71+
72+
- **TypeScript project references**: Use `tsc --build` for incremental compilation
73+
- **Workspace scripts**: Most build/test commands use pnpm workspaces (`--filter` flag), run in topological (dependency) order and fail fast
74+
- **Focus on Node.js packages**: AI development primarily targets the Node.js tooling packages rather than native mobile code
75+
- **No TypeScript type asserts**: You have to ask explicitly and justify if you want to add `as` type assertions.
76+
77+
## Key Patterns
78+
79+
### Babel Transformation
80+
81+
The core magic happens in `packages/host/src/node/babel-plugin/plugin.ts`:
82+
83+
```js
84+
// Input: require("./addon.node")
85+
// Output: require("react-native-node-api").requireNodeAddon("pkg-name--addon")
86+
```
87+
88+
### CMake Integration
89+
90+
For linking against Node-API in CMakeLists.txt:
91+
92+
```cmake
93+
include(${WEAK_NODE_API_CONFIG})
94+
target_link_libraries(addon PRIVATE weak-node-api)
95+
```
96+
97+
### Cross-Platform Naming
98+
99+
Library names use double-dash separation: `package-name--path-component--addon-name`
100+
101+
### Testing
102+
103+
- **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.
104+
- **Cross-package**: Use root-level `pnpm test` for cross-package testing once individual package tests pass
105+
- **Mobile integration**: Available but not the primary AI development focus - ask the developer to run those tests as needed
106+
107+
**Documentation**: Integration details, platform setup, and toolchain configuration are covered in existing repo documentation files.

CLAUDE.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# CLAUDE.md
2+
3+
Guidance for Claude Code working in this repository.
4+
5+
The architecture overview, package layout, coding patterns, and
6+
environment/bootstrap notes are shared with other AI tools and live in the
7+
top-level, tool-agnostic instructions file — prefer editing that over
8+
duplicating content here:
9+
10+
@AGENTS.md
11+
12+
## Claude Code on the web
13+
14+
A `SessionStart` hook (`.claude/hooks/session-start.sh`) bootstraps remote
15+
workers automatically: it selects the Node.js version pinned in `.nvmrc` via
16+
`nvm` (Node 24, as required by `package.json`'s `devEngines`) and pnpm via
17+
Corepack, then runs `pnpm install` and `pnpm run build`, so a fresh session is
18+
ready to build, lint, and test the Node.js tooling packages.
19+
20+
See the "Environment & Bootstrap" section of `AGENTS.md` for the manual steps and
21+
for why native iOS/Android builds are not part of the default bootstrap.
22+
23+
## Formatting
24+
25+
A `PostToolUse` hook (`.claude/hooks/format-file.sh`) runs the workspace's
26+
Prettier on every file Claude Code writes or edits inside the repo. It uses
27+
`--ignore-unknown`, so it is a no-op on files Prettier can't format and it honors
28+
`.prettierignore` — keeping the tree formatted without a separate `prettier:write`
29+
pass. It's a convenience only; CI's `prettier:check` remains the source of truth.

packages/cmake-file-api/copilot-instructions.md renamed to packages/cmake-file-api/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copilot Instructions for cmake-file-api Package
1+
# AGENTS.md — cmake-file-api Package
22

33
This package provides a TypeScript wrapper around the CMake File API using Zod schemas for validation.
44

0 commit comments

Comments
 (0)