@starci/eslint-canon-be 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/testing.mjs CHANGED
@@ -1,454 +1,500 @@
1
- /**
2
- * The rules that hold `testing.md`.
3
- *
4
- * MOST OF THAT LAW IS NOT MACHINE-CHECKABLE, and pretending otherwise would be worse than
5
- * checking nothing. No rule can tell whether a file represents a business flow, whether the
6
- * unhappy path it covers drags a critical flow behind it, or whether the decision branches are
7
- * covered - those are read by a person.
8
- *
9
- * What a rule CAN see is the two shapes that are wrong on their face regardless of intent: a spec
10
- * whose every assertion is about a call rather than a result, and an end-to-end spec that never
11
- * reads any state back. Both were measured before being written: eight specs and one e2e file in
12
- * the source repository, which is why the second lands with a carve-out rather than at zero.
13
- *
14
- * Two more codes turned out to have a genuinely mechanical HALF, added later and kept narrow on
15
- * purpose:
16
- *
17
- * - TESTING-1. A filename cannot be checked against a business sentence - but the anti-pattern
18
- * the law names by example (`rewards-queries.e2e-spec.ts`) IS a closed, denylisted shape: a
19
- * filename whose last segment is an API noun rather than a business one. The rule refuses only
20
- * that shape; it does not claim to certify a good name, and it is the same requirement E2E-1 in
21
- * `e2e-flow.md` states on the same file, so one rule holds both codes rather than reporting the
22
- * same file twice under two names.
23
- * - TESTING-7. "A stub of a model returns a payload the production parser can actually parse" -
24
- * the rule cannot verify the payload parses, but it CAN refuse the one shape the law names by
25
- * example: a bare marker string (`"stubbed"`, `"ok"`, `"test"`) standing in for the whole
26
- * answer. An object literal, or a `JSON.stringify(...)` call, is out of the rule's reach on
27
- * purpose - that is where a false positive would start.
28
- *
29
- * TESTING-5 / TESTING-8 ("a configured lane either holds tests or does not exist") was drafted as a
30
- * filesystem-reading rule and then WITHDRAWN. It reads correctly as JS to `@typescript-eslint/parser`
31
- * (`{"rootDir": "..."}` parses as an `ExpressionStatement` over an `ObjectExpression` under this
32
- * parser, no wrapping needed) and its own file-scoped RuleTester cases passed. It is left out anyway,
33
- * because the question it would answer is not "does this repository's config have this shape" but
34
- * "does the CONSUMING repository's `eslint.config.mjs` even point a linter at this file at all" - and
35
- * in the measured reference repository it does not: `files` there is five TypeScript globs under
36
- * `src`, `apps`, `libs`, `test` and `tests` - none of them `.json` - so `jest-e2e.json` and
37
- * `jest-harness.json` sit outside every one of them. A rule that cannot be reached by the config
38
- * that would run it is not a `warn`-with-a-count away from correct; it is dead code reporting a "0"
39
- * that means "never asked", not "already true" - the one measurement this family refuses to publish
40
- * as if it were the other.
41
- */
42
-
43
- /** Forward-slash form of a filename, so Windows paths compare like every other path. */
44
- const normalizePath = (filename) => String(filename || "").replace(/\\/g, "/")
45
-
46
- /** The fast lane: a plain unit spec, excluding every other suffix that also ends in `spec.ts`. */
47
- const isUnitSpec = (filename) => {
48
- const file = normalizePath(filename)
49
- if (!/\.spec\.ts$/.test(file)) return false
50
- return !/\.(?:e2e|int|harness)-spec\.ts$/.test(file)
51
- }
52
-
53
- /** The flow lane. */
54
- const isE2eSpec = (filename) => /\.e2e-spec\.ts$/.test(normalizePath(filename))
55
-
56
- /** The paid model-quality lane. */
57
- const isHarnessSpec = (filename) => /\.harness-spec\.ts$/.test(normalizePath(filename))
58
-
59
- /** Test-only helpers whose only authority is the harness lane. */
60
- const isHarnessHelper = (filename) => /\/src\/tests\/helpers\//.test(normalizePath(filename))
61
-
62
- /**
63
- * Matchers that assert a CALL happened rather than what came out of it.
64
- *
65
- * Each is legitimate as a second assertion - "the mail went out" is a real observable effect. The
66
- * rule fires only when a whole file has nothing else.
67
- */
68
- const CALL_MATCHERS = new Set([
69
- "toHaveBeenCalled",
70
- "toHaveBeenCalledWith",
71
- "toHaveBeenCalledTimes",
72
- "toHaveBeenLastCalledWith",
73
- "toHaveBeenNthCalledWith",
74
- "toBeCalled",
75
- "toBeCalledWith",
76
- "toBeCalledTimes",
77
- "toHaveReturned",
78
- ])
79
-
80
- /** Names that mean the test read state back out of the database. */
81
- const STATE_READERS = /^(?:entityManager|dataSource|EntityManager|DataSource|getRepository|queryRunner)$/
82
-
83
- /**
84
- * The matcher a given `expect(...)` call ends in.
85
- *
86
- * Climbs the member chain so modifiers pass through: `expect(x).not.toHaveBeenCalled()` answers
87
- * `toHaveBeenCalled`, and `expect(p).resolves.toBe(1)` answers `toBe`.
88
- */
89
- const matcherOf = (expectCall) => {
90
- let cursor = expectCall
91
- let last = null
92
- while (cursor.parent && cursor.parent.type === "MemberExpression" && cursor.parent.object === cursor) {
93
- last = cursor.parent.property && cursor.parent.property.name
94
- cursor = cursor.parent
95
- }
96
- return last
97
- }
98
-
99
- // -- TESTING-6 -------------------------------------------------------------------------------------
100
-
101
- /** A spec whose every assertion is about a call restates the source instead of testing it. */
102
- export const noCallOnlySpec = {
103
- meta: {
104
- type: "problem",
105
- docs: { description: "A unit spec asserts a result or a state change, not only that a call happened." },
106
- schema: [],
107
- messages: {
108
- callOnly:
109
- "Every assertion in this spec is `{{matchers}}` - it restates the handler's own source. Rename a collaborator's method and this file goes red; change the business rule to a wrong value and it stays green. Assert what came back, or what changed. A call assertion is legitimate as a SECOND assertion, where the call itself is the observable effect.",
110
- },
111
- },
112
- create(context) {
113
- if (!isUnitSpec(context.filename || context.getFilename())) return {}
114
- let assertions = 0
115
- let callAssertions = 0
116
- const seen = new Set()
117
- return {
118
- CallExpression(node) {
119
- if (!node.callee || node.callee.type !== "Identifier" || node.callee.name !== "expect") return
120
- const matcher = matcherOf(node)
121
- if (matcher === null) return
122
- assertions += 1
123
- if (!CALL_MATCHERS.has(matcher)) return
124
- callAssertions += 1
125
- seen.add(matcher)
126
- },
127
- "Program:exit"(node) {
128
- // the file asserts nothing at all, or asserts something other than a call somewhere
129
- if (assertions === 0 || callAssertions !== assertions) return
130
- context.report({ node, messageId: "callOnly", data: { matchers: [...seen].join(", ") } })
131
- },
132
- }
133
- },
134
- }
135
-
136
- // -- TESTING-2 -------------------------------------------------------------------------------------
137
-
138
- /** An end-to-end spec that never reads state back proves only that the server replied. */
139
- export const e2eAssertsPersistedState = {
140
- meta: {
141
- type: "problem",
142
- docs: { description: "An e2e spec reads state back rather than asserting only on the response." },
143
- schema: [],
144
- messages: {
145
- noState:
146
- "This e2e never reads any state back - it asserts on responses alone, so the flow can stop persisting and this file stays green. Read the row, the balance or the entitlement out of the database and assert THAT. A flow that genuinely has no persisted consequence needs an eslint-disable naming what it observes instead.",
147
- },
148
- },
149
- create(context) {
150
- if (!isE2eSpec(context.filename || context.getFilename())) return {}
151
- let readsState = false
152
- return {
153
- Identifier(node) {
154
- if (STATE_READERS.test(node.name)) readsState = true
155
- },
156
- "Program:exit"(node) {
157
- if (readsState) return
158
- context.report({ node, messageId: "noState" })
159
- },
160
- }
161
- },
162
- }
163
-
164
- // -- TESTING-9 -------------------------------------------------------------------------------------
165
-
166
- /** Provider SDKs. Importing one into a flow test is a real model call by any other name. */
167
- const PROVIDER_PACKAGES = /^(?:@anthropic-ai\/|openai$|openai\/|ollama$|@google\/generative-ai|@mistralai\/|cohere-ai)/
168
-
169
- /**
170
- * The harness's own model helpers, which exist to reach a provider.
171
- *
172
- * Matched WITHOUT the `tests/` prefix: an e2e sits beside the helper folder, so the import it
173
- * actually writes is the relative `../helpers/models.service`, and a pattern anchored on the
174
- * absolute path never sees it.
175
- */
176
- const HARNESS_MODEL_HELPERS = /helpers\/models(?:\.service)?$/
177
-
178
- /** An e2e never calls a model; the harness is the only lane that does. */
179
- export const noModelCallInE2e = {
180
- meta: {
181
- type: "problem",
182
- docs: { description: "An e2e spec never reaches a model provider." },
183
- schema: [],
184
- messages: {
185
- provider:
186
- "`{{source}}` reaches a model provider from an e2e. A model call costs money, takes seconds and answers differently every time - so this makes the flow suite expensive, slow and flaky at once, and the assertion has to be loosened until it stops catching anything. Stub the model and assert what can actually break: the entitlement, the quota, the persisted answer. Judging the answer itself belongs in the harness.",
187
- },
188
- },
189
- create(context) {
190
- if (!isE2eSpec(context.filename || context.getFilename())) return {}
191
- return {
192
- ImportDeclaration(node) {
193
- const source = node.source && node.source.value
194
- if (typeof source !== "string") return
195
- if (!PROVIDER_PACKAGES.test(source) && !HARNESS_MODEL_HELPERS.test(source)) return
196
- context.report({ node, messageId: "provider", data: { source } })
197
- },
198
- }
199
- },
200
- }
201
-
202
- // -- TESTING-10 ------------------------------------------------------------------------------------
203
-
204
- /** Official provider clients that a harness may call without a house routing layer. */
205
- const DIRECT_PROVIDER_PACKAGES = /^(?:@anthropic-ai\/sdk(?:\/|$)|openai(?:\/|$)|@google\/genai(?:\/|$)|@google\/generative-ai(?:\/|$)|@mistralai\/(?:mistralai|mistralai-ts)(?:\/|$)|cohere-ai(?:\/|$)|ollama(?:\/|$))/
206
-
207
- /** Helpers that hide a model call or impersonate the production gateway. */
208
- const FORBIDDEN_HARNESS_HELPERS = /(?:^|\/)(?:models(?:\.service)?|harness-invoke(?:\.service)?)$/
209
-
210
- /** Consumer/CLI credential authorities that are never provider API credentials. */
211
- const FORBIDDEN_HARNESS_AUTH = /(?:CLAUDE_CODE_OAUTH_TOKEN|claude-code-token|sk-ant-oat|OAUTH_BETA|CHATGPT_(?:SESSION|AUTH)_TOKEN|CODEX_(?:SESSION|AUTH)_TOKEN|auth-profile)/i
212
-
213
- /** Imported symbols that reveal a fake production gateway in the harness lane. */
214
- const FORBIDDEN_HARNESS_SYMBOLS = new Set([
215
- "AiInvokeService",
216
- "HarnessInvokeService",
217
- "createHarnessInvoke",
218
- ])
219
-
220
- /** Return the static property name of an object/member property when one exists. */
221
- const propertyName = (property) => {
222
- if (!property) return null
223
- if (property.type === "Identifier") return property.name
224
- if (property.type === "Literal") return property.value
225
- return null
226
- }
227
-
228
- /** Find the `AiInvokeService` token in a `Pick<AiInvokeService, ...>` type. */
229
- const aiInvokePickToken = (sourceCode) => {
230
- const tokens = sourceCode.getTokens(sourceCode.ast)
231
- for (let index = 0; index < tokens.length - 2; index += 1) {
232
- if (tokens[index].value !== "Pick" || tokens[index + 1].value !== "<") continue
233
- if (tokens[index + 2].value === "AiInvokeService") return tokens[index + 2]
234
- }
235
- return null
236
- }
237
-
238
- /** A harness calls one explicit provider SDK and never disguises it as `AiInvokeService`. */
239
- export const harnessCallsProviderDirectly = {
240
- meta: {
241
- type: "problem",
242
- docs: { description: "A model-quality harness calls an approved provider SDK directly with provider API credentials." },
243
- schema: [],
244
- messages: {
245
- missingProvider:
246
- "This harness imports no approved provider SDK. A model-quality harness calls the declared provider client directly; a house helper, tier or gateway override can make it green about a model production does not use.",
247
- gateway:
248
- "`{{name}}` impersonates or replaces the production AI gateway from a harness. Reuse the production prompt builder and parser around a direct provider SDK call instead.",
249
- helper:
250
- "`{{source}}` hides the provider call behind a house harness helper. Import and call the approved provider SDK in this harness; only credential loading may be shared.",
251
- consumerAuth:
252
- "`{{authority}}` is a consumer or CLI credential authority, not a provider-issued server API key. Read an explicit harness API-key environment variable instead.",
253
- },
254
- },
255
- create(context) {
256
- const filename = context.filename || context.getFilename()
257
- const harness = isHarnessSpec(filename)
258
- const authScope = harness || isHarnessHelper(filename)
259
- if (!authScope) return {}
260
-
261
- let hasProviderImport = false
262
- return {
263
- ImportDeclaration(node) {
264
- const source = node.source && node.source.value
265
- if (typeof source !== "string") return
266
- if (harness && DIRECT_PROVIDER_PACKAGES.test(source)) hasProviderImport = true
267
- if (harness && FORBIDDEN_HARNESS_HELPERS.test(source.replace(/\.(?:ts|js)$/, ""))) {
268
- context.report({ node, messageId: "helper", data: { source } })
269
- }
270
- if (!harness) return
271
- for (const specifier of node.specifiers) {
272
- const imported = specifier.imported && (specifier.imported.name || specifier.imported.value)
273
- const local = specifier.local && specifier.local.name
274
- const name = imported || local
275
- if (!FORBIDDEN_HARNESS_SYMBOLS.has(name)) continue
276
- context.report({ node: specifier, messageId: "gateway", data: { name } })
277
- }
278
- },
279
- Literal(node) {
280
- if (typeof node.value !== "string" || !FORBIDDEN_HARNESS_AUTH.test(node.value)) return
281
- context.report({ node, messageId: "consumerAuth", data: { authority: node.value } })
282
- },
283
- Property(node) {
284
- if (!harness || propertyName(node.key) !== "provide") return
285
- if (!node.value || node.value.type !== "Identifier" || node.value.name !== "AiInvokeService") return
286
- context.report({ node, messageId: "gateway", data: { name: "provide: AiInvokeService" } })
287
- },
288
- CallExpression(node) {
289
- if (!harness || !node.callee || node.callee.type !== "MemberExpression") return
290
- if (propertyName(node.callee.property) !== "overrideProvider") return
291
- const argument = node.arguments && node.arguments[0]
292
- if (!argument || argument.type !== "Identifier" || argument.name !== "AiInvokeService") return
293
- context.report({ node, messageId: "gateway", data: { name: "overrideProvider(AiInvokeService)" } })
294
- },
295
- "Program:exit"(node) {
296
- if (!harness) return
297
- const sourceCode = context.sourceCode || context.getSourceCode()
298
- const pickToken = aiInvokePickToken(sourceCode)
299
- if (pickToken) {
300
- context.report({ node: pickToken, messageId: "gateway", data: { name: 'Pick<AiInvokeService, "run">' } })
301
- }
302
- if (!hasProviderImport) context.report({ node, messageId: "missingProvider" })
303
- },
304
- }
305
- },
306
- }
307
-
308
- // -- TESTING-1 (and E2E-1 in e2e-flow.md, the same requirement on the same file) -------------------
309
-
310
- /** Nouns that name an API shape rather than a business promise. `rewards-queries` is the anchor. */
311
- const API_SHAPED_FILENAME_NOUNS = new Set([
312
- "queries", "query", "mutations", "mutation",
313
- "resolvers", "resolver", "endpoints", "endpoint",
314
- "controllers", "controller", "handlers", "handler",
315
- "modules", "module", "apis", "api",
316
- ])
317
-
318
- /** The hyphen-separated words of an e2e filename, with the lane suffix stripped. */
319
- const e2eFilenameSegments = (filename) => {
320
- const base = normalizePath(filename).split("/").pop() || ""
321
- return base.replace(/\.e2e-spec\.ts$/, "").split("-").filter(Boolean)
322
- }
323
-
324
- /** An e2e file is one business flow; the filename may not be an API-shape noun wearing its clothes. */
325
- export const noApiShapedE2eFilename = {
326
- meta: {
327
- type: "problem",
328
- docs: {
329
- description:
330
- "An e2e filename reads as a business sentence (TESTING-1; E2E-1 in e2e-flow.md), not a resolver/endpoint/module group.",
331
- },
332
- schema: [],
333
- messages: {
334
- apiShaped:
335
- "`{{segment}}` names an API shape - a resolver group, an endpoint, a module - not a business promise. TESTING-1 / E2E-1: one e2e file is one business flow and the filename IS that flow; `rewards-queries.e2e-spec.ts` is exactly the shape this refuses. Name the file for the sentence it proves (`course-purchase.e2e-spec.ts`), not the API surface it happens to hit.",
336
- },
337
- },
338
- create(context) {
339
- const filename = context.filename || context.getFilename()
340
- if (!isE2eSpec(filename)) return {}
341
- const segments = e2eFilenameSegments(filename)
342
- if (segments.length === 0) return {}
343
- const last = segments[segments.length - 1].toLowerCase()
344
- if (!API_SHAPED_FILENAME_NOUNS.has(last)) return {}
345
- return {
346
- Program(node) {
347
- context.report({ node, messageId: "apiShaped", data: { segment: last } })
348
- },
349
- }
350
- },
351
- }
352
-
353
- // -- TESTING-7 -------------------------------------------------------------------------------------
354
-
355
- /** Test-infra helpers, where the shared model stub this code governs actually lives. */
356
- const isTestHelperFile = (filename) => /\/tests\/helpers\//.test(normalizePath(filename))
357
-
358
- /** Bare markers a stub returns when nobody gave it a real answer to stand in for. */
359
- const MARKER_STRINGS = new Set(["stubbed", "stub", "ok", "test", "mock", "fake", "todo", "tbd", "n/a", "pending", ""])
360
-
361
- /** A string literal that is nothing but a marker - not an object, not a `JSON.stringify(...)` call. */
362
- const isMarkerLiteral = (node) => Boolean(
363
- node && node.type === "Literal" && typeof node.value === "string" && MARKER_STRINGS.has(node.value.trim().toLowerCase()),
364
- )
365
-
366
- /** The world's default model stub returns a payload the production parser can actually parse. */
367
- export const noMarkerModelStub = {
368
- meta: {
369
- type: "problem",
370
- docs: {
371
- description: "A model stub in test infra returns a parseable payload (TESTING-7), never a bare marker string.",
372
- },
373
- schema: [],
374
- messages: {
375
- marker:
376
- "This stub resolves to `{{value}}`, a marker rather than an answer. TESTING-7: a stub of a model returns a payload the production parser can actually parse - a marker string means the strict-JSON parser the real flow depends on never runs, so the most fragile seam in the flow is never exercised. Return the shape the production parser expects (an object, or a `JSON.stringify(...)` of one), the way `DEFAULT_MODEL_ANSWER` does.",
377
- },
378
- },
379
- create(context) {
380
- if (!isTestHelperFile(context.filename || context.getFilename())) return {}
381
- return {
382
- CallExpression(node) {
383
- const callee = node.callee
384
- if (!callee || callee.type !== "MemberExpression" || callee.computed) return
385
- const method = callee.property.name
386
- if (method === "mockResolvedValue" || method === "mockReturnValue") {
387
- const argument = node.arguments[0]
388
- if (isMarkerLiteral(argument)) {
389
- context.report({ node: argument, messageId: "marker", data: { value: JSON.stringify(argument.value) } })
390
- }
391
- return
392
- }
393
- if (method !== "mockImplementation") return
394
- const fn = node.arguments[0]
395
- if (!fn || (fn.type !== "ArrowFunctionExpression" && fn.type !== "FunctionExpression")) return
396
- if (fn.body.type !== "BlockStatement") {
397
- if (isMarkerLiteral(fn.body)) {
398
- context.report({ node: fn.body, messageId: "marker", data: { value: JSON.stringify(fn.body.value) } })
399
- }
400
- return
401
- }
402
- for (const statement of fn.body.body) {
403
- if (statement.type === "ReturnStatement" && isMarkerLiteral(statement.argument)) {
404
- context.report({ node: statement.argument, messageId: "marker", data: { value: JSON.stringify(statement.argument.value) } })
405
- }
406
- }
407
- },
408
- }
409
- },
410
- }
411
-
412
- /** The rules this law contributes to the plugin. */
413
- export const rules = {
414
- "no-call-only-spec": noCallOnlySpec,
415
- "e2e-asserts-persisted-state": e2eAssertsPersistedState,
416
- "no-model-call-in-e2e": noModelCallInE2e,
417
- "harness-calls-provider-directly": harnessCallsProviderDirectly,
418
- "no-api-shaped-e2e-filename": noApiShapedE2eFilename,
419
- "no-marker-model-stub": noMarkerModelStub,
420
- }
421
-
422
- /**
423
- * The level this law asks for, as the plugin's own opinion.
424
- *
425
- * MEASURED WITH THE RULES THEMSELVES against the reference repository, which is the only count
426
- * worth writing down: 1 call-only spec of 181, and 1 e2e of 48 that never reads state back.
427
- *
428
- * That number was wrong twice before it was right, and both ways are worth knowing. A grep-based
429
- * estimate said EIGHT - it counted files whose assertions merely INCLUDED a call matcher, which is
430
- * the shape this rule deliberately permits. A first run of the rule itself said THREE - it counted
431
- * eslint's own complaints about inline `eslint-disable` comments referring to rules the measuring
432
- * config never loaded. Count only the reports carrying this rule's own id.
433
- *
434
- * Both land at `warn` with the count, get burned down, and flip to `error` at zero.
435
- *
436
- * The two later rules were measured the same way, against the same repository:
437
- *
438
- * - `no-api-shaped-e2e-filename` found TWO of 77 - `installment-plan-queries.e2e-spec.ts` and
439
- * `rewards-queries.e2e-spec.ts`, the exact anchor example the law names. Real, pre-existing debt
440
- * this task does not fix, so it lands at `warn` rather than claiming a burn-down that has not
441
- * happened.
442
- * - `no-marker-model-stub` found ZERO of the world's own stub in `tests/helpers/flow-world.ts`:
443
- * the default answer is `JSON.stringify({ answer: "..." })`, which is a `CallExpression`, not a
444
- * `Literal`, so the rule does not even see it as a candidate. It ships at `error` as a
445
- * regression guard on a shape that is already correct.
446
- */
447
- export const recommended = {
448
- "starci-be/no-call-only-spec": "error", // no=0 of 182 - burned down from 1
449
- "starci-be/e2e-asserts-persisted-state": "error", // no=0 of 47 - burned down from 1
450
- "starci-be/no-model-call-in-e2e": "error", // no=0 of 47
451
- "starci-be/harness-calls-provider-directly": "error",
452
- "starci-be/no-api-shaped-e2e-filename": "warn", // no=2 of 77 - pre-existing, not burned down here
453
- "starci-be/no-marker-model-stub": "error", // no=0 - the world's stub already returns JSON.stringify(...)
454
- }
1
+ /**
2
+ * The rules that hold `testing.md`.
3
+ *
4
+ * MOST OF THAT LAW IS NOT MACHINE-CHECKABLE, and pretending otherwise would be worse than
5
+ * checking nothing. No rule can tell whether a file represents a business flow, whether the
6
+ * unhappy path it covers drags a critical flow behind it, or whether the decision branches are
7
+ * covered - those are read by a person.
8
+ *
9
+ * What a rule CAN see is the two shapes that are wrong on their face regardless of intent: a spec
10
+ * whose every assertion is about a call rather than a result, and an end-to-end spec that never
11
+ * reads any state back. Both were measured before being written: eight specs and one e2e file in
12
+ * the source repository, which is why the second lands with a carve-out rather than at zero.
13
+ *
14
+ * Two more codes turned out to have a genuinely mechanical HALF, added later and kept narrow on
15
+ * purpose:
16
+ *
17
+ * - TESTING-1. A filename cannot be checked against a business sentence - but the anti-pattern
18
+ * the law names by example (`rewards-queries.e2e-spec.ts`) IS a closed, denylisted shape: a
19
+ * filename whose last segment is an API noun rather than a business one. The rule refuses only
20
+ * that shape; it does not claim to certify a good name, and it is the same requirement E2E-1 in
21
+ * `e2e-flow.md` states on the same file, so one rule holds both codes rather than reporting the
22
+ * same file twice under two names.
23
+ * - TESTING-7. "A stub of a model returns a payload the production parser can actually parse" -
24
+ * the rule cannot verify the payload parses, but it CAN refuse the one shape the law names by
25
+ * example: a bare marker string (`"stubbed"`, `"ok"`, `"test"`) standing in for the whole
26
+ * answer. An object literal, or a `JSON.stringify(...)` call, is out of the rule's reach on
27
+ * purpose - that is where a false positive would start.
28
+ *
29
+ * TESTING-5 / TESTING-8 ("a configured lane either holds tests or does not exist") was drafted as a
30
+ * filesystem-reading rule and then WITHDRAWN. It reads correctly as JS to `@typescript-eslint/parser`
31
+ * (`{"rootDir": "..."}` parses as an `ExpressionStatement` over an `ObjectExpression` under this
32
+ * parser, no wrapping needed) and its own file-scoped RuleTester cases passed. It is left out anyway,
33
+ * because the question it would answer is not "does this repository's config have this shape" but
34
+ * "does the CONSUMING repository's `eslint.config.mjs` even point a linter at this file at all" - and
35
+ * in the measured reference repository it does not: `files` there is five TypeScript globs under
36
+ * `src`, `apps`, `libs`, `test` and `tests` - none of them `.json` - so `jest-e2e.json` and
37
+ * `jest-harness.json` sit outside every one of them. A rule that cannot be reached by the config
38
+ * that would run it is not a `warn`-with-a-count away from correct; it is dead code reporting a "0"
39
+ * that means "never asked", not "already true" - the one measurement this family refuses to publish
40
+ * as if it were the other.
41
+ */
42
+
43
+ import { statSync } from "node:fs"
44
+ import { normalizePath } from "./lib/path.mjs"
45
+
46
+ /** The fast lane: a plain unit spec, excluding every other suffix that also ends in `spec.ts`. */
47
+ const isUnitSpec = (filename) => {
48
+ const file = normalizePath(filename)
49
+ if (!/\.spec\.ts$/.test(file)) return false
50
+ return !/\.(?:e2e|int|harness)-spec\.ts$/.test(file)
51
+ }
52
+
53
+ /** The flow lane. */
54
+ const isE2eSpec = (filename) => /\.e2e-spec\.ts$/.test(normalizePath(filename))
55
+
56
+ /** The paid model-quality lane. */
57
+ const isHarnessSpec = (filename) => /\.harness-spec\.ts$/.test(normalizePath(filename))
58
+
59
+ /** Test-only helpers whose only authority is the harness lane. */
60
+ const isHarnessHelper = (filename) => /\/src\/tests\/helpers\//.test(normalizePath(filename))
61
+
62
+ /**
63
+ * Matchers that assert a CALL happened rather than what came out of it.
64
+ *
65
+ * Each is legitimate as a second assertion - "the mail went out" is a real observable effect. The
66
+ * rule fires only when a whole file has nothing else.
67
+ */
68
+ const CALL_MATCHERS = new Set([
69
+ "toHaveBeenCalled",
70
+ "toHaveBeenCalledWith",
71
+ "toHaveBeenCalledTimes",
72
+ "toHaveBeenLastCalledWith",
73
+ "toHaveBeenNthCalledWith",
74
+ "toBeCalled",
75
+ "toBeCalledWith",
76
+ "toBeCalledTimes",
77
+ "toHaveReturned",
78
+ ])
79
+
80
+ /** Names that mean the test read state back out of the database. */
81
+ const STATE_READERS = /^(?:entityManager|dataSource|EntityManager|DataSource|getRepository|queryRunner)$/
82
+
83
+ /**
84
+ * The matcher a given `expect(...)` call ends in.
85
+ *
86
+ * Climbs the member chain so modifiers pass through: `expect(x).not.toHaveBeenCalled()` answers
87
+ * `toHaveBeenCalled`, and `expect(p).resolves.toBe(1)` answers `toBe`.
88
+ */
89
+ const matcherOf = (expectCall) => {
90
+ let cursor = expectCall
91
+ let last = null
92
+ while (cursor.parent && cursor.parent.type === "MemberExpression" && cursor.parent.object === cursor) {
93
+ last = cursor.parent.property && cursor.parent.property.name
94
+ cursor = cursor.parent
95
+ }
96
+ return last
97
+ }
98
+
99
+ // -- TESTING-6 -------------------------------------------------------------------------------------
100
+
101
+ /** A spec whose every assertion is about a call restates the source instead of testing it. */
102
+ export const noCallOnlySpec = {
103
+ meta: {
104
+ type: "problem",
105
+ docs: { description: "A unit spec asserts a result or a state change, not only that a call happened." },
106
+ schema: [],
107
+ messages: {
108
+ callOnly:
109
+ "Every assertion in this spec is `{{matchers}}` - it restates the handler's own source. Rename a collaborator's method and this file goes red; change the business rule to a wrong value and it stays green. Assert what came back, or what changed. A call assertion is legitimate as a SECOND assertion, where the call itself is the observable effect.",
110
+ },
111
+ },
112
+ create(context) {
113
+ if (!isUnitSpec(context.filename || context.getFilename())) return {}
114
+ let assertions = 0
115
+ let callAssertions = 0
116
+ const seen = new Set()
117
+ return {
118
+ CallExpression(node) {
119
+ if (!node.callee || node.callee.type !== "Identifier" || node.callee.name !== "expect") return
120
+ const matcher = matcherOf(node)
121
+ if (matcher === null) return
122
+ assertions += 1
123
+ if (!CALL_MATCHERS.has(matcher)) return
124
+ callAssertions += 1
125
+ seen.add(matcher)
126
+ },
127
+ "Program:exit"(node) {
128
+ // the file asserts nothing at all, or asserts something other than a call somewhere
129
+ if (assertions === 0 || callAssertions !== assertions) return
130
+ context.report({ node, messageId: "callOnly", data: { matchers: [...seen].join(", ") } })
131
+ },
132
+ }
133
+ },
134
+ }
135
+
136
+ // -- TESTING-7 -------------------------------------------------------------------------------------
137
+
138
+ const UNIT_TEST_BUCKET = /\/(?:src\/tests|tests?\/unit)(?:\/|$)/
139
+ const HFS_TEST_CATEGORY = /\/src\/tests\/(?:integration|fixtures|harness|e2e)\//
140
+ const SUBJECT_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs"]
141
+
142
+ /** A structural test in an HFS test category may test a source file beside it. */
143
+ const isColocatedTestInfrastructureSpec = (filename) => {
144
+ if (!HFS_TEST_CATEGORY.test(filename) || !isUnitSpec(filename)) return false
145
+ const subject = filename.slice(0, -".spec.ts".length)
146
+ return SUBJECT_EXTENSIONS.some((extension) => {
147
+ try {
148
+ return statSync(`${subject}${extension}`).isFile()
149
+ } catch {
150
+ return false
151
+ }
152
+ })
153
+ }
154
+
155
+ /** Backend units are colocated `.spec.ts` files, including structural test infrastructure. */
156
+ export const unitTestColocated = {
157
+ meta: {
158
+ type: "problem",
159
+ docs: { description: "Backend unit tests are colocated `.spec.ts` files; generic `.test.ts` and non-colocated unit buckets are forbidden." },
160
+ schema: [],
161
+ messages: {
162
+ suffix: "`{{name}}` uses `.test.ts`. A backend unit is a colocated `.spec.ts` file beside its production owner.",
163
+ bucket: "`{{path}}` files a unit away from its subject. Colocate it with the owner, or place a structural spec beside the test-infrastructure file it checks in an HFS src/tests category.",
164
+ },
165
+ },
166
+ create(context) {
167
+ const file = normalizePath(context.filename || context.getFilename())
168
+ const genericUnit = /\.test\.ts$/.test(file)
169
+ const bucketedUnit = isUnitSpec(file) && UNIT_TEST_BUCKET.test(file) && !isColocatedTestInfrastructureSpec(file)
170
+ if (!genericUnit && !bucketedUnit) return {}
171
+ return {
172
+ Program(node) {
173
+ if (genericUnit) context.report({ node, messageId: "suffix", data: { name: file.slice(file.lastIndexOf("/") + 1) } })
174
+ if (bucketedUnit) context.report({ node, messageId: "bucket", data: { path: file } })
175
+ },
176
+ }
177
+ },
178
+ }
179
+
180
+ // -- TESTING-2 -------------------------------------------------------------------------------------
181
+
182
+ /** An end-to-end spec that never reads state back proves only that the server replied. */
183
+ export const e2eAssertsPersistedState = {
184
+ meta: {
185
+ type: "problem",
186
+ docs: { description: "An e2e spec reads state back rather than asserting only on the response." },
187
+ schema: [],
188
+ messages: {
189
+ noState:
190
+ "This e2e never reads any state back - it asserts on responses alone, so the flow can stop persisting and this file stays green. Read the row, the balance or the entitlement out of the database and assert THAT. A flow that genuinely has no persisted consequence needs an eslint-disable naming what it observes instead.",
191
+ },
192
+ },
193
+ create(context) {
194
+ if (!isE2eSpec(context.filename || context.getFilename())) return {}
195
+ let readsState = false
196
+ return {
197
+ Identifier(node) {
198
+ if (STATE_READERS.test(node.name)) readsState = true
199
+ },
200
+ "Program:exit"(node) {
201
+ if (readsState) return
202
+ context.report({ node, messageId: "noState" })
203
+ },
204
+ }
205
+ },
206
+ }
207
+
208
+ // -- TESTING-9 -------------------------------------------------------------------------------------
209
+
210
+ /** Provider SDKs. Importing one into a flow test is a real model call by any other name. */
211
+ const PROVIDER_PACKAGES = /^(?:@anthropic-ai\/|openai$|openai\/|ollama$|@google\/generative-ai|@mistralai\/|cohere-ai)/
212
+
213
+ /**
214
+ * The harness's own model helpers, which exist to reach a provider.
215
+ *
216
+ * Matched WITHOUT the `tests/` prefix: an e2e sits beside the helper folder, so the import it
217
+ * actually writes is the relative `../helpers/models.service`, and a pattern anchored on the
218
+ * absolute path never sees it.
219
+ */
220
+ const HARNESS_MODEL_HELPERS = /helpers\/models(?:\.service)?$/
221
+
222
+ /** An e2e never calls a model; the harness is the only lane that does. */
223
+ export const noModelCallInE2e = {
224
+ meta: {
225
+ type: "problem",
226
+ docs: { description: "An e2e spec never reaches a model provider." },
227
+ schema: [],
228
+ messages: {
229
+ provider:
230
+ "`{{source}}` reaches a model provider from an e2e. A model call costs money, takes seconds and answers differently every time - so this makes the flow suite expensive, slow and flaky at once, and the assertion has to be loosened until it stops catching anything. Stub the model and assert what can actually break: the entitlement, the quota, the persisted answer. Judging the answer itself belongs in the harness.",
231
+ },
232
+ },
233
+ create(context) {
234
+ if (!isE2eSpec(context.filename || context.getFilename())) return {}
235
+ return {
236
+ ImportDeclaration(node) {
237
+ const source = node.source && node.source.value
238
+ if (typeof source !== "string") return
239
+ if (!PROVIDER_PACKAGES.test(source) && !HARNESS_MODEL_HELPERS.test(source)) return
240
+ context.report({ node, messageId: "provider", data: { source } })
241
+ },
242
+ }
243
+ },
244
+ }
245
+
246
+ // -- TESTING-10 ------------------------------------------------------------------------------------
247
+
248
+ /** Official provider clients that a harness may call without a house routing layer. */
249
+ const DIRECT_PROVIDER_PACKAGES = /^(?:@anthropic-ai\/sdk(?:\/|$)|openai(?:\/|$)|@google\/genai(?:\/|$)|@google\/generative-ai(?:\/|$)|@mistralai\/(?:mistralai|mistralai-ts)(?:\/|$)|cohere-ai(?:\/|$)|ollama(?:\/|$))/
250
+
251
+ /** Helpers that hide a model call or impersonate the production gateway. */
252
+ const FORBIDDEN_HARNESS_HELPERS = /(?:^|\/)(?:models(?:\.service)?|harness-invoke(?:\.service)?)$/
253
+
254
+ /** Consumer/CLI credential authorities that are never provider API credentials. */
255
+ const FORBIDDEN_HARNESS_AUTH = /(?:CLAUDE_CODE_OAUTH_TOKEN|claude-code-token|sk-ant-oat|OAUTH_BETA|CHATGPT_(?:SESSION|AUTH)_TOKEN|CODEX_(?:SESSION|AUTH)_TOKEN|auth-profile)/i
256
+
257
+ /** Imported symbols that reveal a fake production gateway in the harness lane. */
258
+ const FORBIDDEN_HARNESS_SYMBOLS = new Set([
259
+ "AiInvokeService",
260
+ "HarnessInvokeService",
261
+ "createHarnessInvoke",
262
+ ])
263
+
264
+ /** Return the static property name of an object/member property when one exists. */
265
+ const propertyName = (property) => {
266
+ if (!property) return null
267
+ if (property.type === "Identifier") return property.name
268
+ if (property.type === "Literal") return property.value
269
+ return null
270
+ }
271
+
272
+ /** Find the `AiInvokeService` token in a `Pick<AiInvokeService, ...>` type. */
273
+ const aiInvokePickToken = (sourceCode) => {
274
+ const tokens = sourceCode.getTokens(sourceCode.ast)
275
+ for (let index = 0; index < tokens.length - 2; index += 1) {
276
+ if (tokens[index].value !== "Pick" || tokens[index + 1].value !== "<") continue
277
+ if (tokens[index + 2].value === "AiInvokeService") return tokens[index + 2]
278
+ }
279
+ return null
280
+ }
281
+
282
+ /** A harness calls one explicit provider SDK and never disguises it as `AiInvokeService`. */
283
+ export const harnessCallsProviderDirectly = {
284
+ meta: {
285
+ type: "problem",
286
+ docs: { description: "A model-quality harness calls an approved provider SDK directly with provider API credentials." },
287
+ schema: [],
288
+ messages: {
289
+ missingProvider:
290
+ "This harness imports no approved provider SDK. A model-quality harness calls the declared provider client directly; a house helper, tier or gateway override can make it green about a model production does not use.",
291
+ gateway:
292
+ "`{{name}}` impersonates or replaces the production AI gateway from a harness. Reuse the production prompt builder and parser around a direct provider SDK call instead.",
293
+ helper:
294
+ "`{{source}}` hides the provider call behind a house harness helper. Import and call the approved provider SDK in this harness; only credential loading may be shared.",
295
+ consumerAuth:
296
+ "`{{authority}}` is a consumer or CLI credential authority, not a provider-issued server API key. Read an explicit harness API-key environment variable instead.",
297
+ },
298
+ },
299
+ create(context) {
300
+ const filename = context.filename || context.getFilename()
301
+ const harness = isHarnessSpec(filename)
302
+ const authScope = harness || isHarnessHelper(filename)
303
+ if (!authScope) return {}
304
+
305
+ let hasProviderImport = false
306
+ return {
307
+ ImportDeclaration(node) {
308
+ const source = node.source && node.source.value
309
+ if (typeof source !== "string") return
310
+ if (harness && DIRECT_PROVIDER_PACKAGES.test(source)) hasProviderImport = true
311
+ if (harness && FORBIDDEN_HARNESS_HELPERS.test(source.replace(/\.(?:ts|js)$/, ""))) {
312
+ context.report({ node, messageId: "helper", data: { source } })
313
+ }
314
+ if (!harness) return
315
+ for (const specifier of node.specifiers) {
316
+ const imported = specifier.imported && (specifier.imported.name || specifier.imported.value)
317
+ const local = specifier.local && specifier.local.name
318
+ const name = imported || local
319
+ if (!FORBIDDEN_HARNESS_SYMBOLS.has(name)) continue
320
+ context.report({ node: specifier, messageId: "gateway", data: { name } })
321
+ }
322
+ },
323
+ Literal(node) {
324
+ if (typeof node.value !== "string" || !FORBIDDEN_HARNESS_AUTH.test(node.value)) return
325
+ context.report({ node, messageId: "consumerAuth", data: { authority: node.value } })
326
+ },
327
+ Property(node) {
328
+ if (!harness || propertyName(node.key) !== "provide") return
329
+ if (!node.value || node.value.type !== "Identifier" || node.value.name !== "AiInvokeService") return
330
+ context.report({ node, messageId: "gateway", data: { name: "provide: AiInvokeService" } })
331
+ },
332
+ CallExpression(node) {
333
+ if (!harness || !node.callee || node.callee.type !== "MemberExpression") return
334
+ if (propertyName(node.callee.property) !== "overrideProvider") return
335
+ const argument = node.arguments && node.arguments[0]
336
+ if (!argument || argument.type !== "Identifier" || argument.name !== "AiInvokeService") return
337
+ context.report({ node, messageId: "gateway", data: { name: "overrideProvider(AiInvokeService)" } })
338
+ },
339
+ "Program:exit"(node) {
340
+ if (!harness) return
341
+ const sourceCode = context.sourceCode || context.getSourceCode()
342
+ const pickToken = aiInvokePickToken(sourceCode)
343
+ if (pickToken) {
344
+ context.report({ node: pickToken, messageId: "gateway", data: { name: 'Pick<AiInvokeService, "run">' } })
345
+ }
346
+ if (!hasProviderImport) context.report({ node, messageId: "missingProvider" })
347
+ },
348
+ }
349
+ },
350
+ }
351
+
352
+ // -- TESTING-1 (and E2E-1 in e2e-flow.md, the same requirement on the same file) -------------------
353
+
354
+ /** Nouns that name an API shape rather than a business promise. `rewards-queries` is the anchor. */
355
+ const API_SHAPED_FILENAME_NOUNS = new Set([
356
+ "queries", "query", "mutations", "mutation",
357
+ "resolvers", "resolver", "endpoints", "endpoint",
358
+ "controllers", "controller", "handlers", "handler",
359
+ "modules", "module", "apis", "api",
360
+ ])
361
+
362
+ /** The hyphen-separated words of an e2e filename, with the lane suffix stripped. */
363
+ const e2eFilenameSegments = (filename) => {
364
+ const base = normalizePath(filename).split("/").pop() || ""
365
+ return base.replace(/\.e2e-spec\.ts$/, "").split("-").filter(Boolean)
366
+ }
367
+
368
+ /** An e2e file is one business flow; the filename may not be an API-shape noun wearing its clothes. */
369
+ export const noApiShapedE2eFilename = {
370
+ meta: {
371
+ type: "problem",
372
+ docs: {
373
+ description:
374
+ "An e2e filename reads as a business sentence (TESTING-1; E2E-1 in e2e-flow.md), not a resolver/endpoint/module group.",
375
+ },
376
+ schema: [],
377
+ messages: {
378
+ apiShaped:
379
+ "`{{segment}}` names an API shape - a resolver group, an endpoint, a module - not a business promise. TESTING-1 / E2E-1: one e2e file is one business flow and the filename IS that flow; `rewards-queries.e2e-spec.ts` is exactly the shape this refuses. Name the file for the sentence it proves (`course-purchase.e2e-spec.ts`), not the API surface it happens to hit.",
380
+ },
381
+ },
382
+ create(context) {
383
+ const filename = context.filename || context.getFilename()
384
+ if (!isE2eSpec(filename)) return {}
385
+ const segments = e2eFilenameSegments(filename)
386
+ if (segments.length === 0) return {}
387
+ const last = segments[segments.length - 1].toLowerCase()
388
+ if (!API_SHAPED_FILENAME_NOUNS.has(last)) return {}
389
+ return {
390
+ Program(node) {
391
+ context.report({ node, messageId: "apiShaped", data: { segment: last } })
392
+ },
393
+ }
394
+ },
395
+ }
396
+
397
+ // -- TESTING-7 -------------------------------------------------------------------------------------
398
+
399
+ /** Test-infra helpers, where the shared model stub this code governs actually lives. */
400
+ const isTestHelperFile = (filename) => /\/tests\/helpers\//.test(normalizePath(filename))
401
+
402
+ /** Bare markers a stub returns when nobody gave it a real answer to stand in for. */
403
+ const MARKER_STRINGS = new Set(["stubbed", "stub", "ok", "test", "mock", "fake", "todo", "tbd", "n/a", "pending", ""])
404
+
405
+ /** A string literal that is nothing but a marker - not an object, not a `JSON.stringify(...)` call. */
406
+ const isMarkerLiteral = (node) => Boolean(
407
+ node && node.type === "Literal" && typeof node.value === "string" && MARKER_STRINGS.has(node.value.trim().toLowerCase()),
408
+ )
409
+
410
+ /** The world's default model stub returns a payload the production parser can actually parse. */
411
+ export const noMarkerModelStub = {
412
+ meta: {
413
+ type: "problem",
414
+ docs: {
415
+ description: "A model stub in test infra returns a parseable payload (TESTING-7), never a bare marker string.",
416
+ },
417
+ schema: [],
418
+ messages: {
419
+ marker:
420
+ "This stub resolves to `{{value}}`, a marker rather than an answer. TESTING-7: a stub of a model returns a payload the production parser can actually parse - a marker string means the strict-JSON parser the real flow depends on never runs, so the most fragile seam in the flow is never exercised. Return the shape the production parser expects (an object, or a `JSON.stringify(...)` of one), the way `DEFAULT_MODEL_ANSWER` does.",
421
+ },
422
+ },
423
+ create(context) {
424
+ if (!isTestHelperFile(context.filename || context.getFilename())) return {}
425
+ return {
426
+ CallExpression(node) {
427
+ const callee = node.callee
428
+ if (!callee || callee.type !== "MemberExpression" || callee.computed) return
429
+ const method = callee.property.name
430
+ if (method === "mockResolvedValue" || method === "mockReturnValue") {
431
+ const argument = node.arguments[0]
432
+ if (isMarkerLiteral(argument)) {
433
+ context.report({ node: argument, messageId: "marker", data: { value: JSON.stringify(argument.value) } })
434
+ }
435
+ return
436
+ }
437
+ if (method !== "mockImplementation") return
438
+ const fn = node.arguments[0]
439
+ if (!fn || (fn.type !== "ArrowFunctionExpression" && fn.type !== "FunctionExpression")) return
440
+ if (fn.body.type !== "BlockStatement") {
441
+ if (isMarkerLiteral(fn.body)) {
442
+ context.report({ node: fn.body, messageId: "marker", data: { value: JSON.stringify(fn.body.value) } })
443
+ }
444
+ return
445
+ }
446
+ for (const statement of fn.body.body) {
447
+ if (statement.type === "ReturnStatement" && isMarkerLiteral(statement.argument)) {
448
+ context.report({ node: statement.argument, messageId: "marker", data: { value: JSON.stringify(statement.argument.value) } })
449
+ }
450
+ }
451
+ },
452
+ }
453
+ },
454
+ }
455
+
456
+ /** The rules this law contributes to the plugin. */
457
+ export const rules = {
458
+ "no-call-only-spec": noCallOnlySpec,
459
+ "unit-test-colocated": unitTestColocated,
460
+ "e2e-asserts-persisted-state": e2eAssertsPersistedState,
461
+ "no-model-call-in-e2e": noModelCallInE2e,
462
+ "harness-calls-provider-directly": harnessCallsProviderDirectly,
463
+ "no-api-shaped-e2e-filename": noApiShapedE2eFilename,
464
+ "no-marker-model-stub": noMarkerModelStub,
465
+ }
466
+
467
+ /**
468
+ * The level this law asks for, as the plugin's own opinion.
469
+ *
470
+ * MEASURED WITH THE RULES THEMSELVES against the reference repository, which is the only count
471
+ * worth writing down: 1 call-only spec of 181, and 1 e2e of 48 that never reads state back.
472
+ *
473
+ * That number was wrong twice before it was right, and both ways are worth knowing. A grep-based
474
+ * estimate said EIGHT - it counted files whose assertions merely INCLUDED a call matcher, which is
475
+ * the shape this rule deliberately permits. A first run of the rule itself said THREE - it counted
476
+ * eslint's own complaints about inline `eslint-disable` comments referring to rules the measuring
477
+ * config never loaded. Count only the reports carrying this rule's own id.
478
+ *
479
+ * Both land at `warn` with the count, get burned down, and flip to `error` at zero.
480
+ *
481
+ * The two later rules were measured the same way, against the same repository:
482
+ *
483
+ * - `no-api-shaped-e2e-filename` found TWO of 77 - `installment-plan-queries.e2e-spec.ts` and
484
+ * `rewards-queries.e2e-spec.ts`, the exact anchor example the law names. Real, pre-existing debt
485
+ * this task does not fix, so it lands at `warn` rather than claiming a burn-down that has not
486
+ * happened.
487
+ * - `no-marker-model-stub` found ZERO of the world's own stub in `tests/helpers/flow-world.ts`:
488
+ * the default answer is `JSON.stringify({ answer: "..." })`, which is a `CallExpression`, not a
489
+ * `Literal`, so the rule does not even see it as a candidate. It ships at `error` as a
490
+ * regression guard on a shape that is already correct.
491
+ */
492
+ export const recommended = {
493
+ "starci-be/no-call-only-spec": "error", // no=0 of 182 - burned down from 1
494
+ "starci-be/unit-test-colocated": "error",
495
+ "starci-be/e2e-asserts-persisted-state": "error", // no=0 of 47 - burned down from 1
496
+ "starci-be/no-model-call-in-e2e": "error", // no=0 of 47
497
+ "starci-be/harness-calls-provider-directly": "error",
498
+ "starci-be/no-api-shaped-e2e-filename": "warn", // no=2 of 77 - pre-existing, not burned down here
499
+ "starci-be/no-marker-model-stub": "error", // no=0 - the world's stub already returns JSON.stringify(...)
500
+ }