@xhulz/nina 0.28.19 → 0.28.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/releases/0.28.20/core/GAPS.md +125 -0
- package/releases/0.28.20/core/answered.json +29 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/qa.md +2 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.28.20/core/tree/.claude/agents/architect.md +125 -0
- package/releases/0.28.20/core/tree/.claude/agents/dba.md +107 -0
- package/releases/0.28.20/core/tree/.claude/agents/devops.md +113 -0
- package/releases/0.28.20/core/tree/.claude/agents/implementer.md +155 -0
- package/releases/0.28.20/core/tree/.claude/agents/integration-tester.md +160 -0
- package/releases/0.28.20/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.28.20/core/tree/.claude/agents/qa.md +146 -0
- package/releases/0.28.20/core/tree/.claude/agents/reviewer.md +180 -0
- package/releases/0.28.20/core/tree/.claude/agents/secops.md +121 -0
- package/releases/0.28.20/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.28.20/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.28.20/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.28.20/core/tree/.claude/graph.md +73 -0
- package/releases/0.28.20/core/tree/.claude/patterns.md +212 -0
- package/releases/0.28.20/core/tree/.claude/pills/README.md +145 -0
- package/releases/0.28.20/core/tree/.claude/pipeline.md +102 -0
- package/releases/0.28.20/core/tree/.claude/retrieval.md +104 -0
- package/releases/0.28.20/core/tree/.claude/router.md +201 -0
- package/releases/0.28.20/core/tree/.claude/templates/integration.md +45 -0
- package/releases/0.28.20/core/tree/CLAUDE.md +155 -0
- package/releases/0.28.20/core/tree/scripts/edit-guard.mjs +16 -0
- package/releases/0.28.20/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.28.20/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.28.20/core/vocabulary.json +12 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/router.md +9 -0
- package/releases/0.28.20/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/architect.md +8 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/dba.md +84 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/devops.md +14 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/implementer.md +5 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/integration-tester.md +13 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/reviewer.md +25 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/patterns.md +29 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/router.md +10 -0
- package/releases/0.28.20/surfaces/db/tree/CLAUDE.md +23 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/devops.md +31 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/router.md +21 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/reviewer.md +62 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/patterns.md +15 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/retrieval.md +5 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/router.md +17 -0
- package/releases/0.28.20/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/graph.md +9 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.28.20/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.28.20/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.28.20/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.28.21/core/GAPS.md +125 -0
- package/releases/0.28.21/core/answered.json +29 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/qa.md +2 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.28.21/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.28.21/core/tree/.claude/agents/architect.md +125 -0
- package/releases/0.28.21/core/tree/.claude/agents/dba.md +107 -0
- package/releases/0.28.21/core/tree/.claude/agents/devops.md +113 -0
- package/releases/0.28.21/core/tree/.claude/agents/implementer.md +155 -0
- package/releases/0.28.21/core/tree/.claude/agents/integration-tester.md +160 -0
- package/releases/0.28.21/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.28.21/core/tree/.claude/agents/qa.md +146 -0
- package/releases/0.28.21/core/tree/.claude/agents/reviewer.md +180 -0
- package/releases/0.28.21/core/tree/.claude/agents/secops.md +121 -0
- package/releases/0.28.21/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.28.21/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.28.21/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.28.21/core/tree/.claude/graph.md +73 -0
- package/releases/0.28.21/core/tree/.claude/patterns.md +212 -0
- package/releases/0.28.21/core/tree/.claude/pills/README.md +145 -0
- package/releases/0.28.21/core/tree/.claude/pipeline.md +102 -0
- package/releases/0.28.21/core/tree/.claude/retrieval.md +104 -0
- package/releases/0.28.21/core/tree/.claude/router.md +201 -0
- package/releases/0.28.21/core/tree/.claude/templates/integration.md +45 -0
- package/releases/0.28.21/core/tree/CLAUDE.md +155 -0
- package/releases/0.28.21/core/tree/scripts/edit-guard.mjs +16 -0
- package/releases/0.28.21/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.28.21/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.28.21/core/vocabulary.json +12 -0
- package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.28.21/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.28.21/surfaces/blockchain/tree/.claude/router.md +9 -0
- package/releases/0.28.21/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/agents/architect.md +8 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/agents/dba.md +84 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/agents/devops.md +14 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/agents/implementer.md +5 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/agents/integration-tester.md +13 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/agents/reviewer.md +25 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/patterns.md +29 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.28.21/surfaces/db/tree/.claude/router.md +10 -0
- package/releases/0.28.21/surfaces/db/tree/CLAUDE.md +23 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/devops.md +31 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/.claude/router.md +21 -0
- package/releases/0.28.21/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/reviewer.md +62 -0
- package/releases/0.28.21/surfaces/frontend/tree/.claude/patterns.md +15 -0
- package/releases/0.28.21/surfaces/frontend/tree/.claude/retrieval.md +5 -0
- package/releases/0.28.21/surfaces/frontend/tree/.claude/router.md +17 -0
- package/releases/0.28.21/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/graph.md +9 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.28.21/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.28.21/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.28.21/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.28.21/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.28.21/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.28.21/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.28.21/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.28.21/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.21/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.28.21/surfaces/pii/tree/CLAUDE.md +5 -0
- package/src/commands/pipeline.mjs +2 -4
- package/src/commands/stats.mjs +83 -48
- package/src/commands/upgrade.mjs +19 -4
- package/src/look.mjs +118 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<!-- nina:slot frontend.2 -->
|
|
2
|
+
- **If the diff touches `{{APP_DIR}}/**`: the spec must carry a Visual acceptance section, and you check the render against it — see *Visual gate* below.** No section → REJECT upstream to the architect.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot frontend.3 -->
|
|
5
|
+
- `{{BUILD_CMD}}` for the affected frontend packages (`{{APP_DIR}}`)
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot frontend.4 -->
|
|
8
|
+
## Visual gate — you are the stage that looks at the screen
|
|
9
|
+
|
|
10
|
+
Deciding whether a screen is right is review, not operations, and it belongs here. qa runs vitest in
|
|
11
|
+
jsdom, which has **no layout engine**: `toHaveClass('flex')` passes whether or not a single pixel
|
|
12
|
+
landed anywhere. Every gate before you reads text. This is why backend work lands first time through
|
|
13
|
+
this pipeline and frontend work does not — the frontend defects were never expressible as a test.
|
|
14
|
+
|
|
15
|
+
For any diff touching `{{APP_DIR}}/**`:
|
|
16
|
+
|
|
17
|
+
1. Build and serve it — you already run `{{BUILD_CMD}}` for frontend diffs; serve that build locally.
|
|
18
|
+
Every protected screen redirects to `/login` without a session, so a plain static server
|
|
19
|
+
screenshots the login page. Use `scripts/visual-fixture-server.mjs`, which serves the built
|
|
20
|
+
`dist` AND a canned API from ONE origin (no auth, no DB, no backend):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
export PATH=/opt/homebrew/opt/node@22/bin:$PATH
|
|
24
|
+
cd {{APP_DIR}} && VITE_API_BASE_URL=http://localhost:5199 npx vite build --outDir /tmp/dist-vis
|
|
25
|
+
node ../../scripts/visual-fixture-server.mjs /tmp/dist-vis 5199
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`VITE_API_BASE_URL` must match the fixture's origin at BUILD time — Vite inlines it, and a
|
|
29
|
+
mismatch ships a screen that renders but fetches nothing. The fixture logs `UNSTUBBED: <path>`
|
|
30
|
+
for any endpoint it does not know; add a handler there rather than screenshotting a broken
|
|
31
|
+
screen. `FIXTURE_EMPTY=1` switches list endpoints to `[]` to exercise empty states, and
|
|
32
|
+
`FIXTURE_ERROR=<CODE>` fails every write with that error code so an error state is
|
|
33
|
+
reachable at all — otherwise reaching one means finding real data that collides, which
|
|
34
|
+
usually means you cannot reach it.
|
|
35
|
+
2. `browser_navigate` to the changed screen. Screenshot at **1440** and at **375**
|
|
36
|
+
(`browser_resize` first — the viewport resets to a narrow default across navigations, so
|
|
37
|
+
resize AFTER navigating and confirm the width in the screenshot before you judge it).
|
|
38
|
+
|
|
39
|
+
Many screens are not reachable by URL. Tabs on `/identification` and `/settings` are local
|
|
40
|
+
React state, never reflected in the route, so `browser_click` is the ONLY way in — a
|
|
41
|
+
navigate-and-screenshot lands on the default tab and proves nothing about the changed one.
|
|
42
|
+
3. **Measure, do not squint.** `browser_evaluate` turns "looks fine" into a number, and an
|
|
43
|
+
overflow you can measure is one you can attribute. To decide whether horizontal scroll is
|
|
44
|
+
yours or pre-existing, measure `document.documentElement.scrollWidth` vs `clientWidth` on
|
|
45
|
+
the changed screen AND on an untouched one (`/dashboard`) — and, when it matters, build the
|
|
46
|
+
parent commit into a second dist and measure both. Reporting a pre-existing app-wide defect
|
|
47
|
+
as a regression wastes a cycle; excusing a real one as "probably pre-existing" ships it.
|
|
48
|
+
4. Check each screenshot against the spec's **Visual acceptance** list, item by item. Not "looks
|
|
49
|
+
fine" — the specific claims the spec made.
|
|
50
|
+
5. `browser_console_messages`. An error there is a defect even when the page renders.
|
|
51
|
+
|
|
52
|
+
A screen that contradicts the spec is `REJECTED`, with the screenshot and what is wrong in it. Say in
|
|
53
|
+
your report which widths you looked at; a frontend approval that does not mention having looked is
|
|
54
|
+
the failure this section exists to end.
|
|
55
|
+
|
|
56
|
+
<!-- nina:slot frontend.1 -->
|
|
57
|
+
, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_resize, mcp__plugin_playwright_playwright__browser_take_screenshot, mcp__plugin_playwright_playwright__browser_snapshot, mcp__plugin_playwright_playwright__browser_console_messages, mcp__plugin_playwright_playwright__browser_click, mcp__plugin_playwright_playwright__browser_evaluate, mcp__plugin_playwright_playwright__browser_close
|
|
58
|
+
|
|
59
|
+
<!-- nina:slot frontend.5 -->
|
|
60
|
+
- **visual** — the spec's Visual acceptance section exists and the render matches it (see *Visual gate*).
|
|
61
|
+
A reviewer checking only another dimension skips it, so it is its own: owned by **patterns-and-scope**
|
|
62
|
+
when no visual reviewer is dispatched, and never nobody's.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- nina:slot frontend.1 -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Frontend (Vite + React) conventions
|
|
6
|
+
|
|
7
|
+
- React **function components, hooks only**. No class components.
|
|
8
|
+
- **TanStack Query for all server state.** Never fetch in `useEffect`.
|
|
9
|
+
- **shadcn/ui + Tailwind** for styling. No CSS modules, no styled-components, no Emotion.
|
|
10
|
+
- **Single-app layout:** `{{APP_DIR}}` serves all roles (`user`, `admin`) with a role-aware sidebar. Routes live in `{{APP_DIR}}/src/routes/`.
|
|
11
|
+
- **Reusable UI and utilities belong in `packages/web-shared` (`{{PKG_SCOPE}}/web-shared`)** when they are stable cross-cutting primitives: `apiFetch`, {{AUTH_LIB}} client, hooks (`useAuth`, `useMe`, `useAccount`, etc.), `AuthGuard`, all shadcn `ui/*`, `formatBrl`/`parseBrl`, test helpers, the Tailwind preset.
|
|
12
|
+
- Import shadcn primitives from `{{PKG_SCOPE}}/web-shared/components/ui/<name>`.
|
|
13
|
+
- Import `formatBrl` / `parseBrl` from `{{PKG_SCOPE}}/web-shared/lib`.
|
|
14
|
+
- **App-specific UI stays in `{{APP_DIR}}/src/components/`** when not consumed elsewhere.
|
|
15
|
+
- No global state libs unless genuinely global (auth user, active account). Prefer URL state and TanStack Query cache.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot frontend.1 -->
|
|
2
|
+
| Frontend (single app — `{{APP_DIR}}`) | `.claude/architecture.md` § *Frontend*, `{{APP_DIR}}/src/routes/` | implementer → reviewer → qa |
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot frontend.2 -->
|
|
5
|
+
| Shared frontend code (`packages/web-shared`) | `.claude/patterns.md` § *Frontend conventions*, `packages/web-shared/package.json` | architect → implementer → reviewer → qa |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- nina:slot frontend.1 -->
|
|
2
|
+
| `{{APP_DIR}}/**` page-load perf audit (LCP/INP/CLS/bundle) | `cloudflare:web-perf` |
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot frontend.2 -->
|
|
5
|
+
|
|
6
|
+
### Nothing before devops can see the screen
|
|
7
|
+
The reviewer reads a diff. qa runs vitest in jsdom, which has **no layout engine** — `toHaveClass('flex')`
|
|
8
|
+
passes whether or not a pixel landed anywhere. Every backend invariant is checkable in text, which is why
|
|
9
|
+
the same pipeline lands backend work first time and lets frontend defects through every gate: they were
|
|
10
|
+
never expressible as a test.
|
|
11
|
+
|
|
12
|
+
So any diff touching `{{APP_DIR}}/**` carries a visual contract: the **architect** writes a *Visual acceptance*
|
|
13
|
+
checklist (what must be true on screen at 1440 and 375), the **reviewer** rejects an FE spec without one, the
|
|
14
|
+
**implementer** looks at its own screen before handing off, and the **reviewer** builds, serves and screenshots it,
|
|
15
|
+
rejecting on a mismatch — judging the screen is review, not operations. **devops** only smokes the
|
|
16
|
+
deployed preview for a blank page. Browser access is granted through the Playwright MCP `browser_*` tools in those agents'
|
|
17
|
+
`tools:` lists — it is not a skill, and an agent without those tools in its list cannot see anything.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot frontend.1 -->
|
|
2
|
+
| Audit page-load perf on `{{APP_DIR}}` — LCP, INP, CLS, bundle size | `cloudflare:web-perf` | Core Web Vitals + Chrome DevTools MCP integration |
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot frontend.2 -->
|
|
5
|
+
| **ANY diff touching `{{APP_DIR}}/**`** | architect writes a **Visual acceptance** checklist; the **reviewer builds, serves and screenshots at 1440 and 375** and rejects on a mismatch; devops smokes the deployed preview for a blank page. jsdom has no layout engine — a green suite says nothing about the screen |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
- **Integration premises (MANDATORY when an integration boundary is in scope)** — list every behavior of the dependency the implementation depends on. Each premise is a one-sentence claim followed by the evidence its kind requires:
|
|
3
|
+
- `installed-library` → `node_modules/.pnpm/<lib>@<version>/.../<file>:<line>`, from the version installed here
|
|
4
|
+
- `live-api` → a contract-test case, or a response observed against the service and captured verbatim
|
|
5
|
+
- `platform-binding` → behavior observed under the local emulator, plus the platform's installed types
|
|
6
|
+
- or a `P<n>` reference into `.claude/integrations/<slug>.md`, which itself carries one of the above
|
|
7
|
+
|
|
8
|
+
A premise without a citation is an **assumption**, not a fact. The integration-tester rejects the spec if a cited line does not validate at runtime, and rejects it outright if it lists uncited premises. When in doubt: read the source, or call the service and keep the response. Integration regressions ship precisely when a premise is assumed instead of cited.
|
|
9
|
+
|
|
10
|
+
Also mark **"INTEGRATION-TESTER REQUIRED"** prominently at the top of the spec when this section is present.
|
|
11
|
+
|
|
12
|
+
<!-- nina:slot integrations.3 -->
|
|
13
|
+
- **For every integration behavior the implementation will rely on, get the evidence its kind requires** — open the installed source and cite `file:line`, or exercise the service and keep the response. Library docs and editor autocomplete are not citations. This is the single most important rule for avoiding integration regressions.
|
|
14
|
+
|
|
15
|
+
<!-- nina:slot integrations.4 -->
|
|
16
|
+
- **State an integration's behavior as fact without the evidence its kind requires.** Uncited premises are assumptions, and assumptions are what ship integration bugs to prod. If you cannot find the citation, the answer is to read more source or escalate — not to write the spec anyway.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
- If you change anything that touches an integration boundary, or add a call into one, flag it explicitly in the diff summary so the reviewer dispatches **integration-tester** before approving.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot integrations.3 -->
|
|
5
|
+
|
|
6
|
+
## Integrations
|
|
7
|
+
- All access to a dependency goes through its **boundary module**, named in `.nina/profile.json`. **Never** reach it directly from a service, route, queue consumer or scheduled handler.
|
|
8
|
+
- For a `live-api`, the contract-test suite is the source of truth and **both** implementations must pass it. Inject the mock in tests — and never let the mock be the only thing that ran.
|
|
9
|
+
- Honor the spec's **Integration premises** verbatim. If one looks wrong while you are writing code against it, STOP and loop back to the architect. Do not work around a premise: the premise is the contract, and a contract that is wrong gets fixed, not bypassed.
|
|
10
|
+
|
|
11
|
+
<!-- nina:slot integrations.4 -->
|
|
12
|
+
- Reach an integration from anywhere outside its boundary module. Inject the mock in tests.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!-- nina:slot integrations.2 -->
|
|
2
|
+
2. **Live APIs.** A third-party service you reach over the network — payments, messaging, automation, mail. *Their* API is the contract; we conform to it. There IS a real service to hit, so you run the **contract-test suite** for that boundary against **BOTH** the real client and the mock, and you check each `P<n>` premise in `.claude/integrations/<slug>.md` against what the service actually returns.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot integrations.3 -->
|
|
5
|
+
- The boundary module of any declared integration, or a new call into one
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot integrations.6 -->
|
|
8
|
+
- For the integration you are gating: `.claude/integrations/<slug>.md`. Read EVERY premise it lists, not a sample — the one you skip is the one the diff depends on.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot integrations.7 -->
|
|
11
|
+
|
|
12
|
+
## You MUST — live APIs
|
|
13
|
+
|
|
14
|
+
- **Run the contract-test suite** for the boundary against **both** implementations. Every `P<n>` in the integration's doc maps to a case — verify every one the diff touches.
|
|
15
|
+
- **Never accept a green mock as proof.** If only the mock run is green you have verified nothing about production. Say so, and reject.
|
|
16
|
+
- **Confirm nothing reaches the service outside its boundary module.** Grep the diff and the tree for raw HTTP or client construction past the boundary. Any leak is a hard reject.
|
|
17
|
+
- **Confirm the boundary validates responses** in both implementations, so drift in the service surfaces structurally rather than as a type error three layers downstream.
|
|
18
|
+
|
|
19
|
+
<!-- nina:slot integrations.8 -->
|
|
20
|
+
- **Update `.claude/integrations/<slug>.md` when you observe new behavior**, with the evidence that settled it: a contract case, an observed response, or a source citation.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
- **If an integration boundary was touched anywhere in the diff, or a new call into one was added: confirm `integration-tester` ran and approved.** If not → dispatch it now, or request changes. Additionally verify the architect's spec carries an **Integration premises** section, each premise with the evidence its kind requires. If the section is missing or uncited, REJECT upstream — do not approve a diff against an incomplete spec.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot integrations.3 -->
|
|
5
|
+
- The diff touches an integration boundary — **integration-tester also required**
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot integrations.4 -->
|
|
8
|
+
- Skip the integration-tester check "because the unit tests pass." Unit tests honor the spec's mock, not the real dependency — only the integration-tester catches drift.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot integrations.5 -->
|
|
11
|
+
- Integration boundary touched but integration-tester missed → dispatch **integration-tester** now.
|
|
12
|
+
|
|
13
|
+
<!-- nina:slot integrations.6 -->
|
|
14
|
+
- Spec touches an integration boundary but lacks the **Integration premises** section with citations → back to **architect** for spec amendment (do NOT approve the implementer's diff against an incomplete spec).
|
|
15
|
+
|
|
16
|
+
<!-- nina:slot integrations.7 -->
|
|
17
|
+
- **The one exception is a `live-api` contract-test case.** Its job is to catch the real service
|
|
18
|
+
diverging from the mock, not to encode a mutation of code this project owns, so there is often no
|
|
19
|
+
change on this side that turns it red. Name the divergence it would catch instead — "the provider
|
|
20
|
+
renames a field the mock still sends" — and do not reject it for lacking a mutation: that test is
|
|
21
|
+
integration-tester's gate, and rejecting it here would remove the one check that watches the mock.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
- **Input validation / injection / SSRF** — validate at every trust boundary: request body, query, queue message, webhook payload, and every integration response. Parameterized queries only — never string-built SQL. Outbound HTTP to an integration: SSRF and URL-allowlist posture. Start with the unauthenticated internet-facing paths — a webhook receiver, a health route, a dev-only surface — and verify their signature or secret gate cannot be bypassed or replayed.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
- **Integration guardrail** → a spec describes behavior the implementer relies on, and unit tests with structural mocks honor the *spec*, not the dependency. The integration-tester runs the real thing: real flows for an installed library or a platform binding, and for a live API the contract suite against both the real client and the mock, so the two cannot diverge. Drift between assumption and reality is caught before deploy rather than after.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot integrations.2 -->
|
|
5
|
+
| **integration-tester** | Mandatory gate on any integration boundary; runs real flows, and for a `live-api` the contract suite against both implementations; verifies each cited premise; keeps `.claude/integrations/<slug>.md` current | `.claude/agents/integration-tester.md` |
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot integrations.3 -->
|
|
8
|
+
- **`.claude/integrations/`** — one doc per integration, holding its verified premises
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
- `integration-tester` — gate: runs the real dependency when an integration boundary is touched
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot integrations.2 -->
|
|
5
|
+
- `implementer` → `integration-tester` on `DIFF-READY` — the diff touches an integration boundary
|
|
6
|
+
- `integration-tester` → `reviewer` on `APPROVED`
|
|
7
|
+
- `integration-tester` → `architect` on `REJECTED` — a premise is wrong · max 2
|
|
8
|
+
- `integration-tester` → `implementer` on `REJECTED` — the premise holds and the code does not honor it · max 2
|
|
9
|
+
- `qa` → `integration-tester` on `FAIL` — the failure is the environment, not the code · max 2
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
- **Queue** (`{{API_DIR}}/src/queues/<q>.ts`) / **Webhook** (`{{API_DIR}}/src/routes/<source>-webhook.ts`): same discipline — validate the input, build deps, call one service function.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot integrations.2 -->
|
|
5
|
+
- **Every integration is reached through its boundary module.** Services import the boundary's typed interface; they never construct an HTTP client, an SDK instance or a binding handle themselves.
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot integrations.3 -->
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Integrations (enforced by integration-tester + reviewer)
|
|
12
|
+
|
|
13
|
+
An **integration** is a dependency whose behavior this project does not define. Each one is declared in `.nina/profile.json` with a `kind`, and the kind decides what counts as evidence:
|
|
14
|
+
|
|
15
|
+
| kind | what it is | a premise is settled by |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `installed-library` | code on disk under `node_modules` | `node_modules/.pnpm/<lib>@<version>/.../<file>:<line>` |
|
|
18
|
+
| `live-api` | a third-party HTTP API | a contract-test case, or a response observed against the service and captured verbatim |
|
|
19
|
+
| `platform-binding` | a runtime the host provides | behavior observed under the local emulator, plus the platform's installed types |
|
|
20
|
+
|
|
21
|
+
- **One boundary per integration.** All access goes through the module named as its `boundary`. A service, route, queue consumer or scheduled handler that reaches the dependency directly is a hard reject: there is then no single place to mock it, retry it, log it, or fix it.
|
|
22
|
+
- **A `live-api` gets two implementations behind one interface** — the real client, and a deterministic in-memory mock. One **contract-test suite** exercises the interface and **BOTH** must pass it. The mock passing alone proves nothing about production; when the two disagree, the mock is wrong, never the other way round.
|
|
23
|
+
- **Validate responses at the boundary**, in both implementations, so drift in the dependency surfaces structurally instead of as a type error three layers downstream.
|
|
24
|
+
- **Typed errors at the boundary.** It maps the dependency's failures to this project's own error types; callers never handle a raw HTTP status or a vendor error code.
|
|
25
|
+
|
|
26
|
+
### Adding an integration
|
|
27
|
+
|
|
28
|
+
Four things, and none of them is optional:
|
|
29
|
+
|
|
30
|
+
1. **The doc** — `.claude/integrations/<slug>.md`, from `.claude/templates/integration.md`. It opens with a premise index, then one `## P<n>` section per premise: a one-sentence claim, and the evidence that settles it in the form its kind requires.
|
|
31
|
+
2. **The boundary** — one module, named in the profile entry. For a `live-api`: the interface, both implementations, and the contract suite.
|
|
32
|
+
3. **The profile entry** — `slug`, `name`, `kind`, `boundary` and `skill` in `.nina/profile.json`. This is what makes the integration visible to the gates; an integration that is not declared is not gated.
|
|
33
|
+
4. **The skill binding, when one exists.** If an installed skill covers this dependency, name it in the entry's `skill` field and add its row to the *Skills you MUST consult* table of every agent that touches the surface. The table ends in a `skills` slot for exactly this: write the row in `.nina/project/tree/.claude/agents/<role>.md` under that slot's marker (`nina where .claude/agents/<role>.md` names it), with the skill in the first cell and the trigger in the second (where the section is a list or a paragraph, the skill in bold), then `nina compose`. `nina check` confirms the agent's `tools:` grants `Skill`, which is an allowlist, not a hint. **If no installed skill covers it, bind none:** an invented binding is worse than none, because it reads as retrieval while being recall.
|
|
34
|
+
|
|
35
|
+
The **integration-tester** owns the doc: it creates it on first contact and appends a premise whenever it observes behavior the doc does not yet carry. The architect cites it, the implementer honors it, and the reviewer refuses a spec that touches the surface without it.
|
|
36
|
+
|
|
37
|
+
<!-- nina:slot integrations.4 -->
|
|
38
|
+
- **`live-api` integrations:** the contract-test suite for the boundary runs against BOTH implementations. Tests elsewhere inject the mock; the integration gate exercises the real service.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
|
|
3
|
+
### Integration-tester (mandatory for integration boundaries)
|
|
4
|
+
|
|
5
|
+
- **Trigger:** any diff touching the boundary module of a declared integration, or adding a call into one.
|
|
6
|
+
- **Input:** architect spec (with its **Integration premises** section) + implementer diff + repo state.
|
|
7
|
+
- **Output:** approve or reject. Approve includes a verified-premise table (premise → verdict → citation). Reject identifies which premise or contract case failed, and whether the fix is upstream (respec) or in code.
|
|
8
|
+
- **Tools:** Read, Write, Edit, Glob, Grep, Bash. Edits scoped to integration/contract test dirs and `.claude/integrations/**`.
|
|
9
|
+
- **Checks:**
|
|
10
|
+
- **`installed-library` / `platform-binding`:** run the real flow against the real dependency. Read `.claude/integrations/<slug>.md` and probe each premise it lists.
|
|
11
|
+
- **`live-api`:** run the **contract-test suite** for that boundary against **both** implementations. A mock that is green while the real client is not is a lie. Confirm nothing reaches the service outside its boundary module.
|
|
12
|
+
- Update `.claude/integrations/<slug>.md` when new behavior is observed.
|
|
13
|
+
- **Position:** after implementer, in parallel with any other gate the diff triggered. Reviewer verifies both gates ran.
|
|
14
|
+
|
|
15
|
+
<!-- nina:slot integrations.2 -->
|
|
16
|
+
- **If an integration boundary was touched → integration-tester approved.**
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
|
|
3
|
+
## Integration docs (load whenever an integration is touched)
|
|
4
|
+
|
|
5
|
+
`.claude/integrations/<slug>.md` holds the verified premises for one integration. The architect MUST cite premises from it — or add new ones, carrying the evidence the integration's kind requires — in any spec that touches the boundary. The integration-tester probes each cited premise before the reviewer can approve.
|
|
6
|
+
|
|
7
|
+
This project's integrations:
|
|
8
|
+
|
|
9
|
+
<!-- nina:slot integrations.2 -->
|
|
10
|
+
|
|
11
|
+
The integration-tester owns these docs. It creates one on first contact with a dependency and appends a premise whenever it observes behavior the doc does not yet carry, written against the version actually installed here or the response actually observed — never against the vendor's documentation.
|
|
12
|
+
|
|
13
|
+
Read the doc's index and the premise sections your task cites. **Not the whole file:** an integration doc for a large dependency runs to thousands of lines, and reading it end to end is one of the biggest time sinks in this pipeline.
|
|
14
|
+
|
|
15
|
+
<!-- nina:slot integrations.3 -->
|
|
16
|
+
| Integration boundary change | `.claude/integrations/<slug>.md`, and the boundary module named in the profile | architect (cites premises) → implementer → **integration-tester** → reviewer → qa |
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
|
|
3
|
+
### Integration-tester is a guardrail for integration boundaries
|
|
4
|
+
Invoke **integration-tester** any time the diff touches the boundary module of a declared integration, or adds a call into one. It runs the real dependency, never a mock of it; for a `live-api` it runs the **contract-test suite against BOTH implementations**, so the mock can never pass while the live path is broken. No such change merges without its approval.
|
|
5
|
+
|
|
6
|
+
The architect's spec MUST carry an **Integration premises** section: every behavior the implementation relies on, each with the evidence its kind requires — `node_modules/.pnpm/<lib>@<version>/...:<line>` for an installed library, a contract case or an observed response for a live API, the local emulator for a platform binding. Never by inference.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- nina:slot integrations.1 -->
|
|
2
|
+
| **ANY integration surface** (the boundary module of a declared integration, or a new call into one) | architect cites a premise, with the evidence its kind requires, for every dependency behavior the code relies on; **+ integration-tester before reviewer** |
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot integrations.3 -->
|
|
5
|
+
- **integration-tester** → mandatory gate on any integration boundary; runs the real dependency rather than a mock of it, and for a `live-api` runs the contract-test suite against both implementations so they cannot diverge; verifies every cited premise; keeps `.claude/integrations/<slug>.md` current
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot integrations.4 -->
|
|
8
|
+
11. **Every integration has one boundary and one doc.** A dependency whose behavior you do not define — an installed library, a live third-party API, a platform binding — is reached through exactly ONE module, and nothing else speaks to it: no raw HTTP, SDK instance or binding handle in a route, service, queue consumer or scheduled handler. Each is declared in `.nina/profile.json` with its `kind` and `boundary`, and documented at `.claude/integrations/<slug>.md`. A `live-api` carries two implementations behind one interface — the real client and a deterministic mock — and one contract-test suite that BOTH must pass; a mock that is green while the real client is not is a lie, and a hard reject.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot integrations.5 -->
|
|
11
|
+
12. **A premise about an integration requires a citation, and the form depends on the kind.** The architect MAY NOT state a dependency's behavior as fact without evidence: for an `installed-library`, `node_modules/.pnpm/<lib>@<version>/.../<file>:<line>` from the version actually installed here; for a `live-api`, a contract-test case or a response observed against the service and captured verbatim; for a `platform-binding`, behavior observed under the local emulator. Vendor docs, a README, a changelog and recall are NOT citations. An uncited premise is an assumption, and the spec is rejected by the reviewer.
|
|
12
|
+
|
|
13
|
+
<!-- nina:slot integrations.6 -->
|
|
14
|
+
13. **Integration boundaries require the integration-tester gate before reviewer.** Any diff touching a boundary module, or adding a call into one, MUST pass the integration-tester before the reviewer can approve. Unit-test mocks honor the spec, not the dependency — a mock is green because we wrote it that way. Only a run against the real thing, or a contract suite both implementations pass, catches drift.
|
|
15
|
+
|
|
16
|
+
<!-- nina:slot integrations.7 -->
|
|
17
|
+
- **an integration boundary**: a call to an external service, or a webhook or callback from one
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
- **Money invariants** — if the spec touches distribution, transfer generation, or a send to the payment integration, explicitly confirm the design honors:
|
|
3
|
+
- Money is integer {{MINOR_UNIT}} end to end; percentages are basis points. Never a floating-point type.
|
|
4
|
+
- A distribution may NEVER exceed the amount → **block** (reject the operation, do not clamp it).
|
|
5
|
+
- A distribution **less** than the amount → **allow**, surface the shortfall, leave the remainder **parked** — never silently distributed.
|
|
6
|
+
- Transfers are **idempotent** — never sent twice. Dedupe happens at the serialization point, via the key, before the side effect.
|
|
7
|
+
- Every legal state transition writes an audit row (who / when / from → to). Illegal transitions throw.
|
|
8
|
+
|
|
9
|
+
If the design cannot satisfy all of these, redesign before submitting the spec.
|
|
10
|
+
|
|
11
|
+
<!-- nina:slot money.2 -->
|
|
12
|
+
- Design a money movement that can exceed the amount it distributes, silently absorb a shortfall, or send a transfer twice. If the design needs monetary computation, that computation lives in `{{CORE_PKG}}` as a pure function — never inline in a service or route.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
- **No cache on a read that feeds a money write.** Anything that loads an amount or a balance before computing, approving or dispatching a transfer MUST NOT carry a `cacheStrategy`. A stale amount contaminates a real money movement, and that damage is not undone by fixing the cache afterwards. **Hard reject if violated.**
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot money.2 -->
|
|
5
|
+
- **Monetary columns are integer.** Money is stored as an integer of the currency's minor unit ({{MINOR_UNIT}}); percentages are basis points. Reject `Decimal`, `Float`, or any type that can hold a fraction of the minor unit.
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot money.3 -->
|
|
8
|
+
- **Money-integrity constraints.** When the change touches the record of an outbound transfer, verify the **unique constraint on its idempotency key** exists (`@unique` in the schema → `UNIQUE` in the migration SQL). That constraint is what still holds when the application-level guard fails; a missing or dropped one is a **hard reject**. Verify likewise any uniqueness the ownership model depends on. Report it in its own *Money-integrity* section, before the final verdict.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot money.4 -->
|
|
11
|
+
- Approve a query annotated with `cacheStrategy` if it feeds a money write.
|
|
12
|
+
|
|
13
|
+
<!-- nina:slot money.5 -->
|
|
14
|
+
- Approve a change to an outbound-transfer record that lacks the unique constraint on its idempotency key.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
|
|
3
|
+
## Money invariants you MUST honor
|
|
4
|
+
- **A distribution may NEVER exceed the amount** → block. Implement the reject; never clamp and continue.
|
|
5
|
+
- **A shortfall is parked, not distributed.** Surface it and leave the remainder where it is — never auto-distribute it, never absorb it quietly.
|
|
6
|
+
- **Transfers are idempotent — never sent twice.** Dedupe at the serialization point via the key, *before* the side effect. A retry, a re-delivered message or a double-click must not produce a second transfer.
|
|
7
|
+
- **Every state transition writes an audit row** (entity, from → to, actor). No transition without a trail.
|
|
8
|
+
- The deterministic money logic lives in `{{CORE_PKG}}` — call it as a pure function; never embed the arithmetic in a service.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot money.2 -->
|
|
11
|
+
- Embed money arithmetic (sums, deltas, value comparisons, distribution math) inline in a service. Computation lives in `{{CORE_PKG}}`.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
- **Confirm idempotency holds end to end.** Two sends with the same key → one logical transfer, no double send, and the serialization point refuses the second. This is the highest-blast-radius invariant in the system — drive it explicitly against the real service; do not infer it from reading the code.
|
|
3
|
+
- **Confirm money crosses the boundary as an integer of the minor unit**, and that conversion happens only in the module that owns it — never in a service, never through a floating-point type.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
- **Enforce the money invariants. Each violation is a HARD REJECT → loop back to architect:**
|
|
3
|
+
- **A distribution may NEVER exceed the amount.** It is blocked, not clamped. A retention is just another destination — same arithmetic, in integer {{MINOR_UNIT}}.
|
|
4
|
+
- **A shortfall must be parked, not silently distributed.** The remainder stays where it is, and the shortfall is surfaced to whoever is owed it.
|
|
5
|
+
- **Transfers must be idempotent — never sent twice.** Every transfer carries a key, and dedupe happens at the serialization point before the side effect. A retry, a re-delivered message or a double-click must never produce a second transfer.
|
|
6
|
+
- **Every state transition writes an audit row** (entity type, id, from, to, actor) and follows the transitions declared in `.claude/architecture.md`. An undeclared transition, or one missing its audit row, is a hard reject.
|
|
7
|
+
|
|
8
|
+
<!-- nina:slot money.2 -->
|
|
9
|
+
- **money-invariants** — a distribution never exceeds the amount; a shortfall is parked, never
|
|
10
|
+
distributed; transfers idempotent at the serialization point; an audit row on every transition;
|
|
11
|
+
integer {{MINOR_UNIT}} and basis points; no arithmetic outside `{{CORE_PKG}}`.
|
|
12
|
+
|
|
13
|
+
<!-- nina:slot money.3 -->
|
|
14
|
+
- `{{CORE_PKG}}` money logic touched (distribution and transfer generation are high-blast-radius)
|
|
15
|
+
|
|
16
|
+
<!-- nina:slot money.4 -->
|
|
17
|
+
- Approve a change that lets a distribution exceed the amount, silently distributes a shortfall, sends a transfer without idempotent dedupe, or skips an audit row — those are hard rejects, loop back to architect.
|
|
18
|
+
|
|
19
|
+
<!-- nina:slot money.5 -->
|
|
20
|
+
- **If APPROVED:** ≤300 words. Sections: Verification commands run (typecheck/lint/build only — NEVER vitest), Spec-match (✅/❌ per item), Hard-rule checks (tenant scoping ✅, integer {{MINOR_UNIT}} / bps ✅, each gate the diff triggered ✅ (`.claude/graph.md`), money invariants ✅ — distribution ≤ amount, shortfall parked, idempotent transfer, audit row on transition), QA-attention flags (list any condition from the "stricter audit" list so QA knows what to focus on), Final verdict.
|
|
21
|
+
|
|
22
|
+
<!-- nina:slot money.6 -->
|
|
23
|
+
- Money invariant violated (distribution exceeds the amount, shortfall silently distributed, non-idempotent transfer, missing audit row) → back to **architect** for redesign.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
- **Money-movement abuse (Hard Rules #3, #4, #5)** — **the money path reaches a real service; this is not a stub audit.** Is the idempotency contract actually enforced at the seam — the unique key, the serialization point, and the dedupe happening *before* the side effect? Can a replay, a double-click, a queue redelivery, or a manual reset of processed state cause a double send? Can a distribution exceed the amount, or a shortfall be silently distributed? Is whatever authorizes money OUT forgeable — a webhook without a verified signature, a callback trusted on its word? Is every state transition forced through an audit write? Flag any seam that is structurally unable to enforce its invariant, even where nothing has exploited it yet.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot money.2 -->
|
|
5
|
+
- **Idempotency-key & replay surface** — keys unguessable and collision-resistant where it matters; dedup enforced before the side effect, not after.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
- **The conservation property, asserted rather than argued.** Find the test that proves in equals
|
|
3
|
+
out plus retained, and check it holds on the adversarial paths too, not only the intended one.
|
|
4
|
+
Rounding that always favours the same party is a finding; so is a fee path with no upper bound.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
- **State the conservation property and assert it in the suite.** A contract that holds or routes an
|
|
3
|
+
amount has one — what goes in equals what goes out plus what is retained — and it belongs in a
|
|
4
|
+
test, not in a comment. Rounding is part of it: say which way it goes and who it favours.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Monetary values & percentages
|
|
6
|
+
|
|
7
|
+
- **Money is an integer of the currency's minor unit**, end to end — {{MINOR_UNIT}}, never a float, never a type that can hold a fraction of one. Source of precision: `{{MONEY_MODULE}}`.
|
|
8
|
+
- **Percentages are basis points** (`40% → 4000`, `100% → 10000`) so a distribution is exact without floating point.
|
|
9
|
+
- **Parsing, formatting and percentage arithmetic live in one module and nowhere else.** A second place that turns a string into money is a second rounding rule, and the two will disagree on a value somebody is owed.
|
|
10
|
+
- **Never** convert to a floating-point type before formatting, and never let a display format round a stored value.
|
|
11
|
+
- Monetary database columns are integer. JSON responses serialize them as **strings**, not numbers, so no client parses them into a float. The service owns the conversion.
|
|
12
|
+
|
|
13
|
+
<!-- nina:slot money.2 -->
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Distribution invariants (enforced by reviewer + unit tests)
|
|
18
|
+
|
|
19
|
+
The distribution engine lives in `{{CORE_PKG}}`. It is pure and total over integer {{MINOR_UNIT}}:
|
|
20
|
+
|
|
21
|
+
- A destination is either a percentage (basis points of the amount) or a fixed value.
|
|
22
|
+
- `total = Σ destinations`, computed in {{MINOR_UNIT}}.
|
|
23
|
+
- `total > amount` → **reject.** Never clamp, never distribute what is not there.
|
|
24
|
+
- `total < amount` → **allow**, surface the shortfall, and leave the remainder **parked**. Silence is the defect here: an unexplained remainder is indistinguishable from a bug.
|
|
25
|
+
- `total = amount` → OK.
|
|
26
|
+
- A retention is just another destination — the same arithmetic applies to it, with no special case.
|
|
27
|
+
|
|
28
|
+
<!-- nina:slot money.3 -->
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## State machines & idempotency (enforced by reviewer)
|
|
33
|
+
|
|
34
|
+
- **Every money-moving entity is a state machine.** Its states and legal transitions are declared in `.claude/architecture.md`; the code follows the declaration, not the other way round. Illegal transitions throw; they are never silently coerced.
|
|
35
|
+
- **Every transition writes an audit row** (entity type, entity id, from, to, actor). No transition without a trail — the trail is what makes a money bug diagnosable after the money has moved.
|
|
36
|
+
- **Outbound transfers are idempotent.** Each carries a unique key, and dedupe happens before the side effect, at the serialization point.
|
|
37
|
+
- **One serialization point per balance.** Whatever computes and dispatches against a single balance never runs concurrently with itself. Two concurrent runs over one balance is how a double send happens even when every individual write is correct.
|
|
38
|
+
|
|
39
|
+
<!-- nina:slot money.4 -->
|
|
40
|
+
- **Money computation and dispatch go through the serialization point.** Routes, queue consumers and tools never run a distribution or a transfer synchronously in the request path.
|
|
41
|
+
|
|
42
|
+
<!-- nina:slot money.5 -->
|
|
43
|
+
- **The distribution engine lives in `{{CORE_PKG}}`.** Services call it as a pure function; they do not embed money logic inline.
|
|
44
|
+
|
|
45
|
+
<!-- nina:slot money.6 -->
|
|
46
|
+
- Idempotency on money-moving writes: the record carries an idempotency key, and the serialization point refuses the second attempt *before* the side effect.
|
|
47
|
+
|
|
48
|
+
<!-- nina:slot money.7 -->
|
|
49
|
+
- `{{CORE_PKG}}` is the most-tested package — pure functions over fixtures. Cover the distribution invariants exhaustively: over, under, exact, mixed percentage and fixed, and rounding.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
3. **An outbound transfer is idempotent — it is NEVER sent twice.** Every transfer carries an idempotency key, and deduplication happens at a single serialization point **before** the side effect, never after it. A retry, a re-delivered queue message, a double-click and a replayed webhook must all collapse to one transfer. This is the highest-blast-radius invariant in the system: almost everything else can be corrected afterwards, and a duplicated transfer cannot.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot money.2 -->
|
|
5
|
+
4. **Conservation — what comes in equals what goes out plus what is retained.** A distribution may NEVER exceed the amount it distributes → **block**. If it is **less**, **alert** and leave the remainder **parked** and visible — never silently distributed, never silently absorbed. All arithmetic is integer {{MINOR_UNIT}}; percentages are basis points. The LLM never computes a monetary value.
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot money.3 -->
|
|
8
|
+
5. **State-machine integrity.** Every money-moving entity has an explicit set of states and legal transitions, declared in `.claude/architecture.md` § *State machines* — not inferred from whatever code happens to exist. Only declared transitions are legal; an illegal one throws and is never silently coerced. **Every transition writes an audit row** (entity, id, from → to, actor, when). Skipping a state, inventing one, or landing a transition with no audit row is a hard reject.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot money.4 -->
|
|
11
|
+
- **money movement**: anything that creates, moves, settles or records a balance
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
- **Privacy.** No sensitive value in clear text where it must not be. The categories are declared in `.claude/architecture.md` § *Privacy* — they are not decided per query, and not decided by you. **Document bytes live only in the object store, referenced by key: the column holds the key, never the bytes.** Any column the architect's spec flagged for masking must be encrypted at rest. Reject a schema that widens what is stored in the clear.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
|
|
3
|
+
**Point it at an UNAUTHENTICATED route — a sign-in page is the right one.** Never sign in, and never
|
|
4
|
+
screenshot a signed-in page. Accounts on deployed environments hold real personal data, and a
|
|
5
|
+
screenshot or a console dump pulls it into agent transcripts and logs, where it persists and nobody
|
|
6
|
+
ever looks for it again. That is a privacy leak with no upside here: the sign-in page proves
|
|
7
|
+
everything this check is for. It renders from the same bundle, it exercises the same build-time env
|
|
8
|
+
inlining (a missing variable blanks it identically), and it needs no session.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
- Check for privacy regressions: **no sensitive value in a log, an error message, a URL or a test fixture**, per the categories in `.claude/architecture.md` § *Privacy*; document bytes only in the object store, never plaintext in the database; no leaked secret.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot pii.2 -->
|
|
5
|
+
- **privacy** — the privacy check above, in full: no sensitive value in a log, an error message, a
|
|
6
|
+
URL or a test fixture; document bytes only in the object store; no leaked secret. Where
|
|
7
|
+
**tenant-and-privacy** is dispatched, its reviewer owns this too and there is no separate one; where
|
|
8
|
+
it is not, this is a dimension of its own. Either way it is never nobody's.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
- **Privacy (Hard Rule #8)** — the categories declared in `.claude/architecture.md` § *Privacy* must never appear in logs, in responses that do not need them, in error paths, or in the database in clear text. Check every endpoint that returns a person's record for fields it includes by accident: a `select` that quietly grew a column is the usual way this leaks. Check the error paths too — they are written last and reviewed least.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
- **Nothing personal reaches the chain.** What is written on-chain is public, permanent, and beyond
|
|
3
|
+
deletion by anyone — including by you, and including after a subject asks. A hash of a personal
|
|
4
|
+
identifier is not a protection: it is a lookup table for whoever holds the input set. Reject a
|
|
5
|
+
personal datum stored in state, emitted in an event, or passed as calldata.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
8. **Sensitive personal data is classified, and the classification is enforced.** What counts as sensitive here — identity documents, financial identifiers, anything a person would not expect to be readable by whoever can read a log — is declared in `.claude/architecture.md` § *Privacy*, along with the regime that governs it ({{PRIVACY_REGIME}}). Encrypted at rest. **No sensitive value in a log, an error message, a URL, an analytics event, or an agent transcript** — the places nobody thinks about are exactly the ones that persist. Document bytes live only in the object store, referenced by key, never in the database in clear text.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot pii.2 -->
|
|
5
|
+
- **personal data**: where it is collected, stored, logged or sent
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
14
14
|
import { basename, join, resolve } from 'node:path';
|
|
15
|
-
import {
|
|
15
|
+
import { dim, pink } from '../look.mjs';
|
|
16
16
|
import { HARNESS, slugFor, snapshotsDir } from '../paths.mjs';
|
|
17
17
|
import { TERMINALS, parseGraph } from '../graph.mjs';
|
|
18
18
|
import { required } from '../tools.mjs';
|
|
@@ -220,9 +220,7 @@ export async function pipeline(argv) {
|
|
|
220
220
|
chosen = picked[0];
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
const
|
|
224
|
-
const name = (s) => (color && !TERMINALS.has(s) ? `\x1b[38;2;${PINK.join(';')}m${s}\x1b[0m` : s);
|
|
225
|
-
const dim = (s) => (color ? `\x1b[2m${s}\x1b[0m` : s);
|
|
223
|
+
const name = (s) => (TERMINALS.has(s) ? s : pink(s));
|
|
226
224
|
const width = Math.max(80, Math.min(process.stdout.columns ?? 110, 140));
|
|
227
225
|
const fit = (s, room) => (s.length > room ? `${s.slice(0, Math.max(room - 1, 1))}…` : s);
|
|
228
226
|
const arrow = ' ─▶ ';
|