<!--
  Parsec Sdn. Bhd. · AMIR
  Generated by the Parsec Sdn. Bhd. AI Development Framework v2
  © 2026 Parsec Sdn. Bhd.. All rights reserved.
  Internal use only. Unauthorised reproduction or use outside of
  Parsec Sdn. Bhd.-authorised projects is prohibited.
-->

# CLAUDE.md — AMIR Project Memory

## Behavioral Guidelines

**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.

### Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them — don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.

### Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.

Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.

### Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it — don't delete it.

When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.

The test: Every changed line should trace directly to the user's request.

### Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"

For multi-step tasks, state a brief plan:
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]

Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.

---

## Before Writing Any Code

**Read these files first** — they are auto-loaded by Laravel Boost on every session start, and they encode every architectural rule that applies to AMIR:

1. `.ai/guidelines/project-architecture.md` — Action class pattern, controller rules, domain events, model rules, API resources, Spatie policies, queue/Horizon structure for AMIR specifically.
2. `.ai/guidelines/data-conventions.md` — money (cents), IDs (UUID v7), phones (E.164), timestamps (timestampsTz), enums, encrypted fields, migrations.
3. `.ai/guidelines/testing-standards.md` — `AssertsQueryPerformance` trait, Pest patterns, factory conventions, test folder structure, query-count assertions.

Then run **Boost's live introspection tools** before planning:
- `database_schema` — read the actual current schema; don't assume.
- `application_info` — installed packages and versions.
- `tinker` — verify model relationships and queries before writing code.

Read **`docs/living/`** (all seven files) before planning or troubleshooting. They reflect the actual current state of the codebase, updated every sprint-close.

---

## 1. Project Overview

