Files
openswarm/linter

Code Quality Tools

This folder contains the project's code quality tooling: a structural linter, dead code detection, and type checking — covering both the Python backend and TypeScript frontend.

What gets checked

Structural rules

File length — Every source file must stay under the configured line limit (see max-file-lines in config.json). Big files are hard to read, review, and maintain. If a file is getting long, it's a sign it should be split.

Folder size — Every folder must contain fewer than max-folder-items entries (configured in config.json). Keeping folders small forces you to organize code into logical groups.

Unused Python code (Vulture) — Flags unused functions, classes, variables, and imports in the backend. Integrated into the linter's watch loop — findings appear as warnings in the Problems panel alongside structural errors. Confidence thresholds are configurable via vulture-min-confidence and vulture-error-threshold.

These rules apply to .py, .ts, .tsx, .js, and .jsx files.

Orphaned endpoints

Endpoint check — Cross-references backend API routes (decorator and add_api_route patterns) with the frontend source and other backend files. Routes whose static path segments don't appear anywhere else are flagged as orphaned. Backend-only endpoints (health checks, OAuth callbacks, etc.) can be excluded via the endpoints exception list or endpoint-ignore-routes patterns in config.json.

Class analysis

Class check — Analyses classes in backend Python files. Pydantic BaseModel subclasses are auto-whitelisted (every annotated field is part of the serialization schema). Non-framework classes are reserved for future cross-reference analysis.

Unused TypeScript code

Per-file (ESLint) — Catches unused variables, parameters, and imports within each file. Runs in real-time through the VS Code ESLint extension.

Project-wide (Knip) — Finds unused exports, unused files, and unused package.json dependencies across the entire frontend. Run manually or in CI.

Type checking

Python (Pyright/Pylance) — Strict type checking for the backend, configured via config/pyrightconfig.json. Works through the Pylance extension in real-time.

TypeScript — The tsconfig.json in frontend/ has strict mode enabled. TypeScript errors show in the editor automatically.

Naming & access conventions

No leading underscores (no-underscore-names) — Backend Python may not name anything with a leading _ (functions, variables, arguments, attributes, import aliases). Dead-code tooling (Pylance, ruff, vulture) treats _name as "intentionally unused" and stops reporting it, so a leading underscore is a blind spot. Use the p_ prefix to signal "private" instead. Dunders (__init__) and the bare _ throwaway are exempt.

Private access boundaries (p-private) — A p_-prefixed name is private to its owning file (module level) or class (attribute). The check flags any p_ name read or imported from another file/class: if something is used across a boundary, it isn't private — drop the prefix and make it public. This makes p_ a real access modifier, not just a naming style.

