From fde2d5f717af53396c93e9880211fb9114bd0c57 Mon Sep 17 00:00:00 2001 From: Fernanda Toledo Date: Fri, 4 Sep 2026 15:57:44 -0300 Subject: [PATCH 1/3] chore(cli): install rn-toolkit plugin on project creation --- .claude/skills/review-changes/SKILL.md | 71 -------------------------- cli/index.js | 4 ++ cli/install-claude-plugin.js | 55 ++++++++++++++++++++ 3 files changed, 59 insertions(+), 71 deletions(-) delete mode 100644 .claude/skills/review-changes/SKILL.md create mode 100644 cli/install-claude-plugin.js diff --git a/.claude/skills/review-changes/SKILL.md b/.claude/skills/review-changes/SKILL.md deleted file mode 100644 index 3130a8276..000000000 --- a/.claude/skills/review-changes/SKILL.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -name: review-changes -description: Reviews the current branch diff against this project's architecture, conventions, and code review checklist. Use when the user asks to "review my changes", "review the diff", "check my code", "run a code review", or "review this branch". -allowed-tools: Bash(git:*), Read ---- - -# Review Changes - -## Purpose - -Perform a code review of the current branch diff against the project's architecture rules, coding conventions, and the REVIEW.md checklist. Report findings grouped by category, with file and line references where possible. - -## Workflow - -1. Read the project rules: - - `AGENTS.md` — critical constraints and glossary - - `REVIEW.md` — the review checklist; this is the primary source of truth for what to flag - - `agent_docs/architecture.md` — layer diagram, routing conventions, dual auth system - - `agent_docs/conventions.md` — API hooks, styling, forms, i18n, anti-patterns - -2. Inspect the branch diff: - - Run `git fetch origin master`. - - Run `git log origin/master..HEAD --oneline` to understand the commit scope. - - Run `git diff origin/master...HEAD --stat` to see which files changed. - - Run `git diff origin/master...HEAD` to read the full diff. - -3. If a changed file needs more context to review correctly (e.g. to check imports, types, or hook usage), use Read to open it. - -4. Review the diff against every category in REVIEW.md: - - API & Query layer - - Auth & security - - Type safety - - i18n - - Styling - - Testing - - Performance - -5. Also check for violations of the critical constraints in `AGENTS.md`: - - Package manager (`pnpm` only) - - Env values via `Env` from `@/lib/env` - - No auth logic in screen components - - No manual camelCase↔snake_case conversion - - No direct MMKV access in components - - All strings through `useTranslation()` - - `createMutation` / `createQuery` from `react-query-kit` - - `createQueryKeys` in `src/api/query-factory.ts` - - `@shopify/flash-list` for growable lists - - `expo-crypto` for random IDs - - `react-hook-form` + `zod` for forms - - `moti` or `react-native-reanimated` for animations (no `Animated` from RN directly) - -6. Check layer boundaries from `agent_docs/architecture.md`: - - Data flows down: `app/ → components/ → api/ → lib/` - - Route guards belong only in `src/app/_layout.tsx`, never in screen files - - Auth state management belongs only in `src/components/providers/auth.tsx` and `src/lib/auth/index.tsx` - -## Output Format - -Group findings by REVIEW.md category. For each finding: - -``` -**[Category]** `path/to/file.ts` — short description of the issue and what the correct approach is. -``` - -If a category has no issues, skip it. - -End the report with one of: -- **No issues found** — if the diff is clean across all categories. -- **X issue(s) found** — a one-line summary count. - -Do not suggest stylistic preferences not grounded in REVIEW.md, AGENTS.md, or agent_docs/. Do not flag things that are already handled automatically by the interceptors or tooling (e.g. case conversion, token injection). diff --git a/cli/index.js b/cli/index.js index 2d3bd1d36..ee16543a1 100755 --- a/cli/index.js +++ b/cli/index.js @@ -4,6 +4,7 @@ const { consola } = require('consola'); const { showMoreDetails } = require('./utils.js'); const { cloneLatestTemplateRelease } = require('./clone-repo.js'); const { setupProject, installDependencies } = require('./setup-project.js'); +const { installClaudeToolkit } = require('./install-claude-plugin.js'); const pkg = require('./package.json'); const { name: packageName } = pkg; @@ -27,6 +28,9 @@ const createRootstrapApp = async () => { // install project dependencies using pnpm await installDependencies(projectName); + // install the Rootstrap rn-toolkit Claude Code plugin + await installClaudeToolkit(projectName); + // show instructions to run the project + link to the documentation showMoreDetails(projectName); }; diff --git a/cli/install-claude-plugin.js b/cli/install-claude-plugin.js new file mode 100644 index 000000000..70551487e --- /dev/null +++ b/cli/install-claude-plugin.js @@ -0,0 +1,55 @@ +const { exec } = require('node:child_process'); +const { consola } = require('consola'); + +const MARKETPLACE_REPOSITORY = 'rootstrap/rn-claude-toolkit'; +const MARKETPLACE_NAME = 'rootstrap'; +const PLUGIN_NAME = 'rn-toolkit'; + +// Quiet variant of execShellCommand: failures here are expected and handled +const run = (cmd) => + new Promise((resolve, reject) => { + exec(cmd, (error, stdout, stderr) => { + if (error) { + reject(error); + return; + } + resolve(stdout || stderr); + }); + }); + +const isClaudeCodeAvailable = async () => { + try { + await run('command -v claude'); + return true; + } catch { + return false; + } +}; + +const installClaudeToolkit = async (projectName) => { + if (!(await isClaudeCodeAvailable())) { + consola.info( + `Claude Code CLI not found, skipping ${PLUGIN_NAME} plugin installation.\n` + + ` Install it later from the project root with: claude plugin marketplace add ${MARKETPLACE_REPOSITORY} --scope project && claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project` + ); + return; + } + + consola.start(`Installing the ${PLUGIN_NAME} Claude Code plugin 🤖`); + try { + await run( + `cd ${projectName} && claude plugin marketplace add ${MARKETPLACE_REPOSITORY} --scope project && claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project` + ); + consola.success(`${PLUGIN_NAME} plugin installed`); + } catch { + consola.warn( + `Could not install the ${PLUGIN_NAME} plugin. The marketplace repository is private, ` + + `so you need git access to ${MARKETPLACE_REPOSITORY} as a member of the Rootstrap org.\n` + + ` Retry from the project root with: claude plugin marketplace add ${MARKETPLACE_REPOSITORY} --scope project && claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project` + ); + } +}; + +module.exports = { + installClaudeToolkit, +}; From c2d1d6a34dfb70d4db76b4124c1ee32315f52299 Mon Sep 17 00:00:00 2001 From: Fernanda Toledo Date: Fri, 4 Sep 2026 16:20:58 -0300 Subject: [PATCH 2/3] fix(cli): use exec cwd and portable claude availability check --- .gitignore | 3 +++ cli/install-claude-plugin.js | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index c471eaa12..45e0e8ab2 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ cli/README.md # PR description scratchpad (agents) PR_DESCRIPTION.md + +# PR review triage scratchpad (agents) +PR_REVIEW_TRIAGE.md diff --git a/cli/install-claude-plugin.js b/cli/install-claude-plugin.js index 70551487e..30300676b 100644 --- a/cli/install-claude-plugin.js +++ b/cli/install-claude-plugin.js @@ -6,9 +6,9 @@ const MARKETPLACE_NAME = 'rootstrap'; const PLUGIN_NAME = 'rn-toolkit'; // Quiet variant of execShellCommand: failures here are expected and handled -const run = (cmd) => +const run = (cmd, options) => new Promise((resolve, reject) => { - exec(cmd, (error, stdout, stderr) => { + exec(cmd, options, (error, stdout, stderr) => { if (error) { reject(error); return; @@ -19,7 +19,7 @@ const run = (cmd) => const isClaudeCodeAvailable = async () => { try { - await run('command -v claude'); + await run('claude --version'); return true; } catch { return false; @@ -38,7 +38,8 @@ const installClaudeToolkit = async (projectName) => { consola.start(`Installing the ${PLUGIN_NAME} Claude Code plugin 🤖`); try { await run( - `cd ${projectName} && claude plugin marketplace add ${MARKETPLACE_REPOSITORY} --scope project && claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project` + `claude plugin marketplace add ${MARKETPLACE_REPOSITORY} --scope project && claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project`, + { cwd: projectName } ); consola.success(`${PLUGIN_NAME} plugin installed`); } catch { From f091baf87c2ebfde2f4056c2f6c2ff2fdde08a24 Mon Sep 17 00:00:00 2001 From: Fernanda Toledo Date: Fri, 4 Sep 2026 16:33:30 -0300 Subject: [PATCH 3/3] fix(cli): run claude via execFile to avoid a shell interpreter --- cli/install-claude-plugin.js | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/cli/install-claude-plugin.js b/cli/install-claude-plugin.js index 30300676b..e05125835 100644 --- a/cli/install-claude-plugin.js +++ b/cli/install-claude-plugin.js @@ -1,14 +1,18 @@ -const { exec } = require('node:child_process'); +const { execFile } = require('node:child_process'); const { consola } = require('consola'); const MARKETPLACE_REPOSITORY = 'rootstrap/rn-claude-toolkit'; const MARKETPLACE_NAME = 'rootstrap'; const PLUGIN_NAME = 'rn-toolkit'; -// Quiet variant of execShellCommand: failures here are expected and handled -const run = (cmd, options) => +const MANUAL_INSTALL_HINT = + `claude plugin marketplace add ${MARKETPLACE_REPOSITORY} --scope project && ` + + `claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project`; + +// Quiet variant of execShellCommand: no shell interpreter, and failures here are expected and handled +const runClaude = (args, options) => new Promise((resolve, reject) => { - exec(cmd, options, (error, stdout, stderr) => { + execFile('claude', args, options, (error, stdout, stderr) => { if (error) { reject(error); return; @@ -19,7 +23,7 @@ const run = (cmd, options) => const isClaudeCodeAvailable = async () => { try { - await run('claude --version'); + await runClaude(['--version']); return true; } catch { return false; @@ -30,23 +34,28 @@ const installClaudeToolkit = async (projectName) => { if (!(await isClaudeCodeAvailable())) { consola.info( `Claude Code CLI not found, skipping ${PLUGIN_NAME} plugin installation.\n` + - ` Install it later from the project root with: claude plugin marketplace add ${MARKETPLACE_REPOSITORY} --scope project && claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project` + ` Install it later from the project root with: ${MANUAL_INSTALL_HINT}` ); return; } consola.start(`Installing the ${PLUGIN_NAME} Claude Code plugin 🤖`); try { - await run( - `claude plugin marketplace add ${MARKETPLACE_REPOSITORY} --scope project && claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project`, - { cwd: projectName } + const options = { cwd: projectName }; + await runClaude( + ['plugin', 'marketplace', 'add', MARKETPLACE_REPOSITORY, '--scope', 'project'], + options + ); + await runClaude( + ['plugin', 'install', `${PLUGIN_NAME}@${MARKETPLACE_NAME}`, '--scope', 'project'], + options ); consola.success(`${PLUGIN_NAME} plugin installed`); } catch { consola.warn( `Could not install the ${PLUGIN_NAME} plugin. The marketplace repository is private, ` + `so you need git access to ${MARKETPLACE_REPOSITORY} as a member of the Rootstrap org.\n` + - ` Retry from the project root with: claude plugin marketplace add ${MARKETPLACE_REPOSITORY} --scope project && claude plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME} --scope project` + ` Retry from the project root with: ${MANUAL_INSTALL_HINT}` ); } };