**AMIR** is an AI-native, Malaysia-first SME accounting SaaS — built first for koperasi (cooperative societies) under SKM regulation. The product is multi-tenant, packs-based (Core + Koperasi Pack #1, with Sukmk and Sukkk packs to follow), and delivered as a single Laravel + Inertia + React app with three role contexts (Tenant Admin, Tenant User, Platform Operator). The koperasi pack is the v1 commercial entry point and the SKM tender (S005/2026) deliverable.

Solo founder + AI-assisted development. Pre-tender demo deadline: 21 May 2026. Full v1 (production koperasi pilot) target: ~25 August 2026.

---

## 2. Tech Stack (versions are read live by Boost — recorded here for reference)

| Layer | Choice | Notes |
|---|---|---|
| Language | PHP 8.3 | Strict types enabled in every domain file |
| Framework | Laravel 12 | Composer `"laravel/framework": "^12.0"` |
| Database | PostgreSQL 16 | Malaysia residency (AWS ap-southeast-3) |
| Cache + Queue | Redis 7 | Sessions, cache, Horizon queue |
| Frontend | Inertia + React 18 + Tailwind 3 | Single-page app, server-rendered routes |
| Auth | Sanctum (stateful sessions) + Spatie Permission v6 + Fortify | Cookie-based, CSRF-protected |
| Queue | Laravel Horizon | Single Redis-backed queue, multiple supervisors |
| Storage | S3-compatible | Malaysia region preferred |
| Audit log | spatie/laravel-activitylog v4 | Auto-logging on configured models |
| Testing | Pest | Browser tests via Playwright |
| Error tracking | Sentry (managed) | PII-scrubbed via beforeSend; replays disabled |

---

## 3. Domain Folder Structure

Every business domain owns a folder under `app/Domain/[Name]/`. The 20 modules from ARCHITECTURE.md map to these domains:

```
app/Domain/
  Auth/            (Module A — auth, sessions, MFA, login audit)
  Onboarding/      (Module B — tenant setup wizard)
  Parties/         (Module C — debtors, creditors, banks, auditors)
  Accounting/      (Module D — CoA, periods, journal entries, trial balance)
  Transactions/    (Module E — receipt, payment, journal, contra)
  Banking/         (Module F — bank reconciliation)
  FixedAssets/     (Module G — assets + depreciation)
  Inventory/       (Module H)
  Members/         (Module I — Koperasi pack: members, shares, subscriptions)
  ArRahnu/         (Module J — Islamic pawn, gold valuation, auctions)
  EInvoice/        (Module K — MyInvois, LHDN integration)
  Reporting/       (Module L — Penyata Kewangan, Trial Balance, P&L, etc.)
  Surplus/         (Module M — Koperasi pack: surplus distribution, AGM)
  Signals/         (Module N — anomaly + health-score + LLM explanations)
  Oversight/       (Module O — PO cross-tenant view, SKM-facing)
  Operations/      (Module P — PO ops: tenants, packs, billing, alerts)
  Integrations/    (Module Q — generic integration framework)
  DataMigration/   (Module R — import/export, opening balances)
  Pdpa/            (Module S — DSAR, breach, retention, portability)
  Tax/             (Module T — Cukai Pendapatan + Zakat draft computation)
```

Inside each domain folder:
```
app/Domain/[Name]/
  Actions/         — single-purpose action classes (one per business operation)
  Events/          — domain events emitted by actions
  Models/          — Eloquent models for this domain only
  Enums/           — backed enums for status/type values
  Resources/       — API/Inertia resources
  Requests/        — FormRequest classes for input validation
  Policies/        — Spatie-aware authorisation policies
  Jobs/            — queued jobs (one per async operation)
  Listeners/       — domain event listeners
  Services/        — only for genuinely shared logic (avoid; prefer actions)
```

**Cross-domain calls are explicit via Actions, never via direct model coupling.** `Members\Actions\CreateMember` may dispatch a `MemberRegistered` event that `Accounting\Listeners\AllocateMemberAccount` listens to — but `Members\Models\Member` does not import `Accounting\Models\Account`.

---

## 4. Code Pattern Examples

### Controller (thin — delegates to Action)
```php
<?php
declare(strict_types=1);

namespace App\Http\Controllers\Members;

use App\Domain\Members\Actions\CreateMember;
use App\Domain\Members\Requests\CreateMemberRequest;
use App\Domain\Members\Resources\MemberResource;
use Illuminate\Http\JsonResponse;

final class CreateMemberController
{
    public function __invoke(CreateMemberRequest $request, CreateMember $action): JsonResponse
    {
        $member = $action->execute(
            tenantId: $request->user()->currentTenantId(),
            data: $request->validated(),
        );

        return MemberResource::make($member)->response()->setStatusCode(201);
    }
}
```

### Action class (single public `execute` method, transactional)
```php
<?php
declare(strict_types=1);

namespace App\Domain\Members\Actions;

use App\Domain\Members\Events\MemberRegistered;
use App\Domain\Members\Models\Member;
use Illuminate\Support\Facades\DB;

final class CreateMember
{
    public function execute(string $tenantId, array $data): Member
    {
        return DB::transaction(function () use ($tenantId, $data): Member {
            $member = Member::create([
                'tenant_id' => $tenantId,
                'ic_no_encrypted' => $data['ic_number'],   // input field 'ic_number' → storage column 'ic_no_encrypted' (per E3)
                'full_name' => $data['full_name'],
                'phone_e164' => $data['phone_e164'],
                'shares_held' => 0,
                'subscription_balance_cents' => 0,
                'status' => MemberStatus::Active,
            ]);

            event(new MemberRegistered($member->id, $tenantId));

            return $member->fresh();
        });
    }
}
```

### FormRequest (validation only — no business logic)
```php
<?php
declare(strict_types=1);

namespace App\Domain\Members\Requests;

use Illuminate\Foundation\Http\FormRequest;

final class CreateMemberRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', \App\Domain\Members\Models\Member::class);
    }

    public function rules(): array
    {
        return [
            'ic_number' => ['required', 'string', 'regex:/^\d{6}-\d{2}-\d{4}$/'],
            'full_name' => ['required', 'string', 'max:255'],
            'phone_e164' => ['required', 'string', 'regex:/^\+60\d{9,10}$/'],
        ];
    }
}
```

---

## 5. Data Conventions Summary

| Convention | Rule | Example |
|---|---|---|
| Money | Always integer **cents** in `BIGINT` columns named `*_cents`. Never `decimal(N,2)`. | `total_amount_cents BIGINT NOT NULL` |
| IDs | **UUID v7** primary keys, generated application-side via `HasUuids` trait override. Never auto-increment. | `id UUID PRIMARY KEY` |
| Foreign keys | UUID FKs with explicit `ON DELETE` strategy (RESTRICT default, CASCADE only when child cannot exist independently). | `tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE RESTRICT` |
| Status | Backed PHP enum + `string` column; never raw strings in code. | `MemberStatus::Active`, column `status VARCHAR(32)` |
| Phones | E.164 format in `phone_e164` columns, `+60123456789` style. | Validated by regex `/^\+60\d{9,10}$/` |
| Timestamps | `timestampsTz()` on every table — always timezone-aware. UTC in DB, MYT for display. | `created_at TIMESTAMPTZ NOT NULL` |
| Soft deletes | `softDeletes()` on entities with audit-trail lifecycle. | `deleted_at TIMESTAMPTZ NULL` |
| Tenant scope | Every tenant-scoped table has `tenant_id UUID NOT NULL`. Use `BelongsToTenant` trait — never manual `where('tenant_id', ...)`. | See `app/Concerns/BelongsToTenant.php` |
| Encrypted fields | Sensitive identifiers (NRIC, bank account numbers, TINs) use the **two-column pattern from E3**: `*_encrypted` for ciphertext + `*_hash` for SHA-256 lookup with per-tenant salt. MFA secrets and integration credentials use the single-column `'encrypted'` cast. | `protected $casts = ['ic_no_encrypted' => 'encrypted']` |
| Audit | spatie/laravel-activitylog **v5** on every model that needs an audit trail. Import: `Spatie\Activitylog\Models\Concerns\LogsActivity`. Query pattern: `Activity::query()->where('subject_type', Foo::class)->where('subject_id', $id)->count()` — NOT `activity()->forSubject()->count()` (v5 removed that API). | `use LogsActivity;` |

Full rules in `.ai/guidelines/data-conventions.md`.

---

## 6. Git Rules

### Branch naming
`feature/s{N}-{NN}-{slug}` — example: `feature/s06-01-member-master-crud`
`fix/{slug}` for bug-fix branches off `dev`
`hotfix/{slug}` for emergency patches off `main`

### Commit message format
```
[type]([scope]): [description]
.--. .- .-. ... . -.-.

[body — optional, only if context > 1 line]
```

The `.--. .- .-. ... . -.-.` Morse separator is **mandatory** on every commit. The pre-commit guard hook hard-blocks commits without it.

`[type]` ∈ `feat`, `fix`, `chore`, `refactor`, `docs`, `test`, `style`, `build`, `ci`.
`[scope]` is the domain folder (e.g. `members`, `accounting`) or `core` for cross-cutting.

**Forbidden:**
- `Co-Authored-By:` lines (hard-blocked by pre-commit guard)
- Commits referencing the agent in the message body
- Commits with debug artifacts (`dd()`, `dump()`, `console.log`, `var_dump()`) — hard-blocked

### PR rules
- Every PR closes one task in `.sprint-backlog.json`
- PR description follows the template in `.claude/skills/writing-pr-descriptions.md`
- Run `/verify` before opening the PR — 13 steps, mandatory
- Every PR must have an associated test (no test = no merge)

---

## 7. DO NOT List (architecture violations specific to AMIR)

- ❌ Never query a tenant-scoped model without `BelongsToTenant` trait (silent data leak across tenants)
- ❌ Never use `decimal` for money — only integer cents in `BIGINT`
- ❌ Never store NRIC in plaintext — use `encrypted` cast
- ❌ Never skip `tenant_id` on a new tenant-scoped table (caught by `post-edit-tenant-scope-check.sh` hook)
- ❌ Never mutate financial data (transactions, journal entries, posted amounts) directly — always via the appropriate Action class so audit log fires
- ❌ Never use raw `where('status', 'XYZ')` — use the backed enum: `where('status', MemberStatus::Active)`
- ❌ Never reproduce song lyrics, copyrighted material, real public-figure quotes anywhere in the codebase including comments and seed data
- ❌ Never let frontend ALL-CAPS strings drift from PHP enums (caught by `post-edit-enum-sync-check.sh` hook)
- ❌ Never write a migration that drops a column without a deprecation sprint first (see `.claude/skills/writing-migrations.md`)
- ❌ Never use `console.log` / `dd()` / `dump()` in code that goes to PR — hard-blocked at commit
- ❌ Never include PII in analytics events or Sentry breadcrumbs (caught by `PIIGuard` service + `beforeSend` scrubber)
- ❌ Never bypass `TenantScope` without the explicit `withoutGlobalScope(TenantScope::class)` call AND a justification comment
- ❌ Never call `$request->session()` in middleware without first checking `$request->hasSession()` — API routes (registered via `api:` in `withRouting()`) have no session and will throw `RuntimeException: Session store not set on request`
- ❌ Never ship an Action / Listener / Policy class with zero callers — wire it into a Controller, Job, or event listener, or delete it (caught by `dead-code-scanner.sh` hook)
- ❌ Never ship a React component in `resources/js/components/` that isn't imported by a page or layout — mount it where it's used or remove it (caught by `orphan-component-scanner.sh` hook)
- ❌ Never add a migration column without a corresponding model attribute, Resource field, or Action write — inert columns are debt, not future-proofing (caught by `column-consumer-audit.sh` hook)

---

## 8. Design System Compliance

**Before building any page or UI component, read `UI_UX_SPEC.md` Section 7.4 (Component Inventory).**

### Component Usage (mandatory)
- Use design system components for ALL UI. Never build raw HTML when a component exists.
  Use `<Button>`, not `<button>`. Use `<DataTable>`, not `<table>`.
  Use `<FormField>`, not `<label>` + `<input>` + `<span class="error">`.
  Use `<StatusBadge status={x}>`, not `<span className="badge badge-success">`.
- If you need a component that doesn't exist, flag it — do not build an ad-hoc version.
  Write: "MISSING COMPONENT: need [description]. Should this be added to the design system?"
- Every page must use a layout pattern from Section 7.4.4 (L0–L5). Do not invent new layouts.

### Tokens (mandatory)
- Never hardcode colour values. Use design tokens: `var(--color-primary)`, `text-primary`, etc.
- Never hardcode spacing values. Use tokens: `var(--space-4)`, `p-4`, `gap-6`, etc.
- Never hardcode font sizes. Use tokens: `var(--text-sm)`, `text-sm`, etc.
- Never hardcode border radius, shadows, or breakpoints. Use tokens.

### Status Badges (mandatory)
- Every status value must use `<StatusBadge>` with the status enum value.
- StatusBadge reads from `STATUS_CONFIG` (see CONTENT_COPY.md §4) — never choose badge colours manually.
- If a status value is missing from `STATUS_CONFIG`, add it there first — not inline.

### BM-EN parity (mandatory)
- All user-facing strings exist in both BM and EN per CONTENT_COPY.md.
- Use the `__()` helper for translation. Never hardcode user-visible strings.
- For dynamic content, use locale-aware formatters (date, number, currency).

### Before submitting frontend work, verify:
- [ ] Every UI element maps to a design system component
- [ ] Zero hardcoded colour/spacing/typography values
- [ ] Page uses a standard layout pattern (L0–L5)
- [ ] All status displays use `StatusBadge` with enum values
- [ ] No duplicate component implementations (grep for similar patterns)
- [ ] Every user-facing string uses `__()` and exists in both BM and EN

---

## 9. Performance Rules

Every integration test **must** assert query performance. The `AssertsQueryPerformance` trait provides:
- `assertQueryCountLessThan(int $max)` — guards against N+1
- `assertNoDuplicateQueries()` — guards against repeated identical queries
- `assertNoSlowQueries(int $thresholdMs = 100)` — guards against unindexed scans

Default budget: **dashboard endpoints ≤ 15 queries**, **list endpoints ≤ 20 queries**, **detail endpoints ≤ 10 queries**, **post-action endpoints ≤ 25 queries**. Stricter budgets per endpoint live in the test file.

For dashboard widget queries, always use precomputed aggregates from `analytics_aggregates_daily` — never raw `analytics_events`.

---

## 10. Self-Verification Protocol (`/verify` — 13 steps)

Run before every PR. Each step must pass before advancing.

1. **Read the task** — re-read the task in `.sprint-backlog.json`. Confirm acceptance criteria.
2. **Run tests** — `php artisan test --filter=...` for the new test. Confirm green.
3. **Format** — `./vendor/bin/pint` (auto-applied by `post-edit-format.sh` but verify).
4. **Static analysis** — `./vendor/bin/phpstan analyse` (or larastan). Zero new errors.
5. **Frontend lint** — `npm run lint` if any frontend changes. Zero new errors.
6. **Frontend build** — `npm run build` succeeds.
7. **Tenant scope check** — every new model uses `BelongsToTenant` (or has a `// platform-scoped` comment).
8. **Code quality — LLM bloat check** (per `.claude/skills/reviewing-code.md`). Single-use helpers, defensive nulls for non-nullable types, single-implementation abstractions, commented-out code, unconnected scaffolding — all removed.
9. **Audit log fires** — for every model write that requires audit trail, confirm the test asserts the activity log entry.
10. **Run full suite** — `php artisan test` passes (not just the new test).
11. **Simplify check** — run `/simplify` on the changed files. Apply suggested cleanups.
12. **Final diff review** — re-read the entire diff. Ask: would a senior engineer approve this?
13. **Living docs** — if domain boundaries, schema, or APIs changed, update `docs/living/`.

If any step fails, fix and restart from step 1.

---

## 11. Boost Tools Reference

Laravel Boost provides live introspection. Use these instead of guessing:

| Tool | When to use |
|---|---|
| `database_schema` | Before writing a migration. Confirms the actual current schema. |
| `application_info` | Confirms installed packages and versions. |
| `tinker` | Verify Eloquent relationships and query results before writing code. |
| `last_error` | Read the most recent application error if a test or browser action fails. |
| `read_log` | Tail the Laravel log. |
| `browser_logs` | Read browser console output during a Playwright run. |
| `list_routes` | List routes for the current app. |
| `get_config` | Read config values without restarting. |
| `list_artisan_commands` | Discover available Artisan commands. |

Never assume schema, package versions, route names, or config values. Use Boost.

---

## 12. Current Sprint / Focus

**Sprint:** S01 (Phase A — Foundation). L3 pipeline proven. PRs #86–#91 open for founder review.
**Goal:** Agent scaffolding verified — team-lead, backend-dev, frontend-dev, test-writer, code-reviewer agents; dispatch.sh; sprint commands; User Invitation as canonical L5 pipeline run.
**Level:** L3 (Manual). One agent at a time. Founder reviews every PR.
**Backlog ID format:** `S{NN}.{NN}` for production sprints. `scope_tag` filtering: only `TENDER` and `TENDER_DONE` tasks are visible — `COMMERCIAL_FORK` tasks are hidden from all sprint commands.

After S01–S05 (Phase A):
- S06-S40 (Phases B-K): Module build-out (35 sprints, L5 stable)
- S41-S42 (Phase L): Hardening + pilot-ready

---

## 13. Key Files

### Planning documents (the source of truth, never auto-edit)
- `PROJECT_CONTEXT.md`, `VIABILITY_REPORT.md`, `BUSINESS_FLOWS.md`, `USER_STORIES.md`, `ARCHITECTURE.md`, `DECISIONS_LOG.md`, `UI_UX_SPEC.md`, `SCREEN_BRIEFS.md`, `TEST_SCENARIOS.md`, `ACCOUNTING_INVARIANTS.md`, `CONTENT_COPY.md`, `ANALYTICS_PLAN.md`, `SPRINT_PLAN.md`

### Operating documents (used every session)
- `.sprint-backlog.json` — the task queue
- `.ai/guidelines/*.md` — auto-loaded by Boost
- `.claude/rules/laravel.md`, `.claude/rules/AGENT_GUIDE.md` — auto-loaded every session
- `.claude/skills/*.md` — loaded on demand by slash commands
- `.claude/hooks/*.sh` — fire automatically on lifecycle events
- `.claude/settings.json` — permissions, model, hooks config

### Living codebase documents (kept current — read before planning)
- `docs/living/CODEBASE_MAP.md` — domain boundaries and class index
- `docs/living/DATA_MODEL.md` — current schema snapshot
- `docs/living/DOMAIN_GUIDE.md` — state machines and business rules
- `docs/living/API_REFERENCE.md` — current API surface
- `docs/living/VELOCITY_LOG.md` — sprint timing data
- `docs/living/DEVIATION_LOG.md` — places where the implementation deviated from the plan
- `docs/living/PRINCIPLES.md` — what we learned about this codebase

**Read all seven files in `docs/living/` before planning or troubleshooting. They reflect the actual current state of the codebase.**

---

## 14. Common Pitfalls (discovered in S01)

These are non-obvious mistakes that cost debugging time during S01. Read before coding anything in the Auth domain or adding new middleware.

### spatie/laravel-activitylog v5 API
The installed version is **v5**, not v4. Key differences from old docs:
- **Correct import:** `Spatie\Activitylog\Models\Concerns\LogsActivity` (not `Spatie\Activitylog\Traits\LogsActivity`)
- **Query pattern in tests:** `Activity::query()->where('subject_type', MyModel::class)->where('subject_id', $id)->count()` — the `forSubject()` scope was removed in v5
- **`logOnly([...])` + `logOnlyDirty()`** is the right config in `getActivitylogOptions()`; never `logAll()`

### External security tokens ≠ UUID v7 PKs
PKs use UUID v7 via `HasUuids` trait override. But **external tokens** (invitation tokens, password-reset tokens, email-verification tokens) use **UUID v4** (`Str::uuid()->toString()`) because they're security-sensitive and must be unpredictable. The plaintext is sent to the user; only the SHA-256 hash is stored in the database.

### API routes have no session
Routes registered via `api:` in `bootstrap/app.php`'s `withRouting()` use the `api` middleware group, which has no session. Any middleware that calls `$request->session()` will throw `RuntimeException: Session store not set on request`. Always guard with `$request->hasSession()` first. See `SetTenantContext` middleware for the canonical fix.

### First API routes file
The project's first `routes/api.php` was added in S01-010. All API routes go here under `Route::prefix('v1')` to produce `/api/v1/` URLs. Register new route groups inside that prefix — don't create separate `routes/api_v2.php` or similar files.

### scope_tag filtering in sprint commands
`.sprint-backlog.json` contains three scope tags: `TENDER` (227 tasks, sprints 1–19), `TENDER_DONE` (sprint 0), and `COMMERCIAL_FORK` (298 tasks, sprints 107–142). The sprint commands (`/sprint-status`, `/sprint-start`, `/task-done`) filter to `TENDER` and `TENDER_DONE` only. If you add a raw `jq` query against `.sprint-backlog.json`, include the filter: `select(.scope_tag == "TENDER" or .scope_tag == "TENDER_DONE")`.

### L5 dispatch workflow — agent invocation
When using `/team-launch` (Level 5), the sequence is:
1. **team-lead** (Sonnet, effort:high) — reads task brief, decomposes, writes sub-task specs for each specialist
2. **backend-dev** + **frontend-dev** (parallel, Sonnet) — implement their respective sub-tasks
3. **test-writer** (Sonnet) — adds integration + unit tests after implementation is stable
4. **code-reviewer** (Sonnet) — reviews the full diff; blocks on violations; approves if clean

Each agent reads its spec from `.claude/agents/[role].md`. Specs are in `.claude/agents/`. Do not invoke agents directly with ad-hoc prompts — always go through the skill which loads the spec.

---

## Workspace: tmux Preview Pane

This project runs in a 4-agent tmux workspace launched by `tmux-work` in the project root.
The right panel is a dedicated preview shell. Its pane ID is available as `$TMUX_PREVIEW_PANE` in every agent session.

To surface a file for the developer to review, push it to the preview pane:
```bash
tmux send-keys -t "$TMUX_PREVIEW_PANE" 'q' C-m "preview \"path/to/file\"" C-m
```

Supported file types: `.md`, `.pdf`, `.docx`, `.xlsx`, `.pptx`, and any plain text or code file.

Use this when you produce a document the developer should read, finish a feature the developer should review visually, or are asked to "preview", "show", or "open" a file.

---

## Decisions Trace

This file's rules trace back to decisions in `DECISIONS_LOG.md`. The full trace is in `.ai/guidelines/project-architecture.md` §16, `.ai/guidelines/data-conventions.md` §15, and `.ai/guidelines/testing-standards.md` §17. The most consequential decisions to know:

- **D15-R1** — UUID v7 PKs (irreversible)
- **D16** — Money cents + MoneyCast (irreversible)
- **D17** — UTC storage, MYT display (irreversible)
- **D28** — `tenant_id` + `BelongsToTenant` (costly)
- **D30** — Action class pattern (costly)
- **D31** — Single-action controllers (costly)
- **D32** — `final class` mandate (reversible)
- **D33** — Phone E.164 in `phone_e164` columns (costly)
- **E1** — User-tenant junction table (irreversible)
- **E3** — Two-column encryption pattern (irreversible)

If you want to deviate from any rule, raise a revision (`Dxx-R1`) in `DECISIONS_LOG.md` first. Do not silently drift in a single PR.
