Skip to content

PRD: Code Quality Linting for Squad Monorepo #477

Closed

Description

@diberry

@bradygaster — PRD for your review.


Code Quality Linting for Squad Monorepo

Author: Book (Docs Agent)
Requested By: Dina Berry
Date: 2026-03-19
Status: Draft — Awaiting Brady's Review
Target Repository: bradygaster/squad


1. Problem Statement & Context

The Squad monorepo (bradygaster/squad) currently has no runtime behavior linter. The existing lint script runs tsc --noEmit, which provides type checking but cannot detect several classes of runtime anti-patterns:

What TypeScript strict mode misses:

  • Synchronous I/O inside async functions — Blocking fs.readFileSync()/writeFileSync() calls in async handlers, defeating the purpose of async declarations and blocking the Node.js event loop
  • Fire-and-forget promises — Missing await on async calls, causing race conditions or silent errors
  • Unused code — Dead variables, imports, and parameters (noUnusedLocals/noUnusedParameters are disabled)
  • Promise misuse — Conditionals testing promises instead of awaiting them

Real-world impact: Investigation of the SDK revealed 581 instances of sync file system calls inside async functions. For example, tools/index.ts contains squad_decide, squad_memory, and squad_skill handlers that are declared async but use readFileSync/writeFileSync. Every time an agent calls these tools, the event loop blocks. As the codebase grows and more contributors (including AI agents like Copilot) submit PRs, these patterns will compound without automated detection.

What we need: A lightweight, configurable linter that surfaces these issues during development without blocking the current workflow.


2. Goals & Success Metrics

Goals:

  1. Detect event loop blockers — Flag synchronous I/O inside async functions in hot paths (SDK tool handlers, platform utilities)
  2. Catch promise misuse — Identify missing await, floating promises, and incorrect promise conditionals
  3. Maintain velocity — No disruption to existing build/CI workflows; informational-only initially
  4. Enable incremental adoption — Team can triage findings and gradually increase enforcement

Success Metrics:

  • Hot paths (SDK tool handlers) converted from sync to async I/O within 2 weeks of adoption
  • Zero new sync-in-async patterns introduced after enforcement enabled
  • Developer satisfaction: "Helpful findings, not noise" (measured via team feedback)
  • CI remains green during Phase 1-2 rollout (no build blocking)

Non-goals:

  • Replace TypeScript type checking
  • Enforce opinionated code style preferences (e.g., semicolons, indentation)
  • Block PRs immediately (phased enforcement only)

3. Key User Scenarios

Scenario 1: Developer submits PR with sync I/O in async handler

  • Before: Pattern ships undetected, discovered later during performance testing
  • After: Linter flags it in local development or CI logs; developer converts to fs.promises before merge

