Skip to content

docs: operating-manual CLAUDE.md + new-feature / ship-pr / verifier-web skills#29

Merged
timothylee58 merged 2 commits into
mainfrom
claude/funny-johnson-bVave
Jul 7, 2026
Merged

docs: operating-manual CLAUDE.md + new-feature / ship-pr / verifier-web skills#29
timothylee58 merged 2 commits into
mainfrom
claude/funny-johnson-bVave

Conversation

@timothylee58

@timothylee58 timothylee58 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

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:

    • The dead root-level prototype (app/, lib/, public/) as "Mistake #0" — the easiest way for an AI agent to waste an hour editing code that never deploys
    • Named frontend traps: the hydration flash (guest mode + localStorage), the silent server crash (missing "use client"), the easing string (Framer Motion tsc failure), function props over the server→client boundary
    • Named backend traps: the unregistered router, the mock Sarah profile, missing-column TypeErrors against prod, direct-Anthropic calls instead of the ilmu proxy, json.loads on raw Claude output
    • Checkable quality bar per deliverable type (any commit / frontend / backend / schema / full-stack), not adjectives
    • Exact escalation ladder: proceed silently → decide-but-flag-in-PR → stop-and-ask (6 hard cases) → report-and-stop
    • Corrects stale facts: per-session branch instead of claude/nextjs-app-router-migration-sEMx8, the check pipeline is a plain GraphState dataclass (not LangGraph), globals.css beats DESIGN.md on token conflicts
  • Three 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 prevents
    • ship-pr — pre-flight gates, the CI-noise triage table (Cloudflare Workers always fails; Netlify visual diffs fail on any UI change; only web/api are blocking), and the Gemini review-bot protocol
    • verifier-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 verdicts

Test plan

  • Docs/skills only — no runtime surface. web and api CI jobs should pass unchanged.
  • Skim CLAUDE.md for factual errors — every rule was derived from the current code (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.md pointer to AGENTS.md with 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), and verifier-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.md into a self-contained operating manual and added three workflow skills for building, shipping, and verifying changes. Docs-only; no runtime changes and no impact on web or api CI.

  • 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.md is 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.css tokens).
    • Fully qualified all file paths from the repo root across docs/skills to prevent editing the dead root prototype; corrected nudge_agent path to apps/api/app/nudge_agent/service.py.

Written for commit 03aa9c6. Summary will update on new commits.

Review in cubic

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
@netlify

netlify Bot commented Jul 7, 2026

Copy link
Copy Markdown

Deploy Preview for bajet-buddy canceled.

Name Link
🔨 Latest commit 03aa9c6
🔍 Latest deploy log https://app.netlify.com/projects/bajet-buddy/deploys/6a4c8bedd49a90000813b8b4

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@timothylee58, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0dc0d0b5-d441-4031-8927-b5cbeb0496a8

📥 Commits

Reviewing files that changed from the base of the PR and between d88add0 and 03aa9c6.

📒 Files selected for processing (4)
  • .claude/skills/new-feature/SKILL.md
  • .claude/skills/ship-pr/SKILL.md
  • .claude/skills/verifier-web/SKILL.md
  • CLAUDE.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/funny-johnson-bVave

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 7, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
bajet-buddy 03aa9c6 Jul 07 2026, 05:18 AM

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread CLAUDE.md Outdated
Comment on lines +77 to +79
**Rule: `"use client"` on the first line of every component that touches
`useState`/`useEffect`/handlers/`window`/`localStorage`. All files in
`hooks/` carry it.**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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).

Suggested change
**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.**

Comment thread CLAUDE.md Outdated
Comment on lines +105 to +106
- Data fetching: Server Components use `fetch()` directly; Client Components
use hooks in `/hooks/` or the typed wrappers in `lib/api.ts`. No React Query.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
- 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.

Comment thread CLAUDE.md Outdated
Comment on lines +107 to +109
- `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.**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
- `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.**

Comment thread CLAUDE.md Outdated

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 →

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
`app/agents/reasoning_graph.py`: `observe → load_context → evaluate_risk →
`apps/api/app/agents/reasoning_graph.py`: `observe → load_context → evaluate_risk →`

Comment thread CLAUDE.md
Comment on lines +127 to +130
- **"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.**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
- **"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.**

Comment thread CLAUDE.md Outdated
- [ ] New fetches: loading skeleton + error state + retry exist

**Backend change (additionally):**
- [ ] New router registered in `main.py`; new deps in `requirements.txt`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
- [ ] 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`

Comment thread CLAUDE.md Outdated
Comment on lines +234 to +237
**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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Fully qualify all paths in the 8-step order checklist to prevent AI agents from navigating to the dead root-level prototype (Mistake #0).

Suggested change
**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.

Comment thread .claude/skills/new-feature/SKILL.md Outdated
Comment on lines +99 to +101
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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.

Comment thread .claude/skills/verifier-web/SKILL.md Outdated
```

Facts that make this work without secrets:
- `proxy.ts` skips session refresh when Supabase env is missing → all routes

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Specify the full path apps/web/proxy.ts instead of proxy.ts to prevent AI agents from looking in the root-level directory.

Suggested change
- `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

Comment thread .claude/skills/verifier-web/SKILL.md Outdated
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
- 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
@timothylee58
timothylee58 marked this pull request as ready for review July 7, 2026 05:42

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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";.

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. ```

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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread CLAUDE.md
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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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";.

@@ -0,0 +1,92 @@ +environments use the pre-installed browser: + +```js +const { chromium } = require("playwright"); +const browser = await chromium.launch({ + executablePath: process.env.PLAYWRIGHT_BROWSERS_PATH ```

const { chromium } = require("playwright");
const browser = await chromium.launch({
executablePath: process.env.PLAYWRIGHT_BROWSERS_PATH
? "/opt/pw-browsers/chromium" : undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ../..

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@timothylee58
timothylee58 merged commit 0d73f7f into main Jul 7, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants