Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
c59bf06
feat(diary): EPIC-13 construction diary schema, types, and ADR (#811)
steilerDev Mar 14, 2026
604fe27
feat(diary): add diary entry data model and CRUD API (#812)
steilerDev Mar 14, 2026
771d807
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 14, 2026
1b1747c
feat(diary): add diary timeline view and detail page (#813)
steilerDev Mar 14, 2026
840309e
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 14, 2026
e72006e
feat(diary): add automatic system event logging to diary (#814)
steilerDev Mar 14, 2026
e7333ec
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 14, 2026
b4ffd61
feat(diary): add entry creation, editing, and deletion UI (#815)
steilerDev Mar 14, 2026
176f907
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 14, 2026
78ecba7
feat(diary): integrate photo attachments on diary entries (#816)
steilerDev Mar 14, 2026
0277d24
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 14, 2026
3112d04
feat(diary): add signature capture for daily log and site visit entri…
steilerDev Mar 14, 2026
76a79e8
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 14, 2026
a7a0729
feat(diary): add PDF export and print stylesheet (#818)
steilerDev Mar 14, 2026
e8a086f
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 14, 2026
cbdb43f
feat(diary): responsive design and accessibility polish (#819)
steilerDev Mar 14, 2026
1f0af50
fix(e2e): fix failing diary E2E tests for promotion (#822)
steilerDev Mar 15, 2026
46549ab
build(docker): optimize multi-arch build performance (#825)
steilerDev Mar 15, 2026
e8a41a5
chore: add env drift check to epic-close workflow (#826)
steilerDev Mar 15, 2026
2eaff72
chore: add UAT feedback loop to epic-close workflow (#827)
steilerDev Mar 15, 2026
9dc4b37
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 15, 2026
82cc85f
fix(diary): address 20 UAT feedback items (#828)
steilerDev Mar 15, 2026
2bc94bc
chore: add test failure diagnostic protocol for structured debugging …
steilerDev Mar 15, 2026
3ac5914
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 15, 2026
756e185
chore(diary): address refinement items for epic #446 (#830)
steilerDev Mar 15, 2026
03d087c
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 15, 2026
4f01668
fix: replace gh pr checks --watch with CI gate polling pattern (#831)
steilerDev Mar 15, 2026
7a3ebf1
ci(release): register GitHub deployments for Docker Hub pushes (#833)
steilerDev Mar 15, 2026
64cd0fc
test(e2e): EPIC-13 diary E2E coverage — fix PUT→PATCH regression, add…
steilerDev Mar 15, 2026
398f513
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 15, 2026
cc0a704
fix(diary): add missing /api/ prefix to exportDiaryPdf endpoint (#835)
steilerDev Mar 15, 2026
04540e5
fix(diary): address 10 UAT feedback items for EPIC-13 (#836-#845) (#846)
steilerDev Mar 15, 2026
f75c826
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 15, 2026
afb2daa
chore: update review metrics for PR #846 (#849)
steilerDev Mar 15, 2026
905c09a
chore: add component reuse policy to agent definitions and CLAUDE.md …
steilerDev Mar 16, 2026
dd95bec
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
fa320b5
chore: create generic Badge component and migrate badge variants (#85…
steilerDev Mar 16, 2026
bbc9871
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
5133492
chore: create generic SearchPicker and consolidate picker implementat…
steilerDev Mar 16, 2026
619a861
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
abf65ed
chore: create generic Modal component and refactor InvoiceLinkModal (…
steilerDev Mar 16, 2026
18b68f7
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
4fb51f8
chore: migrate CSS to design tokens in Sidebar, TagPill, and BudgetBa…
steilerDev Mar 16, 2026
684fea8
chore: create Skeleton and EmptyState shared components (#860) (#861)
steilerDev Mar 16, 2026
067661e
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
0cf6aa3
chore: create FormError and SignatureSection shared components (#862)…
steilerDev Mar 16, 2026
28d3db6
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
4512443
chore: add stylelint token enforcement and update Style Guide (#864) …
steilerDev Mar 16, 2026
90d3957
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
b5c3dfb
fix(diary): implement UAT Round 2 fixes for diary pages (#870)
steilerDev Mar 16, 2026
041349d
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
375b1b6
chore: update review metrics for PR #870 (#874)
steilerDev Mar 16, 2026
0232d0b
fix(diary): fix signature capture and extend signing to all entry typ…
steilerDev Mar 16, 2026
a51f1f6
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
c8e3682
fix(diary): UAT Round 3 polish for detail view and automatic events (…
steilerDev Mar 16, 2026
b61182a
chore: update review metrics for PR #877 (#878)
steilerDev Mar 16, 2026
5d71fe5
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
ab1257d
chore: trigger CI for promotion PR #879 (#880)
steilerDev Mar 16, 2026
b24646c
fix(e2e): update diary source link assertion for R3 changes (#881)
steilerDev Mar 16, 2026
8166d61
chore: trigger CI for final promotion (#882)
steilerDev Mar 16, 2026
a0d0639
fix(e2e): update photo section visibility tests for R3 changes (#884)
steilerDev Mar 16, 2026
d89d28a
chore: trigger final promotion CI (#888)
steilerDev Mar 16, 2026
ea0c427
chore(deps): bump the prod-dependencies group with 6 updates (#887)
dependabot[bot] Mar 16, 2026
a33bf23
chore(deps): bump actions/create-github-app-token from 2 to 3 (#885)
dependabot[bot] Mar 16, 2026
0ea5f48
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
c433374
chore: final CI trigger (#889)
steilerDev Mar 16, 2026
5ca23be
fix(diary): UAT Round 4 fixes for auto-events, delivery metadata, sig…
steilerDev Mar 16, 2026
c97c9f3
chore: trigger CI for promotion (#896)
steilerDev Mar 16, 2026
b4ef0cb
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 16, 2026
bf144bf
chore: CI trigger (#898)
steilerDev Mar 16, 2026
f0466e2
fix(diary): UAT Round 5 polish fixes (#899)
steilerDev Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 99 additions & 4 deletions .claude/agent-memory/e2e-test-engineer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,12 @@ that — check always-rendered elements (containers, summary rows) rather than c
Example: BudgetSourcesPage bar chart `barLegendLabel` only renders for non-zero segments;
use `summaryItem` spans (Total/Available/Planned) for unconditional assertions.

## Dashboard Card Count: 9 not 10 (2026-03-14)
## Dashboard Card Count: 10 (UAT fix #844 added Recent Diary, 2026-03-15)

DashboardPage has 9 CARD_DEFINITIONS. Both desktop grid AND mobile sections container render
ALL cards simultaneously (CSS media queries control visibility, not conditional rendering).
So dismiss button count in DOM = up to 18 (9 × 2 containers). Use `>= 9` not `>= 10`.
DashboardPage has 10 CARD_DEFINITIONS (added 'recent-diary' in UAT fix #844). Both desktop
grid AND mobile sections container render ALL cards simultaneously (CSS media queries control
visibility, not conditional rendering). Dismiss button count in DOM = up to 20 (10 × 2 containers).
Use `>= 10`. DashboardPage POM CARD_TITLES and DashboardCardId type updated to include 'recent-diary'.

## Dashboard Card Persistence After Reload (2026-03-14)

Expand All @@ -102,3 +103,97 @@ POM helper `getSuccessBannerText()` wraps `waitFor` in try/catch, returns null o
This masks failures: `expect(null).toContain(X)` throws confusing error. Use:
`await expect(sourcesPage.successBanner).toBeVisible()` (uses expect.timeout with retry).
Also add `waitForResponse` BEFORE save click — confirms API 200 before checking UI.

## Diary Forms E2E (Story #805, 2026-03-14)

Files: `e2e/pages/DiaryEntryCreatePage.ts`, `e2e/pages/DiaryEntryEditPage.ts`,
`e2e/tests/diary/diary-forms.spec.ts`. DiaryEntryDetailPage.ts extended with edit/delete locators.

Key selectors:

- Create page type cards: `getByTestId('type-card-{type}')` — clicking immediately transitions to form
- Create form: `#entry-date`, `#title`, `#body` (common); `#weather`, `#temperature`, `#workers`
(daily_log); `#inspector-name`, `#inspection-outcome` (site_visit); `#severity`,
`#resolution-status` (issue); `[name="material-input"]` (delivery)
- Create submit: `getByRole('button', { name: /Create Entry|Creating\.\.\./i })`
- Edit page: `getByRole('heading', { level: 1, name: 'Edit Diary Entry' })`
- Edit back: `getByRole('button', { name: /← Back to Entry/i })`
- Edit save: `getByRole('button', { name: /Save Changes|Saving\.\.\./i })`
- Edit delete opens modal: `getByRole('button', { name: 'Delete Entry', exact: true })`
- Detail Edit button: `getByRole('link', { name: 'Edit', exact: true })` (anchor, not button)
- Detail Delete button: `getByRole('button', { name: 'Delete', exact: true })` (NOT "Delete Entry")
- Modal: `getByRole('dialog')` — conditionally rendered; confirmDelete inside modal scope
- Confirm delete: `modal.getByRole('button', { name: /Delete Entry|Deleting\.\.\./i })`
- Edit/Delete buttons NOT rendered for automatic entries (`isAutomatic: true`)
- DiaryEntryEditPage.save() registers waitForResponse (PATCH) BEFORE click — returns after API
NOTE: PR #830 changed updateDiaryEntry from PUT to PATCH — save() was broken; fixed in PR #832

## Diary E2E (Story #804, 2026-03-14)

Files: `e2e/pages/DiaryPage.ts`, `e2e/pages/DiaryEntryDetailPage.ts`,
`e2e/tests/diary/diary-list.spec.ts`, `e2e/tests/diary/diary-detail.spec.ts`.

Key selectors:

- DiaryPage heading: `getByRole('heading', { level: 1, name: 'Construction Diary' })`
- Filter bar: `getByTestId('diary-filter-bar')`, search: `getByTestId('diary-search-input')`
- Type switcher: REMOVED from DiaryPage (UAT fix #840 removed DiaryEntryTypeSwitcher)
- Entry cards: `getByTestId('diary-card-{id}')`, date groups: `getByTestId('date-group-{date}')`
- Type chips: `getByTestId('type-filter-{entryType}')`, clear: `getByTestId('clear-filters-button')`
- Pagination: `getByTestId('prev-page-button')` / `getByTestId('next-page-button')`
- Detail back button: `getByLabel('Go back to diary')` (aria-label="Go back to diary"), back link: `getByRole('link', { name: 'Back to Diary' })`
- Metadata wrappers: `getByTestId('daily-log-metadata|site-visit-metadata|delivery-metadata|issue-metadata')`
- Outcome badge: `getByTestId('outcome-{pass|fail|conditional}')`, severity: `getByTestId('severity-{level}')`
- Automatic badge: `locator('[class*="badge"]').filter({ hasText: 'Automatic' })`

API: `POST /api/diary-entries` returns `DiaryEntrySummary` with `id` at top level (not nested).
Empty state uses shared.emptyState CSS module class (conditional render — use `.not.toBeVisible()` not `.toBeHidden()`).
DiaryPage.waitForLoaded() races: timeline visible OR emptyState visible OR errorBanner visible.

## Photos API Mock Must Return { photos: [] } Not [] (2026-03-15)

`GET /api/photos?entityType=...&entityId=...` returns `{ photos: [] }` (wrapped object).
`getPhotosForEntity()` in `photoApi.ts` does `.then(r => r.photos)` — if mock returns `[]`,
`r.photos` is `undefined` → `setPhotos(undefined)` → `PhotoGrid` crashes on `photos.length`.
ALWAYS mock photos as: `body: JSON.stringify({ photos: [] })` not `body: '[]'`.

## waitForURL on WebKit Tablet: pass `{ timeout: 15_000 }` for navigation after browser-back

Applied to: diary-detail.spec.ts Scenarios 2 and 3.

## Diary E2E Extended (Stories #806-#809, 2026-03-15)

Files: `diary-photos-signatures.spec.ts`, `diary-automatic-events.spec.ts`
POMs extended: DiaryEntryDetailPage (photoHeading, photoEmptyState, signatureSection, photoCountBadge),
DiaryPage (photoCountBadge).
NOTE: diary-export.spec.ts DELETED (UAT fix #845 removed export/print feature).
DiaryEntryDetailPage.printButton locator REMOVED. DiaryPage.exportButton/exportDialog REMOVED.

Key selectors:

- Photo count badge on entry card: `data-testid="photo-count-{entryId}"` (only rendered when photoCount > 0)
- Photo section heading: `[class*="photoHeading"]` — text "Photos (N)"
- Photo empty state: `[class*="photoEmptyState"]` — text "No photos attached yet."
- Signature section: `[class*="signatureSection"]` — conditional render (isSigned entries)
- `isSigned=true` entries (UAT fix #837): Edit hidden, Delete VISIBLE, "Add photos" VISIBLE
- `isAutomatic=true` entries: Edit hidden, Delete hidden, "Add photos" hidden
- Auto events: must mock photos endpoint (`**/api/photos*`) when mocking diary detail entries
- "Add photos" guard is `!isAutomatic` (not `!isAutomatic && !isSigned` as it was before #837)

## Diary UAT Fixes E2E (2026-03-15)

File: `e2e/tests/diary/diary-uat-fixes.spec.ts`

Key behavioral changes validated:

- Post-create navigation: `/diary/:id` (detail, NOT `/diary/:id/edit`) — UAT R2 fix #867 reverted #843
- Detail back button: `getByLabel('Go back to diary')` navigates to `/diary` (NOT browser-back) — #842
- Source link text: `data-testid="source-link-{sourceEntityId}"` shows `sourceEntityTitle` — #842
- Automatic events: flat `<div data-testid="automatic-section-{date}">` with "Automated Events" heading — UAT R2 #868
(was collapsible `<details>/<summary>` in UAT R1 #838 — CHANGED in UAT R2)
- Dashboard "Recent Diary" card: title='Recent Diary', `recentDiaryCard()` helper in DashboardPage POM — #844
- RecentDiaryCard "View All" link only rendered when `entries.length > 0` — mock with ≥1 entry
- New Entry button: `getByRole('link', { name: 'New Entry', exact: true })` (no "+" prefix) — UAT R2 #866-C
- Signed badge on cards: `data-testid="signed-badge-{entryId}"` text "✓ Signed" — UAT R2 #869
- Mode filter chips: `data-testid="mode-filter-all/manual/automatic"` — UAT R2 #866-A
- Photo input on create: `data-testid="create-photo-input"` (file, multiple, accept image/\*) — UAT R2 #867
10 changes: 10 additions & 0 deletions .claude/agent-memory/qa-integration-tester/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
> Detailed notes live in topic files. This index links to them.
> See: `budget-categories-story-142.md`, `e2e-pom-patterns.md`, `e2e-parallel-isolation.md`, `story-358-document-linking.md`, `story-360-document-a11y.md`, `story-epic08-e2e.md`, `story-509-manage-page.md`, `story-471-dashboard.md`

## Modal Component Testing Patterns (2026-03-15, PR #856)

**Test file**: `client/src/components/Modal/Modal.test.tsx` (16 tests)

**Critical: createPortal changes query scope**. `Modal` uses `createPortal(…, document.body)`. The `container` from `render()` is the React root div — it does NOT contain portal content. Use `document.querySelector()` or `baseElement.querySelector()` for portal content. `screen` queries work fine because they query `document.body`.

**Critical: `contentRef` includes the header close button**. The `ref` is on the whole content panel div, which wraps header (close button) + body + footer. `querySelectorAll('button, ...')` on `contentRef.current` finds the close button FIRST (DOM order). So `firstFocusable` is always the close button, not any inputs/buttons in the body children. Do NOT write tests expecting a body input to receive focus on mount.

**Backdrop selector**: `document.querySelector('[class*="modalBackdrop"]')` — identity-obj-proxy returns class names as-is (`modalBackdrop` not a hashed string).

## UAT Fixes #729/#730/#731 Dashboard (2026-03-10)

**Files changed**: deleted `AtRiskItemsCard.test.tsx`; updated `InvoicePipelineCard.test.tsx`, `UpcomingMilestonesCard.test.tsx`, `CriticalPathCard.test.tsx`, `MiniGanttCard.test.tsx`, `DashboardPage.test.tsx`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: diary-uat-fixes
description: Patterns and notes from writing tests for diary UAT fixes (sourceEntityTitle, export removal, RecentDiaryCard, detail page changes)
type: project
---

## Diary UAT Fixes Test Notes (2026-03-15)

**Branch**: `fix/836-845-diary-uat-fixes`
**Commit**: `e29f37c`

### Key changes tested

1. `sourceEntityTitle: string | null` added to `DiaryEntrySummary` — every fixture using this type MUST include the field (TypeScript strict mode rejects missing required fields). Affected: `DiaryEntryCard.test.tsx`, `DiaryEntryDetailPage.test.tsx`, `DiaryPage.test.tsx`.

2. **`/api/diary-entries/export` removed** — Fastify interprets "export" as the `:id` param for `GET /:id`, so the test asserts 404 (service throws `NotFoundError` for unknown id "export"). No route-level 404 — it hits the existing `GET /:id` handler.

3. **`RecentDiaryCard`** — new component at `client/src/components/RecentDiaryCard/`. Props: `{ entries, isLoading, error }`. Loading uses `shared.loading`, error uses `shared.bannerError`. Empty state returns early with `/diary/new` link. Footer links: `+ New Entry` → `/diary/new`, `View All` → `/diary`. Entry items: `data-testid="recent-diary-{entry.id}"`.

4. **Back button navigate(-1) → navigate('/diary')** — test clicks the back button and asserts the `/diary` route renders. Requires MemoryRouter with both routes registered.

5. **Print button removed** — assert `queryByRole('button', { name: /print/i })` is not in document.

### Milestone insert needs `.returning()` for auto-increment id

Milestones table has `id: integer().primaryKey({ autoIncrement: true })`. To get the auto-generated ID:

```ts
const milestone = db.insert(milestones).values({...}).returning({ id: milestones.id }).get();
const milestoneId = String(milestone!.id);
```

### Invoice insert requires vendor FK

`invoices.vendorId` is `NOT NULL`. Always insert a vendor row first before inserting an invoice in service tests.

**Why:** Learned during sourceEntityTitle resolution tests for invoice type.
**How to apply:** When testing invoice-related sourceEntityTitle lookups, insert vendor → invoice → diary entry in that order.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: Story 15.4 — Invoice Budget Lines Section spec decisions
description: Key design decisions for the Invoice Detail Page budget lines section (Issue #606)
type: project
---

Spec posted at https://github.com/steilerDev/cornerstone/issues/606#issuecomment-4020101918

**Why:** EPIC-15 invoice rework needed a dedicated budget lines panel on InvoiceDetailPage.

**How to apply:** Reference when implementing or reviewing InvoiceDetailPage changes.

Key decisions:

- Section placement: between Invoice Details card and LinkedDocumentsSection, full-width `.card`
- Edit modal: old WI/HI pickers removed entirely; modal may shrink from `modalContentWide` (48rem) back to default 28rem
- Remaining row: `var(--color-bg-secondary)` bg, `border-top: 2px solid var(--color-border-strong)` (double weight), italic label
- Remaining color: `var(--color-text-primary)` (>0), `var(--color-success-text-on-light)` (=0), `var(--color-danger-text-on-light)` (<0)
- HI vs WI entity type pill: use `var(--color-role-member-bg)` / `var(--color-role-member-text)` for "HI" discriminator chip
- Picker modal: `min(640px, calc(100vw - 2rem))` — between default and wide
- InvoiceDetailPage.module.css uses `box-shadow: var(--shadow-sm)` for cards (NOT `border: 1px solid`) — diverges from WorkItemDetailPage pattern
4 changes: 2 additions & 2 deletions .claude/agents/backend-developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ For each piece of work, follow this order:
Before considering any task complete, verify:

- [ ] Pre-commit hook passes (triggers on commit: selective tests, typecheck, build, audit)
- [ ] CI checks pass after push (wait with `gh pr checks <pr-number> --watch`)
- [ ] CI checks pass after push (use the **CI Gate Polling** pattern from `CLAUDE.md`)
- [ ] New code is structured for testability (clear interfaces, injectable dependencies)
- [ ] API responses match the contract shapes exactly
- [ ] Error responses use correct HTTP status codes and error shapes from the contract
Expand Down Expand Up @@ -169,7 +169,7 @@ Before considering any task complete, verify:
3. Commit with conventional commit message and your Co-Authored-By trailer (the pre-commit hook runs all quality gates automatically — selective lint/format/tests on staged files + full typecheck/build/audit)
4. Push: `git push -u origin <branch-name>`
5. Create a PR targeting `beta`: `gh pr create --base beta --title "..." --body "..."`
6. Wait for CI: `gh pr checks <pr-number> --watch`
6. Wait for CI using the **CI Gate Polling** pattern from `CLAUDE.md` (beta variant)
7. **Request review**: After CI passes, the orchestrator launches `product-architect` and `security-engineer` to review the PR. Both must approve before merge.
8. **Address feedback**: If a reviewer requests changes, fix the issues on the same branch and push. The orchestrator will re-request review from the reviewer(s) that requested changes.
9. After merge, clean up: `git checkout beta && git pull && git branch -d <branch-name>`
Expand Down
63 changes: 57 additions & 6 deletions .claude/agents/dev-team-lead.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ The spec document you return must follow this structure exactly:
- Each spec must be self-contained — the implementing agent should not need to read the wiki
- Include exact file paths, type signatures, and code patterns
- Reference existing files for patterns rather than describing patterns abstractly
- Frontend specs must reference the shared component library (Badge, SearchPicker, Modal, Skeleton, EmptyState, FormError) where applicable — include which shared components to use in the step-by-step instructions
- If the spec introduces a new UI pattern that resembles an existing shared component, use the shared component instead

## Work Decomposition Rules

Expand Down Expand Up @@ -203,6 +205,8 @@ After the orchestrator routes work to implementation agents, you review all modi
- Check for TypeScript strict mode compliance
- Verify ESM import conventions (`.js` extensions, `type` imports)
- Look for security issues (unsanitized input, missing auth checks, SQL injection)
- Verify shared component usage — if the PR introduces new badge, picker, modal, skeleton, or empty state components instead of using the shared library, flag as CHANGES_REQUIRED
- Verify CSS token compliance — no hardcoded color, spacing, radius, or font-size values (must use `var(--token-name)` from `tokens.css`)

**Return format:**

Expand Down Expand Up @@ -232,6 +236,57 @@ VERDICT: CHANGES_REQUIRED

Each issue in a `CHANGES_REQUIRED` verdict must include enough detail for the orchestrator to route a targeted fix spec to the appropriate agent.

## Test Failure Diagnostic Protocol (Mode: review)

This protocol activates **only** when test failure reports are included in the review input. When all tests pass, skip this section entirely (zero overhead on the happy path).

### Source-of-Truth Hierarchy

**Spec/Contract > Production code > Test code.** A correct test must never be weakened to accommodate buggy production code. Correct production code must never be broken to satisfy a wrong test.

### Decision Tree

When test failures are present in the review input, walk through these steps for each failure:

1. **Read the spec** — Find the relevant acceptance criterion, API contract endpoint, or schema definition that governs the behavior under test. Record the spec reference.
2. **Read the test assertion** — Identify exactly what the test expects (expected value, HTTP status, UI state, etc.).
3. **Read the production code** — Trace the code path that produces the actual result.
4. **Classify the root cause** — Use the table below:

| Test matches spec? | Code matches spec? | Root cause | Fix target |
| ------------------ | ------------------ | ------------------ | --------------------- |
| Yes | No | `CODE_BUG` | Production code |
| No | Yes | `TEST_BUG` | Test code |
| No | No | `BOTH_WRONG` | Both (code first) |
| Yes | Yes | `TEST_ENVIRONMENT` | Test setup/config |
| Ambiguous | — | `SPEC_AMBIGUOUS` | Escalate to architect |

5. **Produce diagnosis** — For each failure, emit a structured diagnosis block (see format below).

### Diagnostic Output Format

Extend the standard `CHANGES_REQUIRED` verdict with diagnosis fields for each test-failure issue:

```
VERDICT: CHANGES_REQUIRED

## Issue 1: <title>
- **File**: <path>
- **Line(s)**: <line numbers>
- **Problem**: <description>
- **Fix**: <exact change needed>
- **Agent**: backend-developer | frontend-developer | qa-integration-tester | e2e-test-engineer
- **Diagnosis**: CODE_BUG | TEST_BUG | BOTH_WRONG | TEST_ENVIRONMENT
- **Reasoning**: <1-2 sentences explaining why this classification was chosen>
- **Spec reference**: <link or excerpt from spec/contract/schema that governs this behavior>
```

### Escalation Rules

- **`SPEC_AMBIGUOUS`** — The spec does not clearly define the expected behavior. Return `VERDICT: ESCALATE_TO_ARCHITECT` instead of `CHANGES_REQUIRED`. Do not produce a fix spec — the product-architect must clarify the spec first, then the review is re-run.
- **`BOTH_WRONG`** — Produce two fix specs: one for production code (routed to backend-developer or frontend-developer) and one for tests (routed to qa-integration-tester or e2e-test-engineer). The orchestrator applies production code fixes first, then test fixes.
- **`TEST_ENVIRONMENT`** — The fix spec targets test setup, fixtures, or configuration — not the test assertions or production code.

## Commit & Push Details (Mode: commit)

1. Stage all changes: `git add <specific-files>` (prefer specific files over `git add -A`)
Expand Down Expand Up @@ -284,11 +339,7 @@ For multi-item batches, include per-item summary bullets and one `Fixes #N` line

### CI Monitoring

Watch CI checks after pushing:

```bash
gh pr checks <pr-number> --watch
```
Watch CI checks after pushing using the **CI Gate Polling** pattern from `CLAUDE.md` (use the beta or main variant based on the PR's target branch).

If CI fails:

Expand Down Expand Up @@ -352,7 +403,7 @@ In `[MODE: commit]`:
2. Stage specific files and commit with conventional message + all contributing agent trailers
3. Push: `git push -u origin <branch-name>`
4. Create PR targeting `beta` (if not already created)
5. Watch CI: `gh pr checks <pr-number> --watch`
5. Watch CI using the **CI Gate Polling** pattern from `CLAUDE.md` (beta variant)
6. If CI fails, return a fix spec (do NOT fix directly)
7. Return PR URL with CI status to orchestrator

Expand Down
Loading
Loading