@ttt-productions/dev-standards 0.0.3 → 0.0.5

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,40 @@ 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 full gate while any question is open** — the Normal Test Flow, or a repo's own `test:quiet`
71
+ gate, ttt-packages' included. An answer can change code, and a gate run before it is worthless.
72
+ The open questions are asked first, any code they change is done, and then the gate 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, and never labels one "FYI" or "not blocking" —
75
+ whether it matters is DJ's call.
76
+ - When DJ asks whether the conversation is good to end, or says to start a new one, the session first
77
+ says how many questions are open and asks whether to answer them here or move them into the new
78
+ conversation's prompt.
54
79
 
55
80
  ## Git, publish, deploy, and the dev stacks
56
81
 
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.
82
+ - DJ does ALL git — staging, commits, pushes, branches, publishing, installs he has not handed over,
83
+ and deployments. An AI runs none of it unless DJ commands that exact action in the conversation;
84
+ otherwise it hands him the exact command. DJ stages files continuously while work is running so he
85
+ can see what is changing; never treat a staged/unstaged change as a sign of someone else's work,
86
+ and never bring up staging, the index, or other git state with DJ.
87
+ - Git and GitHub (`git log`, `git status`, diffs, `gh`, Actions runs) are read only when DJ says it is
88
+ needed. A problem DJ brings is fixed from the current code; when and why it changed is not looked
89
+ up unless he asks.
62
90
  - Never instruct DJ to push or deploy — deploy IS `git push`. Never nag about deploys, indexes, or
63
91
  seed buttons.
64
92
  - Never discard, overwrite, reset, clean, or revert existing work unless DJ explicitly asks.
65
93
  Preserve unrelated uncommitted work; DJ or another tool may be working concurrently.
66
94
  - `dev:local` (DJ's manual dev stack) is DJ-only, always — never start or stop it.
67
95
  - `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.
96
+ on DJ's explicit command. DJ's in-conversation instruction overrides any flow step and governs
97
+ exactly what it names, for that conversation only.
70
98
  - Never kill a running dev stack on your own judgment ("ports look wrong" = say what you see and
71
99
  WAIT). Never run e2e against a stale build.
72
100
  - Hosted-dev E2E runs happen only when DJ commands one, and follow the repo's hosted-E2E run doc
@@ -118,6 +146,13 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
118
146
  - Verify at the cost of the fix: a one-file fix runs its single file first; the broad gate only
119
147
  when the blast radius warrants it. Never run multiple full repository gates concurrently.
120
148
  - A flaky test is a real bug — root-cause it; more than one flake is structural.
149
+ - **The quickest, easiest fix is never the goal; the right one is.** A fix is ready to propose only
150
+ once the session has worked out the real problem rather than its symptom; whether it is a class
151
+ of problem and where else it lives; whether an owner already solves it and whether the fix sits
152
+ in the right home; whether the fix is a hack around a larger design change; what else it changes
153
+ — data, the other product, docs, tests, guards; what would let the class back in; and whether the
154
+ problem as stated is even true in the code. A fix that contradicts a rule or a design is a
155
+ question for DJ.
121
156
 
122
157
  ## Engineering judgment
123
158
 
@@ -130,15 +165,26 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
130
165
  - No half-working, dead, or known-bad code — build it or remove it. On a second sighting of a defect
131
166
  class, tell DJ and propose killing the CLASS system-wide plus a regression guard; do it only on
132
167
  DJ's command.
168
+ - **Never make yourself an exception.** Code that breaks a rule, guard, lint check, or type check is
169
+ fixed. An exception is asked for only when there is genuinely no other way, and it goes to DJ with
170
+ that reason, why no fix works. Nothing is exempted without his yes. Every exception he approves carries
171
+ its reason at the exception itself, so anyone reading it years later knows at once why it exists:
172
+ the guard entry's reason field, the comment on the `eslint-disable` / `prettier-ignore` /
173
+ `@ts-expect-error` / `@ts-ignore` line, or, for a design-level exception, the design doc it
174
+ belongs to. The reason states why the exception exists, never who approved it. Keeping an
175
+ exception an agent added is the same decision, and goes to DJ the same way.
133
176
  - Reviewing an external plan (ChatGPT/Codex/etc.) means answering "is this the BEST code to solve
134
177
  my problem" — never just verifying its claims. Evaluate the design space (alternatives, root cause,
135
178
  structure) and fold an optimality verdict into the reviewed doc.
136
179
  - V1-scope discipline: confirm the trigger exists in V1 before asking DJ to design.
137
180
  - Docs stay lean — never a "done" log. Tracking docs hold only remaining work; finished work is
138
181
  DELETED.
182
+ - **Working files** a session needs — extracted text, a one-off script, intermediate output — go in
183
+ `C:\DjDev\claude-scratchpad\<YYYY-MM-DD>-<topic>\`, never in a temp folder, loose in `C:\DjDev`,
184
+ or inside a repo. A session writes there only when the work needs a file, never for a conversation
185
+ that is only talking, and deletes its folder when that work is done.
139
186
  - 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.
187
+ claims — lives in the repo beside the work it tracks and is deleted when the work finishes.
142
188
  - Help with EVERY step, including non-code and operational ones — real artifacts (policies,
143
189
  runbooks, click-by-click), never a "your job" checklist. Walk-throughs go one step at a time, dev
144
190
  AND prod. A non-code or console step becomes a self-contained Claude web prompt (full context,
@@ -150,11 +196,24 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
150
196
 
151
197
  ## Agents
152
198
 
199
+ - **Every conversation is inline unless DJ says, in that conversation, that it is an orchestrator.**
200
+ No doc, plan, ledger, or prompt makes a conversation an orchestrator.
201
+ - **Inline:** the session does the research and the work itself. When a piece of the work would be
202
+ done better by agents — LOW for mechanical work applied from written examples, HIGH for thinking
203
+ work or review — it proposes them to DJ — how many, each one's tier, its exact task, and its
204
+ files — and launches them only on his yes. Launching agents, a predefined wave included, never makes it an orchestrator; it
205
+ still runs its own Normal Test Flow.
206
+ - **Orchestrator:** the session saves its context for managing agents and does not read or edit code
207
+ itself. It first launches read-only agents to understand the system, then gives each building
208
+ agent the high-level picture and the design: build this, this way. An agent that finds a better or
209
+ different way stops and comes back instead of building it. Agents run in waves of a few at a time,
210
+ each owning its files, no file owned twice. LOW agents never decide; they apply the examples the
211
+ HIGH session gives them.
153
212
  - The main session owns edits unless DJ explicitly approves parallel implementers with disjoint file
154
213
  ownership. Nothing spawns without DJ's yes.
155
214
  - Every prompt that spawns an agent names that agent's mandatory reading as explicit
156
- read-before-acting paths (RULE-STD-ENG-010), hard-bans spawning further agents, and carries a stop
157
- clause: anything unexpected, or a needed package change, stops the agent and comes back to the
215
+ read-before-acting paths (RULE-STD-ENG-010), hard-bans spawning further agents and running any
216
+ git or `gh` command, and carries a stop clause: anything unexpected, or a needed package change, stops the agent and comes back to the
158
217
  session.
159
218
  - How a given kind of conversation runs its agents is its development workflow's procedure
160
219
  (the shared `<DEV_STANDARDS>/workflows/`, plus any app-only one in `<APP_WORKFLOWS>`); this file
@@ -1 +1 @@
1
- {"version":3,"file":"eslint.d.ts","sourceRoot":"","sources":["../../src/node/eslint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIrC,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACzB;AAyFD,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAkB1B;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAoC1B"}
1
+ {"version":3,"file":"eslint.d.ts","sourceRoot":"","sources":["../../src/node/eslint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIrC,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACzB;AAiGD,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAmB1B;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAoC1B"}
@@ -15,12 +15,7 @@ const APP_IGNORES = [
15
15
  'out/**',
16
16
  'dist/**',
17
17
  'build/**',
18
- '*.config.js',
19
- '*.config.ts',
20
18
  'next-env.d.ts',
21
- 'instrumentation.ts',
22
- 'instrumentation-client.ts',
23
- 'sentry.*.config.ts',
24
19
  '.next/types/**',
25
20
  '.next/dev/**',
26
21
  'docs/**',
@@ -40,6 +35,7 @@ const APP_RULES = {
40
35
  },
41
36
  },
42
37
  rules: {
38
+ // RULE-STD-MEDIA-101 bans next/image, the replacement this rule demands; served files render through the media renderer.
43
39
  '@next/next/no-img-element': 'off',
44
40
  '@typescript-eslint/no-unused-vars': [
45
41
  'error',
@@ -49,7 +45,6 @@ const APP_RULES = {
49
45
  destructuredArrayIgnorePattern: '^_',
50
46
  },
51
47
  ],
52
- '@typescript-eslint/no-explicit-any': 'off',
53
48
  'react-hooks/rules-of-hooks': 'error',
54
49
  'react-hooks/exhaustive-deps': 'error',
55
50
  'react-hooks/set-state-in-effect': 'error',
@@ -59,6 +54,18 @@ const APP_RULES = {
59
54
  'react-hooks/purity': 'error',
60
55
  },
61
56
  };
57
+ // Type-aware: each TypeScript file is read through the nearest tsconfig.json that includes it.
58
+ const typeAwareNoAny = (files) => ({
59
+ files,
60
+ languageOptions: { parserOptions: { projectService: true } },
61
+ rules: {
62
+ '@typescript-eslint/no-unsafe-argument': 'error',
63
+ '@typescript-eslint/no-unsafe-assignment': 'error',
64
+ '@typescript-eslint/no-unsafe-call': 'error',
65
+ '@typescript-eslint/no-unsafe-member-access': 'error',
66
+ '@typescript-eslint/no-unsafe-return': 'error',
67
+ },
68
+ });
62
69
  // eslint-config-next registers jsx-a11y and flat config errors on a second registration, so this
63
70
  // sets rules only; a severity-only entry keeps the options Next set (its Image alt-text options).
64
71
  function jsxA11yRecommended(nextConfigs, severity) {
@@ -93,6 +100,7 @@ export async function createAppEslintConfig(options = {}) {
93
100
  ...nextTypescript,
94
101
  jsxA11yRecommended(nextCoreWebVitals, options.a11y ?? 'error'),
95
102
  APP_RULES,
103
+ typeAwareNoAny(['**/*.ts', '**/*.tsx']),
96
104
  ...(options.configs ?? []),
97
105
  prettierCompat,
98
106
  ];
@@ -111,7 +119,6 @@ export async function createFunctionsEslintConfig(options = {}) {
111
119
  languageOptions: { globals: nodeGlobals },
112
120
  rules: {
113
121
  '@typescript-eslint/no-unused-expressions': ['error', { allowShortCircuit: true }],
114
- '@typescript-eslint/no-explicit-any': 'off',
115
122
  '@typescript-eslint/no-unused-vars': ['error', UNUSED_VARS_ALLOW_UNDERSCORE],
116
123
  'no-undef': 'off',
117
124
  },
@@ -130,6 +137,7 @@ export async function createFunctionsEslintConfig(options = {}) {
130
137
  'no-unused-vars': ['error', UNUSED_VARS_ALLOW_UNDERSCORE],
131
138
  },
132
139
  },
140
+ typeAwareNoAny(['**/*.ts']),
133
141
  ...(options.configs ?? []),
134
142
  prettierCompat,
135
143
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"eslint.js","sourceRoot":"","sources":["../../src/node/eslint.ts"],"names":[],"mappings":"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","sourcesContent":["import js from '@eslint/js';\nimport type { Linter } from 'eslint';\nimport prettierCompat from 'eslint-config-prettier';\nimport tseslint from 'typescript-eslint';\n\nexport interface EslintPresetOptions {\n ignores?: readonly string[];\n configs?: readonly Linter.Config[];\n}\n\nexport interface AppEslintPresetOptions extends EslintPresetOptions {\n a11y?: 'error' | 'warn';\n}\n\nconst ZERO_SUPPRESSION: Linter.Config = { linterOptions: { noInlineConfig: true } };\n\nconst APP_IGNORES = [\n '.next/**',\n 'node_modules/**',\n '.firebase/**',\n 'functions/**',\n // ESLint does not honor .gitignore, so the E2E harness's generated artifacts are ignored here.\n 'e2e-hosted-dev/.browser-profiles/**',\n 'e2e-hosted-dev/test-results/**',\n 'e2e-hosted-dev/failed-run-archive/**',\n 'test-results/**',\n 'out/**',\n 'dist/**',\n 'build/**',\n '*.config.js',\n '*.config.ts',\n 'next-env.d.ts',\n 'instrumentation.ts',\n 'instrumentation-client.ts',\n 'sentry.*.config.ts',\n '.next/types/**',\n '.next/dev/**',\n 'docs/**',\n];\n\nconst UNUSED_VARS_ALLOW_UNDERSCORE = {\n argsIgnorePattern: '^_',\n varsIgnorePattern: '^_',\n};\n\nconst APP_RULES: Linter.Config = {\n files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs'],\n // eslint-config-next registers react-hooks and flat config errors on a redefinition: rules only.\n languageOptions: {\n parserOptions: {\n ecmaVersion: 'latest',\n sourceType: 'module',\n ecmaFeatures: { jsx: true },\n },\n },\n rules: {\n '@next/next/no-img-element': 'off',\n '@typescript-eslint/no-unused-vars': [\n 'error',\n {\n ...UNUSED_VARS_ALLOW_UNDERSCORE,\n caughtErrorsIgnorePattern: '^_',\n destructuredArrayIgnorePattern: '^_',\n },\n ],\n '@typescript-eslint/no-explicit-any': 'off',\n 'react-hooks/rules-of-hooks': 'error',\n 'react-hooks/exhaustive-deps': 'error',\n 'react-hooks/set-state-in-effect': 'error',\n 'react-hooks/refs': 'error',\n 'react-hooks/immutability': 'error',\n 'react-hooks/incompatible-library': 'error',\n 'react-hooks/purity': 'error',\n },\n};\n\ntype RuleEntry = NonNullable<Linter.Config['rules']>[string];\n\n// eslint-config-next registers jsx-a11y and flat config errors on a second registration, so this\n// sets rules only; a severity-only entry keeps the options Next set (its Image alt-text options).\nfunction jsxA11yRecommended(\n nextConfigs: Linter.Config[],\n severity: 'error' | 'warn',\n): Linter.Config {\n const plugin = nextConfigs.find((config) => config.plugins?.['jsx-a11y'])?.plugins?.['jsx-a11y'];\n const recommended = (plugin?.configs?.recommended as Linter.Config | undefined)?.rules;\n if (!recommended) {\n throw new Error(\n 'eslint-config-next no longer registers eslint-plugin-jsx-a11y with a recommended config',\n );\n }\n const rules: Record<string, RuleEntry> = {};\n for (const [name, entry] of Object.entries(recommended)) {\n if (entry === undefined) continue;\n const level = Array.isArray(entry) ? entry[0] : entry;\n if (level === 'off' || level === 0) continue;\n rules[name] = Array.isArray(entry) ? [severity, ...entry.slice(1)] : severity;\n }\n return { files: ['**/*.tsx', '**/*.jsx'], rules };\n}\n\nexport async function createAppEslintConfig(\n options: AppEslintPresetOptions = {},\n): Promise<Linter.Config[]> {\n // Imported here: the Functions tree loads this module without eslint-config-next installed.\n const [{ default: nextCoreWebVitals }, { default: nextTypescript }] = await Promise.all([\n import('eslint-config-next/core-web-vitals'),\n import('eslint-config-next/typescript'),\n ]);\n return [\n { ignores: [...APP_IGNORES, ...(options.ignores ?? [])] },\n ZERO_SUPPRESSION,\n js.configs.recommended,\n ...(tseslint.configs.recommended as Linter.Config[]),\n ...(nextCoreWebVitals as Linter.Config[]),\n ...(nextTypescript as Linter.Config[]),\n jsxA11yRecommended(nextCoreWebVitals as Linter.Config[], options.a11y ?? 'error'),\n APP_RULES,\n ...(options.configs ?? []),\n prettierCompat,\n ];\n}\n\nexport async function createFunctionsEslintConfig(\n options: EslintPresetOptions = {},\n): Promise<Linter.Config[]> {\n // Imported here: the Next.js tree loads this module without globals installed.\n const { default: globals } = await import('globals');\n const nodeGlobals = { ...globals.node, ...globals.es2021 };\n return [\n { ignores: ['lib/**', 'build/**', ...(options.ignores ?? [])] },\n ZERO_SUPPRESSION,\n js.configs.recommended,\n ...(tseslint.configs.recommended as Linter.Config[]),\n {\n files: ['src/**/*.ts'],\n languageOptions: { globals: nodeGlobals },\n rules: {\n '@typescript-eslint/no-unused-expressions': ['error', { allowShortCircuit: true }],\n '@typescript-eslint/no-explicit-any': 'off',\n '@typescript-eslint/no-unused-vars': ['error', UNUSED_VARS_ALLOW_UNDERSCORE],\n 'no-undef': 'off',\n },\n },\n // noInlineConfig ignores inline /* global */ comments, so Node globals come from config.\n {\n files: ['**/*.mjs'],\n languageOptions: { globals: nodeGlobals, sourceType: 'module' },\n rules: { 'no-unused-vars': ['error', UNUSED_VARS_ALLOW_UNDERSCORE] },\n },\n {\n files: ['lib/**/*.js', '**/*.js'],\n languageOptions: { globals: nodeGlobals, sourceType: 'commonjs' },\n rules: {\n 'no-undef': 'error',\n 'no-unused-vars': ['error', UNUSED_VARS_ALLOW_UNDERSCORE],\n },\n },\n ...(options.configs ?? []),\n prettierCompat,\n ];\n}\n"]}
1
+ {"version":3,"file":"eslint.js","sourceRoot":"","sources":["../../src/node/eslint.ts"],"names":[],"mappings":"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","sourcesContent":["import js from '@eslint/js';\nimport type { Linter } from 'eslint';\nimport prettierCompat from 'eslint-config-prettier';\nimport tseslint from 'typescript-eslint';\n\nexport interface EslintPresetOptions {\n ignores?: readonly string[];\n configs?: readonly Linter.Config[];\n}\n\nexport interface AppEslintPresetOptions extends EslintPresetOptions {\n a11y?: 'error' | 'warn';\n}\n\nconst ZERO_SUPPRESSION: Linter.Config = { linterOptions: { noInlineConfig: true } };\n\nconst APP_IGNORES = [\n '.next/**',\n 'node_modules/**',\n '.firebase/**',\n 'functions/**',\n // ESLint does not honor .gitignore, so the E2E harness's generated artifacts are ignored here.\n 'e2e-hosted-dev/.browser-profiles/**',\n 'e2e-hosted-dev/test-results/**',\n 'e2e-hosted-dev/failed-run-archive/**',\n 'test-results/**',\n 'out/**',\n 'dist/**',\n 'build/**',\n 'next-env.d.ts',\n '.next/types/**',\n '.next/dev/**',\n 'docs/**',\n];\n\nconst UNUSED_VARS_ALLOW_UNDERSCORE = {\n argsIgnorePattern: '^_',\n varsIgnorePattern: '^_',\n};\n\nconst APP_RULES: Linter.Config = {\n files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs'],\n // eslint-config-next registers react-hooks and flat config errors on a redefinition: rules only.\n languageOptions: {\n parserOptions: {\n ecmaVersion: 'latest',\n sourceType: 'module',\n ecmaFeatures: { jsx: true },\n },\n },\n rules: {\n // RULE-STD-MEDIA-101 bans next/image, the replacement this rule demands; served files render through the media renderer.\n '@next/next/no-img-element': 'off',\n '@typescript-eslint/no-unused-vars': [\n 'error',\n {\n ...UNUSED_VARS_ALLOW_UNDERSCORE,\n caughtErrorsIgnorePattern: '^_',\n destructuredArrayIgnorePattern: '^_',\n },\n ],\n 'react-hooks/rules-of-hooks': 'error',\n 'react-hooks/exhaustive-deps': 'error',\n 'react-hooks/set-state-in-effect': 'error',\n 'react-hooks/refs': 'error',\n 'react-hooks/immutability': 'error',\n 'react-hooks/incompatible-library': 'error',\n 'react-hooks/purity': 'error',\n },\n};\n\n// Type-aware: each TypeScript file is read through the nearest tsconfig.json that includes it.\nconst typeAwareNoAny = (files: string[]): Linter.Config => ({\n files,\n languageOptions: { parserOptions: { projectService: true } },\n rules: {\n '@typescript-eslint/no-unsafe-argument': 'error',\n '@typescript-eslint/no-unsafe-assignment': 'error',\n '@typescript-eslint/no-unsafe-call': 'error',\n '@typescript-eslint/no-unsafe-member-access': 'error',\n '@typescript-eslint/no-unsafe-return': 'error',\n },\n});\n\ntype RuleEntry = NonNullable<Linter.Config['rules']>[string];\n\n// eslint-config-next registers jsx-a11y and flat config errors on a second registration, so this\n// sets rules only; a severity-only entry keeps the options Next set (its Image alt-text options).\nfunction jsxA11yRecommended(\n nextConfigs: Linter.Config[],\n severity: 'error' | 'warn',\n): Linter.Config {\n const plugin = nextConfigs.find((config) => config.plugins?.['jsx-a11y'])?.plugins?.['jsx-a11y'];\n const recommended = (plugin?.configs?.recommended as Linter.Config | undefined)?.rules;\n if (!recommended) {\n throw new Error(\n 'eslint-config-next no longer registers eslint-plugin-jsx-a11y with a recommended config',\n );\n }\n const rules: Record<string, RuleEntry> = {};\n for (const [name, entry] of Object.entries(recommended)) {\n if (entry === undefined) continue;\n const level = Array.isArray(entry) ? entry[0] : entry;\n if (level === 'off' || level === 0) continue;\n rules[name] = Array.isArray(entry) ? [severity, ...entry.slice(1)] : severity;\n }\n return { files: ['**/*.tsx', '**/*.jsx'], rules };\n}\n\nexport async function createAppEslintConfig(\n options: AppEslintPresetOptions = {},\n): Promise<Linter.Config[]> {\n // Imported here: the Functions tree loads this module without eslint-config-next installed.\n const [{ default: nextCoreWebVitals }, { default: nextTypescript }] = await Promise.all([\n import('eslint-config-next/core-web-vitals'),\n import('eslint-config-next/typescript'),\n ]);\n return [\n { ignores: [...APP_IGNORES, ...(options.ignores ?? [])] },\n ZERO_SUPPRESSION,\n js.configs.recommended,\n ...(tseslint.configs.recommended as Linter.Config[]),\n ...(nextCoreWebVitals as Linter.Config[]),\n ...(nextTypescript as Linter.Config[]),\n jsxA11yRecommended(nextCoreWebVitals as Linter.Config[], options.a11y ?? 'error'),\n APP_RULES,\n typeAwareNoAny(['**/*.ts', '**/*.tsx']),\n ...(options.configs ?? []),\n prettierCompat,\n ];\n}\n\nexport async function createFunctionsEslintConfig(\n options: EslintPresetOptions = {},\n): Promise<Linter.Config[]> {\n // Imported here: the Next.js tree loads this module without globals installed.\n const { default: globals } = await import('globals');\n const nodeGlobals = { ...globals.node, ...globals.es2021 };\n return [\n { ignores: ['lib/**', 'build/**', ...(options.ignores ?? [])] },\n ZERO_SUPPRESSION,\n js.configs.recommended,\n ...(tseslint.configs.recommended as Linter.Config[]),\n {\n files: ['src/**/*.ts'],\n languageOptions: { globals: nodeGlobals },\n rules: {\n '@typescript-eslint/no-unused-expressions': ['error', { allowShortCircuit: true }],\n '@typescript-eslint/no-unused-vars': ['error', UNUSED_VARS_ALLOW_UNDERSCORE],\n 'no-undef': 'off',\n },\n },\n // noInlineConfig ignores inline /* global */ comments, so Node globals come from config.\n {\n files: ['**/*.mjs'],\n languageOptions: { globals: nodeGlobals, sourceType: 'module' },\n rules: { 'no-unused-vars': ['error', UNUSED_VARS_ALLOW_UNDERSCORE] },\n },\n {\n files: ['lib/**/*.js', '**/*.js'],\n languageOptions: { globals: nodeGlobals, sourceType: 'commonjs' },\n rules: {\n 'no-undef': 'error',\n 'no-unused-vars': ['error', UNUSED_VARS_ALLOW_UNDERSCORE],\n },\n },\n typeAwareNoAny(['**/*.ts']),\n ...(options.configs ?? []),\n prettierCompat,\n ];\n}\n"]}
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.3",
3
+ "version": "0.0.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",
@@ -1306,7 +1306,7 @@ precedes any cap, gate, or counter.
1306
1306
  **Rules:** RULE-STD-FRONTEND-203, RULE-STD-FRONTEND-204. **Scope:** `src`. Deep accessibility →
1307
1307
  `ux-audit-playbook.md`.
1308
1308
  ```bash
1309
- <GUARD_SUITE> -t "an icon-only button carries an accessible name|a button inside a form states its type|a display-only component is never a trigger or a control"
1309
+ <GUARD_SUITE> -t "an icon-only button carries an accessible name|a button inside a form states its type|a display-only component is never a trigger or a control|every dropdown carries an accessible name"
1310
1310
  <GUARD_SUITE> -t "app code uses no transition-all and no Tailwind duration-\* utility|a smooth scroll is requested only by the effective-motion helper|every app stylesheet transition names its property and rides the motion tokens|one store reads the reduced-motion request"
1311
1311
  rg -n "onClick=\{\(\)\s*=>\s*(void\s+)?(navigate|appNavigate)\(" src -g '*.tsx' -g '!**/__tests__/**'
1312
1312
  rg -n "aria-label=\"(More actions|Show [Dd]etails|View [Dd]etails|Remove|Delete|Edit|Open)\"" src -g '*.tsx' -g '!**/__tests__/**'
@@ -1495,6 +1495,20 @@ stays reachable on the admin standing alone (step 70). No installed React packag
1495
1495
  gated on admin standing.
1496
1496
  **Status:** PASS · FAIL · NOT RUN · BLOCKED
1497
1497
 
1498
+ ### 122. No promise rejection is discarded without a reason
1499
+ **Rules:** RULE-STD-ENG-009, RULE-STD-COMMENT-001, RULE-STD-QUALITY-007. **Scope:** every code tree,
1500
+ source and tests.
1501
+ ```bash
1502
+ <GUARD_SUITE> -t "no promise rejection is discarded without a reason"
1503
+ ```
1504
+ **Expected:** the guard is green: every `.catch()` whose handler does nothing — an empty body, or one
1505
+ that returns `undefined`, `void 0`, or `null` — carries a one-line comment saying why discarding the
1506
+ rejection is right (a constraint, or an exception naming its rule). Read each comment against its code:
1507
+ a discard of a failure a person or monitoring needs to see is the finding, whatever the comment says —
1508
+ it gets real handling. Each reviewed entry is a file whose discards genuinely cannot carry a comment;
1509
+ one whose file no longer discards is stale.
1510
+ **Status:** PASS · FAIL · NOT RUN · BLOCKED
1511
+
1498
1512
  ## Pass / Fail
1499
1513
 
1500
1514
  Per [README.md](README.md) § Verdict.
@@ -21,7 +21,7 @@ rule's coverage in its additions; they are not listed here.
21
21
  | RULE-STD-ENG-006 | The backend is the security boundary | codebase-hygiene 23, 53, 77, 99, 101, 107; auth-boundary 8, 9, 12, 13; callable-functions 11, 12, 13; client-write-security-audit 2, 11; firestore-storage-rules 4, 5, 11; migration 7; testing-coverage 3 | `localOnlyVariables`, `envPublicValues` |
22
22
  | RULE-STD-ENG-007 | Behavior, tests, and documentation move together | testing-coverage 2, 7 | — |
23
23
  | RULE-STD-ENG-008 | Surface discovered problems | codebase-hygiene 25, 27, 73 | — |
24
- | RULE-STD-ENG-009 | Fail truthfully and visibly | codebase-hygiene 60, 75, 83, 87, 88, 90; async-workflow 7; auth-boundary 14; callable-functions 9; data-integrity 13; migration 9; observability 3, 4, 6, 7, 8, 10; performance-audit 3; testing-coverage 11; ux-audit 11 | — |
24
+ | RULE-STD-ENG-009 | Fail truthfully and visibly | codebase-hygiene 60, 75, 83, 87, 88, 90, 122; async-workflow 7; auth-boundary 14; callable-functions 9; data-integrity 13; migration 9; observability 3, 4, 6, 7, 8, 10; performance-audit 3; testing-coverage 11; ux-audit 11 | `swallowedRejections` |
25
25
  | RULE-STD-ENG-010 | Spawned agents inherit the rules | codebase-hygiene 69 — Designated manual review (step 69): verified on the spawning prompt, never by a search of the repo. | — |
26
26
  | RULE-STD-ENG-011 | Once data is live, persisted shapes change with a migration path | client-write-security-audit 8; data-integrity 7; migration 3, 5 | — |
27
27
 
@@ -159,7 +159,7 @@ rule's coverage in its additions; they are not listed here.
159
159
  | RULE-STD-QUALITY-003 | E2E is green only at zero failures AND zero retry-passes | testing-coverage 1, 6 | — |
160
160
  | RULE-STD-QUALITY-004 | E2E tests use the established fixtures and web-first assertions | codebase-hygiene 28; migration 10; testing-coverage 6, 8, 10 | — |
161
161
  | RULE-STD-QUALITY-006 | Lint and format: zero suppression in apps, reasoned disables in packages; one enforced format | codebase-hygiene 73; package-boundaries 7 | — |
162
- | RULE-STD-QUALITY-007 | Guard tests enforce ownership and boundaries, never inventories | codebase-hygiene 35, 42, 63, 80, 83, 87, 88, 90, 91, 95, 96, 117, 118, 119, 120 | — |
162
+ | RULE-STD-QUALITY-007 | Guard tests enforce ownership and boundaries, never inventories | codebase-hygiene 35, 42, 63, 80, 83, 87, 88, 90, 91, 95, 96, 117, 118, 119, 120, 122 | — |
163
163
  | RULE-STD-QUALITY-008 | Rules and E2E run against a fresh dev:e2e | codebase-hygiene 29; dependency-audit 3; firestore-storage-rules 10; testing-coverage 1, 9 | — |
164
164
  | RULE-STD-QUALITY-009 | A test loads the code it tests when its file loads | codebase-hygiene 42, 118; package-boundaries 8 | `testCaseModuleLoads` |
165
165
  | RULE-STD-QUALITY-010 | A test's time limit names a budget | codebase-hygiene 119; package-boundaries 10 | `testTimeouts` |
@@ -180,7 +180,7 @@ rule's coverage in its additions; they are not listed here.
180
180
 
181
181
  | Rule | Title | Audited by (shared steps) | Guards |
182
182
  |---|---|---|---|
183
- | RULE-STD-COMMENT-001 | No comment unless the code cannot say it | codebase-hygiene 89 | — |
183
+ | RULE-STD-COMMENT-001 | No comment unless the code cannot say it | codebase-hygiene 89, 122 | `swallowedRejections` |
184
184
  | RULE-STD-COMMENT-002 | No dates | codebase-hygiene 89 | — |
185
185
  | RULE-STD-COMMENT-003 | No line numbers | codebase-hygiene 89 | — |
186
186
  | RULE-STD-COMMENT-004 | No ticket names or work labels | codebase-hygiene 89 | — |
@@ -75,7 +75,7 @@ reachable through its accessible name or title.
75
75
  **Rules:** RULE-STD-FRONTEND-203, RULE-STD-FRONTEND-204, RULE-STD-FRONTEND-003. **Scope:** the major
76
76
  routes. **Method (axe and Lighthouse on the local dev server, plus a keyboard walkthrough).**
77
77
  **Expected:** beyond the component-level lint rules — one `main` landmark and skip link owned by the
78
- shell, with no page rendering its own `<main>`; icon-only buttons carry an accessible name, and a
78
+ shell, with no page rendering its own `<main>`; icon-only buttons and every dropdown carry an accessible name, and a
79
79
  control repeated per item is named for its item; a custom choice button announces its selected state;
80
80
  a search dropdown is a labeled combobox whose highlighted result is announced as it moves, with Enter
81
81
  to pick and Escape to close, and two searches on one page never share an id; form fields associate
@@ -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,21 +13,25 @@ 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.
18
- 3. Propose to DJ: the number of agents and each one's exact scope and owned files. Nothing spawns
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.
21
+ 3. Propose to DJ: the number of agents, each one's tier, 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
21
24
  app work never run at the same time — all package work, its gate, and DJ's publish come first.
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.