The Tool Stack

LayerToolScope
Frontend lint + formatBiomeTypeScript/TSX, 40+ enforced rules
Frontend dead codeKnipUnused exports, imports, dependencies
Frontend typestsc --noEmitTypeScript type checking
Backend lint + formatRuffPython, F401/F841 + style rules
Backend typesmypyPython static types, strict mode
CI quality gateSonarQubeCoverage, code smells, security hotspots

Pre-commit Hooks (Husky)

Five checks run sequentially on every git commit. A failure at any step blocks the commit:

flowchart LR
    C([git commit]) --> B["Biome
lint + format"] B --> R["Ruff
lint + format"] R --> T["tsc
typecheck"] T --> M["mypy
typecheck"] M --> K["Knip
dead code"] K --> OK([commit accepted]) B -->|fail| X([blocked]) R -->|fail| X T -->|fail| X M -->|fail| X K -->|fail| X

git push additionally runs the full test suite (vitest + pytest).

Here’s what a blocked commit looks like - Biome catches any type and an unused variable in the same line:

$ git commit -m "test: deliberate lint error"
Running pre-commit checks...
  [1/5] Checking frontend lint (Biome)...

src/lib/api.ts:28:19 lint/suspicious/noExplicitAny
  × Unexpected any. Specify a different type.

  > 28  const unused_var: any = "test"
                          ^^^

src/lib/api.ts:28:7 lint/correctness/noUnusedVariables
  × This variable unused_var is unused.

  > 28  const unused_var: any = "test"
              ^^^^^^^^^^

Checked 41 files in 42ms. Found 3 errors.
husky - pre-commit script failed (code 1)

After removing the line, the same commit succeeds - all 5 checks pass in sequence and the commit is accepted.

Source: .husky/pre-commit, .husky/pre-push

Key Rules Enforced

Biome (apps/web/biome.json) - selected rules:

{
  "suspicious":   { "noExplicitAny": "error" },
  "correctness":  { "noUnusedVariables": "error", "noUnusedImports": "error", "useHookAtTopLevel": "error" },
  "style":        { "useImportType": "error", "noParameterAssign": "error" },
  "complexity":   { "useOptionalChain": "error" }
}

mypy (apps/api/pyproject.toml):

[tool.mypy]
python_version = "3.12"
strict = true

Strict mode enables --disallow-any-generics, --disallow-untyped-defs, --warn-return-any, and 15 other checks.

Knip (apps/web/knip.config.ts) - all categories set to "error":

rules: {
  files: 'error', dependencies: 'error', exports: 'error',
  types: 'error', duplicates: 'error', unlisted: 'error',
}

SonarQube in CI (SIRA-60)

SonarQube runs as a dedicated quality stage in the GitLab CI pipeline, after all CI jobs complete. Coverage artifacts from CI are consumed directly:

flowchart TB
    subgraph ci [ci stage - parallel]
        WL[web:lint]
        WT["web:test
generates lcov.info"] AL[api:lint] AT["api:test
generates coverage.xml"] end subgraph quality [quality stage] LN[linear:notify] SQ[sonar-scanner-cli] end ci --> quality WT --> SQ AT --> SQ

sonar-project.properties at repo root:

sonar.projectKey=SIRA
sonar.sources=apps/web/src,apps/api/src
sonar.tests=apps/api/tests
sonar.javascript.lcov.reportPaths=apps/web/coverage/lcov.info
sonar.python.coverage.reportPaths=apps/api/coverage.xml

Both coverage reports (Python XML + JS LCOV) flow into a single SonarQube project at sonarqube.cs.ui.ac.id. The SonarQube MCP server is also configured in .mcp.json for AI-assisted quality inspection directly from the editor.

SonarQube dashboard - SIRA project with 0 issues

Beyond Style - Runtime Failure Prevention

Static analysis in SIRA is not limited to formatting and import order. The configuration catches issues that would manifest as runtime failures in production.

mypy Strict Mode: Untyped Code as a Runtime Risk

strict = true enables a class of checks that catch real runtime failures:

mypy flagWhat it prevents
--disallow-untyped-defsFunctions with no type annotations - callers can’t reason about null safety
--disallow-any-genericslist without list[str] - type-erased collections that collapse to Any
--warn-return-anyFunctions returning Any - propagates type unsafety to every caller
--strict-optionalUnenforced Optional checks - missed None dereferences become AttributeError at runtime

The last is the most critical: any T | None return must be narrowed before use. mypy surfaces these as type errors before CI, not as production crashes.

Biome noExplicitAny: Blocking the Type Escape Hatch

TypeScript’s any type disables all type inference - any method call or property access on any is unchecked:

// ❌ blocked - data.amount is unchecked; typos and wrong shapes pass silently
function process(data: any) { return data.amount }

// ✅ required - TypeScript verifies .amount_paid exists on PaymentCreate
function process(data: PaymentCreate) { return data.amount_paid }

With any, data.typo compiles and crashes at runtime. With typed parameters, it’s a compile error caught before the branch executes.

Knip: Dead Code as a Behavioral Risk

Unused exports are not just style noise - they represent a behavioral risk:

  • Unused functions accumulate bugs that tests never exercise
  • Dead branches can drift out of sync with the active codebase’s assumptions
  • Unreachable code paths hide logic errors until accidentally reactivated

exports: "error" ensures every exported symbol is consumed. Unused utilities are removed rather than accumulating drift.

SonarQube: Complexity as a Bug Predictor

SonarQube’s cognitive complexity metric flags functions with high branching depth - a reliable predictor of off-by-one and conditional logic errors. This catches behavioral issues that type checkers miss: not “what type is this value” but “is this branching logic reachable and correct.”


Evidence