docs: operating-manual CLAUDE.md + new-feature / ship-pr / verifier-web skills#29
Conversation
CLAUDE.md is now the canonical, self-contained manual: - names the dead root-level prototype app as the #1 trap - named failure modes with the rule that prevents each (hydration flash, unregistered router, mock profile, missing-column TypeError, easing string, ghost app, CI noise) - checkable quality bar per deliverable type instead of adjectives - exact escalation rules: proceed / flag-in-PR / stop-and-ask / report - corrects stale facts: per-session branch (not the old migration branch), pipeline is a plain GraphState dataclass (not LangGraph), globals.css wins over DESIGN.md on token conflicts New skills in .claude/skills/: - new-feature: the mandatory 8-step full-stack recipe with per-step traps - ship-pr: pre-flight gates, CI-noise triage table, review-bot protocol - verifier-web: launch + drive the app with Playwright on a mobile viewport, hydration double-screenshot, evidence-based verdicts https://claude.ai/code/session_017cLXjZu6kkjXRf2BemMPpp
✅ Deploy Preview for bajet-buddy canceled.
|
|
Warning Review limit reached
Next review available in: 27 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
bajet-buddy | 03aa9c6 | Jul 07 2026, 05:18 AM |
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive documentation and workflow guidelines for the Bajet Buddy project. It adds three new Claude skill files (new-feature, ship-pr, and verifier-web) and significantly expands CLAUDE.md into a detailed operating manual. These additions establish clear protocols for full-stack feature development, PR shipping, CI triage, and Playwright-based web verification, with a strong emphasis on avoiding the legacy root-level prototype. The reviewer's feedback focuses on fixing minor markdown formatting errors (such as mismatched or missing backticks) and consistently using fully qualified paths (e.g., prefixing with apps/web/ or apps/api/) across all documentation to prevent AI agents from navigating to the dead root directory.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| **Rule: `"use client"` on the first line of every component that touches | ||
| `useState`/`useEffect`/handlers/`window`/`localStorage`. All files in | ||
| `hooks/` carry it.** |
There was a problem hiding this comment.
There is a missing backtick after localStorage on line 78, which breaks the markdown formatting. Also, specifying the full path apps/web/hooks/ instead of the shorthand hooks/ helps prevent AI agents from accidentally looking in the root-level prototype directory (Mistake #0).
| **Rule: `"use client"` on the first line of every component that touches | |
| `useState`/`useEffect`/handlers/`window`/`localStorage`. All files in | |
| `hooks/` carry it.** | |
| **Rule: `"use client"` on the first line of every component that touches\n `useState`/`useEffect`/handlers/`window`/`localStorage`. All files in\n `apps/web/hooks/` carry it.** |
| - Data fetching: Server Components use `fetch()` directly; Client Components | ||
| use hooks in `/hooks/` or the typed wrappers in `lib/api.ts`. No React Query. |
There was a problem hiding this comment.
To strictly adhere to the 'Mistake #0' rule and prevent AI agents from navigating to the dead root-level prototype, use the explicit paths apps/web/hooks/ and apps/web/lib/api.ts instead of the shorthand /hooks/ and lib/api.ts.
| - Data fetching: Server Components use `fetch()` directly; Client Components | |
| use hooks in `/hooks/` or the typed wrappers in `lib/api.ts`. No React Query. | |
| - Data fetching: Server Components use `fetch()` directly; Client Components\n use hooks in `apps/web/hooks/` or the typed wrappers in `apps/web/lib/api.ts`. No React Query. |
| - `lib/api.ts` `apiFetch` already attaches the Supabase bearer token and | ||
| strips trailing slashes from `API_URL`. **Rule: new backend calls get a | ||
| typed wrapper in `lib/api.ts`; never `fetch(API_URL + ...)` inline.** |
There was a problem hiding this comment.
Use the explicit path apps/web/lib/api.ts instead of the shorthand lib/api.ts to prevent AI agents from looking into the dead root-level prototype directory.
| - `lib/api.ts` `apiFetch` already attaches the Supabase bearer token and | |
| strips trailing slashes from `API_URL`. **Rule: new backend calls get a | |
| typed wrapper in `lib/api.ts`; never `fetch(API_URL + ...)` inline.** | |
| - `apps/web/lib/api.ts` `apiFetch` already attaches the Supabase bearer token and\n strips trailing slashes from `API_URL`. **Rule: new backend calls get a\n typed wrapper in `apps/web/lib/api.ts`; never `fetch(API_URL + ...)` inline.** |
|
|
||
| Route (thin) → service (all logic, plain async functions) → Supabase/Claude. | ||
| The pre-purchase check runs a 5-node pipeline in | ||
| `app/agents/reasoning_graph.py`: `observe → load_context → evaluate_risk → |
There was a problem hiding this comment.
The path to reasoning_graph.py should be fully qualified as apps/api/app/agents/reasoning_graph.py to prevent AI agents from looking in the root-level directory.
| `app/agents/reasoning_graph.py`: `observe → load_context → evaluate_risk → | |
| `apps/api/app/agents/reasoning_graph.py`: `observe → load_context → evaluate_risk →` |
| - **"The unregistered router."** A new route file that isn't imported and | ||
| `include_router`-ed in `app/main.py` produces 404s with zero errors. | ||
| **Rule: every new router is registered in `main.py` with prefix | ||
| `/api/<domain>` in the same commit that creates it.** |
There was a problem hiding this comment.
Specify the full path apps/api/app/main.py instead of app/main.py or main.py to avoid any confusion with the root-level directory structure.
| - **"The unregistered router."** A new route file that isn't imported and | |
| `include_router`-ed in `app/main.py` produces 404s with zero errors. | |
| **Rule: every new router is registered in `main.py` with prefix | |
| `/api/<domain>` in the same commit that creates it.** | |
| - **"The unregistered router."** A new route file that isn't imported and\n `include_router`-ed in `apps/api/app/main.py` produces 404s with zero errors.\n **Rule: every new router is registered in `apps/api/app/main.py` with prefix\n `/api/<domain>` in the same commit that creates it.** |
| - [ ] New fetches: loading skeleton + error state + retry exist | ||
|
|
||
| **Backend change (additionally):** | ||
| - [ ] New router registered in `main.py`; new deps in `requirements.txt` |
There was a problem hiding this comment.
Specify the full paths apps/api/app/main.py and apps/api/requirements.txt to maintain consistency and prevent AI agents from looking in the root-level directory.
| - [ ] New router registered in `main.py`; new deps in `requirements.txt` | |
| - [ ] New router registered in `apps/api/app/main.py`; new deps in `apps/api/requirements.txt` |
| **Feature (full-stack) — the 8-step order is mandatory:** | ||
| schema → service → route → register in `main.py` → shared TS types → | ||
| `lib/api.ts` wrapper → component(s) under `components/features/<feature>/` → | ||
| page under `app/(app)/<feature>/page.tsx`, then all checks above. |
There was a problem hiding this comment.
Fully qualify all paths in the 8-step order checklist to prevent AI agents from navigating to the dead root-level prototype (Mistake #0).
| **Feature (full-stack) — the 8-step order is mandatory:** | |
| schema → service → route → register in `main.py` → shared TS types → | |
| `lib/api.ts` wrapper → component(s) under `components/features/<feature>/` → | |
| page under `app/(app)/<feature>/page.tsx`, then all checks above. | |
| **Feature (full-stack) — the 8-step order is mandatory:**\nschema → service → route → register in `apps/api/app/main.py` → shared TS types →\n`apps/web/lib/api.ts` wrapper → component(s) under `apps/web/components/features/<feature>/` →\npage under `apps/web/app/(app)/<feature>/page.tsx`, then all checks above. |
| c. **Page** — `apps/web/app/(app)/<feature>/page.tsx` (authed) or under | ||
| `app/` root (public — then also add the path to the public list in | ||
| `proxy.ts`). Copy tone: Manglish where the product is playful. |
There was a problem hiding this comment.
Specify the full paths apps/web/app/ and apps/web/proxy.ts instead of the shorthand app/ and proxy.ts to prevent AI agents from looking in the root-level directory.
| c. **Page** — `apps/web/app/(app)/<feature>/page.tsx` (authed) or under | |
| `app/` root (public — then also add the path to the public list in | |
| `proxy.ts`). Copy tone: Manglish where the product is playful. | |
| c. **Page** — `apps/web/app/(app)/<feature>/page.tsx` (authed) or under\n `apps/web/app/` root (public — then also add the path to the public list in\n `apps/web/proxy.ts`). Copy tone: Manglish where the product is playful. |
| ``` | ||
|
|
||
| Facts that make this work without secrets: | ||
| - `proxy.ts` skips session refresh when Supabase env is missing → all routes |
There was a problem hiding this comment.
Specify the full path apps/web/proxy.ts instead of proxy.ts to prevent AI agents from looking in the root-level directory.
| - `proxy.ts` skips session refresh when Supabase env is missing → all routes | |
| - `apps/web/proxy.ts` skips session refresh when Supabase env is missing → all routes |
| Facts that make this work without secrets: | ||
| - `proxy.ts` skips session refresh when Supabase env is missing → all routes | ||
| reachable. | ||
| - The API forces the demo user (`FORCE_DEMO_USER` in `core/auth.py`) and |
There was a problem hiding this comment.
Specify the full path apps/api/app/core/auth.py instead of core/auth.py to prevent AI agents from looking in the root-level directory.
| - The API forces the demo user (`FORCE_DEMO_USER` in `core/auth.py`) and | |
| - The API forces the demo user (`FORCE_DEMO_USER` in `apps/api/app/core/auth.py`) and |
Every path shorthand (main.py, lib/api.ts, hooks/, core/auth.py, proxy.ts, globals.css) is now written from the repo root so agents can never resolve it into the dead root-level prototype. Used the real nudge_agent path (apps/api/app/nudge_agent/service.py), not the reviewer's guess. https://claude.ai/code/session_017cLXjZu6kkjXRf2BemMPpp
There was a problem hiding this comment.
6 issues found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".claude/skills/ship-pr/SKILL.md">
<violation number="1" location=".claude/skills/ship-pr/SKILL.md:11">
P3: Pre-flight gates are missing `npm run lint -w @bajetbuddy/web`. The CI `web` job runs eslint before the build, so a lint error would pass your local gates and only surface after the CI round-trip, wasting 2+ minutes per push. Add it as a fourth gate (CLAUDE.md's verification section lists it as a separate command).</violation>
</file>
<file name=".claude/skills/new-feature/SKILL.md">
<violation number="1" location=".claude/skills/new-feature/SKILL.md:44">
P2: The Claude client construction in step 2 falls back to `"claude-opus-4-5"` when `settings.ilmu_model` is falsy, but the codebase default in `apps/api/app/core/config.py` is `"nemo-super"`. The `or` fallback is dead code (the config default is always non-empty) and sets the wrong expectation about which model runs by default. Either drop the fallback or align it with the project default.</violation>
<violation number="2" location=".claude/skills/new-feature/SKILL.md:78">
P3: The instruction 'The web app imports it via `@/types`, never via a relative path' is accurate for type-only imports but misses that the codebase also commonly imports from `@bajetbuddy/shared` directly for constants and other non-type exports. New contributors reading 'never via a relative path' might think `import { ... } from "@bajetbuddy/shared"` is wrong, when it's an established pattern used in `constants.ts`, `sentinel/engine.ts`, and `RiskProfileCard.tsx`. Consider adding a note that `@bajetbuddy/shared` is the canonical package name for non-type imports.</violation>
</file>
<file name="CLAUDE.md">
<violation number="1" location="CLAUDE.md:158">
P2: The Claude client construction snippet shows `"claude-opus-4-5"` as the default model, but `apps/api/app/nudge_agent/service.py` uses `"claude-sonnet-4-5"` as the fallback. Update the doc to match the actual code so contributors don't copy the wrong model name.</violation>
</file>
<file name=".claude/skills/verifier-web/SKILL.md">
<violation number="1" location=".claude/skills/verifier-web/SKILL.md:51">
P2: The Playwright scratchpad script uses `require()` (CommonJS) at the top level with `await` (ESM-only), which won't execute as written in either module system. If this is intended as a copy-paste snippet, wrap it in an async IIFE:
```js
(async () => {
const { chromium } = require("playwright");
const browser = await chromium.launch({
executablePath: process.env.PLAYWRIGHT_BROWSERS_PATH
? "/opt/pw-browsers/chromium" : undefined,
});
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
// ...
})();
Or use ESM imports: import { chromium } from "playwright";.
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| api_key=settings.ilmu_api_key or settings.anthropic_api_key, | ||
| base_url=settings.ilmu_anthropic_base_url, | ||
| ) | ||
| model = settings.ilmu_model or "claude-opus-4-5" |
There was a problem hiding this comment.
P2: The Claude client construction in step 2 falls back to "claude-opus-4-5" when settings.ilmu_model is falsy, but the codebase default in apps/api/app/core/config.py is "nemo-super". The or fallback is dead code (the config default is always non-empty) and sets the wrong expectation about which model runs by default. Either drop the fallback or align it with the project default.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/skills/new-feature/SKILL.md, line 44:
<comment>The Claude client construction in step 2 falls back to `"claude-opus-4-5"` when `settings.ilmu_model` is falsy, but the codebase default in `apps/api/app/core/config.py` is `"nemo-super"`. The `or` fallback is dead code (the config default is always non-empty) and sets the wrong expectation about which model runs by default. Either drop the fallback or align it with the project default.</comment>
<file context>
@@ -0,0 +1,113 @@
+ api_key=settings.ilmu_api_key or settings.anthropic_api_key,
+ base_url=settings.ilmu_anthropic_base_url,
+ )
+ model = settings.ilmu_model or "claude-opus-4-5"
+ ```
+ Parse responses by slicing first `{` to last `}` before `json.loads` —
</file context>
| api_key=settings.ilmu_api_key or settings.anthropic_api_key, | ||
| base_url=settings.ilmu_anthropic_base_url, | ||
| ) | ||
| model = settings.ilmu_model or "claude-opus-4-5" |
There was a problem hiding this comment.
P2: The Claude client construction snippet shows "claude-opus-4-5" as the default model, but apps/api/app/nudge_agent/service.py uses "claude-sonnet-4-5" as the fallback. Update the doc to match the actual code so contributors don't copy the wrong model name.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At CLAUDE.md, line 158:
<comment>The Claude client construction snippet shows `"claude-opus-4-5"` as the default model, but `apps/api/app/nudge_agent/service.py` uses `"claude-sonnet-4-5"` as the fallback. Update the doc to match the actual code so contributors don't copy the wrong model name.</comment>
<file context>
@@ -1,33 +1,311 @@
+ api_key=settings.ilmu_api_key or settings.anthropic_api_key,
+ base_url=settings.ilmu_anthropic_base_url,
+ )
+ model = settings.ilmu_model or "claude-opus-4-5"
+ ```
+ DeepSeek is the fallback provider in `apps/api/app/nudge_agent/service.py`;
</file context>
| model = settings.ilmu_model or "claude-opus-4-5" | |
| model = settings.ilmu_model or "claude-sonnet-4-5" |
| environments use the pre-installed browser: | ||
|
|
||
| ```js | ||
| const { chromium } = require("playwright"); |
There was a problem hiding this comment.
P2: The Playwright scratchpad script uses require() (CommonJS) at the top level with await (ESM-only), which won't execute as written in either module system. If this is intended as a copy-paste snippet, wrap it in an async IIFE:
(async () => {
const { chromium } = require("playwright");
const browser = await chromium.launch({
executablePath: process.env.PLAYWRIGHT_BROWSERS_PATH
? "/opt/pw-browsers/chromium" : undefined,
});
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
// ...
})();Or use ESM imports: import { chromium } from "playwright";.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/skills/verifier-web/SKILL.md, line 51:
<comment>The Playwright scratchpad script uses `require()` (CommonJS) at the top level with `await` (ESM-only), which won't execute as written in either module system. If this is intended as a copy-paste snippet, wrap it in an async IIFE:
```js
(async () => {
const { chromium } = require("playwright");
const browser = await chromium.launch({
executablePath: process.env.PLAYWRIGHT_BROWSERS_PATH
? "/opt/pw-browsers/chromium" : undefined,
});
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
// ...
})();
Or use ESM imports: import { chromium } from "playwright";.
| const { chromium } = require("playwright"); | ||
| const browser = await chromium.launch({ | ||
| executablePath: process.env.PLAYWRIGHT_BROWSERS_PATH | ||
| ? "/opt/pw-browsers/chromium" : undefined, |
There was a problem hiding this comment.
P2: The Playwright launch snippet uses PLAYWRIGHT_BROWSERS_PATH as a boolean flag rather than using its value, then hardcodes /opt/pw-browsers/chromium. This env var typically holds a directory path, not a binary path, so the hardcoded path will likely be wrong when the env var is set. Consider either (a) omitting executablePath and letting Playwright resolve from the env var automatically, or (b) constructing the path dynamically from the env var value if the actual binary location is known to be predictable in this environment.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/skills/verifier-web/SKILL.md, line 54:
<comment>The Playwright launch snippet uses `PLAYWRIGHT_BROWSERS_PATH` as a boolean flag rather than using its value, then hardcodes `/opt/pw-browsers/chromium`. This env var typically holds a *directory* path, not a binary path, so the hardcoded path will likely be wrong when the env var is set. Consider either (a) omitting `executablePath` and letting Playwright resolve from the env var automatically, or (b) constructing the path dynamically from the env var value if the actual binary location is known to be predictable in this environment.</comment>
<file context>
@@ -0,0 +1,92 @@
+const { chromium } = require("playwright");
+const browser = await chromium.launch({
+ executablePath: process.env.PLAYWRIGHT_BROWSERS_PATH
+ ? "/opt/pw-browsers/chromium" : undefined,
+});
+const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
</file context>
| ## 1. Pre-flight gates — run all, from repo root | ||
|
|
||
| ```bash | ||
| cd apps/api && ruff check app && python -c "from app.main import app; assert app.title"; cd ../.. |
There was a problem hiding this comment.
P3: Pre-flight gates are missing npm run lint -w @bajetbuddy/web. The CI web job runs eslint before the build, so a lint error would pass your local gates and only surface after the CI round-trip, wasting 2+ minutes per push. Add it as a fourth gate (CLAUDE.md's verification section lists it as a separate command).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/skills/ship-pr/SKILL.md, line 11:
<comment>Pre-flight gates are missing `npm run lint -w @bajetbuddy/web`. The CI `web` job runs eslint before the build, so a lint error would pass your local gates and only surface after the CI round-trip, wasting 2+ minutes per push. Add it as a fourth gate (CLAUDE.md's verification section lists it as a separate command).</comment>
<file context>
@@ -0,0 +1,76 @@
+## 1. Pre-flight gates — run all, from repo root
+
+```bash
+cd apps/api && ruff check app && python -c "from app.main import app; assert app.title"; cd ../..
+cd apps/web && npx tsc --noEmit; cd ../..
+npm run build -w @bajetbuddy/web
</file context>
| - Mirror every Pydantic response model in TypeScript. New fields optional: | ||
| `x?: number | null`. | ||
| - This package is source-imported — no build step needed. The web app imports | ||
| it via `@/types`, never via a relative path. |
There was a problem hiding this comment.
P3: The instruction 'The web app imports it via @/types, never via a relative path' is accurate for type-only imports but misses that the codebase also commonly imports from @bajetbuddy/shared directly for constants and other non-type exports. New contributors reading 'never via a relative path' might think import { ... } from "@bajetbuddy/shared" is wrong, when it's an established pattern used in constants.ts, sentinel/engine.ts, and RiskProfileCard.tsx. Consider adding a note that @bajetbuddy/shared is the canonical package name for non-type imports.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/skills/new-feature/SKILL.md, line 78:
<comment>The instruction 'The web app imports it via `@/types`, never via a relative path' is accurate for type-only imports but misses that the codebase also commonly imports from `@bajetbuddy/shared` directly for constants and other non-type exports. New contributors reading 'never via a relative path' might think `import { ... } from "@bajetbuddy/shared"` is wrong, when it's an established pattern used in `constants.ts`, `sentinel/engine.ts`, and `RiskProfileCard.tsx`. Consider adding a note that `@bajetbuddy/shared` is the canonical package name for non-type imports.</comment>
<file context>
@@ -0,0 +1,113 @@
+- Mirror every Pydantic response model in TypeScript. New fields optional:
+ `x?: number | null`.
+- This package is source-imported — no build step needed. The web app imports
+ it via `@/types`, never via a relative path.
+
+## 7. Frontend
</file context>
Summary
CLAUDE.md rewritten as a self-contained operating manual. It no longer imports AGENTS.md; it names the repo's failure modes explicitly and attaches a rule to each:
app/,lib/,public/) as "Mistake #0" — the easiest way for an AI agent to waste an hour editing code that never deploys"use client"), the easing string (Framer Motion tsc failure), function props over the server→client boundaryTypeErrors against prod, direct-Anthropic calls instead of the ilmu proxy,json.loadson raw Claude outputclaude/nextjs-app-router-migration-sEMx8, the check pipeline is a plainGraphStatedataclass (not LangGraph),globals.cssbeatsDESIGN.mdon token conflictsThree skills in
.claude/skills/:new-feature— the mandatory 8-step full-stack recipe (schema → service → route → register → shared types → api.ts → components → page) with the trap each step preventsship-pr— pre-flight gates, the CI-noise triage table (Cloudflare Workers always fails; Netlify visual diffs fail on any UI change; onlyweb/apiare blocking), and the Gemini review-bot protocolverifier-web— launch the app locally with placeholder env (works because proxy.ts and the demo user tolerate missing Supabase), drive it with Playwright at a mobile viewport, hydration double-screenshot check, evidence-based verdictsTest plan
webandapiCI jobs should pass unchanged.proxy.ts,core/auth.py,reasoning_graph.py,globals.css,ci.yml) or from bugs actually hit in past PRs.https://claude.ai/code/session_017cLXjZu6kkjXRf2BemMPpp
Generated by Claude Code
Note
Low Risk
Docs-only; no runtime, auth, or deployment behavior changes.
Overview
Replaces the thin
CLAUDE.mdpointer toAGENTS.mdwith a standalone operating manual for AI agents and contributors: repo map (including the dead root prototype as “Mistake #0”), Next.js 16 / FastAPI conventions, named failure modes with rules, checklists by deliverable type, CI triage, escalation when to ask vs proceed, and copy-paste verify commands.Adds three Claude skills under
.claude/skills/:new-feature(mandatory full-stack step order through verify),ship-pr(pre-flight, draft PR, CI noise table, Gemini review protocol), andverifier-web(local/Netlify targets, Playwright at mobile viewport, hydration and evidence-based PASS/FAIL).No application code, config, or migrations change—documentation and agent workflow only.
Reviewed by Cursor Bugbot for commit 7290e2c. Configure here.
Summary by cubic
Rewrote
CLAUDE.mdinto a self-contained operating manual and added three workflow skills for building, shipping, and verifying changes. Docs-only; no runtime changes and no impact onweborapiCI.New Features
.claude/skills/new-feature: 8-step full-stack flow with traps to avoid..claude/skills/ship-pr: pre-flight gates, CI triage table, review-bot protocol..claude/skills/verifier-web: Playwright verification at mobile viewport with evidence capture.Refactors
CLAUDE.mdis now the canonical manual: repo map (flags the dead root prototype), named failure modes with rules, quality bar per deliverable, precise escalation rules, and corrected stale facts (session branch, non-LangGraph pipeline,globals.csstokens).nudge_agentpath toapps/api/app/nudge_agent/service.py.Written for commit 03aa9c6. Summary will update on new commits.