Scenario 2: AI agent (Copilot) generates code with floating promise

  • Before: Missing await on database save; PR reviewer spots it manually (or doesn't)
  • After: Linter flags missing await in CI output; human reviewer confirms and fixes

Scenario 3: Maintainer evaluates linter findings

  • Before: No visibility into async anti-patterns across codebase
  • After: Run linter, review 1,058 findings, prioritize top 20 hot paths for immediate fix

Scenario 4: Team decides to enforce linting

  • Before: Manual code review is the only gate
  • After: Incrementally increase severity on critical rules (sync I/O, floating promises), CI enforces them

4. Scope and Non-Goals

In Scope:

  • Detect async/promise anti-patterns (sync I/O, missing await, promise misuse)
  • Flag type safety bypasses (any, non-null assertions !)
  • Identify unused code (variables, imports)
  • Informational warnings initially; phased enforcement later
  • Integration with existing tooling (package.json scripts, GitHub Actions)

Out of Scope:

  • Code formatting (use Prettier or similar if desired)
  • Comprehensive security auditing (use dedicated security scanners)
  • Migration of existing findings (team decides per-rule enforcement timeline)
  • Linting non-TypeScript files (Markdown, JSON, etc.)

5. High-Level Approach: Solution Options

Three options exist, each with trade-offs around ecosystem maturity, speed, and detection capabilities:

Option 1: ESLint 9 with TypeScript Plugin

What it is: Industry-standard JavaScript/TypeScript linter with extensive plugin ecosystem. Uses TypeScript's type system for advanced analysis.

Pros:

  • Type-aware rules — Can detect sync-in-async, floating promises, promise misuse (all require type information)
  • Mature ecosystem — 10+ years, thousands of plugins, well-documented
  • Flat config system — ESLint 9 uses modern eslint.config.mjs format (simpler than old .eslintrc)
  • Proven at scale — Used by Microsoft, Google, Airbnb, Meta

Cons:

  • Heaviest option — 3 npm packages required (eslint, @typescript-eslint/parser, @typescript-eslint/eslint-plugin, plus optional eslint-plugin-n for Node.js rules)
  • Slower — Type-aware analysis requires loading TypeScript compiler, runs slower than non-type-aware linters
  • Steeper learning curve — Extensive configuration options (though flat config helps)

What it catches:

  • ✅ Sync I/O in async functions (via eslint-plugin-n)
  • ✅ Floating promises (no-floating-promises)
  • ✅ Promise misuse (no-misused-promises)
  • ✅ Unused vars, any, non-null assertions

Proof of concept: Branch squad/eslint9-info-only implements this. Found 1,058 informational findings (581 sync-in-async, 17 floating promises, etc.). Technical spec available at .squad/skills/typescript-review/eslint9-spec.md.


Option 2: Biome

What it is: Newer Rust-based toolchain combining linting and formatting. Fast, opinionated, built-in TypeScript support.

Pros:

  • Fast — Rust implementation, 10-100x faster than ESLint on large codebases
  • All-in-one — Linter + formatter in one tool (like Prettier + ESLint combined)
  • Built-in TypeScript — No separate parser plugin needed
  • Simple config — Single biome.json file

Cons:

  • No type-aware rules — Cannot detect sync-in-async or floating promises (requires type system integration, not yet available)
  • Smaller rule set — ~200 rules vs ESLint's 1,000+ (though growing rapidly)
  • Newer ecosystem — Less mature than ESLint, fewer community plugins
  • Opinionated defaults — Less customizable than ESLint

What it catches:

  • ❌ Cannot detect sync I/O in async (no Node.js plugin equivalent)
  • ❌ Cannot detect floating promises (requires type awareness)
  • ✅ Basic unused vars, suspicious patterns
  • ✅ Some TypeScript anti-patterns (but not type-aware ones)

Verdict: Biome is excellent for speed and basic linting, but cannot address the core problem (sync-in-async detection).


Option 3: oxlint (Oxc Project)

What it is: Very fast Rust-based linter implementing subset of ESLint rules. Focused on speed over feature parity.

Pros:

  • Fastest option — 50-100x faster than ESLint (Rust + optimized AST parsing)
  • ESLint-compatible — Implements many ESLint rules, familiar syntax
  • Single binary — No npm dependencies, just download and run
  • Good for basics — Catches common mistakes quickly

Cons:

  • No type-aware analysis — Cannot detect sync-in-async, floating promises, or promise misuse
  • Subset of rules — Only ~200 ESLint rules implemented (vs 1,000+ total)
  • No TypeScript-ESLint rules — Missing @typescript-eslint/no-floating-promises, etc.
  • Limited configurability — Less customizable than ESLint

What it catches:

  • ❌ Cannot detect sync I/O in async (no eslint-plugin-n equivalent)
  • ❌ Cannot detect floating promises (no type awareness)
  • ✅ Basic unused vars, syntax errors
  • ✅ Some suspicious patterns (but limited rule set)

Verdict: oxlint is great for ultra-fast basic linting, but cannot address the core problem.


Recommendation Matrix

Requirement ESLint 9 Biome oxlint
Detects sync-in-async ✅ Yes ❌ No ❌ No
Detects floating promises ✅ Yes ❌ No ❌ No
Speed Moderate Fast Fastest
Ecosystem maturity Highest Medium Lowest
Configurability Highest Medium Low
npm dependencies 3 packages 1 package 0 (binary)

If the priority is detecting sync-in-async and promise misuse (the stated problem), ESLint 9 is the only option that works. Biome and oxlint are excellent tools but cannot perform type-aware analysis needed to catch these patterns.


6. Risks, Dependencies, and Open Questions

Risks:

  • Noisy output overwhelms developers — Mitigation: Start with warnings-only, separate script, opt-in usage
  • Build time increase — Mitigation: ESLint 9 is fast enough for this small monorepo (~5 seconds); can run in parallel with tsc
  • Team rejects after trying — Mitigation: Easy rollback (remove packages + config file)
  • False positives in specific contexts — Mitigation: Test file exclusions already defined; can disable rules per-file if needed

Dependencies:

  • Requires Node.js 18+ (already met)
  • For Option 1: 3 npm packages (eslint, @typescript-eslint/*, eslint-plugin-n)
  • For Option 2/3: Single package or binary
  • No changes to existing CI/build scripts required for Phase 1

Open Questions for Brady:

  1. Which solution option? ESLint 9 (only option that solves sync-in-async), or evaluate Biome/oxlint for future when type-aware rules land?
  2. Triage strategy: Fix high-traffic paths (SDK tools) before enabling in CI, or surface all findings immediately?
  3. Test file linting: Fully exclude, use relaxed rules, or lint with same rules as source?
  4. Enforcement timeline: How long should warnings-only phase last before increasing severity to block builds?
  5. Rule customization: Any specific rules to enable/disable based on team preferences?

Next Steps:

  1. Brady selects solution option and answers open questions
  2. If ESLint 9: Merge proof-of-concept branch squad/eslint9-info-only (ready now)
  3. Team evaluates findings in local development (Phase 1: opt-in)
  4. Add to CI with continue-on-error: true (Phase 2: visible but non-blocking)
  5. Triage findings, tune rules, enable enforcement (Phase 3+)

Technical Specification Reference: For implementation details of Option 1 (ESLint 9), see .squad/skills/typescript-review/eslint9-technical-spec.md.


Proof of concept: Branch squad/eslint9-info-only off dev is ready with Option 1 (ESLint 9) implemented. 1,058 informational findings, exit code 0, nothing blocking.

Companion technical spec with full config, install commands, and adoption ladder is available on request.

cc @diberry

Activity

  1. bradygaster commented on Mar 22, 2026

    @bradygaster
    Collaborator

    🧪 Flight triage: Code quality linting PRD (ESLint 9, async/promise anti-patterns) is a monorepo quality initiative. Routing: FIDO (tests & quality). Ready for Brady's decision on solution option (ESLint 9 recommended for type-aware rules). PoC in squad/eslint9-info-only.

  2. added
    squadSquad triage inbox — Lead will assign to a member
    enhancementNew feature or improvement
    on Mar 22, 2026
  3. github-actions commented on Mar 22, 2026

    @github-actions
    Contributor

    📋 Assigned to FIDO (Quality Owner)

    Issue: #477 — PRD: Code Quality Linting for Squad Monorepo

    FIDO will pick this up in the next Copilot session.

    For Copilot coding agent: If enabled, this issue will be worked automatically.
    Otherwise, start a Copilot session and say:
    FIDO, work on issue #477

  4. github-actions commented on Mar 22, 2026

    @github-actions
    Contributor

    🏗️ Squad Triage — Flight (Lead)

    Issue: #477 — PRD: Code Quality Linting for Squad Monorepo
    Assigned to: FIDO (Quality Owner)
    Reason: Issue relates to testing/quality work

    @copilot evaluation: 🔴 Not suitable — issue involves work outside the coding agent's capability profile.

    Team roster:

    • Flight (Lead) → label: squad:flight
    • Procedures (Prompt Engineer) → label: squad:procedures
    • EECOM (Core Dev) → label: squad:eecom
    • FIDO (Quality Owner) → label: squad:fido
    • PAO (DevRel) → label: squad:pao
    • CAPCOM (SDK Expert) → label: squad:capcom
    • CONTROL (TypeScript Engineer) → label: squad:control
    • Surgeon (Release Manager) → label: squad:surgeon
    • Booster (CI/CD Engineer) → label: squad:booster
    • GNC (Node.js Runtime) → label: squad:gnc
    • Network (Distribution) → label: squad:network
    • RETRO (Security) → label: squad:retro
    • INCO (CLI UX & Visual Design) → label: squad:inco
    • GUIDO (VS Code Extension) → label: squad:guido
    • Telemetry (Aspire & Observability) → label: squad:telemetry
    • VOX (REPL & Interactive Shell) → label: squad:vox
    • DSKY (TUI Engineer) → label: squad:dsky
    • Sims (E2E Test Engineer) → label: squad:sims
    • Handbook (SDK Usability) → label: squad:handbook
    • Ralph (Work Monitor) → label: squad:ralph
    • @copilot (Coding Agent) → label: squad:copilot

    To reassign, remove the current squad:* label and add the correct one.

  5. added a commit that references this issue on Mar 22, 2026
    8b55693
  6. added a commit that references this issue on Mar 22, 2026
    ee09aae
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or improvementgo:needs-researchNeeds investigationnext-upPriority queue — next issues to worksquadSquad triage inbox — Lead will assign to a member

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions