diff --git a/.claude/agents/code-reviewer.md b/.claude/agents/code-reviewer.md new file mode 100644 index 00000000..e312345b --- /dev/null +++ b/.claude/agents/code-reviewer.md @@ -0,0 +1,115 @@ +--- +name: code-reviewer +description: Expert code reviewer for the fluent-mobile React Native repo. Use PROACTIVELY immediately after writing or editing code, before committing, and when preparing or reviewing a PR. Reviews ONLY the changed/diffed code against fluent-mobile conventions (layer boundaries, Android-only, theme tokens, Refs not Closes, PR template, device QA). Flags violations with file:line and a concrete fix, grouped by severity. +tools: Read, Grep, Glob, Bash +model: inherit +--- + +You are a senior code reviewer for **fluent-mobile**, an Expo SDK 57 / React +Native (Android-only) TypeScript app. Your job is to review CHANGED code +against this repo's conventions and report actionable findings. You do not +write or edit code — you review and recommend. + +## Step 1 — Determine the review scope + +Run these to find what changed (review ONLY these files unless the user +explicitly asks for a broader review): + +```bash +git diff --staged --name-only +git diff --name-only +git diff --staged +git diff +``` + +If both staged and unstaged are empty, fall back to `git diff origin/main...HEAD` +(branch vs main) so PR reviews work. State which scope you used. Use the diff +hunks to get accurate `file:line` numbers; Read/Grep the surrounding code when +you need context, but never review files outside the change set unless told to. + +## Step 2 — Enforce the fluent-mobile convention checklist + +For each changed file, check every applicable rule below. Be concrete: cite the +exact `file:line`, name the rule, and give a copy-pasteable fix. + +1. **Layer boundaries.** Screens/components must not call `fetch` or talk to + the HTTP API directly — use `FluentAPI` in `src/services/api.ts` plus sync. + Screens must not write SQL — use `src/db/repository.ts` (writes) and + `src/db/queries.ts` (reads). UI should prefer hooks over importing queries. + See `.cursor/rules/architecture.mdc`. + +2. **Android-only.** New iOS config, assets, CI jobs, scripts, `expo run:ios`, + `--platform ios`, or `--platform all` → flag. Shared `Platform.OS === 'ios'` + branches in UI are fine when they improve Android layout. See + `.cursor/rules/android-only.mdc`. + +3. **Theme tokens.** New StyleSheets with hardcoded hex / raw color literals + should use `theme` from `src/theme/` (`colors`, `spacing`, `radius`, + `typography`). The only legitimate new hex is inside `src/theme/tokens.ts` + (or a documented token file). `src/app/appStyles.ts` is legacy — do not add + new keys there; prefer tokens when touching a file. + +4. **Logging.** `console.log` / `console.warn` / `console.error` in app code + (not tests) → use `logger.create('Tag')` from `src/utils/logger.ts`. + +5. **Env access.** Reading `process.env.EXPO_PUBLIC_API_BASE_URL` (or inventing + `API_BASE_URL`) in new code → use `getApiBaseUrl()` from + `src/config/apiBaseUrl.ts`. + +6. **Schema / migrations.** Ad-hoc `ALTER TABLE` with ignore-errors, or schema + edits without a versioned migration in `src/db/migrations.ts` → flag. + `getDatabase()` before `initializeDatabase()` throws. + +7. **Delivery / PR text.** PR bodies or commit messages that use `Closes` / + `Fixes` / `Resolves` for ticketed work → must be `Refs #NNN`. Agent-opened + PRs must fill `.cursor/templates/pr-template.md` (not a short Summary/Test + plan). One ticket = one PR ([AGENTS.md](../../AGENTS.md)). + +8. **Device QA.** Changes that add native modules or mic / camera / filesystem + / permissions behavior must **flag** that Android device QA is required + before ready-for-review. Do not treat unit tests as sufficient. + +9. **TypeScript.** New `any` (prefer `unknown` + narrowing). Missing types on + exported functions where neighbors have them. + +10. **Scope / abstraction.** New use-case-agnostic adapter/framework without a + second in-repo caller or a ticket ask → flag ([AGENTS.md](../../AGENTS.md) + abstraction budget). Adjacent-ticket stubs in the same PR → flag. + +11. **Generated native.** Edits under `android/` (gitignored CNG output) that + should live in `app.config.ts` / `plugins/` → flag. + +12. **Package manager.** `yarn` / `pnpm` in scripts, docs, or PR steps → must + be `npm`. + +## Step 3 — Report + +Output findings grouped by severity, most severe first: + +- **Blocking** — fetch/SQL in UI (#1), iOS surface area (#2), closing keywords + in PR text (#7), device-QA-required change with no flag (#8), `any` in new + code (#9), generated `android/` edits (#11). +- **Should-fix** — theme hex (#3), console logging (#4), env access (#5), + schema without migration (#6), missing PR template (#7), abstraction/scope + (#10), yarn/pnpm (#12). +- **Nit** — minor style that neighbors already enforce. + +Each finding MUST be one tight entry: + +```text +- `path/to/File.tsx:42` — [rule name] Problem in one sentence. Fix: . +``` + +Rules: + +- Be specific to the actual changed line, not generic advice. Quote the + offending token when short. +- If a file is clean, say so briefly. End with a one-line verdict: + **APPROVE**, **APPROVE WITH NITS**, or **REQUEST CHANGES**. +- Every finding is actionable. **APPROVE WITH NITS** means no + Blocking/Should-fix remain, but listed Nits still require a fix or an + explicit human waiver before commit and before the PR is handed off. +- Do not invent issues. If you cannot see enough context in the diff, Read + the file before flagging. If still unsure, phrase it as a question rather + than a hard finding. +- Stay focused on the diff. Do not review or rewrite unrelated code. diff --git a/.claude/commands/create-pr-branch.md b/.claude/commands/create-pr-branch.md new file mode 100644 index 00000000..a648cb98 --- /dev/null +++ b/.claude/commands/create-pr-branch.md @@ -0,0 +1,175 @@ +# Create PR Branch + +## Overview + +Creates a new git branch using the team naming convention: + +```text +{author}/{type}/{issue-number}-{slug-from-title} +``` + +**Example:** `mrace/chore/173-phase1-agent-process-docs` + +Run with `/create-pr-branch` in Cursor chat. Canonical file: this one. +`/start-issue` **reads and executes** these naming steps (always from `main`) +instead of re-specifying slug/type logic. + +**Tracker:** Project 4 Fluent Mobile Board — see [docs/issue-tracking.md](../../docs/issue-tracking.md). + +## Branch name format + +| Segment | Source | Example | +| ------- | ------ | ------- | +| `author` | Local part of `git config user.email` | `mrace` | +| `type` | `feature`, `fix`, or `chore` only | `chore` | +| `issue-number` | GitHub issue number | `173` | +| `slug` | Meaningful words from issue title | `phase1-agent-process-docs` | + +**Full pattern:** `{author}/{type}/{issue-number}-{slug}` + +## When you run this command + +Execute these steps in order. Do not skip validation or user confirmation before +creating the branch (except when invoked from `/start-issue`, which already is +the explicit ask — still print the branch name). + +### 1. Resolve issue number + +- If the user message or recent conversation already includes a GitHub issue (e.g. `#173` or `173`), use it. +- Otherwise, **ask once**: "What's the GitHub issue number? (e.g. 173)" +- Normalize to digits only for the branch segment. + +### 2. Fetch GitHub issue + +```bash +gh issue view NNN --repo eten-tech-foundation/fluent-mobile --json number,title,labels,body +``` + +If the issue is not found, report the error and stop. + +### 3. Resolve branch author prefix + +Use the **local part** of `git config user.email` (everything before `@`): + +```bash +git config user.email +# mrace@gloo.us → mrace +``` + +Rules: lowercase only; strip `+tag` suffixes if present (e.g. `mrace+wip` → `mrace`); allow `a-z0-9-` only in the final prefix. + +If `user.email` is unset, ask the user to configure git (`git config user.email you@gloo.us`) or provide their email prefix once, then stop. + +### 4. Determine branch type (`feature` | `fix` | `chore`) + +Use **only** these three types. + +**Infer from the GitHub issue (in order):** + +| Signal | Type | +| ------ | ---- | +| Labels contain `bug`, `bugfix`, `hotfix`, `defect` | `fix` | +| Labels contain `chore`, `maintenance`, `tech-debt`, `dependencies`, `documentation` | `chore` | +| Labels contain `feature`, `story`, `enhancement` | `feature` | +| Title starts with or contains strong fix signals: `fix`, `fixes`, `fixed`, `bug`, `hotfix`, `resolve` | `fix` | +| Title signals: `add`, `implement`, `introduce`, `support`, `enable` | `feature` | +| Title signals: `replace`, `migrate`, `upgrade`, `bump`, `refactor`, `remove`, `cleanup`, `chore`, `update`, `adopt`, `docs` (non-user-facing) | `chore` | + +If still ambiguous, use **AskQuestion** with: feature / fix / chore (default from best guess). + +### 5. Build title slug + +From the GitHub issue **title** (not description): + +1. Remove prefixes like `[Mobile App]`, `(Mobile)`, etc. +2. Lowercase the title. +3. Split into words (alphanumeric tokens); split camelCase into words when obvious. +4. **Drop filler words:** `a`, `an`, `the`, `and`, `or`, `but`, `for`, `with`, `from`, `into`, `to`, `of`, `in`, `on`, `at`, `by`, `as`, `is`, `are`, `was`, `were`, `be`, `been`, `being`, `this`, `that`, `these`, `those`, `it`, `its`, `via`, `using`, `use`, `please` +5. Keep **3–6 meaningful words**; cap slug length (~45 chars after issue number). +6. Join with single hyphens; lowercase `a-z0-9-` only. + +**Final branch name:** + +```text +{author}/{type}/{issue-number}-{slug} +``` + +### 6. Pre-flight checks + +```bash +git status --porcelain +git branch --show-current +git branch --list '{proposed-branch-name}' +``` + +- If the proposed branch **already exists**, stop and tell the user (when called + from `/start-issue`, resume that branch instead — see start-issue step 4c). +- If there are **uncommitted changes**, warn and ask whether to continue (do not stash unless the user asks). +- If not on `main`, note the current branch and confirm they want to branch from here. + `/start-issue` always bases on `origin/main`. + +### 7. Confirm with user + +Show a short summary before creating: + +```text +Branch: mrace/chore/173-phase1-agent-process-docs +Issue: #173 — Adopt Phase 1 agent/process docs +Base: main @ abc1234 +``` + +Ask: **"Create this branch?"** (or create immediately if the user was explicit, +including `/start-issue`). + +### 8. Create branch + +```bash +git checkout -b "{author}/{type}/{issue-number}-{slug}" +``` + +If branching from `main` explicitly and behind: + +```bash +git fetch origin main && git checkout main && git pull --ff-only origin main +git checkout -b "..." +``` + +Prefer creating from `origin/main` without updating local `main` when a +checkout of `main` is blocked: + +```bash +git fetch origin main +git checkout -b "{author}/{type}/{issue-number}-{slug}" origin/main +``` + +Report success and remind them to use `/create-pr` (or `/start-issue` will chain +it) when ready. + +## Integration + +- Pairs with `/create-pr`, `/open-pr` (alias), and `/generate-pr-description`. +- `/start-issue` reuses this command for naming + branch creation. +- Issue number in the branch name enables automatic detection in `/create-pr`. +- PR title format: `[#NNN]: Description` (see [docs/issue-tracking.md](../../docs/issue-tracking.md)). + +## Author setup (one-time per machine) + +```bash +git config --global user.email "you@gloo.us" +# → branch prefix: you +``` + +## Usage + +| User says | Action | +| --------- | ------ | +| `/create-pr-branch` | Ask for issue number, then run workflow | +| `/create-pr-branch 173` | Fetch issue, propose branch, create after confirm | + +## Rules + +- **Never** use `feature/` as the first path segment without an author prefix. +- **Never** use branch types outside `feature`, `fix`, `chore`. +- **Never** force-push or delete branches unless the user explicitly asks. +- **Always** show the final branch name before `git checkout -b`. +- Prefer `gh issue view` for the title; do not guess the slug from memory if the issue can be fetched. diff --git a/.claude/commands/create-pr.md b/.claude/commands/create-pr.md new file mode 100644 index 00000000..c69034be --- /dev/null +++ b/.claude/commands/create-pr.md @@ -0,0 +1,271 @@ +--- +description: Open a draft GitHub PR against main with the Fluent template, Refs #NNN, code-reviewer, Project 4 In PR Review, and CI watch to green. Never merge. /open-pr is an alias. +argument-hint: '[optional: targeted test path/pattern]' +--- + +# /create-pr — open a draft PR for fluent-mobile + +You are opening a GitHub pull request for **fluent-mobile**. Proceed +autonomously — make sensible choices and report them. **Never merge.** + +`/open-pr` is an alias of this command (same file). + +**PR body:** Always fill [`.cursor/templates/pr-template.md`](../../.cursor/templates/pr-template.md) +(same sections as `/generate-pr-description`). Mandatory for every agent-opened +PR ([delivery.mdc](../../.cursor/rules/delivery.mdc)). + +**Tracker:** Project 4 Fluent Mobile Board — [docs/issue-tracking.md](../../docs/issue-tracking.md). + +## Critical repo facts you MUST enforce + +- **Base branch is `main`.** Pass `--base main` explicitly. Never push + `origin main`. Never open a PR with `main` as the **head** branch. +- **Title:** `[#NNN]: Title text` (sentence case after the colon). +- **Body:** `Refs #NNN` on its own line under Details. **Never** + `Closes` / `Fixes` / `Resolves`. +- **Assignee:** `--assignee @me`. +- **Draft** by default (`gh pr create --draft`). +- Package manager is **npm** only. + +## Dynamic context (already gathered for you) + +- Current branch: !`git rev-parse --abbrev-ref HEAD` +- Working tree: !`git status -sb` +- gh auth: !`gh auth status 2>&1 | head -3 || echo "gh not authenticated"` + +## Flow — execute these steps in order + +### 1. Validate the branch (hard gate) + +- If the current branch is `main`: **STOP** and tell the developer to create a + ticketed feature branch first (`/create-pr-branch` or `/start-issue`). +- If the branch name does not match `{author}/{type}/{issue-number}-{slug}` with + `type` in `feature` | `fix` | `chore`, warn (do not hard-block) and continue. + +### 2. Sync and inspect the delta against `main` + +```bash +git fetch origin main +git log --oneline origin/main..HEAD +git diff --stat origin/main...HEAD +``` + +- If `git log origin/main..HEAD` is empty: **STOP** — nothing to open a PR for. +- Read the commits and the diffstat. These are source material for title and body. + +### 3. Run local checks (**hard-block** — do not push/open over red) + +```bash +npm run format:check +npm run lint +npm run typecheck +npm test -- --ci +``` + +If `$ARGUMENTS` is provided, treat it as a targeted test scope **in addition to** +the four gates (never skip format/lint/typecheck). If the full test suite looks +slow or times out, fall back to a targeted run over the changed areas (infer from +the diffstat) and clearly note that you did so — still run the other three gates +in full. + +**Hard gate:** If any of the four commands fail, **STOP**. Fix locally and re-run +until green (max practical iterations in-session). Do **not** push or open the PR +while those failures remain. + +Also check: + +```bash +git status --porcelain +git branch --show-current +``` + +Warn on uncommitted changes unless the user wants them included. **Do not push** +uncommitted repairs. + +### 3b. Convention review (**hard-block** on any unfixed finding) + +Run this on the **working diff** (staged + unstaged) **before the final commit / +push**. Launch the repo **`code-reviewer`** agent +(`.claude/agents/code-reviewer.md`) on that working tree (fall back to +`origin/main...HEAD` only if the working tree is clean). + +- Fix **every** finding **before pushing** — **Blocking**, **Should-fix**, + **Nit**. Severity only orders the work; it does **not** make a finding optional. +- After each repair: **commit** it, then re-run local checks (step 3: + `format:check`, lint, typecheck, tests) **and** `code-reviewer`. Repeat until + both are clean. `git push` does not include uncommitted changes — never push + uncommitted repairs. +- If any finding remains **unfixed and not explicitly waived**: **STOP**, fix, + commit, re-run local checks (step 3), and re-run `code-reviewer`. +- The only escape is an **explicit per-item human waiver** documented in the PR + body (what was waived, why, who approved). Do not invent waivers; do not + batch-waive an entire severity. +- Do **not** skip this step to save time. + +Board-move failures later in this command are **non-gating** and unrelated to +this hard gate. + +### 4. Push the branch + +Confirm the working tree has no uncommitted repairs (`git status --porcelain` +clean for files you intend to ship). Feature branch only — **never** +`git push origin main` ([delivery.mdc](../../.cursor/rules/delivery.mdc)): + +```bash +git push -u origin HEAD +``` + +### 5. Create the draft PR + +Detect a valid GitHub issue number from the branch (`…/173-…` or leading +`173-…`). If none is found: **STOP** and request an issue number. Do not open a +PR without `#NNN`. Fetch with: + +```bash +gh issue view NNN --repo eten-tech-foundation/fluent-mobile --json number,title,body +``` + +**Title:** `[#NNN]: Title text` from the issue title (strip `[Mobile App]`-style +prefixes; sentence case after the colon). Do **not** fall back to the branch +slug. + +**Body:** Load [`.cursor/templates/pr-template.md`](../../.cursor/templates/pr-template.md) +and pre-fill: + +| Section | Required content | +| ------- | ---------------- | +| **TLDR** | 2–4 sentences: what / why / impact | +| **Reviewer checklist** | Leave items unchecked unless verified ([AGENTS.md](../../AGENTS.md)) | +| **Details** | `Refs #NNN` on its own line; short summary; type-of-change checkboxes | +| **Technical changes** | Key files as `` `path` `` bullets | +| **Testing** | What gates ran (`format:check`, `lint`, `typecheck`, `npm test -- --ci`) | +| **How to verify** | Numbered steps + **Expected** | +| **Follow-ups** | Deferred AC → linked issues; otherwise say none | + +Do **not** auto-check **Acceptance criteria**, **Scope**, or **Android device +tested** unless verified. Do **not** auto-check device QA for native / mic / +camera / filesystem / permissions changes. + +Keep under ~400 lines; no nested fenced code blocks inside the PR body. + +```bash +gh pr create --draft \ + --base main \ + --title "[#NNN]: Title" \ + --body-file /tmp/pr-body.md \ + --assignee @me +``` + +If the PR already exists without an assignee: + +```bash +gh pr edit --add-assignee @me +``` + +**Issue link (Development sidebar):** `Refs #NNN` is the required non-closing +reference. GitHub’s Development widget is only auto-populated by closing +keywords or a **manual** sidebar link. Do **not** use closing keywords. After +create, if the sidebar is empty, link `#NNN` once in the GitHub UI (or ask the +user to). + +**Reviewers / labels:** CODEOWNERS handles review requests after the file is on +`main`. Omit `--reviewer` unless the user asks. + +Flag high-impact paths in the body when present: `package.json`, +`package-lock.json`, `app.config.ts`, `plugins/`, `eas.json`, `src/db/schema.ts` +(`android/` is gitignored CNG output). + +### 6. Move the linked issue to **In PR Review** (Project 4) + +Best-effort: warn and continue on failure — never fail the PR over it. + +1. Parse `Refs #NNN` from the PR body (fall back to `NNN` in the branch name). + The issue number is required (step 5); do not skip tracking. +2. Do **not** move Product-owned columns (`In Progress (Product)`, + `Product Ready`, `Sprint Shaping`). +3. If the assigned issue is missing from Project 4, **add it**, then set Status + to **`In PR Review`**. Resolve the option **by name** when possible. Verified: + project `PVT_kwDOB8vK1s4A34c5`, Status field `PVTSSF_lADOB8vK1s4A34c5zgs8akY`, + In PR Review `19224fda`. + +```bash +ISSUE= + +ITEM_ID=$(gh api graphql -f query=' + query($n: Int!) { + repository(owner: "eten-tech-foundation", name: "fluent-mobile") { + issue(number: $n) { + projectItems(first: 10) { + nodes { + id + project { number } + fieldValueByName(name: "Status") { + ... on ProjectV2ItemFieldSingleSelectValue { name } + } + } + } + } + } + }' -F n=$ISSUE \ + --jq '.data.repository.issue.projectItems.nodes[] | select(.project.number == 4) | .id') + +if [ -z "$ITEM_ID" ]; then + gh project item-add 4 --owner eten-tech-foundation \ + --url "https://github.com/eten-tech-foundation/fluent-mobile/issues/$ISSUE" + ITEM_ID=$(gh api graphql -f query=' + query($n: Int!) { + repository(owner: "eten-tech-foundation", name: "fluent-mobile") { + issue(number: $n) { + projectItems(first: 10) { + nodes { id project { number } } + } + } + } + }' -F n=$ISSUE \ + --jq '.data.repository.issue.projectItems.nodes[] | select(.project.number == 4) | .id') +fi + +if [ -z "$ITEM_ID" ]; then echo "WARN: issue #$ISSUE still not on Project 4; skipping."; else + gh api graphql -f query=' + mutation($item: ID!) { + updateProjectV2ItemFieldValue(input: { + projectId: "PVT_kwDOB8vK1s4A34c5" + itemId: $item + fieldId: "PVTSSF_lADOB8vK1s4A34c5zgs8akY" + value: { singleSelectOptionId: "19224fda" } + }) { projectV2Item { id } } + }' -F item="$ITEM_ID" >/dev/null \ + && echo "Moved issue #$ISSUE to In PR Review." \ + || echo "WARN: failed to move issue #$ISSUE to In PR Review (continuing)." +fi +``` + +### 7. Wait for CI — **not done until green** + +Opening the PR is **not** the finish line. + +1. Watch required checks: `gh pr checks --watch`. +2. If any required check **fails**, treat it as your job: read the failing log, + fix, **commit** the repair, re-run `format:check`, lint, typecheck, tests, + and `code-reviewer`, then push (never push uncommitted repairs) and re-watch. + Loop until green (or blocked on an out-of-scope base-branch issue — then say + so explicitly and do not claim success). +3. Only then proceed to the Report step. + +Do **not** tell the user the PR work is “done” while CI is red or still running, +or while any code-review finding remains unfixed without an explicit per-item +human waiver in the PR body. **Never merge.** + +### 8. Report + +Print the **PR URL** and a one-line recap (title, base = `main`, local check +results, **code-reviewer verdict**, **CI status = green**, whether the linked +issue was moved to In PR Review). Confirm assignee is the author +(`gh pr view --json assignees`). + +## Related commands + +- `/start-issue` — full issue → branch → implement → this command +- `/create-pr-branch` — branch only +- `/generate-pr-description` — body only, no `gh pr create` +- `/onboard`, `/dep-bump` — setup and dependency changes diff --git a/.claude/commands/dep-bump.md b/.claude/commands/dep-bump.md new file mode 100644 index 00000000..9c1a1345 --- /dev/null +++ b/.claude/commands/dep-bump.md @@ -0,0 +1,82 @@ +# /dep-bump — change a dependency the Expo way + +You are adding, upgrading, aligning, or removing a dependency in **fluent-mobile**. +Naive `npm install @latest` is often **wrong** here: this is an Expo SDK **57** +CNG app (Android-only, custom dev client — not Expo Go). Two health checks must stay +green after dependency work: + +- `npx expo install --check` — Expo-managed packages at SDK-compatible versions +- `npm run doctor` (`expo-doctor`) — config / native compatibility + +Proceed autonomously and report what you did; only stop at hard gates below. + +`$ARGUMENTS` = package(s) to add/bump. If empty, **align + validate** the current tree. + +## Critical facts + +- **Use `npx expo install `** for anything Expo knows about (resolves SDK 57–compatible versions). Use plain `npm install` only for pure-JS packages Expo has no opinion on — then still re-run the checks. +- **`npm run prebuild` is destructive** (`expo prebuild --clean --platform android`). Run it only when the change adds/removes a **native** module or config plugin. Pure-JS deps do not need it. **Never** pass `--platform ios` or omit the platform. +- Package manager is **npm** only (`package-lock.json`). Do not use yarn/pnpm. +- Agent-authored lockfile / doctor fixes still need a **ticketed PR** — never push to `main` ([delivery.mdc](../../.cursor/rules/delivery.mdc)). + +## Flow + +### 1. Inspect working tree + +```bash +git status --porcelain +git branch --show-current +``` + +Warn if on `main` or if unrelated dirty files exist. Do not commit unless the user asks. + +### 2. Apply the change + +- **Add/bump Expo-aware package(s):** `npx expo install …` +- **Align tree only:** `npx expo install --fix` when doctor / install-check reports drift +- **Pure-JS:** `npm install ` then re-check + +### 3. Validate (blocking for this command) + +```bash +npx expo install --check +npm run doctor +``` + +If doctor reports SDK patch drift, `npx expo install --fix`, then re-run **both** +`npx expo install --check` and `npm run doctor`. Do not proceed until both are +clean. + +### 4. Native regen (only if needed) + +If a native module or `app.config.ts` / plugin changed: + +```bash +npm run prebuild +``` + +Do not commit generated `android/`. + +### 5. App gates + +```bash +npm run format:check +npm run lint +npm run typecheck +npm test -- --ci +``` + +Report pass/fail. If gates fail, fix or surface clearly — do not claim success. + +### 6. Report + +Summarize: packages touched, whether prebuild ran, doctor/install-check outcome, what to re-test on Android (especially if native). + +Hand off to `/create-pr` (or `/start-issue` if there is not yet a branch) when the user wants a PR. + +## Related + +- [docs/ci.md](../../docs/ci.md) +- [docs/guides/dependabot-process.md](../../docs/guides/dependabot-process.md) +- [`.cursor/rules/dependabot-workflow.mdc`](../../.cursor/rules/dependabot-workflow.mdc) +- `/handle-dependabot` for Dependabot PR processing diff --git a/.claude/commands/generate-pr-description.md b/.claude/commands/generate-pr-description.md new file mode 100644 index 00000000..266595d0 --- /dev/null +++ b/.claude/commands/generate-pr-description.md @@ -0,0 +1,138 @@ +# Generate PR Description + +## Overview + +Generates a formatted PR description using [`.cursor/templates/pr-template.md`](../../.cursor/templates/pr-template.md) for copy-paste into GitHub. Supported in **Claude Code** and **Cursor** (`/generate-pr-description`). + +**No GitHub CLI required** for this command — text output only. + +**Always wrap the filled template in a single outer code block** for one-click copy. + +**Always include a suggested PR title** — the user should never need a separate follow-up for title wording. + +## How it works + +1. Type `/generate-pr-description` +2. Receive **suggested PR title** (prominent, copy-ready) + full description in one code block +3. Paste title into GitHub PR title field; paste body into description +4. Fill remaining placeholders (screenshots, reviewer notes) + +## PR title (mandatory output) + +Every run **must** output a suggested title before the description block. Do not ask the user to request a title separately. + +### Format + +`[#NNN]: Title text` — present tense, sentence case for title text after the colon. + +| Source | Title ID | Example | +|--------|----------|---------| +| GitHub issue in branch (`…/173-…` or `38-…`) | `#173` | `[#173]: Adopt Phase 1 agent/process docs` | + +**Tracker:** Project 4 Fluent Mobile Board ([docs/issue-tracking.md](../../docs/issue-tracking.md)). + +### Title synthesis (apply in order) + +1. **Detect issue number** from branch name: + - Author/type path: `mrace/chore/173-phase1-…` → `#173` + - Leading digits + hyphen: `38-cloud-sync-status-icon` → `#38` +2. **Fetch issue title** when ID found: + - `gh issue view N --repo eten-tech-foundation/fluent-mobile --json title` +3. **Normalize issue title** for PR title text: + - Strip prefixes like `[Mobile App]` + - Sentence case (first word capitalized; proper nouns unchanged) + - Keep concise (~60 chars after colon); trim filler ("Implement", "Add support for") only if redundant with issue scope +4. **Every change needs a GitHub issue number.** If none can be identified from + the branch or user message: **STOP** and request an issue number. Do not + generate a PR title (no “No ticket” / descriptive-prefix fallback). +5. **Never** output a generic title like "Update files" or only the issue number with no description + +### Title examples (this repo) + +| Branch | Issue title | Suggested PR title | +|--------|-------------|-------------------| +| `mrace/chore/173-phase1-agent-process-docs` | Adopt Phase 1… | `[#173]: Adopt Phase 1 agent/process docs` | +| `38-cloud-sync-status-icon` | Build Cloud Sync Status Icon | `[#38]: Build cloud sync status icon` | +| `45-my-work-tab` | Build My Work tab | `[#45]: Build My Work tab` | + +## Generation steps + +```bash +# Context helpers (read-only) +git branch --show-current +git log origin/main..HEAD --oneline +git diff origin/main...HEAD --stat +gh issue view --repo eten-tech-foundation/fluent-mobile --json title,body # when branch has numeric ID +``` + +1. Load template from `.cursor/templates/pr-template.md` +2. **Synthesize PR title** (mandatory — see above) +3. Detect branch type: `feature/`, `fix/`, `chore/` → suggest change type checkbox +4. Fetch GitHub issue when issue number present (`Refs #NNN` in Details) +5. Pre-fill Technical Changes and Testing from diff + [docs/AGENT_ONBOARDING.md](../../docs/AGENT_ONBOARDING.md) gates + +## Output format + +``` +📋 **PR Title (copy this)** + +[#38]: Build cloud sync status icon + +**Branch:** 38-cloud-sync-status-icon · **Suggested type:** ✨ New feature + +--- + +📋 **PR Description — Ready to Copy** + +[Entire filled template in ONE ``` code block — no nested ``` inside] +``` + +### Critical — no nested code blocks + +- **Never** put triple-backtick fences **inside** the PR description code block +- Use 4-space indentation for code samples inside the block +- Use inline backticks for paths: `src/services/sync.ts` + +**Length:** If filled template exceeds ~400 lines, condense to TLDR, Summary, Technical Changes, Testing, How to Test; tell user to expand on GitHub. + +## Smart suggestions by branch prefix + +| Prefix | Suggested type | +|--------|----------------| +| `feature/` | New feature | +| `fix/`, `bugfix/` | Bug fix | +| `hotfix/` | Bug fix + check Breaking Changes | +| `chore/`, `refactor/` | Maintenance/refactor | +| `docs/` | Documentation update | +| Numeric issue slug (`38-…`, `45-…`) | Usually New feature or Bug fix from issue body | + +## Ticket patterns + +- GitHub issues: `[#123]` / `#123` in PR title; `Refs #123` under Details (never closing keywords) + +## Quality checklist (pre-fill reminders) + +Tell reviewer these were run or should be run: + +- `npm run format:check` +- `npm run lint` +- `npm run typecheck` +- `npm test -- --ci` + +Use **npm** in all instructions (not pnpm/yarn). + +## Testing section defaults + +Fluent Mobile is **Android-only**. Generated PR descriptions must not include iOS testing steps, `npm run ios`, or iOS simulator/device checkboxes. + +**Prerequisites:** Node 24+, `npm install`, `.env` from `.env.example`, `npm start` + `npm run android` + +## Template source + +**Always** read the current [`.cursor/templates/pr-template.md`](../../.cursor/templates/pr-template.md) — do not duplicate template content in this command file. Filling this template is mandatory for agent-opened PRs ([delivery.mdc](../../.cursor/rules/delivery.mdc)), including when generating a body for manual paste. + +## Usage + +`/generate-pr-description` — optional ticket ID in the same message. + +Pairs with `/create-pr-branch`, `/create-pr`, and `/start-issue`. diff --git a/.claude/commands/handle-dependabot.md b/.claude/commands/handle-dependabot.md new file mode 100644 index 00000000..a7149b15 --- /dev/null +++ b/.claude/commands/handle-dependabot.md @@ -0,0 +1,66 @@ +# Handle Dependabot PRs + +## Overview + +Process the open Dependabot queue for **fluent-mobile** end-to-end: triage, rebase, validate (including **Expo Doctor**), merge safe PRs one at a time, rebase the rest. + +Rule: [`.cursor/rules/dependabot-workflow.mdc`](../../.cursor/rules/dependabot-workflow.mdc) +Guide: [`docs/guides/dependabot-process.md`](../../docs/guides/dependabot-process.md) + +## Autonomous mode (default) + +Run the full queue without per-PR confirmation. Stop only on blockers (failed CI, conflicts after rebase, risky RN/react/navigation/native bumps). + +**Do not merge** GitHub Actions / workflow-only PRs unless the user explicitly includes them. + +**Never push agent work to `main`** — infra fixes (expo doctor CI, etc.) need a ticketed feature branch + PR ([delivery.mdc](../../.cursor/rules/delivery.mdc)). This command only squash-merges **existing Dependabot PRs**. + +## Queue loop + +Approved Dependabot authors only: `app/dependabot` and `dependabot[bot]`. Target +branch must be `main` (`baseRefName` == `"main"`). Drop any other author or base. + +1. List open bots targeting `main`: `gh pr list --search "author:app/dependabot state:open base:main" --json number,title,author,baseRefName,mergeable,mergeStateStatus,statusCheckRollup,files` + Then keep only PRs whose author is `app/dependabot` or `dependabot[bot]` **and** + whose `baseRefName` is `main`. +2. Triage: safe / risky / skip / RN upgrade / workflow-only +3. `@dependabot rebase` on conflicting, stale, or post-merge bots (skip bots with fresh `IN_PROGRESS` CI) +4. Pick **oldest safe PR** only when **Lint & Format**, **Unit Tests**, and + **Quality Gates** (TypeScript, Expo Doctor, `expo install --check`) are all + `SUCCESS`. +5. Before approve: `format:check`, `lint`, `typecheck`, `npm test -- --ci`, Expo + Doctor when applicable, and those required build checks must be green. Then + squash-merge **one** PR. +6. Wait for `main` CI green; rebase all remaining bots in parallel +7. Log batch in `docs/guides/dependabot-resolution-log.md` + +## Validation (after each rebase / before merge) + +```bash +npm ci +npm run doctor # Expo SDK alignment — also in Test Check CI +npm run format:check +npm run lint +npm run typecheck +npm test -- --ci +``` + +If `npm run doctor` fails: `npx expo install --check` → `npx expo install --fix` on the PR branch when SDK patch drift; do not merge until clean. + +Risky PRs (`react`, `react-native`, `@react-navigation/*`, native modules): add Android smoke test (`npm run android`). + +## Skip without merge + +- Expo Doctor / any required check failed +- `react` / `react-native` / navigation / `@op-engineering/op-sqlite` major groups (unless user approves) +- Workflow-only PRs (unless user includes them) +- `mergeable: CONFLICTING` after one rebase retry + +## Useful commands + +```bash +gh pr comment --body "@dependabot rebase" +gh pr review --approve --body "CI green. Safe bump per dependabot process." +gh pr merge --squash --delete-branch +gh run list --branch main --limit 3 --json workflowName,conclusion,status +``` diff --git a/.claude/commands/onboard.md b/.claude/commands/onboard.md new file mode 100644 index 00000000..41486144 --- /dev/null +++ b/.claude/commands/onboard.md @@ -0,0 +1,81 @@ +# /onboard — fluent-mobile developer onboarding + +You are guiding a developer through setting up (or repairing) their local +**fluent-mobile** environment. Be concise. Do **not** auto-install or mutate the +machine without the developer's go-ahead. + +**Android-only permanently** — never suggest iOS, CocoaPods, or `expo run:ios`. + +## Before Step 1 — Expo MCP auth + +Authenticate Expo MCP **before Step 1** and before any Expo-related command, +including `npm run prebuild`, `npm run doctor`, `npx expo`, and EAS: + +``` +CallMcpTool server=user-expo-mcp toolName=mcp_auth arguments={} +``` + +Do not run those commands until auth succeeds. Non-Expo setup (`nvm`, `npm install`, +`.env`) can stay in the existing Step 1 order after this gate. + +## Step 1 — Fresh-clone sequence + +Native `android/` is gitignored (CNG). Generate it before the first Android run. + +```bash +# Toolchain: Node >= 24.14.0, npm >= 10, Android Studio + SDK + JDK 17 +nvm use 24 # or otherwise match package.json engines +npm install +cp .env.example .env # set EXPO_PUBLIC_API_BASE_URL (see below) +npm run prebuild # expo prebuild --clean --platform android +``` + +Then run (two terminals, or use `npm run android` which builds the dev client): + +```bash +npm start +npm run android +``` + +Emulator API host: `.env.example` uses `http://10.0.2.2:9999` for host `localhost:9999`. +Hosted / Docker API paths: [docs/guides/local-development-workflow.md](../../docs/guides/local-development-workflow.md). + +## Step 2 — Check environment (read-only) + +Summarize PASS / WARN / FAIL for: + +| Check | Expectation | +| ----- | ----------- | +| Node | `>= 24.14.0` (`node -v` vs `package.json` `engines`) | +| npm | `>= 10` | +| `.env` | Present; `EXPO_PUBLIC_API_BASE_URL` non-empty | +| `android/` | Present after `npm run prebuild` (optional until first device run) | +| Expo MCP | Authenticated before SDK/EAS work ([expo-mcp.mdc](../../.cursor/rules/expo-mcp.mdc)) | + +Offer fixes only after the developer agrees. Safe non-destructive fixes: + +- Missing `.env` → `cp .env.example .env` +- Wrong Node → `nvm install 24 && nvm use 24` +- Missing `android/` → `npm run prebuild` + +## Step 3 — Sanity commands + +Confirm Expo MCP auth (same `mcp_auth` gate) before `npm run doctor` or other +Expo commands. + +```bash +npm run lint +npm run typecheck +npm test -- --ci +npm run doctor # after native-impacting / dependency changes +``` + +## Step 4 — Point at docs + +- [docs/AGENT_ONBOARDING.md](../../docs/AGENT_ONBOARDING.md) — architecture map +- [docs/issue-tracking.md](../../docs/issue-tracking.md) — Project 4 Fluent Mobile Board + PR linking +- [docs/ci.md](../../docs/ci.md) — CI inventory +- [README.md](../../README.md) — human setup + +Finish when env checks are green (or WARs are acknowledged), then remind them to +start Metro and launch the Android dev client. diff --git a/.claude/commands/open-pr.md b/.claude/commands/open-pr.md new file mode 100644 index 00000000..721932c0 --- /dev/null +++ b/.claude/commands/open-pr.md @@ -0,0 +1,8 @@ +# /open-pr — alias of /create-pr + +Do **not** duplicate instructions here. + +Read and **fully execute** `.claude/commands/create-pr.md`. + +Delivery-only (branch already has commits). For issue → branch → implement → +PR, use **`/start-issue`**. diff --git a/.claude/commands/start-issue.md b/.claude/commands/start-issue.md new file mode 100644 index 00000000..0faa1672 --- /dev/null +++ b/.claude/commands/start-issue.md @@ -0,0 +1,259 @@ +--- +description: Claim a GitHub issue, cut a branch from main, plan-gate when scope is large/ambiguous, implement to acceptance criteria, then chain into /create-pr through green CI. Epics auto-pick the highest-priority open child. +argument-hint: ' [optional: create-pr test scope, e.g. src/services/sync.test.ts]' +--- + +# /start-issue — issue → branch → implement → /create-pr + +You are running the **full delivery graph** for one GitHub issue in +**fluent-mobile**. Proceed autonomously except at the **plan gate** (step 5) when +scope is large/ambiguous — then write a short plan and **STOP until the user +approves**. + +`$ARGUMENTS` = required issue number (`324` or `#324`), optionally followed by a +`/create-pr` test scope (e.g. `324 src/services/sync.test.ts`). Parse the first +token as `ISSUE`; the remainder (if any) is passed through to `/create-pr` as +`$ARGUMENTS`. + +``` +issue → (epic? auto-pick child) → assign + In Progress (Dev) → branch from main + → plan gate? → implement → /create-pr → In PR Review → CI green +``` + +**Never merge.** Humans merge. Do **not** create git worktrees unless the user +explicitly asks. Do **not** re-implement `/create-pr` steps inline — read and +execute `.claude/commands/create-pr.md` at the end. + +## Critical repo facts + +- Issues live in `eten-tech-foundation/fluent-mobile`. Board: **Fluent Mobile + Board** (org Project **4**, view 9). See `docs/issue-tracking.md` and + `docs/guides/project-board.md`. +- PRs target **`main`**. Never push or open a PR with `main` as the head branch. +- Branch: `{author}/{type}/{issue-number}-{slug}` (`feature` / `fix` / `chore` + only). Naming rules live in `.claude/commands/create-pr-branch.md` — **read + and execute** that file; do not re-specify slug/type logic here. +- PR body uses **`Refs #NNN`** (never `Closes` / `Fixes` / `Resolves`). Issues + stay open for QA. +- Delivery gate after commits: `/create-pr` (CI gates → code-reviewer → push → + draft PR → board **In PR Review** → watch CI). `/open-pr` is an alias. + +## Dynamic context (already gathered for you) + +- Current branch: !`git rev-parse --abbrev-ref HEAD` +- Working tree: !`git status -sb` +- gh auth: !`gh auth status 2>&1 | head -3 || echo "gh not authenticated"` + +## Flow — execute these steps in order + +### 1. Parse arguments (hard gate) + +- Extract `ISSUE` from the first token of `$ARGUMENTS` (strip a leading `#`). +- If missing or not a positive integer: **STOP** and tell the user to run + `/start-issue ` (e.g. `/start-issue 324`). +- Optional remainder → `CREATE_PR_ARGS` (pass to `/create-pr` later; may be empty). + +### 2. Preflight hard gates + +Run before any mutate: + +```bash +gh auth status +git status --porcelain +gh issue view "$ISSUE" --repo eten-tech-foundation/fluent-mobile --json number,title,state,labels,body,assignees +``` + +**STOP** if: + +- `gh` is not authenticated +- Working tree is **dirty** (non-empty `git status --porcelain`) — tell the user + to stash/commit/discard; do not create a worktree +- Issue `state` is `CLOSED` + +### 3. Epic / children — auto-pick + +Fetch sub-issues (GraphQL) and labels. + +Treat as an epic when the issue has the `epic` label **or** has one or more +**open** children. + +When it is an epic: + +1. List open children. +2. **Auto-pick** the first by priority: label `priority-p0` / `P0` → + `priority-p1` / `P1` → `priority-p2` / `P2` → then oldest `createdAt` among + remaining open children. +3. Announce: parent epic `#P`, chosen child `#C`, title, and why (priority / age). +4. Set `ISSUE` / `CHOSEN` to the child number; remember `PARENT_EPIC` for PR prose. +5. If there are **no** open children but the issue has `epic`: **STOP** and say + the epic has nothing open to pick — do not invent work. + +When it is not an epic: `CHOSEN=$ISSUE`, `PARENT_EPIC` empty (unless a parent +link exists — mention in prose if known). + +Re-fetch the chosen issue body/labels/AC after a pick (work from the child, not +the epic). + +### 4. Kickoff — assign, board, branch + +#### 4a. Assign + +```bash +gh issue edit "$CHOSEN" --repo eten-tech-foundation/fluent-mobile --add-assignee @me +``` + +Best-effort: warn and continue on failure. + +#### 4b. Project 4 — add if missing, then **In Progress (Dev)** + +Follow [docs/guides/project-board.md](../../docs/guides/project-board.md): + +- **STOP** if current Status is Product-owned (`In Progress (Product)`, + `Product Ready`, `Sprint Shaping`) unless the user **explicitly named** this + issue number. +- Do not move neighboring cards. Do not sequence `Dev Ready` by relocating it. +- Allowed for this assigned ticket: add to Project 4 if missing; set + **`In Progress (Dev)`**. + +Best-effort warn-and-continue on API failure (except the Product-column STOP +above). Resolve Status option IDs **by name** when possible. Verified Project 4 +IDs (eten-tech-foundation project #4): + +- Project: `PVT_kwDOB8vK1s4A34c5` +- Status field: `PVTSSF_lADOB8vK1s4A34c5zgs8akY` +- `In Progress (Dev)`: `db53740f` + +```bash +ISSUE=$CHOSEN +CURSOR="" +ITEM_ID="" + +while :; do + if [ -z "$CURSOR" ]; then + PAGE=$(gh api graphql -f query=' + query($n: Int!) { + repository(owner: "eten-tech-foundation", name: "fluent-mobile") { + issue(number: $n) { + projectItems(first: 20) { + pageInfo { hasNextPage endCursor } + nodes { + id + project { number } + fieldValueByName(name: "Status") { + ... on ProjectV2ItemFieldSingleSelectValue { name } + } + } + } + } + } + }' -F n=$ISSUE) + else + PAGE=$(gh api graphql -f query=' + query($n: Int!, $c: String!) { + repository(owner: "eten-tech-foundation", name: "fluent-mobile") { + issue(number: $n) { + projectItems(first: 20, after: $c) { + pageInfo { hasNextPage endCursor } + nodes { + id + project { number } + fieldValueByName(name: "Status") { + ... on ProjectV2ItemFieldSingleSelectValue { name } + } + } + } + } + } + }' -F n=$ISSUE -f c="$CURSOR") + fi + + ITEM_ID=$(printf '%s' "$PAGE" | jq -r '[.data.repository.issue.projectItems.nodes[] | select(.project.number == 4) | .id] | first // empty') + [ -n "$ITEM_ID" ] && break + HAS=$(printf '%s' "$PAGE" | jq -r '.data.repository.issue.projectItems.pageInfo.hasNextPage') + CURSOR=$(printf '%s' "$PAGE" | jq -r '.data.repository.issue.projectItems.pageInfo.endCursor // empty') + [ "$HAS" = "true" ] && [ -n "$CURSOR" ] || break +done + +# If missing from Project 4: gh project item-add 4 --owner eten-tech-foundation \ +# --url https://github.com/eten-tech-foundation/fluent-mobile/issues/$ISSUE +# then re-query ITEM_ID (same paginated GraphQL + jq scalar `.id`). + +# If Status is In Progress (Product) | Product Ready | Sprint Shaping → STOP. +# Else set Status to In Progress (Dev) (option db53740f). +``` + +#### 4c. Branch from `main` + +If a local or `origin` branch matching `*/${CHOSEN}-*` already exists: **resume** +it (`git switch` / track remote). Do not recreate. + +If an **open PR** already exists for that head: skip implement (step 6); +announce and hand off to `.claude/commands/create-pr.md`. + +Otherwise **read and execute** `.claude/commands/create-pr-branch.md` for +`$CHOSEN`: + +- Always base on `origin/main` (`git fetch origin main`). Do not branch off a + random feature branch. +- Skip the “Create this branch?” prompt (this command is the explicit ask). +- Still print the final branch name before `git checkout -b`. + +### 5. Plan gate (large / ambiguous → STOP for approval) + +**Require a short plan and wait for explicit user approval** before coding when +**any** of: + +- Chosen issue still has the `epic` label, or body describes multiple phases / + stacked PRs +- Acceptance criteria imply measurement + conditional config +- Clear multi-area / architecture / native-module / dependency-bump scope +- Requirements are underspecified (you cannot name the primary files to touch) + +**Skip the plan** (implement immediately) when: single clear bugfix, small +docs/chore, or AC that maps to an obvious small file set. + +When the gate fires: + +1. Write a short plan (objective, files/areas, AC mapping, risks, that + `/create-pr` will use `Refs #` and any parent-epic prose). +2. **STOP** and ask the user to approve (or adjust). Do not start implementation. +3. After they approve, continue from step 6 — do not re-ask. + +### 6. Implement + +- Implement to the chosen issue’s acceptance criteria ([AGENTS.md](../../AGENTS.md)). +- Follow `CLAUDE.md`, `.cursor/rules/`, and matching `.claude/skills/*/SKILL.md` + when the work matches a skill. +- Layer boundaries: no `fetch` in screens; no SQL in UI; reads via `queries.ts` + ([architecture.mdc](../../.cursor/rules/architecture.mdc)). +- New UI uses `theme` from `src/theme/`. Android-only — no iOS config/CI/docs. +- Keep the working tree ready for `/create-pr` (code-reviewer runs on the + working diff inside `/create-pr`). + +### 7. Chain into `/create-pr` + +Read and **fully execute** `.claude/commands/create-pr.md` with `CREATE_PR_ARGS` +as that command’s `$ARGUMENTS`. + +Ensure the PR body: + +- `Refs #` on its own line under Details +- If `PARENT_EPIC` is set: prose `Part of #` (do **not** use + closing keywords on the epic) + +`/create-pr` owns: local CI gates, code-reviewer, push, `gh pr create --draft +--base main`, board → **In PR Review**, CI watch to green, never merge. + +### 8. Report + +Print: + +- Chosen issue URL (+ parent epic if any, and auto-pick rationale) +- Branch name +- Plan: skipped | approved (one line) +- PR URL +- Local checks + code-reviewer verdict + **CI = green** +- Board: In Progress (Dev) then In PR Review, or WARN if a move failed + +Do **not** claim done while CI is red/pending or review findings remain unfixed +without an explicit per-item human waiver in the PR body. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..64c03448 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|Write|MultiEdit", + "hooks": [ + { + "type": "command", + "command": "node \"$CLAUDE_PROJECT_DIR\"/scripts/architecture-guard.mjs" + } + ] + } + ] + } +} diff --git a/.claude/skills/sync-claude-primitives/SKILL.md b/.claude/skills/sync-claude-primitives/SKILL.md new file mode 100644 index 00000000..0c0a25af --- /dev/null +++ b/.claude/skills/sync-claude-primitives/SKILL.md @@ -0,0 +1,168 @@ +--- +name: sync-claude-primitives +description: >- + Audit and update this repo's Claude/agent configuration primitives so they stay in + sync with the actual state of the codebase. Use this whenever the user wants to + refresh, audit, update, or "catch up" their Claude config, skills, agents, + commands, CLAUDE.md, AGENTS.md, docs, or .cursor bridge after architecture / CI / + config / dependency churn — even if they only say "our agent setup is probably + stale", "make sure the agents are up to date", or "audit our claude primitives". + Detects drift by diffing recently merged PRs + Project 4 + the codebase against + what the primitives claim, has an independent subagent verify every finding, + presents each proposed change for per-change approval, then applies approved + edits and opens a PR. +--- + +# Sync Claude Primitives + +Keep fluent-mobile's agent configuration honest. Architecture, CI, tooling, and +dependencies drift; the primitives that describe them — skills, agents, commands, +`CLAUDE.md`, `AGENTS.md`, docs — rot until an agent reads a stale instruction. +This skill finds that rot, proves it's real, and fixes it with the user in +control of every edit. + +## Operating principles + +- **Read-only until the user approves.** Phases 1–4 never modify a file. The only + writes happen in phase 5, applying edits the user explicitly selected. +- **Every finding is evidence-based.** A claim is "stale" only when you can cite + the primitive's `file:line` AND the source-of-truth `file:line` that contradicts + it. No vibes. +- **Verify before you show.** An independent subagent re-checks each finding + against the live codebase before it reaches the user. +- **Degrade gracefully.** Missing `gh`, no project board, first run, shallow + clone — all expected. Fall back, note what you skipped, keep going. + +## Target model — two classes of file + +### EDIT targets — primitives that get audited and updated + +These describe the app. When they disagree with reality, *they* are wrong. + +- `.claude/skills/*/SKILL.md` +- `.claude/agents/*.md` +- `.claude/commands/*.md` +- Root `CLAUDE.md` and `AGENTS.md` +- `.github/copilot-instructions.md` — audit against the same agent contracts as + `CLAUDE.md` / `AGENTS.md` so Copilot guidance stays aligned +- `docs/*.md` and `docs/guides/*.md` +- Nested `src/**/AGENTS.md` +- Convention / onboarding scripts: `scripts/*architecture*`, `scripts/*claude*`, + `.cursor/hooks/*` +- **`.cursor/`** + - `.cursor/commands/*.md` — **parity shims** only (must point at + `.claude/commands/*`; no duplicated logic) + - `.cursor/rules/*.mdc` — always-on contract; audit for drift vs + `package.json` / workflows / docs + - `.cursor/templates/pr-template.md` and `.github/PULL_REQUEST_TEMPLATE.md` + (keep these two identical) + +### SOURCE-OF-TRUTH inputs — read-only "reality" side, NEVER edited + +- `package.json` (scripts, deps, engines), `app.config.ts`, `eas.json` +- `tsconfig.json`, ESLint config, `jest.config.cjs`, `babel.config.js` +- `.github/workflows/*.yml`, `.github/dependabot.yml` +- `src/` layout (architecture claims) + +> Before relying on this list, glob the repo — if you find another file that +> clearly encodes agent state, treat it as an EDIT target and tell the user. + +## State file — `.claude/.config-sync-state.json` + +Committed so the team can see when the last sync ran. Shape: + +```json +{ + "lastSyncedSha": "", + "lastSyncedAt": "", + "lastRunBy": "", + "notes": "" +} +``` + +If the file is absent (first run), treat it as such (see phase 1). + +## The workflow + +Track these as five todos so you don't skip verification or approval gates. + +### Phase 1 — Detect the change window + +1. Read `.claude/.config-sync-state.json`. If present, the window is + `lastSyncedSha..HEAD`. If absent, fall back to roughly the last 20 merged + PRs / ~30 days — and say so. +2. Gather what changed, each step degrading gracefully: + - **Merged PRs:** `gh pr list --state merged --base main --limit 50 + --json number,title,mergedAt,labels,url` then filter to the window. + - **Project board:** org Project **4** (Fluent Mobile Board). If `gh project` + errors, skip and note that. + - **Raw diff:** `git diff --stat ..HEAD` and `git log --oneline ..HEAD`. +3. Summarize the window for the user in 3–6 lines before auditing. + +### Phase 2 — Audit (read-only) + +Walk every EDIT target against the SOURCE-OF-TRUTH inputs and the phase-1 diff. +Hunt **three** finding types deliberately (run contradiction and gap as separate +passes): + +**Type A — Contradiction** (a primitive states something now false): +- Version / dependency claims vs `package.json`, `app.config.ts` +- Command / script names vs `package.json` scripts and `.github/workflows` +- File paths that moved or were deleted +- Conventions that changed +- Removed/renamed primitives leaving dangling `.cursor` shims or bridge-rule lists + +**Type B — Gap** (a primitive *should* teach something new but is silent): +For each merged PR / done board item, ask: "did this introduce a convention, +helper, script, env var, or pattern that a future agent would need — and does +any primitive actually say so?" + +**Type C — Opportunity** (a net-new primitive is worth creating): +- A repeated multi-step flow that could be a command/skill +- Bar: used repeatedly, encodes non-obvious repo-specific knowledge, prevents a + real mistake. Reject anything a base model handles fine. + +Emit structured findings tagged by type (contradiction / gap / opportunity) +with `target_file`, evidence, and a proposed edit. + +Dispatch parallel explore subagents by target group. Keep raw file dumps out of +the main context. + +### Phase 3 — Independent verification + +Hand the full findings list to a fresh review subagent (`.claude/agents/code-reviewer.md` +or `general-purpose`) whose ONLY job is to refute each finding against the live +codebase. Drop anything it can't confirm. + +### Phase 4 — Present and approve (per-change) + +Present numbered `[fix]` (contradiction) and `[add]` (gap) diffs. Then a +separate **Opportunities** section. Ask the user to pick which numbered changes +to apply. Default to applying nothing they didn't select. Wait for their +selection. + +### Phase 5 — Execute and open a PR + +1. Create a ticketed branch via `/create-pr-branch` (or `/start-issue` if the + user named an issue). Do not invent a branch off `main` without a ticket. +2. Launch an **execution subagent** given ONLY the approved findings. It applies + each edit precisely and reports back. +3. Sanity-check **before** writing the state file: `git diff` matches the + approved set. For **every** change (not only scripts/config), run: + `npm run format:check`, `npm run lint`, `npm run typecheck`, + `npm test -- --ci`. +4. Only after validation passes, update `.claude/.config-sync-state.json` with + timestamp, `lastRunBy` (`git config user.name`), a one-line note, and + `lastSyncedSha` set to the **source HEAD** whose approved changes passed + validation. Treat this state-file edit as an explicit workflow-generated + change **excluded** from the approved-findings comparison. Do **not** write + the state file if validation failed — failed runs must not mark the tree as + synced. +5. Chain into `/create-pr` (`.claude/commands/create-pr.md`). Include + `Refs #` — never closing keywords. + +## Notes + +- PRs target **`main`**. Package manager is **npm**. Android-only. +- If the change window is empty (already in sync), say so and stop — don't + manufacture edits to look busy. diff --git a/.claude/skills/write-test/SKILL.md b/.claude/skills/write-test/SKILL.md new file mode 100644 index 00000000..e2d1c793 --- /dev/null +++ b/.claude/skills/write-test/SKILL.md @@ -0,0 +1,102 @@ +--- +name: write-test +description: >- + Write a Jest unit test for a file or symbol in fluent-mobile, matching the + repo's real test conventions (colocated src/**/*.test.ts(x), jest-expo, + global Expo mocks, QueryClientTestWrapper). Use this skill WHENEVER the user + asks to write, add, scaffold, or generate a test, unit test, or Jest test for + any source file, hook, component, service, or util — even if they just say + "test this file", "cover this with tests", or paste a path under src/. Also + use when a PR review asks for missing tests. +--- + +# write-test + +Generate a Jest test for a fluent-mobile source file that looks like it was +written by someone who already knows this repo's conventions. The win is mock +setup: each target type has an established pattern; copy the matching canonical +example instead of inventing boilerplate. + +## Environment facts (don't re-derive these) + +- Jest 29, preset `jest-expo`, `@testing-library/react-native`. +- Config: `jest.config.cjs`. Setup: `jest.env.cjs`, `jest.setup.expo-fs.cjs`. +- **Global module mocks** (mapped in `jest.config.cjs` — do NOT re-mock these + per file): `expo-secure-store`, `expo-file-system`, `expo-audio`. When a test + mutates shared mock state, call `resetSecureStoreMock()` / + `resetFileSystemMock()` / `resetAudioMock()` in `beforeEach` (see + `src/test/mocks/expoMocks.test.ts`). +- **Tests are colocated** next to source: `src/foo/bar.ts` → + `src/foo/bar.test.ts` (or `.tsx` for components). The only root-level app + test is `__tests__/App.test.tsx` (heavy mocks for `op-sqlite`, navigation, + sync, SVG, icons — follow it when testing root flow). +- There is no MSW. Mocking is per-file via `jest.mock(...)` plus + `jest.mocked(...)` typed handles. +- For **service endpoint unit tests**, mock global `fetch` (see + `src/services/httpClient.test.ts`: `jest.spyOn(globalThis, 'fetch')`) and + **restore** the mock after each test (`jest.restoreAllMocks()` in `afterEach`). + Follow neighboring `src/services/*.test.ts` patterns. +- Live API test `src/services/fluent-api.test.ts` is **skipped by default**; + do not copy that pattern for unit tests. Opt-in is + `RUN_LIVE_API_TESTS=1 npm test -- fluent-api.test.ts`. + +See `.cursor/rules/testing.mdc`. + +## Workflow + +### 1. Identify the target and its type + +Read the source file. Classify it into ONE of the types below. When a file is +mixed, prefer the type that covers the symbol the user asked about; if unsure, +test the pure logic first. + +| Target type | What it looks like | Canonical example to copy | +| --- | --- | --- | +| Pure logic / util | exported fns, no hooks, no I/O | `src/utils/logger.test.ts`, `src/utils/validateEmail.test.ts` | +| Hook | `use*` with state/effects | `src/hooks/useLogin.test.tsx` | +| Component | screen / layout / ui rendering | `src/components/layout/PageHeader.test.tsx` | +| Service | `FluentAPI` / sync / storage | `src/services/httpClient.test.ts`, `src/services/authSession.test.ts` | +| DB | repository / queries / migrations | `src/db/migrations.test.ts`, `src/db/recordingsRepository.test.ts` | + +If you are unsure which example fits, OPEN the candidate example(s) and compare +their shape to the target. The examples are the source of truth. + +### 2. Place the test file + +Colocate: same directory as the source, `Name.test.ts` or `Name.test.tsx`. +Do **not** invent a centralized `__tests__/unit_tests/` tree. + +### 3. Scaffold from the matching example + +- Mock ONLY the target's direct dependencies. Skip anything already mapped in + `jest.config.cjs`. +- Use `jest.mocked(importedFn)` for typed mock handles. Reset with + `jest.clearAllMocks()` in `beforeEach`. +- For hooks: `renderHook` + `act` / `waitFor` from + `@testing-library/react-native`. Wrap with `QueryClientTestWrapper` from + `src/test/queryClientWrapper` when the hook uses react-query (see + `useLogin.test.tsx`). +- For navigation: mock `@react-navigation/native` (`useNavigation`, `useRoute`) + the way neighboring screen tests do. +- Mock `op-sqlite` / `getDatabase` the way existing `src/db/*.test.ts` files do. + Never require a real device for unit tests. + +### 4. Verify + +Run the single file: + +```bash +npm test -- src/path/to/Name.test.ts +``` + +Fix failures before reporting done. Also keep `npm run typecheck` in mind — +new tests should typecheck. + +## Hard rules + +- Never re-mock `expo-secure-store`, `expo-file-system`, or `expo-audio`. +- Never put the test under a new centralized `__tests__/unit_tests/` tree — + colocate next to source. +- Do not add tests that only assert trivial constants. +- `no-console` is off in test files; `no-focused-tests` is error. +- Use **npm** (`npm test -- --ci` for the full suite). diff --git a/.cursor/commands/create-pr-branch.md b/.cursor/commands/create-pr-branch.md index baefd08c..53868b68 100644 --- a/.cursor/commands/create-pr-branch.md +++ b/.cursor/commands/create-pr-branch.md @@ -1,158 +1,12 @@ -# Create PR Branch +# Create PR Branch (Cursor) -## Overview +Cursor entry point — **do not duplicate instructions here.** -Creates a new git branch using the team naming convention: +Read and follow the canonical command: -```text -{author}/{type}/{issue-number}-{slug-from-title} -``` +**`.claude/commands/create-pr-branch.md`** -**Example:** `mrace/chore/173-phase1-agent-process-docs` +Branch-only: `{author}/{type}/{issue-number}-{slug}` from a GitHub issue. +For the full issue → implement → PR loop, use **`/start-issue`**. -Run with `/create-pr-branch` in Cursor chat. - -**Tracker:** Project 4 Fluent Mobile Board — see [docs/issue-tracking.md](../../docs/issue-tracking.md). - -## Branch name format - -| Segment | Source | Example | -| ------- | ------ | ------- | -| `author` | Local part of `git config user.email` | `mrace` | -| `type` | `feature`, `fix`, or `chore` only | `chore` | -| `issue-number` | GitHub issue number | `173` | -| `slug` | Meaningful words from issue title | `phase1-agent-process-docs` | - -**Full pattern:** `{author}/{type}/{issue-number}-{slug}` - -## When you run this command - -Execute these steps in order. Do not skip validation or user confirmation before creating the branch. - -### 1. Resolve issue number - -- If the user message or recent conversation already includes a GitHub issue (e.g. `#173` or `173`), use it. -- Otherwise, **ask once**: "What's the GitHub issue number? (e.g. 173)" -- Normalize to digits only for the branch segment. - -### 2. Fetch GitHub issue - -```bash -gh issue view NNN --repo eten-tech-foundation/fluent-mobile --json number,title,labels,body -``` - -If the issue is not found, report the error and stop. - -### 3. Resolve branch author prefix - -Use the **local part** of `git config user.email` (everything before `@`): - -```bash -git config user.email -# mrace@gloo.us → mrace -``` - -Rules: lowercase only; strip `+tag` suffixes if present (e.g. `mrace+wip` → `mrace`); allow `a-z0-9-` only in the final prefix. - -If `user.email` is unset, ask the user to configure git (`git config user.email you@gloo.us`) or provide their email prefix once, then stop. - -### 4. Determine branch type (`feature` | `fix` | `chore`) - -Use **only** these three types. - -**Infer from the GitHub issue (in order):** - -| Signal | Type | -| ------ | ---- | -| Labels contain `bug`, `bugfix`, `hotfix`, `defect` | `fix` | -| Labels contain `chore`, `maintenance`, `tech-debt`, `dependencies`, `documentation` | `chore` | -| Labels contain `feature`, `story`, `enhancement` | `feature` | -| Title starts with or contains strong fix signals: `fix`, `fixes`, `fixed`, `bug`, `hotfix`, `resolve` | `fix` | -| Title signals: `add`, `implement`, `introduce`, `support`, `enable` | `feature` | -| Title signals: `replace`, `migrate`, `upgrade`, `bump`, `refactor`, `remove`, `cleanup`, `chore`, `update`, `adopt`, `docs` (non-user-facing) | `chore` | - -If still ambiguous, use **AskQuestion** with: feature / fix / chore (default from best guess). - -### 5. Build title slug - -From the GitHub issue **title** (not description): - -1. Remove prefixes like `[Mobile App]`, `(Mobile)`, etc. -2. Lowercase the title. -3. Split into words (alphanumeric tokens); split camelCase into words when obvious. -4. **Drop filler words:** `a`, `an`, `the`, `and`, `or`, `but`, `for`, `with`, `from`, `into`, `to`, `of`, `in`, `on`, `at`, `by`, `as`, `is`, `are`, `was`, `were`, `be`, `been`, `being`, `this`, `that`, `these`, `those`, `it`, `its`, `via`, `using`, `use`, `please` -5. Keep **3–6 meaningful words**; cap slug length (~45 chars after issue number). -6. Join with single hyphens; lowercase `a-z0-9-` only. - -**Final branch name:** - -```text -{author}/{type}/{issue-number}-{slug} -``` - -### 6. Pre-flight checks - -```bash -git status --porcelain -git branch --show-current -git branch --list '{proposed-branch-name}' -``` - -- If the proposed branch **already exists**, stop and tell the user. -- If there are **uncommitted changes**, warn and ask whether to continue (do not stash unless the user asks). -- If not on `main`, note the current branch and confirm they want to branch from here. - -### 7. Confirm with user - -Show a short summary before creating: - -```text -Branch: mrace/chore/173-phase1-agent-process-docs -Issue: #173 — Adopt Phase 1 agent/process docs -Base: main @ abc1234 -``` - -Ask: **"Create this branch?"** (or create immediately if the user was explicit). - -### 8. Create branch - -```bash -git checkout -b "{author}/{type}/{issue-number}-{slug}" -``` - -If branching from `main` explicitly and behind: - -```bash -git fetch origin main && git checkout main && git pull --ff-only origin main -git checkout -b "..." -``` - -Report success and remind them to use `/create-pr` when ready. - -## Integration - -- Pairs with `/create-pr` and `/generate-pr-description`. -- Issue number in the branch name enables automatic detection in `/create-pr`. -- PR title format: `[#NNN]: Description` (see [docs/issue-tracking.md](../../docs/issue-tracking.md)). - -## Author setup (one-time per machine) - -```bash -git config --global user.email "you@gloo.us" -# → branch prefix: you -``` - -## Usage - -| User says | Action | -| --------- | ------ | -| `/create-pr-branch` | Ask for issue number, then run workflow | -| `/create-pr-branch 173` | Fetch issue, propose branch, create after confirm | - -## Rules - -- **Never** use `feature/` as the first path segment without an author prefix. -- **Never** use branch types outside `feature`, `fix`, `chore`. -- **Never** force-push or delete branches unless the user explicitly asks. -- **Always** show the final branch name before `git checkout -b`. -- Prefer `gh issue view` for the title; do not guess the slug from memory if the issue can be fetched. +Usage: `/create-pr-branch 173` diff --git a/.cursor/commands/create-pr.md b/.cursor/commands/create-pr.md index abb9d81b..52c446a6 100644 --- a/.cursor/commands/create-pr.md +++ b/.cursor/commands/create-pr.md @@ -1,144 +1,14 @@ -# Create Pull Request +# Create Pull Request (Cursor) -## Overview +Cursor entry point — **do not duplicate instructions here.** -Automated PR creation for **fluent-mobile** when you run `/create-pr`. Creates a GitHub PR with title, description from the repo template, and draft settings. Integrates **GitHub Issues** and branch analysis. +Read and follow the canonical command: -Complements team PR conventions (title `[#NNN]: Description`, verification gates). Does not merge PRs. +**`.claude/commands/create-pr.md`** -**Tracker:** Project 4 Fluent Mobile Board — [docs/issue-tracking.md](../../docs/issue-tracking.md). +Opens a **draft** PR against `main` with the Fluent template, `Refs #NNN`, +code-reviewer, Project 4 **In PR Review**, and CI watch to green. Never merge. -## What happens +`/open-pr` is an alias of this command. -1. **Validate prerequisites** — branch pushed (or push with user approval), quality gates, clean intent for draft PR -2. **Fetch GitHub issue** — from branch name via `gh issue view` when possible -3. **Analyze branch** — commits, files changed, change type -4. **Generate title** — `[#NNN]: Title` from issue + branch -5. **Fill template** — [`.cursor/templates/pr-template.md`](../templates/pr-template.md) -6. **Assignee** — always assign the PR author (`--assignee @me`) -7. **Reviewers / labels** — CODEOWNERS auto-requests on `main`; omit manual reviewers unless asked -8. **Create draft PR** — via `gh pr create` - -## Pre-flight (fluent-mobile) - -Run and report results: - -```bash -npm run format:check -npm run lint -npx tsc --noEmit -npm test -- --ci -``` - -Also check: - -```bash -git status --porcelain -git branch --show-current -git log origin/main..HEAD --oneline -``` - -- Warn on uncommitted changes unless user wants them included -- Ensure branch is pushed: `git push -u origin HEAD` (feature branch only — **never** `git push origin main`; see [delivery.mdc](../rules/delivery.mdc)) - -See [docs/AGENT_ONBOARDING.md](../../docs/AGENT_ONBOARDING.md) for full command reference. - -## Title format - -**Always:** `[#NNN]: Title text` (or `#NNN: Title text`) - -**With GitHub issue:** - -- Branch `mrace/chore/173-phase1-agent-process-docs` + issue → `[#173]: Adopt Phase 1 agent/process docs` - -**Without issue (fallback):** - -- `fix/sync-retry-logic` → `Fix: Sync retry logic` -- Strip `[Mobile App]` or similar prefixes from issue titles - -## Template - -**Always** load [`.cursor/templates/pr-template.md`](../templates/pr-template.md) and pre-fill — this is mandatory for **every** agent-opened PR in this repo ([delivery.mdc](../rules/delivery.mdc)), not only when this slash command is used. Do not substitute a short Summary/Test plan body. - -Pre-fill: - -**From GitHub issue:** - -- TLDR, Details, `Refs #NNN` (never `Closes` / `Fixes` / `Resolves`) - -**From branch analysis:** - -- Technical changes (files, line counts) -- Type of change checkboxes -- How to verify (npm setup, Metro + `npm run android` when relevant) - -**Delivery guardrails** (root [`AGENTS.md`](../../AGENTS.md)): - -- Leave **Acceptance criteria**, **Scope**, and **Android device tested** checkboxes **unchecked** unless verified. -- Do **not** auto-check device QA for native / mic / camera / filesystem / permissions changes. -- Do **not** mark such PRs ready for review until a human records device results. -- Deferred AC requires an issue-level waiver and linked follow-up issues — not only a PR “known limitations” note. - -Keep output under ~400 lines; no nested fenced code blocks inside the PR body. - -## GitHub issue integration - -- Detect issue number from branch: `…/173-…` or leading `173-…` -- Fetch with `gh issue view NNN --repo eten-tech-foundation/fluent-mobile` -- Body must include `Refs #NNN` on its own line under Details (non-closing — issues stay open for QA) - -## GitHub (`gh`) - -Always create with the author assigned. Body must include `Refs #NNN` (never closing keywords). - -```bash -gh pr create --draft \ - --title "[#NNN]: Title" \ - --body-file /tmp/pr-body.md \ - --assignee @me -``` - -If the PR already exists without an assignee: - -```bash -gh pr edit --add-assignee @me -``` - -**Issue link (Development sidebar):** `Refs #NNN` in the body is the required non-closing reference (issues stay open for QA). GitHub’s Development “linked issues” UI is only auto-populated by closing keywords (`Closes` / `Fixes` / `Resolves`) or a **manual** sidebar link — there is no public API to create that link without also auto-closing on merge. Do **not** use closing keywords. After `gh pr create`, if the Development sidebar is empty, link issue `#NNN` once in the GitHub UI (or ask the user to). - -**Reviewers / labels:** CODEOWNERS handles review requests after the file is on `main`. Omit `--reviewer` unless the user asks. - -## Package manager - -Use **npm** only in PR text and test steps. Do not reference pnpm or yarn. - -## Framework notes - -- **Expo SDK 57** + **CNG** (RN **0.86**, React **19.2.3**), **Android-only** -- PR CI: lint, test, typecheck, `expo-doctor`, `expo install --check` (see [docs/ci.md](../../docs/ci.md)); native compile via EAS preview label -- EAS project ID: `b0919574-f268-4768-b3bd-7cfa5172bbab` - -## Branch analysis - -- Count files/lines; classify feature / fix / chore / docs -- Flag `package.json`, `package-lock.json`, `app.config.ts`, `plugins/`, `eas.json`, `src/db/schema.ts` as high-impact (`android/` is gitignored CNG output) -- Flag new env vars needing `.env.example` update - -## Usage - -Type `/create-pr` in Cursor chat. - -**After creation:** - -1. Confirm assignee is the author (`gh pr view --json assignees`) -2. Review auto-filled body (confirm AC / Scope / device-QA checkboxes match reality per [`AGENTS.md`](../../AGENTS.md)) -3. If the PR Development sidebar has no linked issue, link `#NNN` manually in the GitHub UI (non-closing — see GitHub section above) -4. Add screenshots if UI changed -5. Confirm CODEOWNERS / reviewers -6. Mark ready for review only when CI gates **and** delivery guardrails pass. Do not merge unless user explicitly asks. - -## Related commands - -- `/create-pr-branch` — create branch before work -- `/generate-pr-description` — body only, manual GitHub PR creation -- `/onboard`, `/dep-bump` — setup and dependency changes +Usage: `/create-pr` (optional targeted test path). diff --git a/.cursor/commands/dep-bump.md b/.cursor/commands/dep-bump.md index ab9b9df1..a1edd565 100644 --- a/.cursor/commands/dep-bump.md +++ b/.cursor/commands/dep-bump.md @@ -1,78 +1,10 @@ -# /dep-bump — change a dependency the Expo way +# Dep Bump (Cursor) -You are adding, upgrading, aligning, or removing a dependency in **fluent-mobile**. -Naive `npm install @latest` is often **wrong** here: this is an Expo SDK **57** -CNG app (Android-only, custom dev client — not Expo Go). Two health checks must stay -green after dependency work: +Cursor entry point — **do not duplicate instructions here.** -- `npx expo install --check` — Expo-managed packages at SDK-compatible versions -- `npm run doctor` (`expo-doctor`) — config / native compatibility +Read and follow the canonical command: -Proceed autonomously and report what you did; only stop at hard gates below. +**`.claude/commands/dep-bump.md`** -`$ARGUMENTS` = package(s) to add/bump. If empty, **align + validate** the current tree. - -## Critical facts - -- **Use `npx expo install `** for anything Expo knows about (resolves SDK 57–compatible versions). Use plain `npm install` only for pure-JS packages Expo has no opinion on — then still re-run the checks. -- **`npm run prebuild` is destructive** (`expo prebuild --clean --platform android`). Run it only when the change adds/removes a **native** module or config plugin. Pure-JS deps do not need it. **Never** pass `--platform ios` or omit the platform. -- Package manager is **npm** only (`package-lock.json`). Do not use yarn/pnpm. -- Agent-authored lockfile / doctor fixes still need a **ticketed PR** — never push to `main` ([delivery.mdc](../rules/delivery.mdc)). - -## Flow - -### 1. Inspect working tree - -```bash -git status --porcelain -git branch --show-current -``` - -Warn if on `main` or if unrelated dirty files exist. Do not commit unless the user asks. - -### 2. Apply the change - -- **Add/bump Expo-aware package(s):** `npx expo install …` -- **Align tree only:** `npx expo install --fix` when doctor / install-check reports drift -- **Pure-JS:** `npm install ` then re-check - -### 3. Validate (blocking for this command) - -```bash -npx expo install --check -npm run doctor -``` - -If doctor reports SDK patch drift, `npx expo install --fix` and re-run doctor. - -### 4. Native regen (only if needed) - -If a native module or `app.config.ts` / plugin changed: - -```bash -npm run prebuild -``` - -Do not commit generated `android/`. - -### 5. App gates - -```bash -npm run format:check -npm run lint -npm run typecheck -npm test -- --ci -``` - -Report pass/fail. If gates fail, fix or surface clearly — do not claim success. - -### 6. Report - -Summarize: packages touched, whether prebuild ran, doctor/install-check outcome, what to re-test on Android (especially if native). - -## Related - -- [docs/ci.md](../../docs/ci.md) -- [docs/guides/dependabot-process.md](../../docs/guides/dependabot-process.md) -- [`.cursor/rules/dependabot-workflow.mdc`](../rules/dependabot-workflow.mdc) -- `/handle-dependabot` for Dependabot PR processing +Expo-correct dependency add/bump/align (`npx expo install`, doctor, optional +prebuild). Hand off to `/create-pr` for a ticketed PR. diff --git a/.cursor/commands/generate-pr-description.md b/.cursor/commands/generate-pr-description.md index 46ae285f..52115399 100644 --- a/.cursor/commands/generate-pr-description.md +++ b/.cursor/commands/generate-pr-description.md @@ -1,138 +1,10 @@ -# Generate PR Description +# Generate PR Description (Cursor) -## Overview +Cursor entry point — **do not duplicate instructions here.** -Generates a formatted PR description using [`.cursor/templates/pr-template.md`](../templates/pr-template.md) for copy-paste into GitHub. Run `/generate-pr-description` in Cursor chat. +Read and follow the canonical command: -**No GitHub CLI required** for this command — text output only. +**`.claude/commands/generate-pr-description.md`** -**Always wrap the filled template in a single outer code block** for one-click copy. - -**Always include a suggested PR title** — the user should never need a separate follow-up for title wording. - -## How it works - -1. Type `/generate-pr-description` -2. Receive **suggested PR title** (prominent, copy-ready) + full description in one code block -3. Paste title into GitHub PR title field; paste body into description -4. Fill remaining placeholders (screenshots, reviewer notes) - -## PR title (mandatory output) - -Every run **must** output a suggested title before the description block. Do not ask the user to request a title separately. - -### Format - -`[#NNN]: Title text` — present tense, sentence case for title text after the colon. - -| Source | Title ID | Example | -|--------|----------|---------| -| GitHub issue in branch (`…/173-…` or `38-…`) | `#173` | `[#173]: Adopt Phase 1 agent/process docs` | -| No ticket | descriptive prefix | `Chore: Add Cursor AI rules` | - -**Tracker:** Project 4 Fluent Mobile Board ([docs/issue-tracking.md](../../docs/issue-tracking.md)). - -### Title synthesis (apply in order) - -1. **Detect issue number** from branch name: - - Author/type path: `mrace/chore/173-phase1-…` → `#173` - - Leading digits + hyphen: `38-cloud-sync-status-icon` → `#38` -2. **Fetch issue title** when ID found: - - `gh issue view N --repo eten-tech-foundation/fluent-mobile --json title` -3. **Normalize issue title** for PR title text: - - Strip prefixes like `[Mobile App]` - - Sentence case (first word capitalized; proper nouns unchanged) - - Keep concise (~60 chars after colon); trim filler ("Implement", "Add support for") only if redundant with issue scope -4. **Fallback** when no issue: derive from branch slug after issue segment (`cloud-sync-status-icon` → `Cloud sync status icon`) or top commit subject (strip `#38:` prefix) -5. **Never** output a generic title like "Update files" or only the issue number with no description - -### Title examples (this repo) - -| Branch | Issue title | Suggested PR title | -|--------|-------------|-------------------| -| `mrace/chore/173-phase1-agent-process-docs` | Adopt Phase 1… | `[#173]: Adopt Phase 1 agent/process docs` | -| `38-cloud-sync-status-icon` | Build Cloud Sync Status Icon | `[#38]: Build cloud sync status icon` | -| `45-my-work-tab` | Build My Work tab | `[#45]: Build My Work tab` | - -## Generation steps - -```bash -# Context helpers (read-only) -git branch --show-current -git log origin/main..HEAD --oneline -git diff origin/main...HEAD --stat -gh issue view --repo eten-tech-foundation/fluent-mobile --json title,body # when branch has numeric ID -``` - -1. Load template from `.cursor/templates/pr-template.md` -2. **Synthesize PR title** (mandatory — see above) -3. Detect branch type: `feature/`, `fix/`, `chore/` → suggest change type checkbox -4. Fetch GitHub issue when issue number present (`Refs #NNN` in Details) -5. Pre-fill Technical Changes and Testing from diff + [docs/AGENT_ONBOARDING.md](../../docs/AGENT_ONBOARDING.md) gates - -## Output format - -``` -📋 **PR Title (copy this)** - -#38: Build cloud sync status icon - -**Branch:** 38-cloud-sync-status-icon · **Suggested type:** ✨ New feature - ---- - -📋 **PR Description — Ready to Copy** - -[Entire filled template in ONE ``` code block — no nested ``` inside] -``` - -### Critical — no nested code blocks - -- **Never** put triple-backtick fences **inside** the PR description code block -- Use 4-space indentation for code samples inside the block -- Use inline backticks for paths: `src/services/sync.ts` - -**Length:** If filled template exceeds ~400 lines, condense to TLDR, Summary, Technical Changes, Testing, How to Test; tell user to expand on GitHub. - -## Smart suggestions by branch prefix - -| Prefix | Suggested type | -|--------|----------------| -| `feature/` | New feature | -| `fix/`, `bugfix/` | Bug fix | -| `hotfix/` | Bug fix + check Breaking Changes | -| `chore/`, `refactor/` | Maintenance/refactor | -| `docs/` | Documentation update | -| Numeric issue slug (`38-…`, `45-…`) | Usually New feature or Bug fix from issue body | - -## Ticket patterns - -- GitHub issues: `[#123]` / `#123` in PR title; `Refs #123` under Details (never closing keywords) - -## Quality checklist (pre-fill reminders) - -Tell reviewer these were run or should be run: - -- `npm run format:check` -- `npm run lint` -- `npx tsc --noEmit` -- `npm test -- --ci` - -Use **npm** in all instructions (not pnpm/yarn). - -## Testing section defaults - -Fluent Mobile is **Android-only** for now. Generated PR descriptions must not include iOS testing steps, `npm run ios`, or iOS simulator/device checkboxes. - -**Prerequisites:** Node 24+, `npm install`, `.env` from `.env.example`, `npm start` + `npm run android` - -## Template source - -**Always** read the current [`.cursor/templates/pr-template.md`](../templates/pr-template.md) — do not duplicate template content in this command file. Filling this template is mandatory for agent-opened PRs ([delivery.mdc](../rules/delivery.mdc)), including when generating a body for manual paste. - - -## Usage - -`/generate-pr-description` — optional ticket ID in the same message. - -Pairs with `/create-pr-branch` and `/create-pr`. +Fills [`.cursor/templates/pr-template.md`](../templates/pr-template.md) for +copy-paste. Does not open a PR. Use `/create-pr` to create one. diff --git a/.cursor/commands/handle-dependabot.md b/.cursor/commands/handle-dependabot.md index fc0f863d..7f155caa 100644 --- a/.cursor/commands/handle-dependabot.md +++ b/.cursor/commands/handle-dependabot.md @@ -1,57 +1,10 @@ -# Handle Dependabot PRs +# Handle Dependabot (Cursor) -## Overview +Cursor entry point — **do not duplicate instructions here.** -Process the open Dependabot queue for **fluent-mobile** end-to-end: triage, rebase, validate (including **Expo Doctor**), merge safe PRs one at a time, rebase the rest. +Read and follow the canonical command: -Rule: [`.cursor/rules/dependabot-workflow.mdc`](../rules/dependabot-workflow.mdc) -Guide: [`docs/guides/dependabot-process.md`](../../docs/guides/dependabot-process.md) +**`.claude/commands/handle-dependabot.md`** -## Autonomous mode (default) - -Run the full queue without per-PR confirmation. Stop only on blockers (failed CI, conflicts after rebase, risky RN/react/navigation/native bumps). - -**Do not merge** GitHub Actions / workflow-only PRs unless the user explicitly includes them. - -**Never push agent work to `main`** — infra fixes (expo doctor CI, etc.) need a ticketed feature branch + PR ([delivery.mdc](../rules/delivery.mdc)). This command only squash-merges **existing Dependabot PRs**. - -## Queue loop - -1. List open bots: `gh pr list --search "author:app/dependabot state:open" --json number,title,mergeable,mergeStateStatus,statusCheckRollup,files` -2. Triage: safe / risky / skip / RN upgrade / workflow-only -3. `@dependabot rebase` on conflicting, stale, or post-merge bots (skip bots with fresh `IN_PROGRESS` CI) -4. Pick **oldest safe PR** with Lint, Test (incl. Expo Doctor), and Build all `SUCCESS` -5. Approve and squash-merge **one** PR -6. Wait for `main` CI green; rebase all remaining bots in parallel -7. Log batch in `docs/guides/dependabot-resolution-log.md` - -## Validation (after each rebase / before merge) - -```bash -npm ci -npm run doctor # Expo SDK alignment — also in Test Check CI -npm run format:check -npm run lint -npm run typecheck -npm test -- --ci -``` - -If `npm run doctor` fails: `npx expo install --check` → `npx expo install --fix` on the PR branch when SDK patch drift; do not merge until clean. - -Risky PRs (`react`, `react-native`, `@react-navigation/*`, native modules): add Android smoke test (`npm run android`). - -## Skip without merge - -- Expo Doctor / any required check failed -- `react` / `react-native` / navigation / `@op-engineering/op-sqlite` major groups (unless user approves) -- Workflow-only PRs (unless user includes them) -- `mergeable: CONFLICTING` after one rebase retry - -## Useful commands - -```bash -gh pr comment --body "@dependabot rebase" -gh pr review --approve --body "CI green. Safe bump per dependabot process." -gh pr merge --squash --delete-branch -gh run list --branch main --limit 3 --json workflowName,conclusion,status -``` +Triage, rebase, validate, and squash-merge **existing** Dependabot PRs one at +a time. Never push agent-authored commits to `main`. diff --git a/.cursor/commands/onboard.md b/.cursor/commands/onboard.md index 492d381d..2d159bf1 100644 --- a/.cursor/commands/onboard.md +++ b/.cursor/commands/onboard.md @@ -1,66 +1,10 @@ -# /onboard — fluent-mobile developer onboarding +# Onboard (Cursor) -You are guiding a developer through setting up (or repairing) their local -**fluent-mobile** environment. Be concise. Do **not** auto-install or mutate the -machine without the developer's go-ahead. +Cursor entry point — **do not duplicate instructions here.** -**Android-only permanently** — never suggest iOS, CocoaPods, or `expo run:ios`. +Read and follow the canonical command: -## Step 1 — Fresh-clone sequence +**`.claude/commands/onboard.md`** -Native `android/` is gitignored (CNG). Generate it before the first Android run. - -```bash -# Toolchain: Node >= 24.14.0, npm >= 10, Android Studio + SDK + JDK 17 -nvm use 24 # or otherwise match package.json engines -npm install -cp .env.example .env # set EXPO_PUBLIC_API_BASE_URL (see below) -npm run prebuild # expo prebuild --clean --platform android -``` - -Then run (two terminals, or use `npm run android` which builds the dev client): - -```bash -npm start -npm run android -``` - -Emulator API host: `.env.example` uses `http://10.0.2.2:9999` for host `localhost:9999`. -Hosted / Docker API paths: [docs/guides/local-development-workflow.md](../../docs/guides/local-development-workflow.md). - -## Step 2 — Check environment (read-only) - -Summarize PASS / WARN / FAIL for: - -| Check | Expectation | -| ----- | ----------- | -| Node | `>= 24.14.0` (`node -v` vs `package.json` `engines`) | -| npm | `>= 10` | -| `.env` | Present; `EXPO_PUBLIC_API_BASE_URL` non-empty | -| `android/` | Present after `npm run prebuild` (optional until first device run) | -| Expo MCP | Authenticated before SDK/EAS work ([expo-mcp.mdc](../rules/expo-mcp.mdc)) | - -Offer fixes only after the developer agrees. Safe non-destructive fixes: - -- Missing `.env` → `cp .env.example .env` -- Wrong Node → `nvm install 24 && nvm use 24` -- Missing `android/` → `npm run prebuild` - -## Step 3 — Sanity commands - -```bash -npm run lint -npm run typecheck -npm test -- --ci -npm run doctor # after native-impacting / dependency changes -``` - -## Step 4 — Point at docs - -- [docs/AGENT_ONBOARDING.md](../../docs/AGENT_ONBOARDING.md) — architecture map -- [docs/issue-tracking.md](../../docs/issue-tracking.md) — Project 4 Fluent Mobile Board + PR linking -- [docs/ci.md](../../docs/ci.md) — CI inventory -- [README.md](../../README.md) — human setup - -Finish when env checks are green (or WARs are acknowledged), then remind them to -start Metro and launch the Android dev client. +Guided Android-only local setup / repair. Do not auto-mutate the machine +without the developer's go-ahead. diff --git a/.cursor/commands/open-pr.md b/.cursor/commands/open-pr.md new file mode 100644 index 00000000..714bd751 --- /dev/null +++ b/.cursor/commands/open-pr.md @@ -0,0 +1,10 @@ +# Open PR (Cursor) + +Cursor entry point — **do not duplicate instructions here.** + +`/open-pr` is an **alias** of `/create-pr`. Read and follow: + +**`.claude/commands/create-pr.md`** + +Delivery-only (branch already has commits). For issue → branch → implement → +PR, use **`/start-issue`** instead. diff --git a/.cursor/commands/start-issue.md b/.cursor/commands/start-issue.md new file mode 100644 index 00000000..4f89a88d --- /dev/null +++ b/.cursor/commands/start-issue.md @@ -0,0 +1,15 @@ +# Start Issue (Cursor) + +Cursor entry point — **do not duplicate instructions here.** + +Read and follow the canonical command: + +**`.claude/commands/start-issue.md`** + +Full graph: claim GitHub issue → Project 4 **In Progress (Dev)** → branch from +`main` → plan-gate when large/ambiguous → implement → chain `/create-pr` +through green CI. + +Usage: `/start-issue 324` (optional trailing `/create-pr` test scope, e.g. +`/start-issue 324 src/services/sync.test.ts`). Epics auto-pick the +highest-priority open child. diff --git a/.cursor/hooks.json b/.cursor/hooks.json index 93c5d362..957cbd1d 100644 --- a/.cursor/hooks.json +++ b/.cursor/hooks.json @@ -5,6 +5,12 @@ { "command": ".cursor/hooks/session-env-check.sh" } + ], + "preToolUse": [ + { + "command": "node .cursor/hooks/architecture-guard.mjs", + "matcher": "Write|StrReplace" + } ] } } diff --git a/.cursor/hooks/architecture-guard.mjs b/.cursor/hooks/architecture-guard.mjs new file mode 100644 index 00000000..dd81d910 --- /dev/null +++ b/.cursor/hooks/architecture-guard.mjs @@ -0,0 +1,62 @@ +#!/usr/bin/env node +/** + * Cursor preToolUse adapter → scripts/architecture-guard.mjs + * Warn-only: never deny Write/StrReplace. + */ +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +const allow = (agentMessage) => { + if (agentMessage) { + process.stdout.write( + JSON.stringify({ permission: 'allow', agent_message: agentMessage }), + ); + } + process.exit(0); +}; + +try { + const raw = await new Promise((resolve) => { + let buf = ''; + process.stdin.on('data', (c) => (buf += c)); + process.stdin.on('end', () => resolve(buf)); + setTimeout(() => resolve(buf), 2000); + }); + + const cursor = JSON.parse(raw || '{}'); + const tool = cursor.tool_name || ''; + const input = cursor.tool_input || cursor.input || {}; + if (!/^(Write|StrReplace)$/.test(tool)) allow(); + + const filePath = input.file_path || input.path || ''; + if (!filePath) allow(); + + const toolMap = { Write: 'Write', StrReplace: 'Edit' }; + const claudePayload = { + tool_name: toolMap[tool] || tool, + tool_input: { + file_path: filePath, + content: input.content ?? input.contents, + new_string: input.new_string, + }, + }; + + const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); + const guard = path.join(root, 'scripts/architecture-guard.mjs'); + const result = spawnSync(process.execPath, [guard], { + input: JSON.stringify(claudePayload), + encoding: 'utf8', + cwd: root, + }); + + if (result.status !== 0) allow(); + + const out = JSON.parse(result.stdout || '{}'); + const msg = + out.agent_message || out.hookSpecificOutput?.additionalContext || ''; + if (msg) allow(msg); + allow(); +} catch { + allow(); +} diff --git a/.cursor/rules/ai-tooling-bridge.mdc b/.cursor/rules/ai-tooling-bridge.mdc new file mode 100644 index 00000000..319d90d1 --- /dev/null +++ b/.cursor/rules/ai-tooling-bridge.mdc @@ -0,0 +1,48 @@ +--- +description: Cursor bridge to shared Claude Code agent docs in this repo +alwaysApply: true +--- + +# Fluent Mobile agent instructions (Cursor) + +**Canonical source:** `CLAUDE.md` — stack, layout, conventions, PR workflow, and +agent scoping. Always-on Cursor rules in `.cursor/rules/*.mdc` remain the +injected contract; do not duplicate them here. + +**Reuse (do not duplicate):** + +- Skills: `.claude/skills/*/SKILL.md` +- Commands / workflows: `.claude/commands/*.md` +- Review agents: `.claude/agents/*.md` +- PR template: `.cursor/templates/pr-template.md` (use with + `/generate-pr-description` or `/create-pr`; GitHub UI mirror + `.github/PULL_REQUEST_TEMPLATE.md`) — **one format only** + +**Agent models:** repo agents under `.claude/agents/` use `model: inherit` so +Cursor Auto / the session model apply (do not pin model aliases). The PR +delivery gate still requires running `code-reviewer` before handoff. + +When a task matches a skill description, read that `SKILL.md` first. To run a +command, read and follow `.claude/commands/.md` directly — every command +works this way in Cursor and any other agent tool, not only Claude Code. + +**Discovery contract:** the globs above are the contract — any skill, command, +or agent added under `.claude/` is automatically in scope for Cursor and other +agent tools, with no per-item registration step. + +**Repo workflow:** PRs target `main`. Track work in **GitHub Issues** on +Project 4 (`Refs #NNN` in the PR body — never closing keywords). See +`docs/issue-tracking.md`. + +**Global IDE user rules:** if they conflict with this repo, prefer `CLAUDE.md`, +`.cursor/rules/`, and this file. + +**Cursor-only:** session env + architecture-guard hooks in `.cursor/hooks.json` +(warn-only; never deny tools). + +**Cursor command shims (optional):** a `.cursor/commands/*.md` file only makes a +command appear in Cursor's `/` palette — it is **not** required for the command +to work (read the canonical `.claude/commands/.md` directly per the +discovery contract above). When a shim exists it points at +`.claude/commands/*.md`; edit the `.claude` file as the single source of truth +and never duplicate logic into the shim. diff --git a/.cursor/rules/commands.mdc b/.cursor/rules/commands.mdc index d52092e1..40a34271 100644 --- a/.cursor/rules/commands.mdc +++ b/.cursor/rules/commands.mdc @@ -39,7 +39,7 @@ Report what ran and the outcome. If a step was skipped, say why. ## PR workflow -Slash commands in `.cursor/commands/`: `/create-pr-branch`, `/generate-pr-description`, `/create-pr`, `/onboard`, `/dep-bump`, `/handle-dependabot`. Align with team PR title format: `[#NNN]: Description` (Project 4 Fluent Mobile Board — [docs/issue-tracking.md](../../docs/issue-tracking.md)). **PR body must be filled from** [`.cursor/templates/pr-template.md`](../templates/pr-template.md) on every agent-opened PR — see [delivery.mdc](./delivery.mdc) (not optional; not only when `/create-pr` is typed). CI map: [docs/ci.md](../../docs/ci.md). +Slash commands (canonical: `.claude/commands/`; Cursor `/` shims in `.cursor/commands/`): `/start-issue`, `/create-pr-branch`, `/generate-pr-description`, `/create-pr` (alias `/open-pr`), `/onboard`, `/dep-bump`, `/handle-dependabot`. Align with team PR title format: `[#NNN]: Description` (Project 4 Fluent Mobile Board — [docs/issue-tracking.md](../../docs/issue-tracking.md)). **PR body must be filled from** [`.cursor/templates/pr-template.md`](../templates/pr-template.md) on every agent-opened PR — see [delivery.mdc](./delivery.mdc) (not optional; not only when `/create-pr` is typed). Full issue loop: `/start-issue NNN` chains to `/create-pr`. CI map: [docs/ci.md](../../docs/ci.md). ## Do not run without user request diff --git a/.cursor/rules/delivery.mdc b/.cursor/rules/delivery.mdc index d43f2088..8a529533 100644 --- a/.cursor/rules/delivery.mdc +++ b/.cursor/rules/delivery.mdc @@ -12,8 +12,8 @@ alwaysApply: true Every change — including infra, CI, docs, Dependabot follow-ups, and `expo install --fix` — goes through: 1. **Ticket on Project 4** — GitHub Issue `#NNN` **on** the [Fluent Mobile Board](https://github.com/orgs/eten-tech-foundation/projects/4/views/9) (org project #4, view 9). Ask if missing. See [docs/issue-tracking.md](../../docs/issue-tracking.md). The repo Issues list is not the triage board. (Not Linear.) Board Status/Priority edits: [docs/guides/project-board.md](../../docs/guides/project-board.md) and [project-board.mdc](./project-board.mdc) — do not sweep Product or `Dev Ready` lanes without named approval. -2. **Feature branch** — `{author}/{type}/{issue-number}-{slug}` (see `/create-pr-branch`) -3. **Pull request** — title `[#NNN]: Description`; body filled from the PR template (below); include `Refs #NNN` under Details (never `Closes` / `Fixes` / `Resolves`); **assign the PR to the author** (`gh pr create --assignee @me` or `gh pr edit --add-assignee @me`); set Project 4 Status to **In PR Review**. Manually link the issue in the PR Development sidebar when needed (GitHub has no non-closing link API — do not use closing keywords). +2. **Feature branch** — `{author}/{type}/{issue-number}-{slug}` (see `/create-pr-branch`, or `/start-issue` for the full loop) +3. **Pull request** — title `[#NNN]: Description`; body filled from the PR template (below); include `Refs #NNN` under Details (never `Closes` / `Fixes` / `Resolves`); **assign the PR to the author** (`gh pr create --assignee @me` or `gh pr edit --add-assignee @me`); set Project 4 Status to **In PR Review**. Manually link the issue in the PR Development sidebar when needed (GitHub has no non-closing link API — do not use closing keywords). `/start-issue` chains to `/create-pr` (`/open-pr` is an alias) which automates the draft PR, board move, and CI watch. 4. **Human merge** — agents open PRs and push **feature branches only**; never merge on GitHub unless the user explicitly asks for a Dependabot squash merge in that session. After merge, set Project 4 Status to **In QA** (issues stay open for QA — do not auto-close) Allowed on `main` locally: `git checkout main`, `git pull`, read-only validation after merges. **Not allowed:** `git push origin main`, commits on `main`, or opening PRs with `main` as the head branch. @@ -34,7 +34,7 @@ Do **not** substitute a short “Summary / Test plan” or ad-hoc bullet list. L | **How to verify** | Numbered steps + **Expected** | | **Follow-ups** | Deferred AC → linked issues (ticket-level waiver); otherwise say none | -Prefer `/create-pr` or `/generate-pr-description` so the template is not skipped. Keep under ~400 lines; no nested fenced code blocks inside the PR body. +Prefer `/start-issue` (full loop) or `/create-pr` / `/generate-pr-description` so the template is not skipped. Keep under ~400 lines; no nested fenced code blocks inside the PR body. This repo’s template **wins** over generic personal or org-wide PR body shapes when they conflict. @@ -46,4 +46,4 @@ Agent-authored fixes (expo doctor CI, lockfile repair, conflict resolution) stil ## Before delivery -Run gates from [commands.mdc](./commands.mdc). Also apply delivery judgment from root [`AGENTS.md`](../../AGENTS.md) (AC, scope, abstraction, human QA) — Cursor pointer: [agent-guardrails.mdc](./agent-guardrails.mdc). Use `/create-pr` when the user wants a PR opened (template still required if opening the PR another way). +Run gates from [commands.mdc](./commands.mdc). Also apply delivery judgment from root [`AGENTS.md`](../../AGENTS.md) (AC, scope, abstraction, human QA) — Cursor pointer: [agent-guardrails.mdc](./agent-guardrails.mdc). Use `/start-issue` for issue → branch → implement → PR, or `/create-pr` when the branch already has commits (template still required if opening the PR another way). See [pr-delivery-ci-gate.mdc](./pr-delivery-ci-gate.mdc). diff --git a/.cursor/rules/docs.mdc b/.cursor/rules/docs.mdc index 74cd75f6..af6e9564 100644 --- a/.cursor/rules/docs.mdc +++ b/.cursor/rules/docs.mdc @@ -57,6 +57,10 @@ Short pointers next to code (keep thin; link to full guides): Warn-only Cursor `sessionStart` hook: [`.cursor/hooks.json`](../hooks.json) + [`.cursor/hooks/session-env-check.sh`](../hooks/session-env-check.sh). Never deny tools. +Warn-only architecture guard (`fetch(` / raw SQL in `src/app/` and `src/components/`): [`.cursor/hooks/architecture-guard.mjs`](../hooks/architecture-guard.mjs) + [`scripts/architecture-guard.mjs`](../../scripts/architecture-guard.mjs). Never deny tools. + +Canonical slash commands live in [`.claude/commands/`](../../.claude/commands/); Cursor `/` files are thin shims. Do not duplicate command lists without updating onboarding + `commands.mdc`. + ## Style - Short, scannable sections and tables diff --git a/.cursor/rules/pr-delivery-ci-gate.mdc b/.cursor/rules/pr-delivery-ci-gate.mdc new file mode 100644 index 00000000..de74ec7c --- /dev/null +++ b/.cursor/rules/pr-delivery-ci-gate.mdc @@ -0,0 +1,28 @@ +--- +description: PR open/push delivery — not done until CI is green +alwaysApply: true +--- + +# PR delivery gate (CI) + +When committing, opening a PR, pushing commits meant for review, handing off, +or saying a PR task is **done**: + +1. Run **CI-parity local checks** first — `npm run format:check`, `npm run lint`, + `npm run typecheck`, `npm test -- --ci` (see [commands.mdc](./commands.mdc)). + Fix before commit. +2. Invoke the repo **`code-reviewer`** agent (`.claude/agents/code-reviewer.md`) + on the **working diff** (before commit). Fix **all** findings before commit + (Blocking, Should-fix, Nit — every severity), then commit → push. Only skip a + finding with an explicit per-item human waiver in the PR body. Do not hand + off or merge while any finding remains unfixed without that waiver. +3. After `gh pr create` / push: **watch CI** (`gh pr checks --watch` or + equivalent) until required checks finish. +4. On failure: fix, push, re-watch. Do not hand off as complete while checks + are red or still running. +5. Only report “done” when required CI is green (or you explicitly document an + out-of-scope base-branch blocker). + +Canonical flow: `.claude/commands/create-pr.md` (local checks + review before +commit + CI wait). `/open-pr` is an alias. Human merges — agents do not merge; +do not merge over unfixed review findings. diff --git a/.cursor/rules/rule-precedence.mdc b/.cursor/rules/rule-precedence.mdc index 83c722cb..51b21ec0 100644 --- a/.cursor/rules/rule-precedence.mdc +++ b/.cursor/rules/rule-precedence.mdc @@ -23,7 +23,7 @@ When an umbrella rule and a **fluent-mobile** rule disagree, **fluent-mobile win |-------|--------| | Architecture | [architecture.mdc](./architecture.mdc) | | Commands / CI gates | [commands.mdc](./commands.mdc) | -| Git / PR / tickets | [delivery.mdc](./delivery.mdc), `/create-pr-branch`, `/create-pr` | +| Git / PR / tickets | [delivery.mdc](./delivery.mdc), `/start-issue`, `/create-pr-branch`, `/create-pr` | | Dependabot | [dependabot-workflow.mdc](./dependabot-workflow.mdc) | | Android / Expo | [android-only.mdc](./android-only.mdc), [expo-mcp.mdc](./expo-mcp.mdc) | | Testing | [testing.mdc](./testing.mdc) | diff --git a/AGENTS.md b/AGENTS.md index b36813fc..c1c7e55a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,7 +56,7 @@ On feature PRs with heavy agent co-authorship, reviewers should prioritize **sco ## Related -- Delivery / branch / PR process: [`.cursor/rules/delivery.mdc`](.cursor/rules/delivery.mdc) — **PR bodies must use** [`.cursor/templates/pr-template.md`](.cursor/templates/pr-template.md) +- Delivery / branch / PR process: [`.cursor/rules/delivery.mdc`](.cursor/rules/delivery.mdc) — **PR bodies must use** [`.cursor/templates/pr-template.md`](.cursor/templates/pr-template.md). Full loop: `/start-issue`; delivery-only: `/create-pr` (`.claude/commands/`). - Issue tracking (Project 4 Fluent Mobile Board): [docs/issue-tracking.md](docs/issue-tracking.md) - Board mutation rules: [docs/guides/project-board.md](docs/guides/project-board.md) · [`.cursor/rules/project-board.mdc`](.cursor/rules/project-board.mdc) - Expo-first deps: [docs/guides/expo-first-dependencies.md](docs/guides/expo-first-dependencies.md) · [`.cursor/rules/expo-first-dependencies.mdc`](.cursor/rules/expo-first-dependencies.mdc) diff --git a/CLAUDE.md b/CLAUDE.md index 12161ca9..6299e0cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,7 @@ This file is the **entrypoint for non-Cursor agents** (Claude Code, Codex, Copil 4. [`docs/guides/project-board.md`](docs/guides/project-board.md) — **board mutation hard rules** 5. [`docs/guides/expo-first-dependencies.md`](docs/guides/expo-first-dependencies.md) — prefer Expo packages 6. [`.cursor/rules/`](.cursor/rules/) — always-on + topic rules (`delivery`, `architecture`, `android-only`, `project-board`, `expo-first-dependencies`, …) +7. [`.claude/commands/`](.claude/commands/) — canonical slash commands (`/start-issue`, `/create-pr`, …). Cursor `/` palette shims live in [`.cursor/commands/`](.cursor/commands/). ## Precedence @@ -18,7 +19,7 @@ This file is the **entrypoint for non-Cursor agents** (Claude Code, Codex, Copil ## Non-negotiables (short) - Android-only permanently; CNG — do not commit generated `android/` -- Never push or merge to `main`; ticketed branch + PR; PR body from [`.cursor/templates/pr-template.md`](.cursor/templates/pr-template.md) +- Never push or merge to `main`; ticketed branch + PR; PR body from [`.cursor/templates/pr-template.md`](.cursor/templates/pr-template.md). Full loop: `/start-issue NNN` (chains to `/create-pr`). Delivery-only: `/create-pr` (`/open-pr` alias). - Do not reorganize Project 4 (`Dev Ready` / Product columns) without an approved change set - Prefer Expo SDK modules; do not rip out React Navigation without a ticket - Mic / camera / filesystem / native modules → human Android device QA before ready-for-review diff --git a/docs/AGENT_ONBOARDING.md b/docs/AGENT_ONBOARDING.md index 48bddba1..8b37b359 100644 --- a/docs/AGENT_ONBOARDING.md +++ b/docs/AGENT_ONBOARDING.md @@ -62,7 +62,8 @@ Recording follow-up: wire source audio dock to real fetch + playback ([#235](htt | [`docs/guides/dependabot-process.md`](guides/dependabot-process.md) | Safe Dependabot merge process | | [`docs/guides/local-development-workflow.md`](guides/local-development-workflow.md) | Hosted dev + local Docker API paths | | [`docs/guides/recordings-sync-contract.md`](guides/recordings-sync-contract.md) | Verse audio upload contract (#102 / fluent-api #224) | -| [`.cursor/commands/`](../.cursor/commands/) | Slash commands (`/create-pr`, etc.) | +| [`.claude/commands/`](../.claude/commands/) | Canonical slash commands (`/start-issue`, `/create-pr`, …) | +| [`.cursor/commands/`](../.cursor/commands/) | Cursor `/` palette shims (point at `.claude/commands/`) | ## Setup @@ -210,7 +211,7 @@ When adding features: mock `op-sqlite`, navigation, and sync in screen tests fol | New API endpoint | `FluentAPI` in `api.ts`, then `sync.ts` step + `repository.ts` | | New table / column | `schema.ts` → prefer versioned migrations (`#110`) → repository inserts → queries → types | | Manual re-sync | Sync page / header sync → `useSync` / `syncAllUsers` | -| PR workflow | `/create-pr-branch`, `/generate-pr-description`, `/create-pr` (see `.cursor/commands/`) | +| PR workflow | `/start-issue` (full loop), `/create-pr-branch`, `/generate-pr-description`, `/create-pr` (alias `/open-pr`) — canonical: `.claude/commands/` | ## Risk areas (change carefully) @@ -240,5 +241,5 @@ When adding features: mock `op-sqlite`, navigation, and sync in screen tests fol - Cursor rules: [`.cursor/rules/`](../.cursor/rules/) — **Android-only:** [android-only.mdc](../.cursor/rules/android-only.mdc) - Dependabot: [guides/dependabot-process.md](guides/dependabot-process.md) — use with `.cursor/rules/dependabot-workflow.mdc` - PR template: [`.cursor/templates/pr-template.md`](../.cursor/templates/pr-template.md) -- Slash commands: `/onboard`, `/dep-bump`, `/create-pr-branch`, `/create-pr`, `/handle-dependabot` (see [`.cursor/commands/`](../.cursor/commands/)) +- Slash commands: `/start-issue`, `/onboard`, `/dep-bump`, `/create-pr-branch`, `/create-pr` (`/open-pr` alias), `/generate-pr-description`, `/handle-dependabot` (canonical: [`.claude/commands/`](../.claude/commands/); Cursor shims: [`.cursor/commands/`](../.cursor/commands/)) - Hot-path notes: [src/services/AGENTS.md](../src/services/AGENTS.md), [src/db/AGENTS.md](../src/db/AGENTS.md) diff --git a/docs/issue-tracking.md b/docs/issue-tracking.md index 063a0101..0699d93f 100644 --- a/docs/issue-tracking.md +++ b/docs/issue-tracking.md @@ -59,13 +59,13 @@ Examples: | `issue-number` | GitHub issue number (digits only in the branch segment) | | `slug` | 3–6 meaningful words from the issue title | -See [`.cursor/commands/create-pr-branch.md`](../.cursor/commands/create-pr-branch.md). +See [`.claude/commands/create-pr-branch.md`](../.claude/commands/create-pr-branch.md) (Cursor shim: `/create-pr-branch`). For the full issue → implement → PR loop, use `/start-issue`. ## Pull requests - **Base branch:** `main` -- **Title:** `[#NNN]: Short description` (or `#NNN: Short description`) — match existing PR style in this repo -- **Body:** **required** — fill [`.github/PULL_REQUEST_TEMPLATE.md`](../.github/PULL_REQUEST_TEMPLATE.md) / [`.cursor/templates/pr-template.md`](../.cursor/templates/pr-template.md) (same content: TLDR, Reviewer checklist, Details, Technical changes, Testing, How to verify, Follow-ups). The **`PR Description`** check fails empty / CodeRabbit-only bodies. Prefer `/generate-pr-description` or `/create-pr`. Do not ship a short Summary/Test plan substitute. +- **Title:** `[#NNN]: Short description` +- **Body:** **required** — fill [`.github/PULL_REQUEST_TEMPLATE.md`](../.github/PULL_REQUEST_TEMPLATE.md) / [`.cursor/templates/pr-template.md`](../.cursor/templates/pr-template.md) (same content: TLDR, Reviewer checklist, Details, Technical changes, Testing, How to verify, Follow-ups). The **`PR Description`** check fails empty / CodeRabbit-only bodies. Prefer `/start-issue` (full loop) or `/generate-pr-description` / `/create-pr` (`/open-pr` is an alias). Do not ship a short Summary/Test plan substitute. - Under Details: `Refs #NNN` on its own line (links the issue without closing it) - Do **not** use GitHub closing keywords (`Closes`, `Fixes`, `Resolves`) — merged PRs must not auto-close issues - **Assignee:** agents must assign the PR to the author (`--assignee @me`) @@ -75,6 +75,18 @@ See [`.cursor/commands/create-pr-branch.md`](../.cursor/commands/create-pr-branc - After merge (or when ready for QA), set Project 4 Status to **`In QA`** — leave the GitHub issue **open** - **Template source of truth:** [`.cursor/templates/pr-template.md`](../.cursor/templates/pr-template.md) — also required by [delivery.mdc](../.cursor/rules/delivery.mdc); generate with `/generate-pr-description` or `/create-pr` +### Agent command: `/start-issue` + +End-to-end agent loop for one issue (Cursor `/` palette or “run `/start-issue NNN`”): + +1. Claim the GitHub issue (assign `@me`; add to Project 4 if missing) +2. Set Status to **`In Progress (Dev)`** (never Product-owned columns — see [guides/project-board.md](guides/project-board.md)) +3. Cut `{author}/{type}/{NNN}-{slug}` from `main` (reuses `/create-pr-branch` naming) +4. Plan-gate when scope is large/ambiguous, then implement to AC +5. Chain `/create-pr`: draft PR, `Refs #NNN`, **In PR Review**, watch CI to green + +Canonical instructions: [`.claude/commands/start-issue.md`](../.claude/commands/start-issue.md). Delivery-only (commits already on a branch): `/create-pr` / `/open-pr`. + ### Linking without auto-close Use `Refs #NNN` (or sidebar linking) so GitHub [does not auto-close](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) the issue on merge. diff --git a/scripts/architecture-guard.mjs b/scripts/architecture-guard.mjs new file mode 100644 index 00000000..9c019822 --- /dev/null +++ b/scripts/architecture-guard.mjs @@ -0,0 +1,76 @@ +#!/usr/bin/env node +/** + * Warn-only architecture guard for fluent-mobile. + * Flags fetch( / raw SQL in src/app and src/components. Never denies. + * + * Accepts Claude PreToolUse JSON or Cursor Write/StrReplace hook JSON on stdin. + */ +import { readFileSync } from 'node:fs'; + +const allow = (agentMessage) => { + if (agentMessage) { + process.stdout.write( + JSON.stringify({ + hookSpecificOutput: { + hookEventName: 'PreToolUse', + permissionDecision: 'allow', + additionalContext: agentMessage, + }, + }), + ); + } + process.exit(0); +}; + +const FETCH_RE = /\bfetch\s*\(/; +const SQL_RE = + /\b(SELECT|INSERT\s+INTO|UPDATE\s+\w+\s+SET|DELETE\s+FROM|executeSql)\b/i; + +const isUiPath = (filePath) => { + const normalized = filePath.replace(/\\/g, '/'); + return ( + /\/src\/app\//.test(normalized) || /\/src\/components\//.test(normalized) + ); +}; + +const isCode = (filePath) => /\.(ts|tsx|js|jsx)$/.test(filePath); + +try { + const raw = await new Promise((resolve) => { + let buf = ''; + process.stdin.on('data', (c) => (buf += c)); + process.stdin.on('end', () => resolve(buf)); + setTimeout(() => resolve(buf), 2000); + }); + + const payload = JSON.parse(raw || '{}'); + const input = payload.tool_input || payload.input || {}; + const filePath = input.file_path || input.path || ''; + if (!filePath || !isUiPath(filePath) || !isCode(filePath)) allow(); + + let text = input.content ?? input.contents ?? input.new_string ?? ''; + if (!text && filePath) { + try { + text = readFileSync(filePath, 'utf8'); + } catch { + allow(); + } + } + + const warnings = []; + if (FETCH_RE.test(text)) { + warnings.push( + `${filePath}: UI layer must not call fetch() — use FluentAPI in src/services/api.ts plus sync.`, + ); + } + if (SQL_RE.test(text)) { + warnings.push( + `${filePath}: UI layer must not write SQL — use src/db/repository.ts (writes) and src/db/queries.ts (reads).`, + ); + } + + if (warnings.length === 0) allow(); + allow(`Architecture warning (not blocking):\n${warnings.join('\n')}`); +} catch { + allow(); +}