@ttt-productions/dev-standards 0.0.4 → 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.
@@ -67,11 +67,12 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
67
67
  verified and a recommendation, and stays open for DJ. When DJ has said he is away, no question
68
68
  blocks: it goes in chat, the session finishes every part that does not depend on it, and moves to
69
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.
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
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.
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.
75
76
  - When DJ asks whether the conversation is good to end, or says to start a new one, the session first
76
77
  says how many questions are open and asks whether to answer them here or move them into the new
77
78
  conversation's prompt.
@@ -145,6 +146,13 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
145
146
  - Verify at the cost of the fix: a one-file fix runs its single file first; the broad gate only
146
147
  when the blast radius warrants it. Never run multiple full repository gates concurrently.
147
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.
148
156
 
149
157
  ## Engineering judgment
150
158
 
@@ -157,13 +165,14 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
157
165
  - No half-working, dead, or known-bad code — build it or remove it. On a second sighting of a defect
158
166
  class, tell DJ and propose killing the CLASS system-wide plus a regression guard; do it only on
159
167
  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
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
163
171
  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.
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.
167
176
  - Reviewing an external plan (ChatGPT/Codex/etc.) means answering "is this the BEST code to solve
168
177
  my problem" — never just verifying its claims. Evaluate the design space (alternatives, root cause,
169
178
  structure) and fold an optimality verdict into the reviewed doc.
@@ -191,8 +200,8 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
191
200
  No doc, plan, ledger, or prompt makes a conversation an orchestrator.
192
201
  - **Inline:** the session does the research and the work itself. When a piece of the work would be
193
202
  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
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
196
205
  still runs its own Normal Test Flow.
197
206
  - **Orchestrator:** the session saves its context for managing agents and does not read or edit code
198
207
  itself. It first launches read-only agents to understand the system, then gives each building
@@ -203,8 +212,8 @@ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
203
212
  - The main session owns edits unless DJ explicitly approves parallel implementers with disjoint file
204
213
  ownership. Nothing spawns without DJ's yes.
205
214
  - Every prompt that spawns an agent names that agent's mandatory reading as explicit
206
- read-before-acting paths (RULE-STD-ENG-010), hard-bans spawning further agents, and carries a stop
207
- 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
208
217
  session.
209
218
  - How a given kind of conversation runs its agents is its development workflow's procedure
