@ttt-productions/dev-standards 0.0.2 → 0.0.4

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.
@@ -144,10 +144,12 @@ Before starting a project's work:
144
144
  Every release of an `@ttt-productions/*` package hands its adoption to each app through a file in
145
145
  `<PACKAGES_REPO>/docs/work/adoptions/<app>/`.
146
146
 
147
- - **The package session writes them.** In the same change as the package edit, before the release
148
- command, it writes one adoption file per released package for each app — except an app-data
149
- package, whose file goes only to its own app. A package session cannot see which packages the
150
- other product installs, so every other released package gets a file for every app.
147
+ - **The package session writes them only for the apps that aren't in the conversation.** In the same
148
+ change as the package edit, before the release command, it writes one adoption file per released
149
+ package for each app outside the conversation — an app-data package's file goes only to its own
150
+ app. A package session cannot see which packages another product installs, so every other released
151
+ package gets a file for each of those apps. The app the conversation is working in adopts the
152
+ release itself, right after the install.
151
153
  - **The version it names** is the version the release will publish: one patch above the package's
152
154
  `package.json` version before the release.
153
155
  - **The file** is one item in the `docs/work/` item format (`<DEV_STANDARDS>/work/README.md`) —
@@ -198,9 +200,8 @@ step 5.
198
200
  ## The Normal `<APP_NAME>` Flows (named workflows — "do the normal dev/test flow" resolves HERE)
199
201
 
200
202
  These two named flows are the standing default for how work in these repos is verified and