Both are backend-only and grandfather pre-existing debt via the no-underscore-names / p-private exception lists; new code must be clean. (Ported from Haik's linter, which also adds Pyright + Ruff and should eventually supersede this subset.)

How it runs

Linter watch (automatic)

When you open the project in Cursor/VS Code, a background task starts watching for file changes. Every save re-checks the codebase. Violations show up in the Problems panel (Cmd+Shift+M).

# one-shot check (exits with code 1 if violations exist)
python3 linter/lint.py --root .

# continuous watch mode
python3 linter/lint.py --watch --root .

ESLint (automatic)

The VS Code ESLint extension picks up frontend/eslint.config.mjs and shows errors inline as you type. To run from the terminal:

cd frontend

# check for problems
npm run lint

# auto-fix what's possible
npm run lint:fix

Knip (manual / CI)

cd frontend
npm run knip

Or use the knip:check VS Code task (Cmd+Shift+P → "Run Task" → "knip:check").

Configuration

config/config.json

{
  "enabled": {
    "max-file-lines": true,       // toggle each check on/off
    "max-folder-items": true,
    "no-nested-imports": true,
    "vulture": true,
    "eslint": true,
    "knip": true,
    "endpoints": true,
    "classes": true,
    "no-underscore-names": true,
    "p-private": true
  },
  "rules": {
    "max-file-lines": 250,        // files with >= this many lines trigger an error
    "max-folder-items": 7,        // folders with >= this many items trigger an error
    "vulture-min-confidence": 80,  // minimum confidence (0-100) to flag a finding
    "vulture-error-threshold": 90, // confidence at which a finding becomes an error
    "no-nested-imports": true,
    "endpoint-ignore-routes": ["*/callback", "*/callback/*"]  // route patterns to skip
  },
  "include_extensions": [".py", ".ts", ".tsx", ".js", ".jsx"],
  "exclude": ["node_modules", ".venv", "..."],
  "exceptions": {
    "max-file-lines": [],      // glob patterns for exempt files
    "max-folder-items": [],    // glob patterns for exempt folders
    "vulture": [],             // glob patterns for files vulture should ignore
    "endpoints": [],           // glob patterns for exempt endpoint files
    "classes": []              // glob patterns for exempt class files
  }
}

Set any key in "enabled" to false to skip that check entirely. Missing keys default to true, so existing configs without the "enabled" section behave identically to before.

Vulture whitelist

config/vulture_whitelist.py suppresses false positives — symbols used by frameworks, entry points, or external consumers that vulture can't detect statically. Add bare names to the file to mark them as intentionally used.

ESLint

frontend/eslint.config.mjs — flat config format (ESLint v9). The key rule for unused code is @typescript-eslint/no-unused-vars. Prefix a variable with _ to suppress the warning.

Knip

frontend/knip.json — Knip auto-detects entry points from webpack.config.js. The project field tells it which files to analyze.

Adding exceptions

If a file legitimately needs to exceed a limit, add a glob to the exceptions list in config/config.json:

{
  "exceptions": {
    "max-file-lines": ["backend/tests/test_analytics.py"],
    "max-folder-items": ["backend/apps/agents"],
    "vulture": ["backend/legacy/*"]
  }
}

Wildcards work: "backend/tests/*" exempts all files in the tests folder.

Code conventions

The project's code conventions live here (a tracked file) rather than in CLAUDE.md, which is gitignored as personal AI working notes and so is never shared or enforced. Some conventions are mechanically enforced by the checks above; the rest are guidance the linter does not (yet) gate.

Enforced by the linter

  • No leading _ — use p_ for private. (no-underscore-names, backend)
  • p_ is a private access boundary — a p_ name used across files/classes must be public. (p-private, backend)
  • No runtime import cycles. (import-cycles)
  • File and folder size caps. (max-file-lines, max-folder-items)

Guidance (not gated — follow it anyway)

  • No barrels. Don't write an __init__.py (or index.ts) whose only job is to re-export. Import from the defining module directly.
  • No relative imports (Python). Always import from the package root: from backend.apps.foo import bar, never from .foo import bar.
  • Single-purpose file naming. If a file exports exactly one function or class, name the file after it.
  • Type everything. Annotate every function/variable. Backend Python uses typeguard's @typechecked and prefers typing generics (List, Dict, Optional) over the builtins; type-checking also runs under Pyright/Pylance strict.
  • Classes are pydantic. Backend classes subclass pydantic BaseModel with model_config = ConfigDict(validate_assignment=True), wrapping unrecognized field types in InstanceOf[...]. Plain data classes and bare dicts are avoided in favour of models — except genuine dynamic-key maps (a registry keyed by a runtime id) and external protocol shapes (the Claude Agent SDK hook returns, model_dump output), where a dict is the correct type. This last pair is why the codebase still contains dicts and why there is no "no-dict" gate.

Frontend (TypeScript)

The naming and structure rules apply to the frontend too — no leading _, no barrels, single-purpose file naming, and full typing (strict tsconfig). The p_/pydantic rules are Python-specific; the TS equivalent of "no dicts" is "model data with typed interfaces, not untyped objects." Note two current gaps: no-underscore-names / p-private are backend-only, and the frontend ESLint config still suggests a _ prefix to silence unused-variable warnings — prefer removing the unused binding over prefixing it.

.lintignore files

You can suppress checks for an entire directory tree by dropping a sentinel file into it — no config edits required.

File Effect
.lintignore Ignores all rules for that directory and its children
.lintignore-<rule> Ignores only <rule> (e.g. .lintignore-max-file-lines)

The linter walks from each file up to the project root looking for these sentinels, so a .lintignore in backend/legacy/ covers everything underneath it.

Folder structure

linter/
  checks/              # check implementations
    __init__.py        # shared filter/match utilities + .lintignore support
    structural.py      # file length, folder size, nested imports
    vulture.py         # vulture dead-code runner
    eslint.py          # eslint runner
    knip.py            # knip unused-code runner
    endpoints.py       # orphaned endpoint detection
    classes.py         # class-level dead code detection
  config/              # all configuration files
    config.json        # enabled checks, rules, exclusions, exceptions
    pyrightconfig.json # python type checking config
    vulture_whitelist.py # false positive suppressions for vulture
  lint.py              # orchestrator (loads config, runs checks, outputs results)
  print_errors.sh      # colored terminal reporter
  README.md

OpenSwarm setup notes

Which checks are on

Only the pure-Python checks run today. Node tooling and the placeholder checks are deferred.

Check State Why
max-file-lines (300) on Our 300-line precedence. Active for new files; existing debt is grandfathered (see below).
max-folder-items (7) on Grandfathered per subtree via .lintignore-max-folder-items markers in backend/, frontend/, debugger/, electron/, scripts/.
vulture on Dead-code detection over backend/. Runs against backend/.venv/bin/vulture.
no-nested-imports off We deliberately use function-level / lazy imports to break import cycles (400+ sites). Flagging them all is wrong for this codebase.
eslint, knip off Node tooling, deferred to a later pass.
endpoints off Orphaned-endpoint triage deferred.
classes off Placeholder check, not wired up.

Running it

The linter imports watchfiles at module load, so run it with an interpreter that has it (the backend venv does, transitively via uvicorn):

backend/.venv/bin/python linter/lint.py --root .

CI installs watchfiles + vulture explicitly (see .github/workflows/lint.yml).

max-file-lines grandfather list

Every file over 300 lines today lives in exceptions["max-file-lines"]. New files are held to the limit. The list splits into two intents:

PERMANENT (one cohesive responsibility that is just genuinely large; not pending a split):

  • backend/apps/agents/agent_manager.py
  • backend/apps/agents/browser_schema.py
  • backend/apps/nine_router/process.py, backend/apps/nine_router/oauth.py
  • backend/main.py
  • the large backend test files (test_v2_invariants.py, test_disconnect_resilience.py, test_service.py, test_v2_label_logic.py, test_outputs_runtime_cleanup.py)
  • electron/main.js, electron/affiliateTracking.test.js
  • vendored backend/mcp-bundles/* (covered by a .lintignore, not a glob entry)

TEMPORARY (pending split) the rest, especially the frontend mega-files:

  • frontend/src/app/pages/AgentChat/ChatInput.tsx
  • frontend/src/app/pages/Settings/Settings.tsx
  • frontend/src/app/pages/Tools/Tools.tsx
  • frontend/src/app/pages/Dashboard/Dashboard.tsx
  • frontend/src/app/pages/AgentChat/ToolCallBubble.tsx
  • and the other backend/frontend files in the list. As these get split below 300 lines, drop their entry from the exception list.

Vulture whitelist

config/vulture_whitelist.py carries OpenSwarm additions: intentional false positives (monkey-patches, kept-for-compat aliases, loop counters) and a clearly-labelled block of suspected genuinely-dead symbols. The latter are whitelisted only because this tooling pass is additive-only and must not edit backend source; a future cleanup should delete the definitions and remove those lines.