210
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":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,cAAc,MAAM,wBAAwB,CAAC;AACpD,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AAWzC,MAAM,gBAAgB,GAAkB,EAAE,aAAa,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,CAAC;AAEpF,MAAM,WAAW,GAAG;IAClB,UAAU;IACV,iBAAiB;IACjB,cAAc;IACd,cAAc;IACd,+FAA+F;IAC/F,qCAAqC;IACrC,gCAAgC;IAChC,sCAAsC;IACtC,iBAAiB;IACjB,QAAQ;IACR,SAAS;IACT,UAAU;IACV,aAAa;IACb,aAAa;IACb,eAAe;IACf,oBAAoB;IACpB,2BAA2B;IAC3B,oBAAoB;IACpB,gBAAgB;IAChB,cAAc;IACd,SAAS;CACV,CAAC;AAEF,MAAM,4BAA4B,GAAG;IACnC,iBAAiB,EAAE,IAAI;IACvB,iBAAiB,EAAE,IAAI;CACxB,CAAC;AAEF,MAAM,SAAS,GAAkB;IAC/B,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC;IAC7E,iGAAiG;IACjG,eAAe,EAAE;QACf,aAAa,EAAE;YACb,WAAW,EAAE,QAAQ;YACrB,UAAU,EAAE,QAAQ;YACpB,YAAY,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE;SAC5B;KACF;IACD,KAAK,EAAE;QACL,2BAA2B,EAAE,KAAK;QAClC,mCAAmC,EAAE;YACnC,OAAO;YACP;gBACE,GAAG,4BAA4B;gBAC/B,yBAAyB,EAAE,IAAI;gBAC/B,8BAA8B,EAAE,IAAI;aACrC;SACF;QACD,oCAAoC,EAAE,KAAK;QAC3C,4BAA4B,EAAE,OAAO;QACrC,6BAA6B,EAAE,OAAO;QACtC,iCAAiC,EAAE,OAAO;QAC1C,kBAAkB,EAAE,OAAO;QAC3B,0BAA0B,EAAE,OAAO;QACnC,kCAAkC,EAAE,OAAO;QAC3C,oBAAoB,EAAE,OAAO;KAC9B;CACF,CAAC;AAIF,iGAAiG;AACjG,kGAAkG;AAClG,SAAS,kBAAkB,CACzB,WAA4B,EAC5B,QAA0B;IAE1B,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC;IACjG,MAAM,WAAW,GAAI,MAAM,EAAE,OAAO,EAAE,WAAyC,EAAE,KAAK,CAAC;IACvF,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAA8B,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACxD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACtD,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;YAAE,SAAS;QAC7C,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAChF,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,UAAkC,EAAE;IAEpC,4FAA4F;IAC5F,MAAM,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACtF,MAAM,CAAC,oCAAoC,CAAC;QAC5C,MAAM,CAAC,+BAA+B,CAAC;KACxC,CAAC,CAAC;IACH,OAAO;QACL,EAAE,OAAO,EAAE,CAAC,GAAG,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,EAAE;QACzD,gBAAgB;QAChB,EAAE,CAAC,OAAO,CAAC,WAAW;QACtB,GAAI,QAAQ,CAAC,OAAO,CAAC,WAA+B;QACpD,GAAI,iBAAqC;QACzC,GAAI,cAAkC;QACtC,kBAAkB,CAAC,iBAAoC,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC;QACjF,SAAS;QACT,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;QAC1B,cAAc;KACf,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,UAA+B,EAAE;IAEjC,+EAA+E;IAC/E,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAC3D,OAAO;QACL,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,EAAE;QAC/D,gBAAgB;QAChB,EAAE,CAAC,OAAO,CAAC,WAAW;QACtB,GAAI,QAAQ,CAAC,OAAO,CAAC,WAA+B;QACpD;YACE,KAAK,EAAE,CAAC,aAAa,CAAC;YACtB,eAAe,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE;YACzC,KAAK,EAAE;gBACL,0CAA0C,EAAE,CAAC,OAAO,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC;gBAClF,oCAAoC,EAAE,KAAK;gBAC3C,mCAAmC,EAAE,CAAC,OAAO,EAAE,4BAA4B,CAAC;gBAC5E,UAAU,EAAE,KAAK;aAClB;SACF;QACD,yFAAyF;QACzF;YACE,KAAK,EAAE,CAAC,UAAU,CAAC;YACnB,eAAe,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE;YAC/D,KAAK,EAAE,EAAE,gBAAgB,EAAE,CAAC,OAAO,EAAE,4BAA4B,CAAC,EAAE;SACrE;QACD;YACE,KAAK,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC;YACjC,eAAe,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE;YACjE,KAAK,EAAE;gBACL,UAAU,EAAE,OAAO;gBACnB,gBAAgB,EAAE,CAAC,OAAO,EAAE,4BAA4B,CAAC;aAC1D;SACF;QACD,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;QAC1B,cAAc;KACf,CAAC;AACJ,CAAC","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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/dev-standards",
3
- "version": "0.0.4",
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
@@ -18,7 +18,7 @@ description: Claude Code workflow (Build or fix). Use when the work is big enoug
18
18
  2. Launch read-only agents to read the routed rules, the `docs/design/*.md` for every area involved,
19
19
  and the current code, and report the system back; the design each building agent gets comes from
20
20
  their reports.
21
- 3. Propose to DJ: the number of agents and each one's exact scope and owned files. Nothing spawns
21
+ 3. Propose to DJ: the number of agents, each one's tier, and each one's exact scope and owned files. Nothing spawns
22
22
  without DJ's yes.
23
23
  4. App-side work: confirm every shared package it depends on is at `@latest` first. Package work and
24
24
  app work never run at the same time — all package work, its gate, and DJ's publish come first.