@xhulz/nina 0.65.0 → 0.66.0
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.66.0/core/GAPS.md +125 -0
- package/releases/0.66.0/core/answered.json +80 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.66.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.66.0/core/tree/.claude/agents/architect.md +133 -0
- package/releases/0.66.0/core/tree/.claude/agents/dba.md +119 -0
- package/releases/0.66.0/core/tree/.claude/agents/design-reviewer.md +76 -0
- package/releases/0.66.0/core/tree/.claude/agents/devops.md +118 -0
- package/releases/0.66.0/core/tree/.claude/agents/implementer.md +178 -0
- package/releases/0.66.0/core/tree/.claude/agents/integration-tester.md +170 -0
- package/releases/0.66.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.66.0/core/tree/.claude/agents/qa.md +150 -0
- package/releases/0.66.0/core/tree/.claude/agents/reviewer.md +187 -0
- package/releases/0.66.0/core/tree/.claude/agents/secops.md +120 -0
- package/releases/0.66.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.66.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.66.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.66.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.66.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.66.0/core/tree/.claude/patterns.md +252 -0
- package/releases/0.66.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.66.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.66.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.66.0/core/tree/.claude/router.md +213 -0
- package/releases/0.66.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.66.0/core/tree/CLAUDE.md +166 -0
- package/releases/0.66.0/core/tree/scripts/cost-watch.mjs +25 -0
- package/releases/0.66.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.66.0/core/tree/scripts/harness-check.mjs +80 -0
- package/releases/0.66.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.66.0/core/vocabulary.json +14 -0
- package/releases/0.66.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.66.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.66.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.66.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.66.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.66.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.66.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.66.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.66.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.66.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.66.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.66.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.66.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.66.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.66.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.66.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.66.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.66.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.66.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.66.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.66.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.66.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.66.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.66.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.66.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.66.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.66.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.66.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.66.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.66.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.66.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.66.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.66.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.66.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.66.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.66.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.66.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.66.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.66.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.66.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.66.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.66.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.66.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.66.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.66.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.66.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.66.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.66.0/surfaces/prisma/tree/CLAUDE.md +5 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
<!-- nina:slot project.1 title -->
|
|
2
|
+
|
|
3
|
+
<!-- nina:slot project.2 mission -->
|
|
4
|
+
|
|
5
|
+
<!-- nina:slot project.3 stack -->
|
|
6
|
+
<!-- nina:slot edge-cf.1 -->
|
|
7
|
+
|
|
8
|
+
> **A subagent can only invoke a skill if `Skill` is in its `tools:` list.** That field is an allowlist, not a hint.<!-- nina:why --> An earlier version of this harness omitted `Skill` on every role, so every mandatory-skill rule was unenforceable, and the skills were measured at 2 invocations in 743 runs — both from built-in agents that carry the full tool set.<!-- /nina:why --> Any new role that binds a skill must grant `Skill`. The `tools:` field is read when the agent is registered, so a change to it needs a new session; the spec body is re-read on every dispatch.
|
|
9
|
+
|
|
10
|
+
Mandatory triggers:
|
|
11
|
+
|
|
12
|
+
| When you are about to… | Invoke skill | Why |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
<!-- nina:slot edge-cf.2 -->
|
|
15
|
+
<!-- nina:slot edge-cf.3 -->
|
|
16
|
+
<!-- nina:slot edge-cf.4 -->
|
|
17
|
+
<!-- nina:slot edge-cf.5 -->
|
|
18
|
+
<!-- nina:slot frontend.1 -->
|
|
19
|
+
| Run the **secops** milestone gate | `security-audit` | the attack-class corpus; guidance mode — its full six-phase workflow is for an explicit whole-tree audit only |
|
|
20
|
+
<!-- nina:slot edge-cf.6 -->
|
|
21
|
+
<!-- nina:slot prisma.1 -->
|
|
22
|
+
<!-- nina:slot prisma.2 -->
|
|
23
|
+
<!-- nina:slot d1.1 -->
|
|
24
|
+
|
|
25
|
+
**Each agent spec now carries its own binding table** (`.claude/agents/<role>.md` § *Skills you MUST consult*), because a rule that lives only here is not in front of a stage when it acts.<!-- nina:why --> It was invoked 2 times in 743 subagent runs.<!-- /nina:why --> `qa` and `planner` have no skill bound — none of the installed skills covers running tests or decomposition, and an invented binding is worse than none.
|
|
26
|
+
<!-- nina:slot edge-cf.7 -->
|
|
27
|
+
|
|
28
|
+
Reviewer rejects any architect spec that touches the platform runtime or its deploy config without citing which skill it consulted, OR explaining why the skill was not needed. Same standard as the premise rule below: retrieval beats recall.
|
|
29
|
+
|
|
30
|
+
## Where things live
|
|
31
|
+
- Full architecture → `.claude/architecture.md`
|
|
32
|
+
- Code conventions → `.claude/patterns.md`
|
|
33
|
+
- Pipeline graph — stages, edges, loop caps → `.claude/graph.md`
|
|
34
|
+
- Agent dispatch rules → `.claude/router.md`
|
|
35
|
+
- Which docs to load per task → `.claude/retrieval.md`
|
|
36
|
+
- Agent pipeline overview → `.claude/agents-overview.md`
|
|
37
|
+
- Stage contracts + examples → `.claude/pipeline.md`
|
|
38
|
+
- Subagent role specs → `.claude/agents/` (dispatchable via Agent tool)
|
|
39
|
+
<!-- nina:slot project.4 product-docs -->
|
|
40
|
+
|
|
41
|
+
**Where a memory goes.** How a stage should work → a pill under `.claude/pills/`, which the stage reads and `nina learn` measures. How a dependency behaves → its `.claude/integrations/` doc. A decision about this system → `.claude/architecture.md`. A rule every project needs → a request to the harness (`nina learn --graduate`). Only a preference of the owner's goes in Claude Code's own memory: it is this machine's alone, no stage reads it, and nothing measures it.
|
|
42
|
+
|
|
43
|
+
**Generated files — edit the layer, not the output.** A file that opens with a
|
|
44
|
+
`<!-- nina:generated -->` comment was composed by `nina compose` from the harness core, the
|
|
45
|
+
surfaces this project declares in `.nina/profile.json`, and this project's own fragments in
|
|
46
|
+
`.nina/project/tree/`. Most of the docs above open with one, and so does this file. The
|
|
47
|
+
comment names the layer to edit instead — or says the text belongs to the harness and cannot
|
|
48
|
+
be changed from this project, in which case **report it rather than editing it**. A hand edit
|
|
49
|
+
is reported as drift by `pnpm harness:check` and overwritten by the next compose, which is
|
|
50
|
+
how a correction written in the wrong place disappears without anyone being told.
|
|
51
|
+
|
|
52
|
+
**The rule is the marker, not the directory.** Most of `.claude/` is yours to write and is
|
|
53
|
+
never composed: `architecture.md`, the code map, `integrations/*`, everything under
|
|
54
|
+
`plans/**`, and the pills under `pills/<role>/`. Do not stop writing those.
|
|
55
|
+
|
|
56
|
+
**A generated line you cannot fix here is a change made upstream.** The harness lives in its own
|
|
57
|
+
repository; this project composes a *frozen* version of it, pinned as `core` in `.nina/profile.json`.
|
|
58
|
+
So a wrong composed line has one of two addresses, and saying which is part of reporting it. **This
|
|
59
|
+
project's own fact** — a path, a provider, a duration, a package name — belongs in a fragment under
|
|
60
|
+
`.nina/project/tree/`, and you can fix it now. **The harness's rule** — what a role must do, what a
|
|
61
|
+
stage hands off, what a report must contain — cannot be changed from this project at all: report it as
|
|
62
|
+
a request against the pinned version, quoting the composed file and line, and it arrives back through
|
|
63
|
+
`nina upgrade`. A correction that names no layer is not actionable by anyone, however right it is.
|
|
64
|
+
|
|
65
|
+
## ⚠️ Agent pipeline — MANDATORY
|
|
66
|
+
|
|
67
|
+
> **Dispatch the subagent chain that fits the task's size and blast radius — before executing anything non-trivial.**
|
|
68
|
+
> The pipeline is **proportional, not one-size-fits-all.** A change on a **critical path** (below) and a substantial multi-file feature get the full chain. Small read-only / display / UI-copy changes get a light chain — or a direct edit. **Under-gating a change on a critical path is a protocol violation. Over-gating a one-file label or list change wastes hours — that is also a failure.**
|
|
69
|
+
|
|
70
|
+
**Critical paths.** A change that touches one is gated in full, and when it is unclear whether a change touches one, it does:
|
|
71
|
+
|
|
72
|
+
- **auth**: how a credential is issued, checked or revoked, and who may do what
|
|
73
|
+
<!-- nina:slot db.8 -->
|
|
74
|
+
<!-- nina:slot integrations.7 -->
|
|
75
|
+
<!-- nina:slot money.4 -->
|
|
76
|
+
<!-- nina:slot pii.2 -->
|
|
77
|
+
<!-- nina:slot blockchain.4 -->
|
|
78
|
+
|
|
79
|
+
### Procedure — follow this every time
|
|
80
|
+
|
|
81
|
+
1. **Classify the task** using the table below *before* touching any file — and before a session's first dispatch, read `.claude/graph.md` and `.claude/router.md`.
|
|
82
|
+
2. **Dispatch the first subagent** in the required chain via the Agent tool.
|
|
83
|
+
3. Each stage hands off a written artifact (spec / diff / review) to the next.
|
|
84
|
+
4. **Run the read-only stages concurrently.** A stage that only reads can run beside any other stage that only reads, so the `reviewer` and every gate the diff triggers go out together after the implementer — in ONE message with multiple Agent calls — and `secops` runs beside `devops` at a milestone's end, once its last `qa` passed. For a diff over ~200 lines, fan the reviewer out by dimension — one reviewer per axis of risk the diff actually carries — instead of asking one agent to carry every checklist. Concurrent **implementers** are the exception: they write, so they run together only on work that shares no file and builds on nothing the other writes — side by side in different packages, each in `isolation: "worktree"` within one. `qa` is always alone (vitest memory). Full rules: `.claude/router.md` § *Parallelization*.
|
|
85
|
+
5. The **reviewer** names every gate the diff triggers — the database gate when the schema or a query was touched — and `qa` goes out only once the reviewer and each of those gates approved.
|
|
86
|
+
6. **Honor the planner's `ONE-SPEC` / `ONE-REVIEW` grouping.** Sibling steps that change no observable behavior on their own get **one** architect spec and **one** reviewer pass over the combined diff, while still being implemented one step at a time. Small steps are good; eight full pipelines to ship one feature are not. This never relaxes a gate — the gates in `.claude/graph.md` fire on the surface touched, however the specs were grouped.
|
|
87
|
+
7. **Right-size before dispatching.** Match the chain to blast radius (see table). A change that touches no critical path and ≤2 files takes the light chain — or a direct edit when a subagent adds nothing (a label, a copy tweak, a list render). Do NOT run planner/architect/secops on that. The gates on a critical path are never optional.
|
|
88
|
+
8. **Commit each pass that closes**, on a working branch — never on the default branch, never pushed (`.claude/router.md` § *Commits*).
|
|
89
|
+
9. **At the END of a spec-SET / milestone** (e.g. all of `6.*`), after the last sub-step's qa, dispatch **secops**. The milestone is not done until secops returns `SECURE`. This is a milestone gate, NOT a per-sub-step stage. **Scope secops to the milestone's diff by default.** A whole-tree sweep surfaces pre-existing debt mid-task and causes scope-creep — run it ONLY when explicitly requested, as a deliberate choice, never as the default.
|
|
90
|
+
|
|
91
|
+
### Required chain by task shape
|
|
92
|
+
|
|
93
|
+
| Task shape | Required dispatch |
|
|
94
|
+
|---|---|
|
|
95
|
+
| Question / exploration / Q&A | none — answer directly |
|
|
96
|
+
| Trivial edit (rename, typo, 1-line log fix) | none — edit directly |
|
|
97
|
+
| **Small read-only / display / UI-copy change** (≤2 files, no critical path) | **implementer → reviewer** — or a direct edit if a subagent adds no value; qa runs ONLY the affected test file, and not at all when no test covers the change. No planner, no secops. |
|
|
98
|
+
| **Spike** — an experiment that answers a question about a service, a library or the data before anything is designed on it | **architect (spike plan) → implementer → reviewer** — no qa, devops or secops; `.claude/router.md` § *A spike answers one question* |
|
|
99
|
+
| Single-file bug fix (TS) | **implementer → reviewer → qa** |
|
|
100
|
+
| Refactor TS (no new behavior) | **architect → implementer → reviewer → qa** |
|
|
101
|
+
| New TS feature / multi-file change (a critical path, or ≥3 files with logic) | **planner → architect → implementer → reviewer → qa** |
|
|
102
|
+
<!-- nina:slot db.3 -->
|
|
103
|
+
<!-- nina:slot integrations.1 -->
|
|
104
|
+
<!-- nina:slot blockchain.2 -->
|
|
105
|
+
| **ANY step that changes a deployed surface** (API, frontend, schema, deploy config, secrets, bindings) | **+ devops after qa** — preview deploy + smoke; prod only on an explicit go; with no preview target yet, the step ends at qa |
|
|
106
|
+
<!-- nina:slot frontend.2 -->
|
|
107
|
+
| **END of a spec-SET / milestone** (a numbered set like `6.*`, or a phase like one package's build-out) | **+ secops after the last sub-step's qa** — milestone security gate; `SECURE` required before the milestone is declared done |
|
|
108
|
+
|
|
109
|
+
**Sizes, and what each decides.** ≤2 files and no critical path: the light chain. Past 4 files or ~400 new lines, the planner decomposes a subtask. One implementer run writes at most {{STEP_FILES}} files and about {{STEP_LINES}} lines: the architect forecasts both and splits a longer step. A `ONE-SPEC` group stays under ~400 lines of diff. Past ~200 lines, the reviewer fans out by dimension. Each size decides only its own thing.
|
|
110
|
+
|
|
111
|
+
**Test execution policy:** vitest is **only** invoked by the **qa** stage, which runs once at the end of the pipeline (after reviewer approves). Implementer writes tests but does NOT execute them. Reviewer does NOT run tests. This prevents the workspace-machine memory blowups from concurrent vitest invocations (vitest is ~2–3 GB per worker). Configs enforce single-fork; the root `pnpm test` uses `turbo run test --concurrency=1`.
|
|
112
|
+
|
|
113
|
+
**Test scoping (speed — this is the single biggest time sink).** qa runs ONLY the test files / packages the diff actually touches during the build→fix loop — never the full suite per cycle. Run it targeted per file while iterating; run the **full** package suite exactly ONCE, right before opening the PR / merging, as the final gate. Vitest stays single-fork and non-concurrent.
|
|
114
|
+
|
|
115
|
+
<!-- nina:slot project.8 slow-suites -->
|
|
116
|
+
|
|
117
|
+
### If you are uncertain which chain applies
|
|
118
|
+
→ If the doubt is whether a **critical path** is touched → treat it as the heavier shape and gate it. If the task clearly touches no critical path and the only question is "how much ceremony" → take the **lighter** chain; an extra planner/secops pass on a display change costs hours, not minutes.
|
|
119
|
+
|
|
120
|
+
<!-- nina:slot project.5 roles-heading -->
|
|
121
|
+
|
|
122
|
+
- **planner** → decompose ambiguous or multi-step tasks into ordered subtasks
|
|
123
|
+
- **architect** → design the TS technical approach AND cite external-library premises (`<package dir>/node_modules/<lib>/<file>:<line>` + version); write a spec before code exists
|
|
124
|
+
- **design-reviewer** → read the spec before any code when the architect's report says the design is the question; back to the architect on a design flaw
|
|
125
|
+
- **implementer** → write TS code AND test files strictly to spec; runs typecheck/lint/build only (NOT vitest)
|
|
126
|
+
<!-- nina:slot db.4 -->
|
|
127
|
+
<!-- nina:slot integrations.3 -->
|
|
128
|
+
<!-- nina:slot blockchain.3 -->
|
|
129
|
+
- **reviewer** → verify diff matches spec, run typecheck/lint/build (NOT vitest), name every gate the diff triggers, confirm spec has a preview-deploy plan
|
|
130
|
+
- **qa** → runs vitest once at the END of the pipeline (after the reviewer and every gate approve) for the affected packages; loops back to implementer on failures
|
|
131
|
+
- **devops** → **deploy owner.** Runs after **qa PASS** on any step that changes a deployed surface, once the project has a preview target. Deploys to preview or staging first; production needs an explicit go. Smokes the deployed preview for a blank page and a clean console — judging whether the screen is *right* is the reviewer's job, not his. Executes Hard Rule #14: clean build, every deploy target, build-time env, migrations in order against the right DB, secret parity, **smoke against preview**, named rollback. Deploys preview/staging on its own; **a production deploy requires an explicit go from {{OWNER}} for that change.** Read-only on code — a failed deploy caused by bad code loops back, it does not get patched here.
|
|
132
|
+
- **secops** → **milestone security gate.** Runs once at the END of a completed spec-SET (a numbered milestone like `6.*`, or a phase like one package's build-out), after the last sub-step's qa — NOT per sub-step. Audits as an attacker and a privacy auditor (auth/session, tenant isolation, secrets and config exposure, PII leakage, abuse of any irreversible operation and its idempotency seams, injection/SSRF, dependency and binding posture). **Scoped to the milestone diff by default; a whole-tree sweep runs only on explicit request** (it surfaces pre-existing debt mid-task → scope-creep). Read-only; CRITICAL/HIGH findings in the milestone's own surface BLOCK it and loop back until re-audited.
|
|
133
|
+
|
|
134
|
+
<!-- nina:slot project.6 doc-pointers -->
|
|
135
|
+
|
|
136
|
+
## Hard rules (non-negotiable)
|
|
137
|
+
|
|
138
|
+
A rule keeps its number in every project, because other documents cite it as `Hard Rule #N`. A rule this project does not have leaves a gap in the numbers — it was not lost, and the list is not renumbered.
|
|
139
|
+
|
|
140
|
+
1. **Run the subagent chain proportional to the task (see the pipeline table).** Under-gating a change on a critical path is a protocol violation — those gates are never skipped, and when uncertain whether one is touched, gate it. But small read-only / display / UI-copy changes (≤2 files, no critical path) take the light chain or a direct edit — over-gating them wastes hours and is also a failure.
|
|
141
|
+
<!-- nina:slot db.5 -->
|
|
142
|
+
<!-- nina:slot money.1 -->
|
|
143
|
+
<!-- nina:slot money.2 -->
|
|
144
|
+
<!-- nina:slot money.3 -->
|
|
145
|
+
<!-- nina:slot db.6 -->
|
|
146
|
+
<!-- nina:slot db.7 -->
|
|
147
|
+
<!-- nina:slot pii.1 -->
|
|
148
|
+
9. **TSDoc is mandatory on every TS declaration** (function, type, interface, class, method, enum) — exported or not, and members are declarations: the fields of an interface, a type alias or an inline type literal, class members, constructors, an object literal's methods and accessors — a hand-built fake's too — and a local whose value is a function, a mock's included. A data local, a literal typed by a documented declaration and a property descriptor passed as an argument need none. Enforced in review, by a sweep of every file touched, not a sample.
|
|
149
|
+
<!-- nina:slot edge-cf.8 -->
|
|
150
|
+
<!-- nina:slot integrations.4 -->
|
|
151
|
+
<!-- nina:slot integrations.5 -->
|
|
152
|
+
<!-- nina:slot integrations.6 -->
|
|
153
|
+
14. **Preview-first deploy. Smoke runs against preview, NEVER prod first.** The **devops** stage owns executing this; the reviewer only verifies the plan exists. Any change that culminates in a production deploy must name a preview URL (a preview deployment for `{{APP_DIR}}`; a staging route for the API) in the architect's spec. Smoke runs against preview FIRST. A spec without a preview-deploy plan is rejected by the reviewer. Until the project has a preview target, a change deploys nowhere: it ends at qa, and the first deploy is a step of its own whose spec names the target.
|
|
154
|
+
15. **Relative imports use the `.js` extension in `.ts`/`.tsx` source — this is intentional ESM, NOT a mistake.** `import { x } from './client.js'` (never `'./client'` or `'./client.ts'`). TypeScript does not rewrite specifiers; emitting packages ({{EMITTING_PKGS}}) ship real `dist/*.js`, so the `.js` form is the only one that resolves at runtime, and the bundlers map it back to `.ts` source transparently. Stripping or `.ts`-ifying the extension breaks the build — do not "fix" it. Full rationale: `.claude/patterns.md` § *Relative imports carry a `.js` extension*.
|
|
155
|
+
<!-- nina:slot blockchain.1 -->
|
|
156
|
+
17. **Every artifact you are handed is a hypothesis; the tree is the truth.** A file list, a line range, a test filter, a cited premise — each was derived by an earlier stage from a tree that may have moved since, so **the part you are about to rely on** is checked against the tree before you act on it. Where it diverges, **report the divergence, naming the stage that produced it**: a stage that quietly works around a wrong input hides the defect from the only stage that can fix it, and the next task inherits it intact. Acting on what the tree says **never widens what you were authorized to touch** — a wrong range inside a file you own is corrected and reported; a divergence that would take you outside your authorized files stops the work and goes back to the stage that drew the boundary. <!-- nina:why -->This is not diligence for its own sake. On one milestone three consecutive stages each found the artifact above them wrong — a test filter that covered two of six blocks gated on a live credential, a line range that missed a second block of orphaned mocks, a spec that named the wrong test as the one blocking a removal — and every one of the three found it only by going to look.<!-- /nina:why -->
|
|
157
|
+
18. **Only the orchestrator commits, and no stage moves the checkout.** A stage reads history (`git diff`, `log`, `show`, `status`, `blame`) and builds or tests another revision in a `git worktree` it removes afterwards. It never stashes, checks out, switches, resets, restores, cleans, commits, merges or rebases in the tree it was dispatched into: the work in flight lives there, uncommitted, and a stash or a checkout takes it from every stage beside it. Its scratch directories are its own as well: each in the session's scratchpad, named for its stage and step and made so that an existing path fails (`mktemp -d "<scratchpad>/<role>-<step>-XXXX"`). Its scratch work creates, links and deletes only paths inside those, checked before each `ln -s`, `cp` or `rm -rf`; runs a probe another stage named from a copy in one of them; and removes them when it ends, except what its report names for a later run. A gate keeps the harness it built — its generators, fuzz and differential drivers — and names each with the command that runs it: the re-check of the fix runs them first, rebuilds any no longer there and says so, and writes only what the fix added. The report names every directory it made. A probe that needs a package's module resolution runs in a scratch copy of the package, never in the tree.<!-- nina:why --> In the third project, five stages ran side by side in one session's scratchpad: a reviewer set up in another stage's `pkg`, overwrote four of its files, linked onto a link and so left a stray link inside the repository's `node_modules`, then removed the directory the dba had named as holding its harness. Another wrote a probe into a test directory four stages were reading.<!-- /nina:why -->
|
|
158
|
+
19. **Every turn re-reads the whole context, so take each thing in once.** Tool calls that do not depend on each other go out in one message. A file you wrote or edited is in your context already: read it again only once something else changed it. Find before you open (`grep -l`, `grep -c`), then read the range you need; output longer than a screen goes to a file you grep. A battery, a fuzz or a differential check of many light cases, run as a script of your own, goes side by side: a worker per core less two (`os.availableParallelism() - 2`), a quarter of that when you are one of the gates that run together, each worker on its own scratch copy of what it changes and in its own process group, killed when it ends or times out. A case that boots a runtime, a database or a browser runs alone, and so does the suite. Wait for it in one call, or split it into calls that each run a share to the end, never with a `sleep` a turn, which re-reads your context only to learn it is not done.<!-- nina:why --> In the project this was learned in, 99% of an implementer's ~350 turns sent a single call, and runs re-read files they had just written 534 times.<!-- /nina:why --><!-- nina:why --> In the third project, one step's stages each ran theirs one case at a time: an implementer round spent 66 of its 92 minutes in `sleep` on 2,536 mutants, an integration-tester 43 of 81 in fuzz commands of nine and ten minutes, and the reviewers that had rejected the change by 11:02 waited until 12:10 on it.<!-- /nina:why -->
|
|
159
|
+
20. **Send work back only for what blocks it.** A finding blocks when shipping it would be wrong: a defect in behavior, security, data or a contract; a requirement of the spec unmet, or a file written outside the ones it authorized; a test that cannot fail, or a case the spec requires left untested; a claim a later stage will act on that is false. The rest is a **note**: wording, naming or style, a claim imprecise but not false, an improvement, an evasion the spec ruled out in writing — never a hole in security, isolation or data, which blocks however the spec scoped it. Nor is a question for the design a note: a form a check, guard or scanner accepts that its design's intent refuses without saying so — what the spec ruled out in writing stays a note — or a choice the spec left open that a later stage will act on, blocks and goes to the architect, even when the code does what its spec says: a note reaches no one who can decide it. A failure that fails closed is not safe for the change when it fails at a size the design did not choose and no test reaches: that one fails in production.<!-- nina:why --> In two steps replayed without the full pipeline, the replay's reviewers saw both design flaws the pipeline had caught, in 3 of 5 reviews and in 4 of 5, and blocked the first in two and the second in none: one wrote that the architect might refuse the form and approved, the others called a chunk sized for another table safe because it throws before any query — which it does only at a size no test reaches.<!-- /nina:why --> A note never sends work back and never goes on the `ISSUES` line: list it in a **Notes** section of your report, with `path:line`, whatever the verdict.<!-- nina:why --> In one step's second round, 7 to 9 of its 19 issues were wording or evasions its spec had scoped out, and with the rest they bought a third round of every stage.<!-- /nina:why -->
|
|
160
|
+
|
|
161
|
+
## Common commands
|
|
162
|
+
|
|
163
|
+
<!-- nina:slot project.7 common-commands -->
|
|
164
|
+
|
|
165
|
+
## When in doubt
|
|
166
|
+
Consult `.claude/retrieval.md` to decide which docs to load before acting.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The cost watch, run by this project's Claude Code hooks on every tool call a subagent makes.
|
|
4
|
+
*
|
|
5
|
+
* A run re-reads its whole context on every turn, so what it costs grows faster than what it does. When the
|
|
6
|
+
* tokens a run has re-read pass {{RUN_READ_WARN}}M, and again at each doubling, this tells the run what a
|
|
7
|
+
* turn now costs and to hand back what is left if it is more than one run should carry, and tells the
|
|
8
|
+
* person in one line. It keeps counts only, under ~/.nina/gate/, and lets every call through if anything
|
|
9
|
+
* goes wrong.
|
|
10
|
+
*
|
|
11
|
+
* The mechanism lives in the NINA package; this file only says which project it watches, and from what.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { dirname, resolve } from 'node:path';
|
|
15
|
+
import { Readable } from 'node:stream';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
// Read before the package is loaded: every tool call passes this hook, and the orchestrator's — which it does
|
|
19
|
+
// not watch — go through without paying for it.
|
|
20
|
+
const chunks = [];
|
|
21
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
22
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
23
|
+
if (!/"agent_id"\s*:\s*"[^"]/.test(raw)) process.exit(0);
|
|
24
|
+
const { runCostWatch } = await import('@xhulz/nina/cost');
|
|
25
|
+
process.exitCode = await runCostWatch({ root: resolve(dirname(dirname(fileURLToPath(import.meta.url)))), warnAt: Number('{{RUN_READ_WARN}}') * 1e6, stdin: Readable.from([raw]) });
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The edit guard, run by this project's `PreToolUse` hook on the edit tools and the shell.
|
|
4
|
+
*
|
|
5
|
+
* A file this harness composes is changed in its layer, never in place: the next compose would
|
|
6
|
+
* overwrite the edit. This refuses an edit to a file carrying the `nina:generated` notice, and the
|
|
7
|
+
* refusal quotes the notice, which names where the change goes. And it refuses a stage the git that
|
|
8
|
+
* would move the checkout its work in flight lives in — Hard Rule #18. The mechanism lives in the NINA
|
|
9
|
+
* package; this file only says which project it guards. It lets every call through if anything goes
|
|
10
|
+
* wrong.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { dirname, resolve } from 'node:path';
|
|
14
|
+
import { Readable } from 'node:stream';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
// Read before the package is loaded: every shell command passes this hook, and the orchestrator's — the one
|
|
18
|
+
// that commits — go through without paying for it.
|
|
19
|
+
const chunks = [];
|
|
20
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
21
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
22
|
+
if (/"tool_name"\s*:\s*"Bash"/.test(raw) && !/"agent_id"\s*:\s*"[^"]/.test(raw)) process.exit(0);
|
|
23
|
+
const { runGuard } = await import('@xhulz/nina/guard');
|
|
24
|
+
process.exitCode = await runGuard({ root: resolve(dirname(dirname(fileURLToPath(import.meta.url)))), stdin: Readable.from([raw]) });
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runs this project's harness drift detectors.
|
|
4
|
+
*
|
|
5
|
+
* The mechanism lives in the NINA package — what counts as drift, what counts as a check
|
|
6
|
+
* that could not run, and how either reaches a Claude Code hook. Only the LIST is here,
|
|
7
|
+
* because only this project knows which detectors it has and what to do when one fires.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/harness-check.mjs human-readable; exit 1 on drift, 2 on error
|
|
11
|
+
* node scripts/harness-check.mjs --hook JSON for a Stop hook — shown to the person; always exit 0
|
|
12
|
+
* node scripts/harness-check.mjs --context JSON for a UserPromptSubmit hook — read by the model
|
|
13
|
+
* before it answers; always exit 0
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { dirname, resolve } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { runDetectors } from '@xhulz/nina/detectors';
|
|
19
|
+
|
|
20
|
+
const ROOT = resolve(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
21
|
+
|
|
22
|
+
process.exit(
|
|
23
|
+
runDetectors(
|
|
24
|
+
[
|
|
25
|
+
// Every composed project has this one, so the core carries it rather than asking each
|
|
26
|
+
// project to write the same nine lines. It disables itself where the project does not
|
|
27
|
+
// declare the npm script, like any other detector.
|
|
28
|
+
{
|
|
29
|
+
name: 'agent specs',
|
|
30
|
+
declaredBy: 'harness:compose:check',
|
|
31
|
+
bin: 'nina',
|
|
32
|
+
args: ['compose', '--check', '--drift', '--quiet'],
|
|
33
|
+
hint: 'these files are generated — run `nina where <file>` to see which layer owns it, then `nina compose`',
|
|
34
|
+
ignore: /^compose: current$/,
|
|
35
|
+
},
|
|
36
|
+
// What the project has not declared yet: vocabulary, its own layer, the documents the layers
|
|
37
|
+
// read, the wiring. On a new project this is the whole to-do list, and it reaches the model
|
|
38
|
+
// before its first answer — so the first conversation starts by filling it, unasked.
|
|
39
|
+
{
|
|
40
|
+
name: 'declaration',
|
|
41
|
+
bin: 'nina',
|
|
42
|
+
args: ['check', '--detector', '--quiet'],
|
|
43
|
+
hint: 'each line says what is missing — `.nina/TODO.md` lists the project’s own items, and `.nina/BRIEF.md`, where the interview wrote one, says what the project is; fill them, then `nina compose`',
|
|
44
|
+
ignore: /^check: declaration is sound/,
|
|
45
|
+
},
|
|
46
|
+
// The one link of the learning cycle that fails in silence: a role keeps being sent back
|
|
47
|
+
// and nothing is written down. It fires on the event — sent back three times since that role's
|
|
48
|
+
// newest lesson — not on a rate, so it says something new when it speaks. A lesson learned
|
|
49
|
+
// a third time it sends to the harness itself, and says so once.
|
|
50
|
+
{
|
|
51
|
+
name: 'lessons',
|
|
52
|
+
bin: 'nina',
|
|
53
|
+
args: ['learn', '--check', '--quiet'],
|
|
54
|
+
hint: 'each line above says what it needs — `nina learn` shows the whole cycle',
|
|
55
|
+
ignore: /^learn: current$/,
|
|
56
|
+
},
|
|
57
|
+
// The loop gate holds the caps in .claude/graph.md from this project's hooks, and it lets every
|
|
58
|
+
// call through when it fails — so a gate that stopped working looks exactly like a pipeline with
|
|
59
|
+
// no loops. This asks it, every turn, whether it is wired and would still hold one.
|
|
60
|
+
{
|
|
61
|
+
name: 'loop gate',
|
|
62
|
+
bin: 'nina',
|
|
63
|
+
args: ['gate', '--selftest', '--quiet'],
|
|
64
|
+
hint: 'the caps in .claude/graph.md are not being held — `nina wire --apply` wires the gate; `nina gate --selftest` says what else is wrong',
|
|
65
|
+
ignore: /^gate: current$/,
|
|
66
|
+
},
|
|
67
|
+
// A step that landed well past its forecast says the steps forecast with it are short by as much, and it took
|
|
68
|
+
// a reviewer noticing, then a whole architect run, to work the rate out. It speaks while they are not forecast again.
|
|
69
|
+
{
|
|
70
|
+
name: 'step sizes',
|
|
71
|
+
bin: 'nina',
|
|
72
|
+
args: ['sizes', '--check', '--quiet'],
|
|
73
|
+
hint: 'forecast those steps again at that rate — a `Size:` line each, the numbers `nina sizes` lists — and send the architect only a step the new number puts past the limit, to split',
|
|
74
|
+
ignore: /^sizes: current$/,
|
|
75
|
+
},
|
|
76
|
+
<!-- nina:slot project.1 detectors -->
|
|
77
|
+
],
|
|
78
|
+
{ root: ROOT, hook: process.argv.includes('--hook'), context: process.argv.includes('--context') },
|
|
79
|
+
),
|
|
80
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The loop gate, run by this project's Claude Code hooks.
|
|
4
|
+
*
|
|
5
|
+
* `.claude/graph.md` caps every loop-back edge. This holds the cap: it records the verdict each stage
|
|
6
|
+
* reports, each dispatch that goes out and each time the owner speaks, and sends the dispatch that would
|
|
7
|
+
* make a round past its edge's cap to the owner to confirm — the graph's `human`. The owner's next
|
|
8
|
+
* message starts every count over. It keeps metadata only, under ~/.nina/gate/.
|
|
9
|
+
*
|
|
10
|
+
* The mechanism lives in the NINA package; this file only says which project it guards. It prints
|
|
11
|
+
* nothing unless it has a decision to report, and it lets every call through if anything goes wrong —
|
|
12
|
+
* `nina gate --selftest`, run by `pnpm harness:check`, is what says so.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { dirname, resolve } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { runGate } from '@xhulz/nina/gate';
|
|
18
|
+
|
|
19
|
+
process.exitCode = await runGate({ root: resolve(dirname(dirname(fileURLToPath(import.meta.url)))) });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ARCHITECT_EFFORT": "xhigh",
|
|
3
|
+
"BUILD_CMD": "pnpm build",
|
|
4
|
+
"DEEP_EFFORT": "high",
|
|
5
|
+
"DEEP_MODEL": "claude-opus-5-5",
|
|
6
|
+
"LINT_CMD": "pnpm lint",
|
|
7
|
+
"RUN_READ_WARN": "40",
|
|
8
|
+
"STEP_FILES": "15",
|
|
9
|
+
"STEP_LINES": "400",
|
|
10
|
+
"TEST_CMD": "pnpm test",
|
|
11
|
+
"TYPECHECK_CMD": "pnpm typecheck",
|
|
12
|
+
"WORK_EFFORT": "xhigh",
|
|
13
|
+
"WORK_MODEL": "claude-sonnet-5-5"
|
|
14
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot blockchain.1 -->
|
|
2
|
+
| a contract, a proxy, or a contracts library component | **`develop-secure-contracts`** · **`upgrade-solidity-contracts`** when storage moves |
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot blockchain.2 -->
|
|
5
|
+
- **Contract surface (MANDATORY when the spec reaches `{{CONTRACTS_DIR}}`).** Name which contracts are touched and whether each is deployed; for a deployed one, say whether this is an upgrade and what preserves the storage layout. State the access-control model in roles, not in adjectives: who may call each state-changing function, and how that role is granted and revoked. List the adversarial tests the implementation owes — re-entry, the unauthorized caller, the boundary amount, the second call that must fail. A spec that reaches a contract and leaves any of these to the implementer is rejected.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot blockchain.1 -->
|
|
2
|
+
- **`upgrade-solidity-contracts`** — before deploying a contract or executing a proxy upgrade, to check the storage layout against what is live.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot blockchain.2 -->
|
|
5
|
+
- **A contract deploy needs `solidity-auditor` `APPROVED` for this exact diff**, and its rollback named before it runs — which for a non-upgradeable contract is a redeploy plus a migration of state, not a revert. Say so *before* deploying. A deploy to a network carrying real value needs an explicit go from {{OWNER}} for that change, the same as production.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot blockchain.1 -->
|
|
2
|
+
- **The solidity-auditor gate ran and returned `APPROVED`** for any diff touching `{{CONTRACTS_DIR}}`, a deploy script, or a contracts library version. Its absence is a blocking finding on its own, whatever the diff looks like to you.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot blockchain.2 -->
|
|
5
|
+
- A contract change written by the implementer rather than **solidity-dev** is rejected on routing alone. The two roles carry different rules about what a defect costs, and the cheaper set is the wrong one here.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- nina:slot blockchain.1 -->
|
|
2
|
+
| **`develop-secure-contracts`** | any library component in the diff — to check the usage against the library's own guidance, not against memory |
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot blockchain.2 -->
|
|
5
|
+
| **`upgrade-solidity-contracts`** | a proxy, an initializer, or a storage layout change |
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot blockchain.3 -->
|
|
8
|
+
| **`security-audit`** | the off-chain half — deploy scripts, key handling, CI, RPC endpoints (guidance mode) |
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot blockchain.4 -->
|
|
11
|
+
- The deployed addresses and role holders in play, where the change targets a live system.
|
|
12
|
+
|
|
13
|
+
<!-- nina:slot blockchain.5 -->
|
|
14
|
+
- **An event for every state change**, and that what it emits matches what was written. Off-chain
|
|
15
|
+
reconstruction is the only history there is, and a wrong event is worse than a missing one.
|
|
16
|
+
|
|
17
|
+
<!-- nina:slot blockchain.6 -->
|
|
18
|
+
- Approve a change to a live contract without stating the upgrade path and naming who can execute it.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- nina:slot blockchain.1 -->
|
|
2
|
+
| **`setup-solidity-contracts`** | a new contract project, a dependency install, or import/remapping configuration |
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot blockchain.2 -->
|
|
5
|
+
| **`develop-secure-contracts`** | any library component — token standards, access control, pausing, reentrancy protection |
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot blockchain.3 -->
|
|
8
|
+
| **`upgrade-solidity-contracts`** | a proxy, an initializer, or anything that moves storage |
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot blockchain.4 -->
|
|
11
|
+
- The contract's deployed state, where one exists. A change to a live contract is an upgrade with a
|
|
12
|
+
storage layout to preserve, not an edit.
|
|
13
|
+
|
|
14
|
+
<!-- nina:slot blockchain.5 -->
|
|
15
|
+
- Widen a function's visibility, or add an address to a role, to make a test pass. The test is
|
|
16
|
+
describing the access control; fix the test or reject the spec.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!-- nina:slot blockchain.1 -->
|
|
2
|
+
- **Contract guardrail** → deployed code is immutable and permanently callable by anyone, so a defect is not a patch away and "nothing calls this" is not a property of it. Reentrancy, a state-changing function with no access control, a storage layout broken by an upgrade, and an audited component used against its own guidance are caught here.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot blockchain.2 -->
|
|
5
|
+
| **solidity-dev** | Write contract sources and their adversarial tests to spec; never deploys | `.claude/agents/solidity-dev.md` |
|
|
6
|
+
| **solidity-auditor** | Mandatory gate on any contract diff; blocks reviewer and devops | `.claude/agents/solidity-auditor.md` |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- nina:slot blockchain.1 -->
|
|
2
|
+
- `solidity-dev` — writes contract code, which cannot be changed once it is live
|
|
3
|
+
- `solidity-auditor` — gate: audits contract code as an attacker before anything deploys
|
|
4
|
+
|
|
5
|
+
<!-- nina:slot blockchain.2 -->
|
|
6
|
+
- `architect` → `solidity-dev` on `SPEC-READY` — the spec changes contract code
|
|
7
|
+
- `solidity-dev` → `solidity-auditor` on `DIFF-READY`
|
|
8
|
+
- `solidity-dev` → `architect` on `BLOCKED` — the change cannot be made safely as specified · max 2
|
|
9
|
+
- `solidity-auditor` → `reviewer` on `APPROVED`
|
|
10
|
+
- `solidity-auditor` → `solidity-dev` on `REJECTED` — a flaw in the contract code · max 2
|
|
11
|
+
- `solidity-auditor` → `architect` on `REJECTED` — a flaw in the design · max 2
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!-- nina:slot blockchain.1 -->
|
|
2
|
+
|
|
3
|
+
### Solidity-auditor is a guardrail for contract changes
|
|
4
|
+
Invoke **solidity-auditor** on any diff that touches `{{CONTRACTS_DIR}}`, a deploy script, or the version of a contracts library — regardless of where you are in the pipeline. No contract change merges without its approval, and none deploys without it. Reviewer verifies it ran; devops verifies it approved.
|
|
5
|
+
|
|
6
|
+
Contract sources are written by **solidity-dev**, not the implementer. Route the work there when the spec's file list reaches `{{CONTRACTS_DIR}}`, and keep the implementer on the off-chain side of the same feature.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!-- nina:slot blockchain.1 -->
|
|
2
|
+
16. **Every contract change goes through the solidity-auditor gate, and deployed code is never patched in place.** A diff touching a contract source, a deploy script, or the version of a contracts library does not reach the reviewer without `VERDICT: APPROVED` from **solidity-auditor**, and does not reach any network without it. Contract sources are written by **solidity-dev**, not by the implementer — the implementer's rules assume a defect is a patch away, and here it is a redeploy or a proxy upgrade at best. Private keys, mnemonics and funded accounts never enter the repository in any form, test fixtures included.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot blockchain.2 -->
|
|
5
|
+
| **ANY step touching a contract, a deploy script, or a contracts library version** | **architect → solidity-dev → solidity-auditor → reviewer → qa** — the auditor is mandatory and blocks the reviewer |
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot blockchain.3 -->
|
|
8
|
+
- **solidity-dev** → write contract sources and their adversarial tests strictly to spec; never deploys, never widens access control to make a test pass
|
|
9
|
+
- **solidity-auditor** → **mandatory gate** on any contract diff; `APPROVED` required before reviewer and before any deploy
|
|
10
|
+
|
|
11
|
+
<!-- nina:slot blockchain.4 -->
|
|
12
|
+
- **contract code**: a contract source, a deploy script, or the version of a contracts library
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- nina:slot d1.1 -->
|
|
2
|
+
| `cloudflare:cloudflare` | D1's limits, its binding's API, `wrangler d1` migrations |
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot d1.2 -->
|
|
5
|
+
- **On D1, migration safety is about table redefinitions, not locks.** SQLite cannot alter a column, so many changes are drafted as a redefinition: a new table, a copy, `DROP TABLE` of the old one, a rename. Under the local emulator `PRAGMA foreign_keys=OFF` had no effect, so that `DROP TABLE` fired every `CASCADE` and `SET NULL` pointing at the table, exited 0 and left `foreign_key_check` clean, and `PRAGMA defer_foreign_keys=on` did not stop it. Until production D1 shows otherwise, hold every migration to:
|
|
6
|
+
- no redefinition of a table that holds data;
|
|
7
|
+
- foreign keys `RESTRICT` or `NO ACTION`, so a redefinition that would destroy rows fails instead. A `CASCADE`, `SET NULL` or `SET DEFAULT` the architecture keeps is named there with its reason, and every migration that redefines a table it reaches is tested on rows it would cascade to;
|
|
8
|
+
- `ADD COLUMN` nullable, or NOT NULL with a constant default — with no default, or a non-constant one such as `unixepoch()` or `CURRENT_TIMESTAMP`, it applied to an empty table and failed on one with rows under the emulator — and an added `REFERENCES` column nullable with no default, which SQLite requires while foreign keys are on;
|
|
9
|
+
- a redefinition's `INSERT … SELECT` naming only columns the old table has: the emulator read an unknown double-quoted name as a string literal and filled every row with it;
|
|
10
|
+
- the migration applied, in a test, to the previous schema holding rows, and a drift test comparing the applied schema (`PRAGMA table_info`, `foreign_key_list`, `index_list`) with the one the code declares.
|
|
11
|
+
|
|
12
|
+
- **On D1, the SQL you read is the text D1 runs, not the file.** Per wrangler 4.148's source and `--local`: `d1 migrations apply` appends `INSERT INTO d1_migrations (name) values ('<file>');` to the text, so a last statement with no `;` merges with it and the deploy fails while the tests, which bind that insert apart, pass; locally, text holding `BEGIN TRANSACTION` loses its first `BEGIN TRANSACTION;` and first `COMMIT;` wherever they stand, comments and strings included; `--remote` hands the text whole to D1's server, whose splitter nobody outside Cloudflare can read; and SQLite reads `$name(…)`, `:name(…)`, `@name(…)` and `#name(…)` as one token that swallows quotes, `--` and `;`. Refuse a migration holding `BEGIN TRANSACTION` or `COMMIT;` anywhere, `[$@:#]name(` outside a string or comment, or a last statement without its `;`. A script that gates migrations is checked differentially against wrangler's own trim and split and SQLite's tokenizer, never a model of one stage.
|
|
13
|
+
|
|
14
|
+
<!-- nina:slot d1.3 -->
|
|
15
|
+
- **On D1, every query holds to `.claude/patterns.md` § *D1*:** an atomic change is one batch, a conditional write carries its guard in SQL, and no statement binds more than 100 parameters. A list lookup "batched" past that limit is an N+1 fix that fails once the list grows.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!-- nina:slot d1.1 -->
|
|
2
|
+
|
|
3
|
+
### D1
|
|
4
|
+
|
|
5
|
+
- **No interactive transaction.** A transaction call sends `begin`, which the local emulator refused, and the type checker accepts the call. The atomic unit is one D1 batch: a statement failing a UNIQUE or NOT NULL constraint rolled the whole batch back under the emulator. Production D1 has not been observed for either; until it is, design as if both hold. Whether a client's batch call is one D1 batch, and which items it can hold, is cited from the integration doc.
|
|
6
|
+
- **A batch rolls back on an error, not on "0 rows changed".** A write that must not happen when an earlier one changed nothing carries its guard in SQL inside the same batch — a statement that fails when the condition does not hold. A `changes() = 0` guard read only the statement right before it, under the emulator.
|
|
7
|
+
- **At most 100 bound parameters per statement** (the emulator ran 100 and refused 101). A lookup by a list of ids, with its tenant filter, and a multi-row insert, at rows × columns, are chunked to stay within it.
|
|
8
|
+
- **Each row written counted once per index it touched**, a text primary key's own index included, in the rows written the emulator reported; how production D1 bills them against a rows-written quota is not yet observed.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<!-- nina:slot d1.1 -->
|
|
2
|
+
| Write or audit a D1 query, batch, migration or binding (architect, implementer, the database gate) | `cloudflare:cloudflare` | D1's limits and its binding's API drift with the platform; `.claude/patterns.md` § *D1* holds what this harness requires on top |
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot db.2 -->
|
|
2
|
+
- **Tenant-scope impact** — state where `{{TENANT_KEY}}` is enforced. Every new query in app code filters by it, or reaches it through the model's foreign keys; only the auth library's own tables are exempt.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot db.3 -->
|
|
5
|
+
- **Database flag** — if the schema or a new query is touched, mark **"DBA REQUIRED"** prominently at the top of the spec, and name each new read's cache policy, or **`NO_CACHE`** for a read that feeds a write whose correctness depends on it being fresh — a stale row contaminates every decision made from it.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!-- nina:slot db.3 -->
|
|
2
|
+
- Any change to the schema.
|
|
3
|
+
- Any new query, or change to one, anywhere in application code.
|
|
4
|
+
- Any migration.
|
|
5
|
+
|
|
6
|
+
<!-- nina:slot db.4 -->
|
|
7
|
+
- The schema diff, and the migration SQL it generates.
|
|
8
|
+
- The application code that uses the new or modified queries.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot db.5 -->
|
|
11
|
+
**Read `.claude/code-map.md`** when the change touches new query patterns — it lists which services consume which models, so you can spot N+1 risk and consumer-side cache implications without re-grepping. (Before there is code, fall back to `.claude/architecture.md` § *Database* for the planned schema and its cache policies.)
|
|
12
|
+
|
|
13
|
+
<!-- nina:slot db.6 -->
|
|
14
|
+
- **The schema validates, and you read the migration's SQL** — offline, from files, with the project's own schema tooling. A check that seems to need a live database is one you report as impossible, not one you run.
|
|
15
|
+
- **An empty diff from a schema change is a failure, not "nothing changed".** A diff tool short of what it needs — a datasource, a config — has printed nothing and exited 0, and so has its `--exit-code`. Before you trust an empty result, make the same command print a change you know is there.
|
|
16
|
+
|
|
17
|
+
<!-- nina:slot db.8 -->
|
|
18
|
+
- **Migration safety on large tables:**
|
|
19
|
+
- No `ALTER TABLE ... ADD COLUMN NOT NULL` without a safe default (causes lock + rewrite).
|
|
20
|
+
- No operations that hold long locks without an explicit batched strategy.
|
|
21
|
+
- Non-breaking for rolling deploy — old application code and new schema must coexist during the deploy window.
|
|
22
|
+
|
|
23
|
+
<!-- nina:slot db.9 -->
|
|
24
|
+
- **Index coverage.** Every new query pattern (`WHERE`, `ORDER BY`, `JOIN`, `aggregate`) must be backed by an index declared in the schema. Reject queries that would table-scan. The most-used compound index is `(<tenant>, <other>)` or, for models keyed by a parent entity, `(<parentId>, <other>)`; verify the leading column matches the query.
|
|
25
|
+
|
|
26
|
+
<!-- nina:slot db.10 -->
|
|
27
|
+
- **Tenant scope.** Every query in app code filters by `{{TENANT_KEY}}`, or by the foreign-key chain that resolves back to it. The auth library's own tables are the only exemption. Reject any app query missing the scope.
|
|
28
|
+
|
|
29
|
+
<!-- nina:slot db.11 -->
|
|
30
|
+
- **Cache discipline.** A cached read names its policy explicitly — **an implicit cache is a bug: reject it** — checked against the cache policies in `.claude/architecture.md` § *Database*. A read that feeds a write whose correctness depends on it being fresh is never cached.
|
|
31
|
+
|
|
32
|
+
<!-- nina:slot db.12 -->
|
|
33
|
+
- **N+1 patterns.** If the code fetches a list then queries per item, reject with "fetch the related rows with the list, or batch."
|
|
34
|
+
|
|
35
|
+
<!-- nina:slot db.13 -->
|
|
36
|
+
- **Secrets.** Whatever credential reaches the database — a connection string, an API token — comes from `{{SECRETS_LOCAL}}` locally and `{{SECRETS_PROD}}` in production — never a committed `.env`, never a literal in code. A database reached through a platform binding has no connection string to check.
|
|
37
|
+
|
|
38
|
+
<!-- nina:slot db.14 -->
|
|
39
|
+
- **Run anything that can write to a real database.** You are a read-only gate: against a live database you run read-only queries and nothing else. If a check seems to require writing, it does not — report the limitation to the orchestrator instead.
|
|
40
|
+
|
|
41
|
+
<!-- nina:slot db.15 -->
|
|
42
|
+
- Approve without validating the schema and reading the migration's SQL, offline, as *You MUST check* says.
|
|
43
|
+
|
|
44
|
+
<!-- nina:slot db.16 -->
|
|
45
|
+
- Wave through "small" schema changes. Small changes cause the worst production incidents.
|
|
46
|
+
|
|
47
|
+
<!-- nina:slot db.17 -->
|
|
48
|
+
- Approve a query in app code missing its `{{TENANT_KEY}}` scope, or the foreign key that owns it.
|
|
49
|
+
|
|
50
|
+
<!-- nina:slot db.18 -->
|
|
51
|
+
**Final report format:** ≤300 words if approved. Sections: Schema validation result, Migration SQL preview (verbatim if non-trivial), Index analysis, Tenant-scope check, Cache verdict, Privacy check, Final verdict. If rejected, list each issue with required action — no length cap.
|
|
52
|
+
|
|
53
|
+
<!-- nina:slot db.19 -->
|
|
54
|
+
- **The tracked schema is the reviewed one, not a generator's output.** A diff that rewrites it whole, or drops a delta its header lists, is a generator writing where it must not (`.claude/patterns.md`): reject it.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot db.2 -->
|
|
2
|
+
- **Migrations, in order, against the right database.** Confirm the target is the database you intend, and that the migration is backward-compatible with the code already deployed, because the two are live together during the rollout.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot db.5 -->
|
|
5
|
+
- **Migrations:** applied / none, with the database they hit.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<!-- nina:slot db.2 -->
|
|
2
|
+
- A new query on a cached read path — verify the cache's behavior live (dba covers the schema; you cover how the cache behaves at runtime)
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot db.3 -->
|
|
5
|
+
If a query behaves differently against a real database than the dba's static analysis suggested, escalate to **dba** — do not fix it yourself.
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot db.4 -->
|
|
8
|
+
- **The auth library's backing store is a real database** — the development database, reached as the
|
|
9
|
+
implementer reaches it locally: a connection from `{{SECRETS_LOCAL}}`, or the local emulator's binding. A suite that is green against an
|
|
10
|
+
in-memory stand-in has not exercised the library, which is the whole point of this stage.
|