$schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
# Require test files to mock dependencies of modules with module-eval side effects.
#
# WHY (the general pattern): some source modules run code at module-evaluation
# time — creating singletons, computing constants from imports, calling functions
# from other modules at module scope (not inside a function/class body). When a
# test file imports such a module, it triggers the side effect. If the dependency
# modules are not mocked, the side effect captures the real API and that state
# persists across the entire test suite — a CI-only, file-ordering-dependent
# failure that never reproduces locally. The sibling rule `no-leaky-module-mocks`
# handles the mirror problem (mock.module leaking into real-import readers).
#
# KNOWN MODULES WITH MODULE-EVAL SIDE EFFECTS:
#
#   (None currently active — the useTasks.ts singleton side effect was fixed by
#    commit 3937d31 which converted resolveApiUrl() and api.task.list() calls to
#    lazy dynamic imports. The singleton _sharedStore = new TaskStore() no longer
#    triggers an API call at module-eval time. When a new side-effect module is
#    discovered, add it here with the pattern shown below.)
#
#   RESOLVED — src/modules/task-kanban/useTasks.ts (fixed 2026-07-08)
#     - Line 6: was const SSE_URL = `${resolveApiUrl()}/events/planning`;
#       → now: const sseUrl = async () => { const { resolveApiUrl } = await import(...); return ...; };
#     - Line 159: was const sharedStore = new TaskStore(); (calls defaultListTasks
#       which statically imported api.task.list) → now: defaultListTasks uses
#       dynamic await import('../../lib/rpc-client'), so construction is lazy.
#     - 73 CI failures, 2026-07-08. Fix: commit 3937d31.
#
# To ADD a new module to this list when another CI-only failure is traced to a
# module-eval side effect: append its import pattern to `rules[].evaluator.config.pattern`
# (alternation) and add the corresponding entry above.
include:
  - "apps/**/tests/**/*.test.ts"
  - "apps/**/tests/**/*.test.tsx"
  - "packages/**/tests/**/*.test.ts"
  - "packages/**/tests/**/*.test.tsx"
  # All known side-effect modules are resolved. When a new one is discovered,
  # exclude files that already mock the dependency before importing it.

rules:
  - id: no-unmocked-module-eval-side-effects
    description: >
      Test files that import a module with module-evaluation side effects
      (singletons created at module scope, imports called at eval time, etc.)
      MUST mock the dependency modules BEFORE the import. Without the mock,
      the side effect captures the real API and leaks into every subsequent
      test file — a CI-only, ordering-dependent failure. Add mock.module()
      for the listed dependency before any import of the side-effect module.
      See the file header for the list of known side-effect modules and their
      dependencies. 73-real CI incidents (useTasks → rpc-client), 2026-07-09.
    severity: warning
    evaluator:
      type: rg
      config:
        # Match imports from modules with KNOWN, ACTIVE module-eval side effects.
        # IMPORTANT: this rule is INERT unless a pattern is present. When a new
        # side-effect module is discovered (CI-only ordering failure traced to
        # an import-time call), add its import pattern here as a string. Remove
        # the pattern once the source module is fixed (side effects made lazy).
        #
        # RESOLVED modules (patterns removed after fix — kept as history):
        #   - task-kanban/useTasks — fixed 2026-07-08, commit 3937d31
        #     (resolveApiUrl/api.task.list converted to lazy dynamic imports)
        pattern: "__no_active_side_effect_modules__"
