$schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
# Runtime boundaries: process execution and fs IO hygiene.
include:
  - "apps/**/src/**/*.ts"
  - "packages/**/src/**/*.ts"
exclude:
  - "**/tests/**"
  - "**/*.test.ts"

rules:
  - id: no-direct-process-spawn
    description: >
      Process execution must go through @gobing-ai/ts-runtime ProcessExecutor
      (or ts-ai-runner for agents). Do not call Bun.spawn/spawnSync, import
      node:child_process, or pull in execa/zx at app/package sources.
    severity: error
    evaluator:
      type: forbidden-import
      config:
        forbidden:
          - specifier: "node:child_process"
          - specifier: "child_process"
          - pattern: "Bun\\.(spawn|spawnSync)\\s*\\("
            matchMode: usage
          - specifier: "execa"
          - specifier: "zx"
        scope:
          include:
            - "apps/**/src/**/*.ts"
            - "packages/**/src/**/*.ts"
          exclude:
            - "**/tests/**"
            - "**/*.test.ts"

            # Electron Node adapter: ts-runtime's streaming handle uses Bun.spawn.
            # Owns the detached process-group handle needed for startup cancellation and quit.
            - "apps/desktop/src/server-process.ts"

            # Low-level system primitives:
            - "packages/app/src/services/quality-gate.ts" # gate runs `sh -c` with stdout+stderr merged onto ONE fd; ProcessExecutor captures streams separately and cannot reproduce the stream-order contract (task 1006 R1; mirrors the history-anatomy bundling exemptions)

  - id: no-direct-fs-io
    description: "Prefer ts-runtime's FileSystem seam for fs IO."
    severity: warning
    evaluator:
      type: forbidden-import
      config:
        forbidden:
          - pattern: "from\\s+['\"]node:fs(?:/promises)?['\"]|require\\(['\"]node:fs"
            matchMode: usage
          - pattern: "\\bBun\\.(?:write)\\("
            matchMode: usage
        scope:
          include:
            - "apps/**/src/**/*.ts"
            - "packages/**/src/**/*.ts"
          exclude:
            - "**/tests/**"
            - "**/*.test.ts"

            # Low-level system primitives (O_EXCL locks, FD byte-tailing, FS watcher, atomic registry, Vite glob fallback):
            - "packages/domain/src/planning/locks.ts" # POSIX atomic O_EXCL file locks
            - "packages/domain/src/retention.ts" # recursive backup-dir reclamation + mtime stat (task 0622 R8); ts-runtime FileSystem seam has no recursive dir remove
            - "packages/app/src/services/token-ledger-service.ts" # FD byte-window log tailing
            - "packages/app/src/services/token-ledger-watcher.ts" # node:fs watch() live watcher
            - "packages/app/src/services/project-registry.ts" # atomic projects.json persistence
            - "packages/app/src/services/project-server-owner.ts" # atomic mkdir lifetime claim + unique-marker stale recovery; FileSystem has no exclusive mkdir primitive (1082)
            - "packages/app/src/services/slash-commands-service.ts" # synchronous ~/.config/spur/slash_commands.json persistence (mirrors project-registry.ts)
            - "packages/app/src/services/agent-usage-producer.ts" # atomic agent-usage snapshot write (tmp + rename, mirrors project-registry.ts; task 0892 R1)
            - "packages/app/src/services/history-service.ts" # versioned analyze artifact + bounded-errors sidecar + latest.json symlink pointer (task 0474); ts-runtime FileSystem seam has no symlink, so the pointer uses node:fs directly (mirrors project-registry.ts persistence exemption)
            - "packages/app/src/services/history-anatomy.ts" # deterministic history-anatomy cache core (task 1005 R2): fsync'd tmp+rename atomic publish, readdir digest scans and sync report reads must stay byte-stable; bundled standalone for the bare-node plugin twin (mirrors agent-usage-producer.ts / history-service.ts exemptions)
            - "packages/app/src/observability/workflow-run-log-sink.ts" # sync FD append for mid-run tail-able all-in-one run log (task 0426 / feature D2); append() is sync from the observability bus
            - "packages/app/src/observability/agent-run-log.ts" # same primitive as the sink above for the per-run agent stream (task 1076 R1, ADR-132); append() is sync from the agent service's own onOutput hook, and the byte cap needs the FD position it tracks
            - "apps/cli/src/commands/workflow.ts" # FD byte-window tail of the mid-run run log for `workflow trace --follow` streaming (task 0428 / feature D2); readSync at offset over the observability sink's FDs
            - "apps/web/src/modules/discover.ts" # Vite/Astro module scanner fallback under bun test
            - "packages/app/src/services/quality-gate.ts" # gate artifacts (log/findings/receipt) written directly; merged-fd gate log and fsync-free atomic writes must stay byte-stable; bundled standalone for the bare-node plugin twin (task 1006 R1; mirrors history-anatomy.ts)
            - "packages/app/src/services/run-storage.ts" # durable-evidence migration core (task 1025 R4): atomic tmp+rename byte copies with digest reread and atomic manifest publish must stay byte-stable (mirrors agent-usage-producer.ts / project-registry.ts exemptions)

            # Electron Node adapters cannot load Bun runtime implementations; keep the
            # synchronous existence/directory probes injected for unit testing (desktop contract).
            - "apps/desktop/src/layout.ts"
            - "apps/desktop/src/server-process.ts"

            # Synchronous bootstrap & path resolution:
            - "packages/config/src/bundled-config.ts" # sync bundledConfigRoot() directory walk
            - "packages/config/src/loader.ts" # sync resolveConfigFile() / schema specifier lookup
            - "packages/config/src/executor-update.ts" # atomic single-entry config update (task 0797): O_EXCL lock, fsync'd tmp+rename commit, lstat symlink refusal, mode preservation — ts-runtime FileSystem seam has no lstat/fsync/chmod/O_EXCL
            - "packages/app/src/services/project-start.ts" # sync CLI binary path checks before daemon spawn
            - "apps/cli/src/commands/serve.ts" # sync resolveServeCwd() directory validation before server startup (task 0805 R2); mirrors task.ts sync-template exemption

            # Synchronous template & workflow resolvers:
            - "apps/cli/src/commands/task.ts" # sync loadTemplateContent/Bodies callbacks
            - "packages/app/src/services/workflow-service.ts" # run-log/checkpoint reclamation realpath confinement (:988, :1014)
            - "packages/app/src/workflow/run-record.ts" # confined sync run-record reads: realpath + fstat byte-window (moved from workflow-service.ts, 0962)
            - "apps/cli/src/release-ops.ts" # sync manifest/workspace reads + Bun.write rewrites inside the git release flow (task 0617); mirrors task.ts sync-template exemption
            - "apps/cli/src/version-carriers.ts" # sync manifest reads + Bun.write rewrites for registered release carriers (task 0854); mirrors release-ops.ts sync exemption