201
- sequenced. They are DEFAULTS, not cages: DJ may override any step with explicit in-conversation
202
- instructions (e.g. "do the normal test flow, but WAIT for my go before starting dev:e2e — I'm
203
- using the machine"), and an override governs exactly what it names, nothing more. A repo changes a
203
+ sequenced. They are DEFAULTS, not cages: DJ may override any step with an explicit instruction; the
204
+ override governs exactly what it names, for that conversation only. A repo changes a
204
205
  flow only under its `<APP_NAME>` additions heading. In a repo whose `<APP_NAME>` is none
205
206
  (ttt-packages) the Test Flow does not apply — its gate is its own `CLAUDE.md` § Verification — and
206
207
  the Dev Flow's steps 1–4 are its whole share.
@@ -221,9 +222,10 @@ the Dev Flow's steps 1–4 are its whole share.
221
222
  (RULE-STD-QUALITY-008 — Claude operates the restart).
222
223
  5. Kill `dev:e2e` when done and STOP — wait for DJ's push.
223
224
 
224
- **When the session is orchestrating agents, stages 2–5 belong to ONE agent, never the orchestrator.**
225
- That agent starts `dev:e2e`, runs the live stages, fixes root causes (restarting the stack per stage
226
- 4), and kills it; the orchestrator only takes its report. "Claude-operated" here means that agent.
225
+ **In a conversation DJ has called an orchestrator, the Normal Test Flow is run by one test agent,
226
+ never the orchestrator.** A failure goes back to the orchestrator, which sends it to the agent that
227
+ built the failing code; that agent fixes it and reports. The same test agent then reruns the flow,
228
+ restarting `dev:e2e` per stage 4. In an inline conversation, the session runs the Test Flow itself.
227
229
 
228
230
  Not part of this flow: hosted-dev E2E runs (always separately commanded — `<HOSTED_E2E_DOC>`) and
229
231
  `npm run dev:local` (DJ's manual dev surface).
@@ -235,29 +237,23 @@ Not part of this flow: hosted-dev E2E runs (always separately commanded — `<HO
235
237
  change as the package edit.
236
238
  3. ttt-packages `npm run test:quiet` green.
237
239
  4. Hand DJ ONLY the release command (`./scripts/release-multiple.sh <folders…> patch`, its own fenced
238
- block) — then STOP. No install commands: Claude does the installs.
239
- 5. DJ runs the publish and says so. Claude then starts a 20-minute clock and checks npm every 30
240
- seconds until every released package's new version is the `@latest` on npm; then Claude runs the
241
- installs itself (`@latest --prefer-online`, at the root and in every consuming folder that declares
242
- the package — `<INSTALL_FOLDERS>`), confirms each lockfile names the new version, adopts the
243
- adoption files (§ Adoption files), and continues straight into the app work with no further go.
244
- While it polls, the session works on whatever does not depend on the release — the publish-wait
245
- overlap in `<DEV_STANDARDS>/development-preferences.md` § Shared-package publish.
246
- A version still missing from npm after 20 minutes = stop and tell DJ which package; never install
247
- anything but the published version.
248
- **What "published" means, and what to poll for.** When DJ says "published" (or "publish is
249
- running"), he has just RUN the release command: the script is still running and the packages are
250
- still building, so nothing may be on npm yet, and the version numbers in the checkout may not be
251
- bumped yet. `release-multiple.sh` first runs its `test:quiet` preflight (minutes); then, per package,
252
- `release-package.sh` bumps the version, commits `Release <pkg> vX.Y.Z`, tags it, and pushes; the
253
- tag triggers ttt-packages' `publish.yml` GitHub Actions job, which builds and publishes to npm. So
254
- the version to wait for is the NEW one — one patch above the package's `package.json` version
255
- before the release, or the version its `Release <pkg> v…` commit names once that lands — never the
256
- `package.json` version in the checkout at the moment DJ speaks, which can still be the old one
257
- (compared against npm `@latest`, it reads a stale match as "live"). A version is available only
258
- when npm `@latest` names it AND its tarball
259
- (`https://registry.npmjs.org/@ttt-productions/<pkg>/-/<pkg>-<version>.tgz`) answers 200: the
260
- registry can report the new `@latest` minutes before it serves the tarball, and an install in that
261
- gap fails `E404`.
240
+ block), with one line under it naming the versions it will publish — the same versions its
241
+ adoption files name, one patch above each package's `package.json` version before the release
242
+ (e.g. "Publishes chat-core 0.11.8 and chat-react 0.13.20.") — then STOP. No install commands:
243
+ Claude does the installs.
244
+ 5. DJ runs the publish and says so, at any point: the script just started, its preflight still
245
+ running, or the packages already published and GitHub Actions running. Claude then starts a
246
+ 20-minute clock and checks npm every 30 seconds for exactly the versions named in step 4 — it never
247
+ re-reads `package.json` or git to find them. A version is available only when npm `@latest` names
248
+ it AND its tarball (`https://registry.npmjs.org/@ttt-productions/<pkg>/-/<pkg>-<version>.tgz`)
249
+ answers 200: the registry can report the new `@latest` minutes before it serves the tarball, and
250
+ an install in that gap fails `E404`. Then Claude runs the installs itself (`@latest
251
+ --prefer-online`, at the root and in every consuming folder that declares the package —
252
+ `<INSTALL_FOLDERS>`), confirms each lockfile names the new version, adopts the release in its own
253
+ app (§ Adoption files), and continues straight into the app work with no further go. While it
254
+ polls, the session works on whatever does not depend on the release — the publish-wait overlap in
255
+ `<DEV_STANDARDS>/development-preferences.md` § Shared-package publish. A version still missing from
256
+ npm after 20 minutes = stop and tell DJ which package; never install anything but the published
257
+ version.
262
258
  6. All `<APP_NAME>` work; when the entire change set is done, run the Normal `<APP_NAME>` Test Flow.
263
259
  7. Stop and wait for DJ's push.
@@ -23,6 +23,16 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
23
23
  it goes. Nothing is filed, parked, or written up without DJ's word.
24
24
  - A doc's "staged / later / deferred" label is never DJ's decision; only DJ's own explicit deferral
25
25
  parks an item.
26
+ - **DJ's rules live only in his rule docs** — the rulebooks, this file, `CLAUDE.md` / `AGENTS.md`,
27
+ and the workflows. An instruction DJ gives in a conversation lasts for that conversation only; it
28
+ becomes a standing rule only when he says to write it into the rules. No plan, work item, ledger,
29
+ or prompt ever holds a rule, an instruction, or reasoning, and a session never takes a rule from
30
+ one.
31
+ - **There are no handoff docs.** When DJ asks to get ready for a new conversation, the session gives
32
+ a small copy-paste prompt in chat that points at the items being worked; the items hold only what
33
+ is left. The prompt carries nothing else except open questions DJ chose to move. An item whose
34
+ only remaining work is the Normal Test Flow or a hosted E2E run is deleted; if a conversation
35
+ switches at that point, the prompt says to run it.
26
36
  - "Finish everything" / "do it all" means every single thing DJ has decided, with nothing left
27
37
  undone or parked. It never means deciding anything for DJ: when finishing needs a decision he has
28
38
  not made, ask that one question and keep doing the rest. Never stop for complexity, size, time of
@@ -51,22 +61,39 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
51
61
  - An answer that REDIRECTS is not approval to build the revision — show the revised artifact in
52
62
  chat and get a clean confirm before implementing.
53
63
  - No new-conversation or handoff proposals unless DJ asks.
64
+ - **Every question is tracked until DJ answers it.** A question about the item being worked is asked
65
+ when it is found; the parts of that item that depend on the answer wait, and everything else keeps
66
+ going. A problem found outside the item never stops work: it is reported in chat with what was
67
+ verified and a recommendation, and stays open for DJ. When DJ has said he is away, no question
68
+ blocks: it goes in chat, the session finishes every part that does not depend on it, and moves to
69
+ the next item.
70
+ - **No Normal Test Flow while any question is open.** An answer can change code, and a test run
71
+ before it is worthless. The open questions are asked first, any code they change is done, and then
72
+ the Test Flow runs once.
73
+ - Open questions are asked one at a time, as questions, never buried in a status message. The
74
+ session never answers, files, or drops one itself.
75
+ - When DJ asks whether the conversation is good to end, or says to start a new one, the session first
76
+ says how many questions are open and asks whether to answer them here or move them into the new
77
+ conversation's prompt.
54
78
 
55
79
  ## Git, publish, deploy, and the dev stacks
56
80
 
57
- - DJ does ALL git: staging, commits, pushes, publishing, installs he has not handed over, and
58
- deployments. Hand him exact commands; never run them. Never commit or push. DJ stages files
59
- continuously while work is running so he can see what is changing; never treat a staged/unstaged
60
- change as a sign of someone else's work, and never bring up staging, the index, or other git state
61
- with DJ.
81
+ - DJ does ALL git — staging, commits, pushes, branches, publishing, installs he has not handed over,
82
+ and deployments. An AI runs none of it unless DJ commands that exact action in the conversation;
83
+ otherwise it hands him the exact command. DJ stages files continuously while work is running so he
84
+ can see what is changing; never treat a staged/unstaged change as a sign of someone else's work,
85
+ and never bring up staging, the index, or other git state with DJ.
86
+ - Git and GitHub (`git log`, `git status`, diffs, `gh`, Actions runs) are read only when DJ says it is
87
+ needed. A problem DJ brings is fixed from the current code; when and why it changed is not looked
88
+ up unless he asks.
62
89
  - Never instruct DJ to push or deploy — deploy IS `git push`. Never nag about deploys, indexes, or
63
90
  seed buttons.
64
91
  - Never discard, overwrite, reset, clean, or revert existing work unless DJ explicitly asks.
65
92
  Preserve unrelated uncommitted work; DJ or another tool may be working concurrently.
66
93
  - `dev:local` (DJ's manual dev stack) is DJ-only, always — never start or stop it.
67
94
  - `dev:e2e` (the verification stack) is session-operated only inside the repo's Normal Test Flow or
68
- on DJ's explicit command. DJ's in-conversation instruction overrides any flow step ("wait for my go
69
- before starting dev:e2e") and governs exactly what it names.
95
+ on DJ's explicit command. DJ's in-conversation instruction overrides any flow step and governs
96
+ exactly what it names, for that conversation only.
70
97
  - Never kill a running dev stack on your own judgment ("ports look wrong" = say what you see and
71
98
  WAIT). Never run e2e against a stale build.
72
99
  - Hosted-dev E2E runs happen only when DJ commands one, and follow the repo's hosted-E2E run doc
@@ -130,15 +157,25 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
130
157
  - No half-working, dead, or known-bad code — build it or remove it. On a second sighting of a defect
131
158
  class, tell DJ and propose killing the CLASS system-wide plus a regression guard; do it only on
132
159
  DJ's command.
160
+ - **Never make yourself an exception.** Code that breaks a rule, guard, or lint check is fixed. An
161
+ exception is asked for only when there is genuinely no other way, and it goes to DJ with that
162
+ reason, why no fix works. Nothing is exempted without his yes. Every exception he approves carries
163
+ its reason at the exception itself, so anyone reading it years later knows at once why it exists:
164
+ the guard entry's reason field, the comment on the `eslint-disable` / `prettier-ignore` line, or,
165
+ for a design-level exception, the design doc it belongs to. The reason states why the exception
166
+ exists, never who approved it.
133
167
  - Reviewing an external plan (ChatGPT/Codex/etc.) means answering "is this the BEST code to solve
134
168
  my problem" — never just verifying its claims. Evaluate the design space (alternatives, root cause,
135
169
  structure) and fold an optimality verdict into the reviewed doc.
136
170
  - V1-scope discipline: confirm the trigger exists in V1 before asking DJ to design.
137
171
  - Docs stay lean — never a "done" log. Tracking docs hold only remaining work; finished work is
138
172
  DELETED.
173
+ - **Working files** a session needs — extracted text, a one-off script, intermediate output — go in
174
+ `C:\DjDev\claude-scratchpad\<YYYY-MM-DD>-<topic>\`, never in a temp folder, loose in `C:\DjDev`,
175
+ or inside a repo. A session writes there only when the work needs a file, never for a conversation
176
+ that is only talking, and deletes its folder when that work is done.
139
177
  - Working state that must outlive the session — orchestration ledgers, agent change logs, file
140
- claims, handoff notes — is never kept in a temp or scratchpad folder. It lives in the repo beside
141
- the work it tracks and is deleted when the work finishes.
178
+ claims — lives in the repo beside the work it tracks and is deleted when the work finishes.
142
179
  - Help with EVERY step, including non-code and operational ones — real artifacts (policies,
143
180
  runbooks, click-by-click), never a "your job" checklist. Walk-throughs go one step at a time, dev
144
181
  AND prod. A non-code or console step becomes a self-contained Claude web prompt (full context,
@@ -150,6 +187,19 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
150
187
 
151
188
  ## Agents
152
189
 
190
+ - **Every conversation is inline unless DJ says, in that conversation, that it is an orchestrator.**
191
+ No doc, plan, ledger, or prompt makes a conversation an orchestrator.
192
+ - **Inline:** the session does the research and the work itself. When a piece of the work would be
193
+ done better by agents — LOW for mechanical work applied from written examples, HIGH for thinking
194
+ work or review — it proposes them to DJ with each agent's exact task and files, and launches them
195
+ only on his yes. Launching agents, a predefined wave included, never makes it an orchestrator; it
196
+ still runs its own Normal Test Flow.
197
+ - **Orchestrator:** the session saves its context for managing agents and does not read or edit code
198
+ itself. It first launches read-only agents to understand the system, then gives each building
199
+ agent the high-level picture and the design: build this, this way. An agent that finds a better or
200
+ different way stops and comes back instead of building it. Agents run in waves of a few at a time,
201
+ each owning its files, no file owned twice. LOW agents never decide; they apply the examples the
202
+ HIGH session gives them.
153
203
  - The main session owns edits unless DJ explicitly approves parallel implementers with disjoint file
154
204
  ownership. Nothing spawns without DJ's yes.
155
205
  - Every prompt that spawns an agent names that agent's mandatory reading as explicit
package/model-tiers.md CHANGED
@@ -22,5 +22,5 @@ follows the session unless a doc names one.
22
22
  task applies a fixed set of written patterns to many files; its prompt names every pattern and
23
23
  makes anything outside them a stop that returns to the HIGH session that spawned it. That session
24
24
  and its reviewer stay HIGH.
25
- - **The orchestrator is the session itself** — DJ picks its model when he starts it. Every SPAWNED
25
+ - **The session runs on the model DJ picks when he starts it.** Every SPAWNED
26
26
  agent's tier is binding.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/dev-standards",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",
@@ -29,8 +29,9 @@ A step can protect more than one area; overlap is intentional.
29
29
 
30
30
  - **Architecture:** 9, 10, 12, 16–18, 25, 31, 35, 38, 41, 51, 52, 55, 61, 63, 64, 80, 83, 96, 99
31
31
  - **Backend:** 10, 13, 20, 21, 23, 29, 32, 33, 36, 40, 49, 50, 56, 67, 77, 80, 87, 97, 98, 102, 105,
32
- 107, 108, 111, 112
33
- - **Frontend:** 1–3, 7, 15, 17, 21, 28, 35, 36, 57, 60, 65, 66, 68, 82, 91, 99, 101, 103, 104, 114
32
+ 107, 108, 111, 112, 121
33
+ - **Frontend:** 1–3, 7, 15, 17, 21, 28, 35, 36, 57, 60, 65, 66, 68, 82, 91, 99, 101, 103, 104, 114,
34
+ 121
34
35
  - **Media:** 4–6, 11, 14, 37, 39, 53, 58, 59, 83
35
36
  - **Quality / documentation:** 25, 27, 28, 30, 40, 42, 46, 63, 69, 73, 75, 88, 89, 90, 95, 117–120
36
37
 
@@ -798,9 +799,10 @@ RULE-STD-ENG-009. **Scope:** `src`.
798
799
  # Swallowed rejections — a user-initiated action's failure always reaches one surface.
799
800
  rg -n "\.catch\(\s*\(\s*\w*\s*\)\s*=>\s*(undefined|null|\{\s*\}|void 0)\s*\)" src -g '*.ts' -g '*.tsx' -g '!**/__tests__/**' -g '!src/test/**'
800
801
  ```
801
- **Expected:** the guards are green: every callable is declared once through the firebase-helpers
802
- factory kind that fixes its retry, surface, and capture, from the app's one callable policy, and is
803
- called through its declaration — a raw callable site opts out of the policy's filter and interceptions.
802
+ **Expected:** the guards are green: every callable is declared once through the factory kind from
803
+ query-core `./react`'s `createCallables` that fixes its retry, surface, and capture, from the app's
804
+ one callable policy, and is called through its declaration — a raw callable site opts out of the
805
+ policy's filter and interceptions.
804
806
  One rejection gets exactly one surface: no second capture or second destructive toast. A CALLABLE
805
807
  failure's copy comes only from the policy's one describer, so a raw SDK string never reaches the user;
806
808
  a READ failure renders the canonical load-error state, which owns the one "could not load" sentence
@@ -816,12 +818,14 @@ half is guard-enforced).
816
818
  ```bash
817
819
  <GUARD_SUITE> -t "every schema bound in the app, its Functions, and its Workers derives from a named limit|a stored enum value renders through its label map, never as raw text|no app module re-declares a constant the core package exports|no app module restates a core literal union"
818
820
  <GUARD_SUITE> -t "every core schema bound derives from a named constant outside the reviewed structural bounds|every free-text property of a core wire input is a declared text field"
821
+ <GUARD_SUITE> -t "app routes are built by the one route owner"
819
822
  rg -n "maxLength=\{[0-9]|minLength=\{[0-9]" src -g '*.tsx' -g '*.ts' -g '!**/__tests__/**'
820
823
  rg -n "\.length\s*[<>]=?\s*[0-9]{2,}|\.slice\(0,\s*[0-9]" src functions/src -g '*.ts' -g '*.tsx' -g '!**/__tests__/**' -g '!*.test.ts' -g '!*.test.tsx'
821
824
  ```
822
825
  **Expected:** the guards are green: every schema bound is a named limit, a text-field declaration, or an
823
826
  enum schema; a stored enum value renders through its label map (never raw, never CSS-transformed); no app
824
- module restates a package constant or union. The greps: no free-text input carries a literal
827
+ module restates a package constant or union; every page path is built through the app's one route
828
+ owner in `<APP_DATA_PACKAGE>`, never written as a route literal. The greps: no free-text input carries a literal
825
829
  `maxLength` — its cap is its `<APP_DATA_PACKAGE>` text-field declaration passed as the input's format;
826
830
  every remaining length attribute reads a named constant; each length or excerpt guard on user text
827
831
  imports its constant or is a reviewed structural literal; and a mode-varied value is never a literal
@@ -1475,6 +1479,22 @@ file in the repository with its license beside it; no stylesheet declares its ow
1475
1479
  nothing names a hosted font service.
1476
1480
  **Status:** PASS · FAIL · NOT RUN · BLOCKED
1477
1481
 
1482
+ ### 121. Admin actions outside the admin area go through the admin-actions affordance
1483
+ **Rules:** RULE-STD-FRONTEND-111, RULE-STD-BACKEND-011. **Scope:** `src`, read against the admin-only
1484
+ callables the guard derives. A FULL audit classifies every reviewed entry; a PARTIAL audit, the
1485
+ touched areas.
1486
+ ```bash
1487
+ <GUARD_SUITE> -t "admin actions outside the admin area go through the admin-actions affordance"
1488
+ ```
1489
+ **Expected:** the guard is green. Outside the admin area, every admin-only callable is reached only
1490
+ through the one shield-marked admin-actions affordance. Every other read of admin standing outside the
1491
+ admin area is a reviewed entry whose stated purpose is navigation to the admin area, an admin's viewing
1492
+ reach, or the admin's inbox. Read each reviewed entry against its code: an entry whose read now offers
1493
+ an action is the finding, and so is an entry whose code is gone. An action that the affordance offers
1494
+ stays reachable on the admin standing alone (step 70). No installed React package renders a control
1495
+ gated on admin standing.
1496
+ **Status:** PASS · FAIL · NOT RUN · BLOCKED
1497
+
1478
1498
  ## Pass / Fail
1479
1499
 
1480
1500
  Per [README.md](README.md) § Verdict.
@@ -36,7 +36,7 @@ rule's coverage in its additions; they are not listed here.
36
36
  | RULE-STD-ARCH-005 | Cross-tree trust contracts: one definition, one proof | codebase-hygiene 31; auth-boundary 13; testing-coverage 2 | — |
37
37
  | RULE-STD-ARCH-006 | Code from another product is adapted, never carried with its identity | **No audit step yet.** No shared step; an app's own step is the candidate. | — |
38
38
  | RULE-STD-ARCH-101 | `<APP_DATA_PACKAGE>` owns cross-boundary contracts | codebase-hygiene 16, 18, 31, 51, 56, 80; client-write-security-audit 8; data-integrity 2, 7, 8; dependency-audit 1, 4, 5; field-lineage 5; migration 1 | `isolatedRoots`, `tsconfigAliasesInRoot` |
39
- | RULE-STD-ARCH-102 | One named declaration; enforcement points derive | codebase-hygiene 31, 51, 61, 99; callable-functions 3; client-write-security-audit 8; data-integrity 7; dependency-audit 5; migration 8 | `definitionRedeclaration`, `schemaLengthLiterals`, `textFieldDeclarations`, `schemaBounds`, `enumLabels` |
39
+ | RULE-STD-ARCH-102 | One named declaration; enforcement points derive | codebase-hygiene 31, 51, 61, 99; callable-functions 3; client-write-security-audit 8; data-integrity 7; dependency-audit 5; migration 8 | `definitionRedeclaration`, `schemaLengthLiterals`, `textFieldDeclarations`, `schemaBounds`, `enumLabels`, `appRouteOwner` |
40
40
  | RULE-STD-ARCH-103 | Persist identity references, not snapshots | codebase-hygiene 35; data-integrity 2, 13; field-lineage 1, 2, 3, 5 | — |
41
41
  | RULE-STD-ARCH-104 | Canonical Firestore paths and compound-camelCase naming | codebase-hygiene 10, 12, 56, 80, 117; data-integrity 8; field-lineage 3; firestore-storage-rules 1 | `firestorePathAssembly`, `firestoreLiteralSegments`, `firestoreRetiredSegments`, `firestoreTestPaths`, `rulesMirrorCore` |
42
42
  | RULE-STD-ARCH-105 | Timestamps are epoch milliseconds via firebase-helpers | codebase-hygiene 9 | `firestoreTimestampNow` |
@@ -62,7 +62,7 @@ rule's coverage in its additions; they are not listed here.
62
62
  | RULE-STD-BACKEND-004 | Admin authority is explicit; the claim is metadata | codebase-hygiene 32, 49, 70; auth-boundary 4; callable-functions 4; firestore-storage-rules 4 | `adminClaimRead` |
63
63
  | RULE-STD-BACKEND-005 | Identity fields must match the authenticated caller | codebase-hygiene 77; auth-boundary 2, 12; callable-functions 2; client-write-security-audit 5, 11; firestore-storage-rules 6; testing-coverage 3 | — |
64
64
  | RULE-STD-BACKEND-010 | Callables enforce App Check attestation | codebase-hygiene 29; auth-boundary 3, 12, 13; callable-functions 6, 12 | `appCheck` |
65
- | RULE-STD-BACKEND-011 | Admin capability is self-sufficient; never coupled to a participant status | codebase-hygiene 70 | — |
65
+ | RULE-STD-BACKEND-011 | Admin capability is self-sufficient; never coupled to a participant status | codebase-hygiene 70, 121 | — |
66
66
  | RULE-STD-BACKEND-012 | Privileged route handlers and server modules carry the callable obligations | codebase-hygiene 23, 77; auth-boundary 12; callable-functions 12; client-write-security-audit 11 | — |
67
67
  | RULE-STD-BACKEND-013 | Named surface classes carry a rate-limit decision | codebase-hygiene 40, 77; auth-boundary 12; callable-functions 5 | `rateLimitSurfaces` |
68
68
  | RULE-STD-BACKEND-101 | All transaction reads before all transaction writes | codebase-hygiene 97; async-workflow 6; callable-functions 8; data-integrity 5 | — |
@@ -124,6 +124,7 @@ rule's coverage in its additions; they are not listed here.
124
124
  | RULE-STD-FRONTEND-108 | Auth-scoped subscribers reset on identity change | codebase-hygiene 35, 114; auth-boundary 14 | `authScopedKeys`, `signOutLiveness` |
125
125
  | RULE-STD-FRONTEND-109 | One owner per data-layer failure's user-facing copy; never a raw SDK string | codebase-hygiene 60; ux-audit 8 | `callableErrorCopy`, `readFailureCopy`, `errorInternalsRender` |
126
126
  | RULE-STD-FRONTEND-110 | An offered action mirrors the backend's predicate | codebase-hygiene 101, 108; auth-boundary 9 | — |
127
+ | RULE-STD-FRONTEND-111 | Admin actions outside the admin area go through the one admin-actions affordance | codebase-hygiene 121 | `adminActionSurface` |
127
128
  | RULE-STD-FRONTEND-201 | Every async control shows its own pending state through the shared primitives | codebase-hygiene 57, 65, 68; ux-audit 2, 5, 6, 11, 12 | `loadingFeedback` |
128
129
  | RULE-STD-FRONTEND-202 | ConsequenceDialog for consequential confirmations | codebase-hygiene 57, 65, 68, 104 | — |
129
130
  | RULE-STD-FRONTEND-203 | Accessibility baseline: landmarks, labels, focus, Escape | codebase-hygiene 68, 103; ux-audit 4, 5, 12 | `accessibleControls`; guard-enforced: the `@ttt-productions/dev-standards/node/a11y` axe run (default, high-contrast, reduced-motion) in the app's e2e stage, and the shared ESLint base's `jsx-a11y` recommended rules |
@@ -89,16 +89,16 @@ names, and exceptions arrive as its facts.
89
89
 
90
90
  ### 5. A callable is declared through the shared callable factories
91
91
  **Rules:** RULE-STD-ENG-002, RULE-STD-ENG-003, RULE-STD-FRONTEND-105. **Scope:** `packages/*/src`.
92
- `firebase-helpers/react`'s
93
- `createCallables` is the one factory set for a callable hook (action, reader, invoker, resumable); the
94
- SDK's callable primitives stay inside firebase-helpers.
92
+ `query-core/react`'s `createCallables` is the one factory set for a callable hook (action, reader,
93
+ invoker, resumable). It builds on query-core's session-aware mutation owner, `useSessionMutation`. The
94
+ SDK's callable primitives stay inside firebase-helpers, behind `callCallable`.
95
95
  ```bash
96
96
  rg -n "^[^/*]*\bhttpsCallable(FromURL)?\(" packages/*/src -g '*.ts' -g '*.tsx' | rg -v "packages/firebase-helpers/"
97
97
  rg -n "useMutation\(" packages/*/src -g '*.ts' -g '*.tsx' | rg -i "callable"
98
98
  ```
99
- **Expected:** the first prints nothing. The second prints only firebase-helpers' own factories; a
100
- package building its own callable mutation hook is the finding — it takes a hook built by
101
- `createCallables` from the app instead.
99
+ **Expected:** the first prints nothing. The second prints nothing: every callable hook is built by
100
+ `createCallables` on the session-aware owner. A package that builds its own callable mutation hook is
101
+ the finding; it takes a hook built by `createCallables` from the app instead.
102
102
  **Status:** PASS · FAIL · NOT RUN · BLOCKED
103
103
 
104
104
  ### 6. Imports inside a package name the defining file by its built extension
@@ -24,7 +24,7 @@ Cross-boundary contracts, canonical definitions, identity, paths, compound ids,
24
24
 
25
25
  **Rule.** Every definition — numbers (length limits, count caps, rate windows, price bounds), string unions and enums (kinds, statuses, item types), option and allowlist arrays, label and display-name maps, verbatim business copy (agreement statements, attestations), and typed shapes — is ONE named declaration in the package that owns the concept: `<APP_DATA_PACKAGE>` for an app's business truth, the owning generic package for a mechanism.
26
26
 
27
- **Apps:** this holds regardless of which layer consumes the definition today: there is no "frontend-only" or "backend-only" definition, because `src/` and `functions/src` are not importable by a second consumer, so a definition living there is a copy waiting to happen. The app owns wiring (routes, composition, layout) — never truths.
27
+ **Apps:** this holds regardless of which layer consumes the definition today: there is no "frontend-only" or "backend-only" definition, because `src/` and `functions/src` are not importable by a second consumer, so a definition living there is a copy waiting to happen. The app owns wiring (mounting pages, composition, layout) — never truths. An app's page paths are a declaration in `<APP_DATA_PACKAGE>`, built through its one route owner; a route literal anywhere else is drift.
28
28
 
29
29
  Enforcement points **derive**, never restate: zod `.max(MAX_X)`, React `maxLength={MAX_X}`, backend guards, `.slice(0, EXCERPT_LEN)`, and type positions referencing the canonical union (never an inline re-declared union). A literal at an enforcement point is drift by definition. Two compliance questions for any declaration: "updating this touches how many places?" (must be one) and "can a second consumer import this?" (if it would ever need to, it belongs in a package).
30
30
 
@@ -16,7 +16,7 @@ A comment these rules do not settle — bad in a way no rule names, or good but
16
16
  - An exception that does not name the rule ID and the reason.
17
17
  - One explanation repeated file after file.
18
18
 
19
- **References.** The required markers — each a comment this rule keeps, and a sweep never removes one: RULE-STD-ARCH-206 (`SANCTIONED EXCEPTION`), RULE-STD-BACKEND-002 (`.passthrough()` reason), RULE-STD-BACKEND-010 (App Check omission reason), RULE-STD-BACKEND-013 (in-file rate-limit decision), RULE-STD-BACKEND-119 (refuse-instead-of-clamp exception), RULE-STD-BACKEND-301 (the post-commit marker), RULE-STD-FRONTEND-102 (raw `staleTime` justification), RULE-STD-QUALITY-006 (reasoned `eslint-disable-next-line`, `prettier-ignore` reason), RULE-STD-QUALITY-007 (guard allowlist justification), RULE-STD-QUALITY-009 (a reviewed entry's why). A rule that adds a required comment joins this list in the same change. RULE-STD-ENG-002 (no "keep in sync" pairs); RULE-STD-COMMENT-012 (no security mechanism).
19
+ **References.** The required markers — each a comment this rule keeps, and a sweep never removes one: RULE-STD-ARCH-206 (`SANCTIONED EXCEPTION`), RULE-STD-BACKEND-002 (`.passthrough()` reason), RULE-STD-BACKEND-010 (App Check omission reason), RULE-STD-BACKEND-013 (in-file rate-limit decision), RULE-STD-BACKEND-119 (refuse-instead-of-clamp exception), RULE-STD-BACKEND-301 (the post-commit marker), RULE-STD-FRONTEND-002 (the utility an `overrides.css` rule beats), RULE-STD-FRONTEND-102 (raw `staleTime` justification), RULE-STD-QUALITY-006 (reasoned `eslint-disable-next-line`, `prettier-ignore` reason), RULE-STD-QUALITY-007 (guard allowlist justification), RULE-STD-QUALITY-009 (a reviewed entry's why). A rule that adds a required comment joins this list in the same change. RULE-STD-ENG-002 (no "keep in sync" pairs); RULE-STD-COMMENT-012 (no security mechanism).
20
20
 
21
21
  ### RULE-STD-COMMENT-002 — No dates
22
22
  **Scope.** both
@@ -64,7 +64,7 @@ How the client reads server state, builds cache keys, invalidates, calls callabl
64
64
  ### RULE-STD-FRONTEND-105 — One callable invocation path; never raw `httpsCallable`
65
65
  **Scope.** apps
66
66
 
67
- **Rule.** Every callable is declared once in `<CALLABLE_DECLARATIONS>`, through the firebase-helpers factory kind that fixes its retry, surface, and capture — an action, a reader, an invoker, or a resumable action — built from the one app callable policy (`<CALLABLE_POLICY>`). Screens and hooks call the declared hook, and the callable's name is written nowhere else. The factories ride the package primitive, which owns the **undefined-strip** (the SDK encodes `undefined` as `null`, which strict `.optional()` `<APP_DATA_PACKAGE>` schemas reject — the primitive drops undefined keys deep), and the policy owns the **error taxonomy**: the expected-answer monitoring filter, the standard error toast, and the answers that have their own surface (the app's addition lists them) — announced and never toasted, which a caller-owned surface skips through the policy's surfaced-elsewhere predicate. A hook that wraps both a callable and a non-callable leg (e.g. a Storage upload) toasts only the leg the callable can't see — gate its local `onError` on the policy's callable-rejection predicate returning false, or declare the call `{ showErrorToast: false }` and own the copy. A React Query READ calls its declared reader, which neither toasts nor captures, and its failure is a rendered state (RULE-STD-FRONTEND-109). **One rejection gets ONE toast, and that budget spans files**: a hook's declared action with its toast on and an `onError`/`catch` toast in a component that calls that hook are one surface between them, so exactly one side owns the copy — either the hook declares the toast off, or the call site drops its toast and lets the action own it.
67
+ **Rule.** Every callable is declared once in `<CALLABLE_DECLARATIONS>`, through the factory kind from query-core `./react`'s `createCallables` that fixes its retry, surface, and capture — an action, a reader, an invoker, or a resumable action — built from the one app callable policy (`<CALLABLE_POLICY>`). Screens and hooks call the declared hook, and the callable's name is written nowhere else. The factories ride firebase-helpers' `callCallable` primitive, which owns the **undefined-strip** (the SDK encodes `undefined` as `null`, which strict `.optional()` `<APP_DATA_PACKAGE>` schemas reject — the primitive drops undefined keys deep), and the policy owns the **error taxonomy**: the expected-answer monitoring filter, the standard error toast, and the answers that have their own surface (the app's addition lists them) — announced and never toasted, which a caller-owned surface skips through the policy's surfaced-elsewhere predicate. A hook that wraps both a callable and a non-callable leg (e.g. a Storage upload) toasts only the leg the callable can't see — gate its local `onError` on the policy's callable-rejection predicate returning false, or declare the call `{ showErrorToast: false }` and own the copy. A React Query READ calls its declared reader, which neither toasts nor captures, and its failure is a rendered state (RULE-STD-FRONTEND-109). **One rejection gets ONE toast, and that budget spans files**: a hook's declared action with its toast on and an `onError`/`catch` toast in a component that calls that hook are one surface between them, so exactly one side owns the copy — either the hook declares the toast off, or the call site drops its toast and lets the action own it.
68
68
 
69
69
  **Why.** A raw `httpsCallable` skips the undefined-strip (the schema rejects valid input) and the error taxonomy (double toasts, unfiltered monitoring noise, and answers that have their own surface toasted as errors).
70
70
 
@@ -72,7 +72,7 @@ How the client reads server state, builds cache keys, invalidates, calls callabl
72
72
  - Importing `httpsCallable`/`getFunctions` from `firebase/functions`, or `callCallable`, anywhere in `src/`; a callable's name written outside `<CALLABLE_DECLARATIONS>`, including as a bare string at a call site; a file-local `callable()` wrapper.
73
73
  - A callable hook that double-surfaces one rejection as two destructive toasts — in ONE file, or split across the hook module and its consuming component.
74
74
 
75
- **References.** RULE-STD-FRONTEND-109; RULE-STD-QUALITY-103; ttt-packages `docs/packages/firebase-helpers.md`.
75
+ **References.** RULE-STD-FRONTEND-109; RULE-STD-QUALITY-103; ttt-packages `docs/packages/query-core.md`, `docs/packages/firebase-helpers.md`.
76
76
 
77
77
  ### RULE-STD-FRONTEND-106 — Gated actions go through the canonical auth gate
78
78
  **Scope.** apps
@@ -153,3 +153,17 @@ Classification predicates are generic and come from `@ttt-productions/firebase-h
153
153
  - A picker whose selectable range disagrees with the backend's rule for the value.
154
154
 
155
155
  **References.** RULE-STD-ENG-002, RULE-STD-ENG-006; RULE-STD-FRONTEND-106 (the auth-readiness gate), RULE-STD-FRONTEND-206.
156
+
157
+ ### RULE-STD-FRONTEND-111 — Admin actions outside the admin area go through the one admin-actions affordance
158
+ **Scope.** both
159
+
160
+ **Rule.** **Apps:** outside the app's admin area, an admin action is offered only through the app's one shield-marked admin-actions affordance, never as an inline control beside a member's own actions. Outside the admin area, a read of the viewer's admin standing is allowed for three purposes only: navigation to the admin area, an admin's viewing reach (seeing what a member could not), or the admin's own inbox. Each such read is a reviewed entry naming its purpose, and every other read of admin standing outside the admin area is a violation. The backend stays the authority on every admin action (RULE-STD-BACKEND-004). **Packages:** a React package never renders a control gated on admin standing. It takes no admin flag; an admin action on a package's surface is the app's, offered through the app's affordance.
161
+
162
+ **Why.** An inline admin control looks like a member action. One page offers it and the next one doesn't, and every screen that gates on admin standing is one more place where that gate can drift away from the one owner. When a package carries an admin control, every app that installs it gets an admin action outside its admin affordance.
163
+
164
+ **Prohibits.**
165
+ - An admin action rendered outside the admin area other than through the admin-actions affordance; a second admin-actions affordance.
166
+ - A read of admin standing outside the admin area that is not a reviewed entry for navigation, viewing reach, or the admin's inbox.
167
+ - A React package component, prop, or config field that shows, hides, or enables a control on admin standing.
168
+
169
+ **References.** RULE-STD-ENG-002, RULE-STD-ENG-006; RULE-STD-ARCH-201; RULE-STD-BACKEND-004, RULE-STD-BACKEND-011; RULE-STD-FRONTEND-001, RULE-STD-FRONTEND-110.
@@ -77,7 +77,7 @@ A test proves the business rule, never the code's current output: its expected v
77
77
  ### RULE-STD-QUALITY-006 — Lint and format: zero suppression in apps, reasoned disables in packages; one enforced format
78
78
  **Scope.** both
79
79
 
80
- **Rule.** The lint half is split by scope; the format half is the same for both. **Apps** are zero suppression: no `eslint-disable` comment anywhere in the linted trees — source, Functions, E2E, scripts, and config. When a rule fires, fix the root cause, and solve a genuine false positive structurally — rename, refactor, or scope the rule — never by suppressing the line; external-authority state pruning uses the sanctioned render-phase adjustment, not a suppressed effect. A `warn`-level finding is a finding to fix, and a rule is never turned off, nor `ignores` widened, to pass failing code. New app code also follows the conventions the lint config forces, listed below. **Packages** permit an `eslint-disable-next-line` only for a narrow intentional case, carrying its reason on the line; it is preferred over weakening the rule, and a real violation is fixed at its source.
80
+ **Rule.** The lint half is split by scope; the format half is the same for both. **Apps** are zero suppression: no `eslint-disable` comment anywhere in the linted trees — source, Functions, E2E, scripts, and config. When a rule fires, fix the root cause, and solve a genuine false positive structurally — rename, refactor, or scope the rule — never by suppressing the line; external-authority state pruning uses the sanctioned render-phase adjustment, not a suppressed effect. A `warn`-level finding is a finding to fix, and a rule is never turned off, nor `ignores` widened, to pass failing code. New app code also follows the conventions the lint config forces, listed below. **Packages** permit an `eslint-disable-next-line` only for a narrow intentional case where no fix works, on DJ's yes, carrying its reason on the line; it is preferred over weakening the rule, and a real violation is fixed at its source.
81
81
 
82
82
  Every code file — source, tests, scripts, and config — is in the one format Prettier writes from the shared Prettier config (this package's `config/prettier.json`); `test:quiet`'s format stage writes that format and then checks it, a check-only run (a package release preflight, `test:all`) only checks, so a release never publishes formatting nobody committed, and the check fails a file that is not formatted; `eslint-config-prettier` keeps ESLint out of formatting. A file is formatted by running Prettier, never reflowed by hand.
83
83
 
@@ -92,14 +92,14 @@ Every code file — source, tests, scripts, and config — is in the one format
92
92
  - An anonymous React wrapper component returned from a test helper (assign `displayName`).
93
93
  - Straight `'`/`"` in JSX text content (use `&apos;`/`&quot;`).
94
94
  - Suppressing `exhaustive-deps` instead of fixing at the source (destructure the stable `mutate`; memoize a derived dep; destructure the specific prop fields used).
95
- - An unformatted code file; a `prettier-ignore` without its reason; an ESLint formatting rule that fights Prettier.
95
+ - An unformatted code file; a `prettier-ignore` without its reason, or added when a fix works or without DJ's yes; an ESLint formatting rule that fights Prettier.
96
96
 
97
97
  **References.** RULE-STD-ENG-008; this package's `node/eslint` bases and `config/prettier.json`; each repo's ESLint config and `.prettierignore`.
98
98
 
99
99
  ### RULE-STD-QUALITY-007 — Guard tests enforce ownership and boundaries, never inventories
100
100
  **Scope.** both
101
101
 
102
- **Rule.** Structural guard tests (the repo-guards suite each repo runs in its gate — an app's guard suite, ttt-packages' boundary suite) enforce a forbidden boundary or single-owner rule against the current clean baseline — every allowlist entry carrying a one-line justification — so that a NEW regression fails the build. A guard must not mirror an exhaustive list of today's files, exports, packages, or domains: an inventory-mirror breaks on every legitimate addition and audits nothing.
102
+ **Rule.** Structural guard tests (the repo-guards suite each repo runs in its gate — an app's guard suite, ttt-packages' boundary suite) enforce a forbidden boundary or single-owner rule against the current clean baseline — every allowlist entry carrying a one-line justification — so that a NEW regression fails the build. An entry is added only when no fix works, on DJ's yes, with its reason in the entry. A guard must not mirror an exhaustive list of today's files, exports, packages, or domains: an inventory-mirror breaks on every legitimate addition and audits nothing.
103
103
 
104
104
  **Why.** A brittle inventory-mirror is churn, not enforcement; the value is catching the next violation, not counting the current files.
105
105
 
@@ -127,7 +127,7 @@ Every code file — source, tests, scripts, and config — is in the one format
127
127
  ### RULE-STD-QUALITY-009 — A test loads the code it tests when its file loads
128
128
  **Scope.** both
129
129
 
130
- **Rule.** A test imports the code it tests at the top of its file — a static import, or a top-level `await import()` — never inside a case, a hook, or a helper they call, and never resets the module registry. A case that needs other state gets it without a reload: an environment the code reads at load is set in `vi.hoisted` before the import, per-instance state is cleared through the module's test-only `__reset…()` seam, and a flag the code reads once is read when it is called instead. A file whose fresh module per case is itself what it proves is a reviewed entry with its why, and an entry whose file no longer does is stale. The repo-guards `testCaseModuleLoads` guard enforces it.
130
+ **Rule.** A test imports the code it tests at the top of its file — a static import, or a top-level `await import()` — never inside a case, a hook, or a helper they call, and never resets the module registry. A case that needs other state gets it without a reload: an environment the code reads at load is set in `vi.hoisted` before the import, per-instance state is cleared through the module's test-only `__reset…()` seam, and a flag the code reads once is read when it is called instead. A file whose fresh module per case is itself what it proves is a reviewed entry with its why — an entry is added only when no fix works, on DJ's yes, with its reason in the entry — and an entry whose file no longer does is stale. The repo-guards `testCaseModuleLoads` guard enforces it.
131
131
 
132
132
  **Why.** A module's first load pulls its whole graph; inside a case that cold load is timed as the case's own work, so a file's first case was slow for nothing it did and, under the full parallel run, crossed its timeout.
133
133
 
@@ -13,8 +13,11 @@ description: Claude Code workflow (Build or fix). Use when the work is big enoug
13
13
 
14
14
  ## Before anything spawns
15
15
 
16
- 1. The normal preflight, plus the routed rules and `docs/design/*.md` for every area involved.
17
- 2. Re-verify the work against the current code.
16
+ 1. The normal preflight. This session saves its context for managing agents and does not read or
17
+ edit code itself (`<DEV_STANDARDS>/development-preferences.md` § Agents).
18
+ 2. Launch read-only agents to read the routed rules, the `docs/design/*.md` for every area involved,
19
+ and the current code, and report the system back; the design each building agent gets comes from
20
+ their reports.
18
21
  3. Propose to DJ: the number of agents and each one's exact scope and owned files. Nothing spawns
19
22
  without DJ's yes.
20
23
  4. App-side work: confirm every shared package it depends on is at `@latest` first. Package work and
@@ -22,12 +25,13 @@ description: Claude Code workflow (Build or fix). Use when the work is big enoug
22
25
 
23
26
  ## Every spawned prompt
24
27
 
28
+ - The high-level picture and the design to build: build this, this way.
25
29
  - The agent's exact scope and owned files, and its mandatory reading as explicit read-before-acting
26
30
  paths: the routed rule files (shared and the app's) and `docs/design/*.md` for its area
27
31
  (RULE-STD-ENG-010).
28
32
  - A hard ban on spawning agents.
29
- - A kill-and-escalate clause: a needed package change, or anything unexpected → stop and report to
30
- this session.
33
+ - A kill-and-escalate clause: a needed package change, a better or different way than the design,
34
+ or anything unexpected → stop and report to this session.
31
35
  - Targeted tests only, on its own work.
32
36
  - Tier per `<DEV_STANDARDS>/model-tiers.md` (implementation agents; a mechanical-only part uses
33
37
  `mechanical-fan-out.md` instead).
@@ -37,7 +41,7 @@ description: Claude Code workflow (Build or fix). Use when the work is big enoug
37
41
  - Adversarial review agents at the HIGH tier over the landed work, each also checking every new or
38
42
  changed test against the business rule it claims to prove (RULE-STD-QUALITY-001; testing-coverage
39
43
  playbook).
40
- - Exactly ONE final full gate after every agent has landed: the Normal `<APP_NAME>` Test Flow. Its
41
- live stage (start `dev:e2e`, run, fix, restart, kill) belongs to ONE agent, never this session —
42
- this session takes its report.
44
+ - Exactly ONE final full gate after every agent has landed: the Normal `<APP_NAME>` Test Flow, run
45
+ as `<DEV_STANDARDS>/claude/CLAUDE.base.md` § The Normal `<APP_NAME>` Flows says for an
46
+ orchestrator.
43
47
  - Delete the finished `docs/work/` items and the ledger, then stop for DJ's push.
@@ -25,7 +25,9 @@ description: Claude Code workflow (Build or fix). Use when DJ wants known, decid
25
25
  Normal `<APP_NAME>` Dev Flow (`<DEV_STANDARDS>/claude/CLAUDE.base.md`).
26
26
  - Every fix ships a regression test that fails before the fix and proves the business rule
27
27
  (RULE-STD-QUALITY-001). Design docs the fix changes are updated in the same change.
28
- - A large mechanical change found along the way → `mechanical-fan-out.md`, which asks DJ first.
28
+ - A piece of the work agents would do better → propose LOW or HIGH agents for it to DJ, staying
29
+ inline (`<DEV_STANDARDS>/development-preferences.md` § Agents); a large mechanical change uses
30
+ `mechanical-fan-out.md`.
29
31
 
30
32
  ## Finishing
31
33
 
@@ -23,8 +23,9 @@ description: Claude Code workflow (Build or fix). Use when a multi-task plan DJ
23
23
 
24
24
  - Packages-first: every package-side task in the plan lands, gates, and is released before any
25
25
  app-side task (Normal `<APP_NAME>` Dev Flow, `<DEV_STANDARDS>/claude/CLAUDE.base.md`).
26
- - Build inline. Large mechanical parts → `mechanical-fan-out.md`; big parallel thinking work →
27
- propose `fan-out-implementation.md` to DJ.
26
+ - Build inline. A piece of the work agents would do better → propose LOW or HIGH agents for it to
27
+ DJ, staying inline (`<DEV_STANDARDS>/development-preferences.md` § Agents); large mechanical parts
28
+ use `mechanical-fan-out.md`.
28
29
  - Approval covers exactly the approved tasks — no adjacent refactors.
29
30
  - Every task ships its regression test (RULE-STD-QUALITY-001) and its doc updates.
30
31
 
@@ -34,5 +34,5 @@ through this; it stays inline or goes to `fan-out-implementation.md`.
34
34
  5. **One HIGH reviewer** checks all the agents' changes together once every agent is done: each
35
35
  change matches a listed pattern, nothing outside the patterns slipped in, and every new or changed
36
36
  test proves the business rule it claims, not the code's current output (RULE-STD-QUALITY-001).
37
- 6. **One final gate.** Back in the calling workflow: the Normal Test Flow once, for the whole change
38
- set. Its live stage belongs to ONE agent, never the orchestrating session.
37
+ 6. **One final gate.** Back in the calling workflow: the inline session runs the Normal Test Flow
38
+ once, for the whole change set.
@@ -21,8 +21,9 @@ description: Claude Code workflow (Build or fix). Use when DJ names a step of a
21
21
  3. Work the step, or one focused slice of it. A step with a publish stop: ALL its package work first,
22
22
  then the release command, per the Normal `<APP_NAME>` Dev Flow
23
23
  (`<DEV_STANDARDS>/claude/CLAUDE.base.md`).
24
- 4. Large mechanical parts → `mechanical-fan-out.md`; big parallel work → propose
25
- `fan-out-implementation.md` to DJ.
24
+ 4. A piece of the work agents would do better → propose LOW or HIGH agents for it to DJ, staying
25
+ inline (`<DEV_STANDARDS>/development-preferences.md` § Agents); large mechanical parts use
26
+ `mechanical-fan-out.md`.
26
27
  5. The Normal `<APP_NAME>` Test Flow, once, on the change set.
27
28
  6. Delete each finished item from the step's work doc. When the step is done, delete its folder and
28
29
  its section in `PLAN.md` — never renumber.