From b5c8eb1436dd218ccc55b849a9faf56cce58a9fa Mon Sep 17 00:00:00 2001 From: Fernanda Toledo Date: Tue, 12 May 2026 17:12:46 -0300 Subject: [PATCH 1/6] docs: add agent documentation structure --- .claude/settings.json | 15 ++++++ AGENTS.md | 50 +++++++++++++++++ CLAUDE.md | 1 + REVIEW.md | 67 +++++++++++++++++++++++ agent_docs/architecture.md | 85 +++++++++++++++++++++++++++++ agent_docs/commands.md | 73 +++++++++++++++++++++++++ agent_docs/conventions.md | 108 +++++++++++++++++++++++++++++++++++++ 7 files changed, 399 insertions(+) create mode 100644 .claude/settings.json create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 REVIEW.md create mode 100644 agent_docs/architecture.md create mode 100644 agent_docs/commands.md create mode 100644 agent_docs/conventions.md diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..12d738c0 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "pnpm type-check 2>&1 | head -30" + } + ] + } + ] + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..dcb0f9c9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,50 @@ +# AGENTS.md + +This is a React Native + Expo template maintained by Rootstrap. It provides a production-ready starting point for mobile apps targeting iOS, Android, and Web. It is used as the base for client projects and internal tooling at Rootstrap. + +When reviewing code, read and apply the rules in ./REVIEW.md + +--- + +## Glossary + +| Term | Definition | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `AuthProvider` | React Context provider in `src/components/providers/auth.tsx` — manages reactive auth state for the UI layer | +| `useAuth` | Hook to consume `AuthProvider` context (UI layer) OR Zustand store selector from `src/lib/auth/index.tsx` (imperative layer) — do not confuse them | +| `hydrateAuth` | Called once at app startup (`src/app/_layout.tsx`) to rehydrate Zustand auth state from MMKV storage | +| `TokenType` | Shape of the Devise Token Auth token object: `{ bearer, access, client, uid, expiry }` — defined in `src/lib/auth/utils.tsx` | +| `HEADER_KEYS` | Constant map of Devise Token Auth header names (`access-token`, `client`, `uid`, `expiry`, `Authorization`) — defined in `src/components/providers/auth.tsx` | +| `isFirstTime` | Boolean persisted in MMKV via `src/lib/hooks/use-is-first-time.tsx` — controls whether the onboarding screen is shown | +| `Env` | Runtime environment config imported from `@/lib/env` (populated via `expo-constants`) — the only safe way to read env values | +| `PaginateQuery` | Paginated API response shape: `{ results: T[], count, next, previous }` — defined in `src/api/types.ts` | +| `normalizePages` | Utility in `src/api/common/utils.tsx` that flattens TanStack infinite query pages into a flat array for FlashList | +| `queryFactory` | Merged query key registry built with `@lukemorales/query-key-factory` — defined in `src/api/query-factory.ts` | +| `createMutation` | Factory from `react-query-kit` for creating typed mutation hooks — required pattern for all mutations | +| `createQuery` | Factory from `react-query-kit` for creating typed query hooks — required pattern for all queries | + +--- + +## Critical constraints + +- **Package manager**: Always use `pnpm`. Never suggest `npm install` or `yarn`. Enforced via `preinstall` hook. +- **Env values**: Never hardcode environment-specific values. Always use `Env` from `@/lib/env`. +- **Auth logic in screens**: Never add authentication state or redirect logic inside screen components. Auth state lives in `AuthProvider`; routing guards live in `src/app/_layout.tsx` via `Stack.Protected`. +- **Case transformation**: Never add manual camelCase↔snake_case conversion. The Axios interceptors in `src/api/common/interceptors.ts` handle this automatically for all requests and responses. +- **MMKV access**: Never call `storage` (from `src/lib/storage.tsx`) or `authStorage` directly inside components. Use the exported helper functions (`getItem`, `setItem`, `removeItem`, `storeTokens`, `getTokenDetails`, `clearTokens`). +- **Strings**: All user-facing strings must use `useTranslation()`. Every key must exist in `src/translations/en.json`. +- **API hooks**: New hooks must use `createMutation` or `createQuery` from `react-query-kit`. Never use raw `useMutation` or `useQuery` from TanStack directly. +- **Query keys**: Every new query domain must be registered in `src/api/query-factory.ts` using `createQueryKeys` from `@lukemorales/query-key-factory`. +- **Lists**: Use `@shopify/flash-list` for any list that can grow. Never use `FlatList` for feed-style content. +- **Crypto/IDs**: Use `expo-crypto` for random IDs. Never use `Math.random()` or `Date.now()` as identifiers. +- **New dependencies**: Before installing any library, verify the latest version compatible with this stack — React Native 0.81, Expo SDK 54, and React 19. Check compatibility via `npx expo install ` (which resolves the Expo-blessed version) or the package's peer dependencies. Do not install the latest npm version blindly — it may not support this SDK version. + +--- + +## Table of contents + +| File | Description | +| ---------------------------- | -------------------------------------------------------------------------------- | +| `agent_docs/architecture.md` | Layer diagram, folder structure, routing conventions, dual auth system explained | +| `agent_docs/conventions.md` | API hook patterns, styling, forms, i18n, anti-patterns | +| `agent_docs/commands.md` | All pnpm commands for dev, test, lint, build, and setup | diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..27bd5410 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +Strictly follow the rules in ./AGENTS.md diff --git a/REVIEW.md b/REVIEW.md new file mode 100644 index 00000000..d0a67d68 --- /dev/null +++ b/REVIEW.md @@ -0,0 +1,67 @@ +# REVIEW.md + +Code review guidelines for human teammates and AI agents. All categories are grounded in patterns found in this codebase. + +--- + +## 1. API & Query layer + +- New hooks must use `createMutation` or `createQuery` from `react-query-kit` — never raw `useMutation` / `useQuery` from TanStack directly. +- Every new query domain must be registered in `src/api/query-factory.ts` using `createQueryKeys`. Query keys must not be hand-rolled strings. +- Requests that send a body should wrap the payload as `{ user: }` (established in `src/api/auth/use-login.ts` and `src/api/auth/use-sign-up.ts`). Flag any deviation from this pattern and confirm with the team whether it's intentional for that endpoint. +- Paginated endpoints must type their response as `PaginateQuery` (see `src/api/types.ts`). Infinite list consumers must use `normalizePages()` from `src/api/common/utils.tsx` to flatten pages before passing data to FlashList. +- Never add manual camelCase↔snake_case conversion in hooks or components — the interceptors in `src/api/common/interceptors.ts` handle this automatically. + +--- + +## 2. Auth & security + +- Auth state and token management must not live in screen components. Business logic belongs in `src/components/providers/auth.tsx`; routing guards belong in `src/app/_layout.tsx`. +- Tokens must be stored exclusively via `storeTokens()` and read via `getTokenDetails()` from `src/components/providers/auth.tsx`. Never store auth tokens in plain React state, `AsyncStorage`, or the general `storage` instance from `src/lib/storage.tsx`. +- All routes that require a logged-in user must be inside the `(app)/` group, wrapped in `Stack.Protected` with `guard={isAuthenticated}` in `src/app/_layout.tsx`. Never replicate this guard inside individual screen files. +- Verify that API calls are not triggered before `ready === true` from `AuthProvider`. Until `ready` is true, the auth state is not yet hydrated from MMKV. +- Token expiry must be evaluated using `dayjs` comparison against the ISO string stored in `authStorage` — do not compare raw Unix timestamps or use `Date` directly. + +--- + +## 3. Type safety + +- All API hook `Variables` and `Response` types must be explicitly defined in the same file as the hook. No `any`, no `unknown` without a type guard. +- Zod validation schemas must drive TypeScript types via `z.infer` — never define the type separately and then write a matching schema; the schema is the source of truth. +- Environment variables must be accessed through `Env` from `@/lib/env`. Never use `process.env` or `Constants.expoConfig?.extra` directly in feature code. +- Axios response/request types must use the types from `src/api/common/axios.d.ts` augmentation where applicable. + +--- + +## 4. i18n + +- No hardcoded English (or any language) strings in JSX. Every user-facing string must be retrieved with `useTranslation()`. +- Every new key added in a component must have a corresponding entry in `src/translations/en.json`. Run `pnpm lint:translations` to verify. +- Avoid constructing translation keys dynamically (e.g., ``t(`error.${code}`)``) unless the full key set is exhaustively defined in the translations file. + +--- + +## 5. Styling + +- Use NativeWind Tailwind utility classes on React Native primitives. No `StyleSheet.create` except for top-level layout containers (e.g., `flex: 1` wrappers) where Tailwind classes are insufficient. +- Components with multiple visual variants must use `tailwind-variants` — avoid conditional className string concatenation with ternaries or template literals. +- Do not mix NativeWind class-based styling with inline `style` props on the same element unless there is no Tailwind equivalent. + +--- + +## 6. Testing + +- Every new component in `src/components/` must have a corresponding test file in `__tests__/components/`. +- Always import `render`, `screen`, `fireEvent`, and `cleanup` from `@/lib/test-utils` (re-exports from `src/lib/test-utils.tsx`) — never import them directly from `@testing-library/react-native`. The custom `render` wraps components with required providers. +- Form component tests must cover at minimum: (1) renders correctly, (2) shows required-field errors on empty submit, (3) shows format validation error on invalid input. See `__tests__/components/login-form.test.tsx` as the canonical reference. +- Native module mocks live in `__mocks__/`. Before adding a new mock, check existing ones (e.g., `react-native-mmkv`, `react-native-gesture-handler`, `moti`) to avoid duplication or conflicts. +- Use `testID` props for interactive elements queried with `getByTestId`. Follow the naming pattern in existing components (e.g., `login-button`, `email-input`, `password-input`). + +--- + +## 7. Performance + +- Use `@shopify/flash-list` for any list that can grow (feeds, search results, infinite scroll). Never use `FlatList` for these cases. +- Do not call API query hooks inside top-level layout components (`_layout.tsx`) if only one child screen actually needs that data. Fetch at the screen level. +- Side effects triggered by user actions (form submit, button press) must go inside mutations — not in `useEffect` watching state changes. `useEffect` is for synchronising with external systems, not for reacting to user interactions. +- Avoid anonymous functions or inline object literals as props to FlashList's `renderItem` and `keyExtractor` — use stable references with `useCallback`. diff --git a/agent_docs/architecture.md b/agent_docs/architecture.md new file mode 100644 index 00000000..c8f910d7 --- /dev/null +++ b/agent_docs/architecture.md @@ -0,0 +1,85 @@ +# Architecture + +## What the app does + +This template ships four top-level concerns out of the box: + +- **Onboarding** — a one-time screen shown on first launch, gated by the `isFirstTime` flag persisted in MMKV. +- **Authentication** — sign-in, sign-up, forgot-password, and update-password flows backed by Devise Token Auth. +- **Feed** — a paginated post list with detail view and a create-post screen. +- **Settings** — theme toggle, language selection, and account deletion. + +--- + +## Layer diagram + +``` +src/app/ ← Expo Router file-based routing, navigation guards + ↓ +src/components/ ← UI primitives (ui/) and feature components; AuthProvider lives here + ↓ +src/api/ ← Axios client, interceptors, react-query-kit hooks + ↓ +src/lib/ ← Auth state (Zustand), storage (MMKV), i18n, env, utilities +``` + +Data flows down; side-effects (navigation, token refresh) are handled at the layer where they originate. + +--- + +## Expo Router conventions + +| Pattern | Meaning | +| ---------------------------------- | ----------------------------------------------------------------------------------- | +| `src/app/(app)/` | Route group for authenticated screens — all screens here require `isAuthenticated` | +| `Stack.Protected` in `_layout.tsx` | Declarative auth and onboarding gating — do not replicate inside individual screens | +| `src/app/[...messing].tsx` | 404 catch-all for unmatched routes | +| `src/app/+html.tsx` | Web-only HTML shell | +| `src/app/www.tsx` | In-app WebView presented as a modal | + +Route guards are centralised in `src/app/_layout.tsx`. The `GuardedStack` component reads `isAuthenticated` and `isFirstTime` and applies `Stack.Protected` — nothing inside `(app)/` screens should check auth state or redirect. + +--- + +## Where each concern lives + +| Concern | Location | +| -------------------------------------------------- | ---------------------------------------------------- | +| Routing & navigation guards | `src/app/_layout.tsx` | +| Authenticated tab layout | `src/app/(app)/_layout.tsx` | +| Auth state — reactive (UI) | `src/components/providers/auth.tsx` | +| Auth state — imperative (outside React) | `src/lib/auth/index.tsx` | +| Token shape & MMKV helpers | `src/lib/auth/utils.tsx` | +| HTTP client (Axios instance) | `src/api/common/client.tsx` | +| Request/response transformation & token injection | `src/api/common/interceptors.ts` | +| Auth-specific interceptors & `authStorage` | `src/components/providers/auth.tsx` | +| API hooks | `src/api//use-.ts` | +| Shared API utilities (pagination, case conversion) | `src/api/common/utils.tsx` | +| Query key registry | `src/api/query-factory.ts` | +| Shared UI primitives | `src/components/ui/` | +| Feature-level components | `src/components//` | +| i18n setup & utilities | `src/lib/i18n/` | +| Translation strings | `src/translations/en.json` | +| Environment config | `src/lib/env.js` (access via `Env` from `@/lib/env`) | +| General persistent storage | `src/lib/storage.tsx` (MMKV) | +| Global TypeScript types | `src/types/index.ts` | + +--- + +## The dual auth system + +There are two auth layers that coexist for different reasons: + +### 1. `src/lib/auth/index.tsx` — Zustand store + +- Holds `TokenType` state and `signIn` / `signOut` / `hydrate` actions. +- Used **imperatively** outside of React — e.g., `hydrateAuth()` is called at the very top of `src/app/_layout.tsx` before any component mounts, so the token is available synchronously when interceptors first run. +- Also consumed by the Axios request interceptor in `src/api/common/interceptors.ts` via `useAuth.getState().token` — this is a Zustand selector called outside React, which is valid. + +### 2. `src/components/providers/auth.tsx` — React Context + +- Wraps the app in `` and exposes `{ token, isAuthenticated, loading, ready, logout }` via `useAuth()`. +- Manages its own `authStorage` MMKV instance and sets up **its own** Axios interceptors for token persistence from response headers and 401 handling. +- Drives the UI — `GuardedStack` in `_layout.tsx` reads `isAuthenticated` and `ready` from this provider. + +**How they relate**: The Zustand store handles the imperative bootstrap path (token hydration before React renders). The Context provider handles the reactive UI path (updating components when auth state changes). They share the same underlying MMKV data but through separate instances. Do not collapse them into one — the separation is intentional. diff --git a/agent_docs/commands.md b/agent_docs/commands.md new file mode 100644 index 00000000..28d8ff50 --- /dev/null +++ b/agent_docs/commands.md @@ -0,0 +1,73 @@ +# Commands + +All commands use `pnpm`. Never use `npm` or `yarn` — the `preinstall` hook will block them. + +--- + +## Development + +| Command | Description | +| ------------------------ | ------------------------------------------------- | +| `pnpm start` | Start Expo dev server (no dotenv injection) | +| `pnpm start:development` | Start with `APP_ENV=development` | +| `pnpm start:qa` | Start with `APP_ENV=qa` | +| `pnpm start:staging` | Start with `APP_ENV=staging` | +| `pnpm start:production` | Start with `APP_ENV=production` | +| `pnpm ios` | Run on iOS simulator | +| `pnpm android` | Run on Android emulator | +| `pnpm web` | Start Expo web (with cache reset) | +| `pnpm xcode` | Open the iOS project in Xcode (`xed -b ios`) | +| `pnpm prebuild` | Generate native `ios/` and `android/` directories | + +--- + +## Testing + +| Command | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `pnpm test` | Run the full Jest unit test suite | +| `pnpm test:watch` | Run Jest in watch mode | +| `pnpm test:ci` | Run Jest with coverage (used in CI) | +| `pnpm e2e-test` | Run Maestro E2E tests against `.maestro/` (requires a running device/emulator with `APP_ID=com.obytes.development`) | +| `pnpm install-maestro` | Install the Maestro CLI locally | + +--- + +## Quality gates + +Run these before opening a PR. The pre-commit hook runs `pnpm type-check` and `pnpm lint-staged` automatically. + +| Command | Description | +| ------------------------ | ------------------------------------------------------------------- | +| `pnpm type-check` | TypeScript strict check (`tsc --noemit`) | +| `pnpm lint` | ESLint across all `.js`, `.jsx`, `.ts`, `.tsx` files | +| `pnpm lint:translations` | ESLint on `src/translations/` to catch missing or invalid i18n keys | +| `pnpm check-all` | Runs lint + type-check + lint:translations + test in sequence | + +--- + +## Build (EAS) + +Builds are created with Expo Application Services. Always set `APP_ENV` via the environment-specific script variants. + +| Command | Description | +| -------------------------------- | ---------------------------------------- | +| `pnpm build:development:ios` | EAS build — development profile, iOS | +| `pnpm build:development:android` | EAS build — development profile, Android | +| `pnpm build:qa:ios` | EAS build — QA profile, iOS | +| `pnpm build:qa:android` | EAS build — QA profile, Android | +| `pnpm build:staging:ios` | EAS build — staging profile, iOS | +| `pnpm build:staging:android` | EAS build — staging profile, Android | +| `pnpm build:production:ios` | EAS build — production profile, iOS | +| `pnpm build:production:android` | EAS build — production profile, Android | +| `pnpm submit:development:mobile` | EAS submit — development profile | + +--- + +## Setup + +| Command | Description | +| -------------- | ------------------------------------------------------------------- | +| `pnpm install` | Install all dependencies | +| `pnpm prepare` | Install Husky git hooks (run after `pnpm install` on a fresh clone) | +| `pnpm doctor` | Run `expo-doctor` to check for SDK/dependency compatibility issues | diff --git a/agent_docs/conventions.md b/agent_docs/conventions.md new file mode 100644 index 00000000..4184678b --- /dev/null +++ b/agent_docs/conventions.md @@ -0,0 +1,108 @@ +# Conventions + +## API hooks + +All API hooks are created with `react-query-kit`. See `src/api/auth/use-login.ts` for a canonical mutation example and `src/api/auth/use-user.tsx` for a query example. + +**Mutation shape:** + +```ts +export const useMyAction = createMutation({ + mutationFn: (variables) => myApiCall(variables), +}); +``` + +**Query shape:** + +```ts +export const useMyResource = createQuery({ + queryKey: queryFactory.domain.list(variables), + fetcher: (variables) => myApiCall(variables), +}); +``` + +- Place hook files at `src/api//use-.ts`. +- Always define explicit `Variables` and `Response` types in the same file. +- Never use raw `useMutation` or `useQuery` from `@tanstack/react-query` directly. + +--- + +## Request body wrapping + +All auth endpoints wrap the payload as `{ user: variables }`: + +```ts +data: { + user: variables; +} +``` + +This is established in `src/api/auth/use-login.ts` and `src/api/auth/use-sign-up.ts`. **This pattern applies to auth endpoints.** For non-auth domains, confirm the API contract before assuming the same wrapping — flag deviations in code review. + +--- + +## Pagination + +Paginated responses follow the `PaginateQuery` shape from `src/api/types.ts`: + +```ts +{ results: T[], count: number, next: string | null, previous: string | null } +``` + +- Use `normalizePages()` from `src/api/common/utils.tsx` to flatten TanStack infinite query pages into a flat array before passing to FlashList. +- Pagination cursors are offset-based — extracted from the `next`/`previous` URL parameters via `getNextPageParam` / `getPreviousPageParam` in `src/api/common/utils.tsx`. + +--- + +## camelCase ↔ snake_case + +- **Always write camelCase in TypeScript** — types, variables, props, and object keys. +- The Axios interceptors in `src/api/common/interceptors.ts` convert request bodies to snake_case before sending and response bodies to camelCase on receipt. +- **Never add manual case conversion** in hooks, components, or services. If a field arrives in the wrong case, the interceptor is the place to fix it. + +--- + +## Styling + +- Use **NativeWind Tailwind utility classes** directly on React Native primitives via the `className` prop. +- Use **`tailwind-variants`** for components that have multiple visual states or variants — avoids messy conditional class concatenation. +- Only use `StyleSheet.create` for top-level layout containers where Tailwind is insufficient (e.g., `flex: 1` on a root view). See `src/app/_layout.tsx` for a real example. +- Do not mix `className` and `style` on the same element unless there is no Tailwind equivalent for the required property. + +--- + +## Forms + +- All forms use `react-hook-form` with a `zod` schema resolver. +- Derive the TypeScript type from the schema with `z.infer` — do not write a separate type. +- Validation errors are surfaced through form state (e.g., `errors.email.message`) and displayed inline. Never use `Alert.alert()` for validation feedback. +- See `src/components/login-form.tsx` and `src/components/sign-up-form.tsx` for the canonical pattern. + +--- + +## i18n + +- All user-facing strings go through `useTranslation()` from `react-i18next`. +- Keys are defined in `src/translations/en.json`. Add the key there before using it in a component. +- Run `pnpm lint:translations` to catch missing or unused keys. +- Do not construct keys dynamically (e.g., ``t(`errors.${code}`)``) unless every possible value of `code` is already in the translations file. +- RTL support is initialised in `src/lib/i18n/index.tsx` — do not override `I18nManager` settings elsewhere. + +--- + +## Token expiry + +Token expiry is evaluated by comparing `dayjs()` to the ISO string stored in `authStorage` under the `expiry` key. See `src/components/providers/auth.tsx` for the `checkToken` function. When writing any token validation logic, use `dayjs` — do not parse the expiry string with `new Date()` or compare Unix timestamps directly. + +--- + +## Anti-patterns + +| Anti-pattern | Why it's wrong | Correct approach | +| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| Duplicating interceptor logic for token injection | Already handled in `src/api/common/interceptors.ts` and `src/components/providers/auth.tsx` | Do not add token headers manually in hooks | +| Adding `Stack.Protected` or `` inside a screen | Auth routing lives in `src/app/_layout.tsx` | Move guards to `_layout.tsx` | +| Calling `storage.getString()` directly in a component | Bypasses the helper abstraction | Use `getItem` / `setItem` / `removeItem` from `src/lib/storage.tsx` | +| Calling `authStorage` directly outside `src/components/providers/auth.tsx` | Tightly couples unrelated code to the auth storage instance | Use `getTokenDetails()` / `storeTokens()` / `clearTokens()` | +| Using `Math.random()` or `Date.now()` for IDs | Not cryptographically safe | Use `expo-crypto` which is already installed | +| Importing `render` from `@testing-library/react-native` directly | Skips provider wrappers | Import from `@/lib/test-utils` | From 4cd9cc6e17fe0de885273a723c58ee14af6f06b3 Mon Sep 17 00:00:00 2001 From: Fernanda Toledo Date: Tue, 12 May 2026 17:46:46 -0300 Subject: [PATCH 2/6] docs: update setup project to update docs --- cli/setup-project.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/cli/setup-project.js b/cli/setup-project.js index 8d644ec1..85f1bf5d 100755 --- a/cli/setup-project.js +++ b/cli/setup-project.js @@ -34,6 +34,7 @@ const removeUnrelatedFiles = () => { 'cli', '.github/workflows/deploy-cli.yml', 'LICENSE', + 'PROMPT_agent_docs.md', ]); }; @@ -149,6 +150,40 @@ const updateGitHubWorkflows = (projectName) => { ]); }; +const updateAgentDocs = (projectName) => { + projectFilesManager.replaceFilesContent([ + { + fileName: 'AGENTS.md', + replacements: [ + { + searchValue: + 'This is a React Native + Expo template maintained by Rootstrap. It provides a production-ready starting point for mobile apps targeting iOS, Android, and Web. It is used as the base for client projects and internal tooling at Rootstrap.', + replaceValue: `${projectName} is a React Native + Expo mobile application. Update this description with what the project does and who it's for.`, + }, + ], + }, + { + fileName: 'agent_docs/architecture.md', + replacements: [ + { + searchValue: + 'This template ships four top-level concerns out of the box:\n\n- **Onboarding** — a one-time screen shown on first launch, gated by the `isFirstTime` flag persisted in MMKV.\n- **Authentication** — sign-in, sign-up, forgot-password, and update-password flows backed by Devise Token Auth.\n- **Feed** — a paginated post list with detail view and a create-post screen.\n- **Settings** — theme toggle, language selection, and account deletion.', + replaceValue: `- **Onboarding** — a one-time screen shown on first launch, gated by the \`isFirstTime\` flag persisted in MMKV.\n- **Authentication** — sign-in, sign-up, forgot-password, and update-password flows backed by Devise Token Auth.\n- _(Update with the features specific to ${projectName})_`, + }, + ], + }, + { + fileName: 'agent_docs/commands.md', + replacements: [ + { + searchValue: 'APP_ID=com.obytes.development', + replaceValue: `APP_ID=com.${projectName.toLowerCase()}.development`, + }, + ], + }, + ]); +}; + const updateProjectReadme = (projectName) => { projectFilesManager.renameFiles([ { @@ -182,6 +217,7 @@ const setupProject = async (projectName) => { updateProjectConfig(projectName); updateGitHubWorkflows(projectName); updateProjectReadme(projectName); + updateAgentDocs(projectName); consola.success(`Clean up and setup your project 🧹`); } catch (error) { consola.error(`Failed to clean up project folder`, error); From c6af5547054f6c1d012b2944dcbd0084817f451f Mon Sep 17 00:00:00 2001 From: Fernanda Toledo Date: Tue, 12 May 2026 18:42:59 -0300 Subject: [PATCH 3/6] chore: add pr description skill --- .agents/skills/pr-description/SKILL.md | 83 ++++++++++++++++++++++++++ .gitignore | 3 + AGENTS.md | 2 + 3 files changed, 88 insertions(+) create mode 100644 .agents/skills/pr-description/SKILL.md diff --git a/.agents/skills/pr-description/SKILL.md b/.agents/skills/pr-description/SKILL.md new file mode 100644 index 00000000..31656590 --- /dev/null +++ b/.agents/skills/pr-description/SKILL.md @@ -0,0 +1,83 @@ +--- +name: pr-description +description: Draft a concise pull request description for this React Native Expo template. Use when the user asks to write, generate, update, or prepare a PR description, PR body, pull request summary, or GitHub pull request text from the current branch changes. +--- + +# PR Description + +## Purpose + +Create a brief, reviewer-friendly PR description from the current branch diff. The output must follow the repository's pull request template exactly, preserving every heading and keeping placeholders where content is unknown. + +## Style Rules + +- Use plain language. Avoid filler like "this PR aims to", "in order to", and "leverages". +- Be concrete. Name the screens, components, flows, commands, or files that changed. +- Keep it short. Each prose section should usually be 1-3 sentences. +- Avoid bullet soup. If a section needs more than 6 bullets, group related items. +- Skip obvious filler such as import cleanup or lint-only noise unless that is the point of the PR. +- Do not invent Jira tickets, screenshots, devices tested, or implementation details not supported by the diff. + +## Workflow + +1. Read the repository instructions first: + - `AGENTS.md` + - `REVIEW.md` when present, because this repo requires it for review-related work. +2. Find the PR template: + - Prefer `.github/PULL_REQUEST_TEMPLATE.md`. + - Fall back to `PULL_REQUEST_TEMPLATE.md` at the repo root only if the GitHub template is absent. +3. Refresh and inspect the branch: + - Run `git fetch origin master`. + - Run `git log origin/master..HEAD --oneline`. + - Run `git diff origin/master...HEAD --stat`. + - Run `git diff origin/master...HEAD`. +4. Draft the PR description using the exact headings and order from the template. +5. Save it as `PR_DESCRIPTION.md` at the repo root, overwriting any existing file. +6. Ensure `PR_DESCRIPTION.md` is ignored by git. If it is missing from `.gitignore`, append it under: + +```gitignore +# PR description scratchpad (agents) +PR_DESCRIPTION.md +``` + +7. Report back with the relative path only, unless the user asks to see the content. + +## Section Guidance For This Repository + +The current template is `.github/PULL_REQUEST_TEMPLATE.md`. Keep the warning comment and separators from the template if they are present. + +### Jira board reference: + +Leave the example placeholder or replace it only if a real Jira ticket is present in the branch name, commits, or user request. Do not invent ticket IDs. + +### What does this do? + +Write 2-4 concise sentences describing what changed. Focus on user-facing behavior, developer workflow changes, or the concrete problem fixed. + +### Why did you do this? + +Write 1-3 concise sentences explaining the reason for the change. Tie it to the bug, product need, maintainability concern, or template behavior visible in the diff. + +### Who/what does this impact? + +Name affected screens, flows, platforms, users, modules, or developer workflows. Use a short sentence or compact bullets. If impact is unclear, leave a placeholder comment instead of guessing. + +### How did you test this? + +List commands run and manual checks performed. Keep the existing checklist from the template and check only items that are supported by evidence or explicitly provided by the user. + +### Notes: + +Include caveats, follow-ups, migration notes, or review hints. If there are none, leave the template placeholder. + +### Screenshots / Previews + +Keep the screenshot/Figma/device placeholders unless real images or recordings are available. Remove irrelevant bug before/after tables only when the template structure still remains clear. + +## Final Response + +After writing the file, say where it was saved, for example: + +`Done: PR_DESCRIPTION.md` + +Do not paste the full PR description unless the user asks. diff --git a/.gitignore b/.gitignore index f8533c50..c471eaa1 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ expo-env.d.ts # cli cli/README.md + +# PR description scratchpad (agents) +PR_DESCRIPTION.md diff --git a/AGENTS.md b/AGENTS.md index dcb0f9c9..b7c2e012 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,3 +48,5 @@ When reviewing code, read and apply the rules in ./REVIEW.md | `agent_docs/architecture.md` | Layer diagram, folder structure, routing conventions, dual auth system explained | | `agent_docs/conventions.md` | API hook patterns, styling, forms, i18n, anti-patterns | | `agent_docs/commands.md` | All pnpm commands for dev, test, lint, build, and setup | + +## Imported Claude Cowork project instructions From fb2b494b39ce6342a7053ed08cc4a6953b78f992 Mon Sep 17 00:00:00 2001 From: Fernanda Toledo Date: Thu, 14 May 2026 11:19:47 -0300 Subject: [PATCH 4/6] fix: sonar medium issues --- cli/project-files-manager.js | 2 +- src/components/ui/select.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/project-files-manager.js b/cli/project-files-manager.js index f1d47e6f..d61f6686 100644 --- a/cli/project-files-manager.js +++ b/cli/project-files-manager.js @@ -1,5 +1,5 @@ const fs = require('fs-extra'); -const path = require('path'); +const path = require('node:path'); class ProjectFilesManager { #projectName; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index ac2f4756..93ce0745 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -178,7 +178,7 @@ export const Select = (props: SelectProps) => { const textValue = useMemo( () => value !== undefined - ? (options?.filter((t) => t.value === value)?.[0]?.label ?? placeholder) + ? (options?.find((t) => t.value === value)?.label ?? placeholder) : placeholder, [value, options, placeholder], ); From 5e9c3443f6fde197d4c1ef3a0ec05a74f78b2d5f Mon Sep 17 00:00:00 2001 From: Fernanda Toledo Date: Thu, 14 May 2026 17:47:28 -0300 Subject: [PATCH 5/6] fix: sonar issues --- src/api/common/utils.tsx | 4 ++-- src/app/+html.tsx | 2 +- src/components/colors.tsx | 8 +++++--- src/components/providers/auth.tsx | 2 +- src/components/ui/input.tsx | 2 +- src/components/ui/select.tsx | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/api/common/utils.tsx b/src/api/common/utils.tsx index 96a53d32..cef90192 100644 --- a/src/api/common/utils.tsx +++ b/src/api/common/utils.tsx @@ -59,7 +59,7 @@ export const toCamelCase = (obj: GenericObject): GenericObject => { const newObj: GenericObject = {}; for (const key in obj) { if (Object.hasOwn(obj, key)) { - const newKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase()); + const newKey = key.replaceAll(/_([a-z])/g, (g) => g[1].toUpperCase()); const value = obj[key]; if (isGenericObject(value)) { newObj[newKey] = toCamelCase(value); @@ -72,7 +72,7 @@ export const toCamelCase = (obj: GenericObject): GenericObject => { }; const camelToSnake = (key: string): string => - key.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); + key.replaceAll(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); export const toSnakeCase = (obj: GenericObject): GenericObject => { const newObj: GenericObject = {}; diff --git a/src/app/+html.tsx b/src/app/+html.tsx index 6dfbeea3..93e93631 100644 --- a/src/app/+html.tsx +++ b/src/app/+html.tsx @@ -5,7 +5,7 @@ import { type ReactNode } from 'react'; // web page during static rendering. // The contents of this function only run in Node.js environments and // do not have access to the DOM or browser APIs. -export default function Root({ children }: { children: ReactNode }) { +export default function Root({ children }: Readonly<{ children: ReactNode }>) { return ( diff --git a/src/components/colors.tsx b/src/components/colors.tsx index 0e6788b2..8ffc0163 100644 --- a/src/components/colors.tsx +++ b/src/components/colors.tsx @@ -21,9 +21,11 @@ const Color = ({ name }: { name: ColorName }) => { {name.toUpperCase()} - {Object.entries(colors[name]).map(([key, value]) => ( - - ))} + {Object.entries(colors[name] as Record).map( + ([key, value]) => ( + + ), + )} ); diff --git a/src/components/providers/auth.tsx b/src/components/providers/auth.tsx index 4713e22c..34f0f5f2 100644 --- a/src/components/providers/auth.tsx +++ b/src/components/providers/auth.tsx @@ -80,7 +80,7 @@ client.interceptors.response.use( const expiration = response.headers[HEADER_KEYS.EXPIRY] ? dayjs - .unix(parseInt(response.headers[HEADER_KEYS.EXPIRY], 10)) + .unix(Number.parseInt(response.headers[HEADER_KEYS.EXPIRY], 10)) .toISOString() : dayjs().add(1, 'hour').toISOString(); diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 573de9cb..183684aa 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -129,7 +129,7 @@ export const Input = forwardRef((props, ref) => { // only used with react-hook-form export function ControlledInput( - props: ControlledInputProps, + props: Readonly>, ) { const { name, control, rules, ...inputProps } = props; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 93ce0745..81310225 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -226,7 +226,7 @@ export const Select = (props: SelectProps) => { // only used with react-hook-form export function ControlledSelect( - props: ControlledSelectProps, + props: Readonly>, ) { const { name, control, rules, onSelect: onNSelect, ...selectProps } = props; From 48af7da0dc98cf4a5dc28d81f50191dcedc8d142 Mon Sep 17 00:00:00 2001 From: Fernanda Toledo Date: Thu, 14 May 2026 17:51:50 -0300 Subject: [PATCH 6/6] fix: remove unnecessary file removal --- cli/setup-project.js | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/setup-project.js b/cli/setup-project.js index 85f1bf5d..cbde3776 100755 --- a/cli/setup-project.js +++ b/cli/setup-project.js @@ -34,7 +34,6 @@ const removeUnrelatedFiles = () => { 'cli', '.github/workflows/deploy-cli.yml', 'LICENSE', - 'PROMPT_agent_docs.md', ]); };