nexus/.planning/codebase/QUALITY.md
Mikkel Georgsen 6c4272ce85 [nexus] chore: migrate .planning/ from agent repo to nexus repo
Planning artifacts (milestones v1.0-v1.2.1, v1.3 queue, PROJECT.md,
STATE.md, config) now live alongside the code they describe.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:55:42 +00:00

13 KiB

Code Quality — Paperclip (Nexus)

Analysis Date: 2026-03-30


Testing Frameworks

Unit/Integration Test Runner:

  • Vitest 3.x, configured at the monorepo root via vitest.config.ts
  • Projects registered: packages/db, packages/adapters/opencode-local, server, ui, cli

E2E Test Runner:

  • Playwright (@playwright/test ^1.58.2)
  • E2E config: tests/e2e/playwright.config.ts
  • Release smoke config: tests/release-smoke/playwright.config.ts

Run Commands:

pnpm test:run          # All vitest tests (CI mode)
pnpm test              # All vitest tests (watch mode)
pnpm test:e2e          # Playwright E2E suite
pnpm test:e2e:headed   # Playwright with browser visible

Test Counts & Coverage

Workspace Test Files describe/it/test calls
server/src/__tests__/ 94 files (+ 1 helpers/ dir) ~649 describe/it/test calls
cli/src/__tests__/ 17 files ~120 calls
ui/src/ (lib + adapters + hooks + context) 18 .test.ts files ~100+ calls
ui/src/ (components) 2 .test.tsx files minimal
packages/db/src/ 3 test files ~20 calls
packages/adapters/opencode-local/ 3 test files ~10 calls
packages/adapters/pi-local/ 2 test files ~10 calls
E2E (tests/e2e/) 1 spec file 1 test
Release smoke (tests/release-smoke/) 1 spec file 1 test

Coverage tooling: No coverage thresholds or reporters are configured. None of the vitest.config.ts files include a coverage block. Coverage is not tracked.


What Is Tested

Well-covered:

  • Server route handlers (via supertest HTTP-level integration tests against a real Express app backed by embedded Postgres) — e.g., server/src/__tests__/routines-e2e.test.ts, approval-routes-idempotency.test.ts
  • Server services (pure logic tested in isolation with vi.fn() mocks) — e.g., issues-service.test.ts, approvals-service.test.ts, company-portability.test.ts
  • Adapter models, parse logic, skill sync for every adapter type (claude-local, codex-local, cursor-local, gemini-local, opencode-local, pi-local, openclaw-gateway)
  • Database runtime config resolution across all source precedence paths — packages/db/src/runtime-config.test.ts
  • CLI commands: worktree management, company import/export, auth flows, home path resolution
  • UI lib utilities: inbox badge computation, assignee logic, routine trigger patches, onboarding routing, company portability
  • Security/redaction: log-redaction.test.ts, forbidden-tokens.test.ts, redaction.test.ts
  • Error handler middleware — error-handler.test.ts
  • Zod validation flow: routes use validate(schema) middleware, covered by route-level tests

Under-tested or not tested:

  • UI components (83 components, only 2 have test files: MarkdownBody.test.tsx, RunTranscriptView.test.tsx)
  • UI pages (39 pages, zero test files)
  • Real-database integration tests skip on unsupported hosts (describe.skip via getEmbeddedPostgresTestSupport) — these tests pass silently in most environments
  • Storage provider implementations (server/src/storage/) — only referenced, not directly tested
  • Plugin lifecycle/loader/worker manager have some tests but plugin tooling is lightly covered

Test Patterns

Standard unit test structure:

import { describe, expect, it, vi } from "vitest";

describe("feature name", () => {
  it("describes the expected behavior", () => {
    expect(result).toEqual(expected);
  });
});

Integration test pattern (HTTP + real DB): Tests in server/src/__tests__/routines-e2e.test.ts and similar spin up an Express app with a real embedded Postgres instance:

const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip;

describeEmbeddedPostgres("feature", () => {
  beforeAll(async () => {
    tempDb = await startEmbeddedPostgresTestDatabase("prefix-");
    db = createDb(tempDb.connectionString);
  }, 20_000);
  afterAll(async () => { await tempDb?.cleanup(); });
  afterEach(async () => { /* truncate tables */ });
});

Service mock pattern (for route-level tests without DB):

const issueSvc = {
  list: vi.fn(),
  create: vi.fn(),
  // ...
};
vi.mock("../services/index.js", () => ({ issueService: () => issueSvc }));

Test helper location: server/src/__tests__/helpers/embedded-postgres.ts re-exports @paperclipai/db test utilities.

Factory functions: Tests consistently define local make*() helpers (e.g., makeApproval(), makeRun(), makeIssue()) rather than shared factories. No shared fixture library exists.


Linting & Formatting

ESLint: Not detected. No .eslintrc*, eslint.config.*, or biome config exists at any level of the monorepo.

Prettier: Not detected. No .prettierrc* found.

Implications: Code style is enforced only by TypeScript's strict mode and reviewer convention. There is no automated formatting gate in CI. Formatting inconsistencies (e.g., line length, trailing commas) are present but generally consistent within files.

Observed style (informal conventions):

  • 2-space indentation throughout
  • Double quotes for imports ("...")
  • Trailing commas in multi-line expressions
  • Named exports preferred over default exports in services and routes
  • Consistent node: prefix on Node built-ins (import fs from "node:fs")

TypeScript Strictness

Shared base config: tsconfig.base.json at repo root:

  • "strict": true — enables all strict checks (noImplicitAny, strictNullChecks, etc.)
  • "target": "ES2023", "module": "NodeNext", "moduleResolution": "NodeNext"
  • "isolatedModules": true
  • "forceConsistentCasingInFileNames": true

UI override: ui/tsconfig.json also sets "strict": true, with "module": "ESNext", "moduleResolution": "bundler".

All packages extend tsconfig.base.json or have equivalent strictness settings.

as any usage: 131 occurrences across 35 files, mostly in test mocks and Express middleware internals (e.g., casting res to any to attach custom error context properties). Production service code rarely uses as any outside of plugin-related services where dynamic dispatch requires it (plugin-host-services.ts: 16 occurrences).

@ts-ignore / @ts-expect-error: Zero occurrences across the entire codebase — a strong signal of type discipline.

Typecheck CI gate: pnpm -r typecheck runs in both the verify job (on PRs) and verify_canary job (on merge). Type errors block CI.


Error Handling Patterns

Custom HTTP error classes: server/src/errors.ts defines HttpError with factory helpers:

export function badRequest(message, details?) { return new HttpError(400, message, details); }
export function notFound(message?) { return new HttpError(404, message ?? "Not found"); }
export function forbidden(message?) { return new HttpError(403, message ?? "Forbidden"); }
// + unauthorized, conflict, unprocessable

Centralized error handler: server/src/middleware/error-handler.ts handles:

  • HttpError → structured JSON response with correct status
  • ZodError → 400 with validation details
  • Unknown errors → 500 with "Internal server error" (no leak)
  • Attaches res.__errorContext for logging (consumed by pino-http)

Route error propagation: Routes use async handler functions with next(err) pass-through, relying on the global Express error handler. No try/catch noise in route files.

Validation: Zod schemas defined in @paperclipai/shared (e.g., createIssueSchema) are applied via the validate(schema) middleware in server/src/middleware/validate.ts. Schema parse errors are automatically caught by the error handler.


Logging

Framework: Pino + pino-http + pino-pretty

Configuration: server/src/middleware/logger.ts

  • info level to stdout (colorized via pino-pretty)
  • debug level to server.log file (plain text)
  • HTTP requests logged with custom log levels: 5xx → error, 4xx → warn, 2xx → info
  • Error context (body, params, query) attached to error-level log entries
  • Log directory resolved from PAPERCLIP_LOG_DIR env, config file, or default home path

Production code: Minimal console.* usage (9 files in server, mostly in plugin services and test console.warn for unsupported environments). UI has 6 console calls total, confined to plugin slots.


CI/CD Quality Gates

On every PR to master (.github/workflows/pr.yml):

Check Job
Block manual pnpm-lock.yaml edits policy
Validate Dockerfile deps stage completeness policy
Validate dependency resolution on manifest changes policy
pnpm -r typecheck verify
pnpm test:run (all Vitest tests) verify
pnpm build verify
Canary release dry run verify
Playwright E2E (skip-LLM mode) e2e

On merge to master (.github/workflows/release.yml):

  • Full re-run of typecheck + tests before publishing canary

Release smoke tests (.github/workflows/release-smoke.yml): Separate workflow for post-release Docker auth/onboarding smoke.

E2E tests (.github/workflows/e2e.yml): Manual dispatch only, supports full LLM execution via ANTHROPIC_API_KEY.

No coverage gate in CI. No lint gate in CI.


Code Organization & Consistency

Service factory pattern: All server services are instantiated as factory functions receiving a Db instance:

export function issueService(db: Db) {
  return {
    list: async (params) => { ... },
    create: async (data) => { ... },
  };
}

This pattern is consistent across all 64 service files in server/src/services/.

Route factory pattern: Routes are factory functions receiving db and optional storage:

export function issueRoutes(db: Db, storage: StorageService) {
  const router = Router();
  const svc = issueService(db);
  // ...
  return router;
}

Monorepo workspace structure: Clear separation between server/, ui/, cli/, packages/shared/, packages/db/, packages/adapters/*. Cross-package imports use @paperclipai/* namespace.

Import organization: Node built-ins first with node: prefix, then external packages, then internal workspace packages (@paperclipai/*), then relative imports. No enforced by tooling but consistently applied.


Documentation Quality

Inline comments: Sparse but purposeful. Comments appear where non-obvious logic is present (e.g., SSRF protection in plugin-host-services.ts has a section comment block). Files generally are self-documenting through naming.

JSDoc/TSDoc: Not used. No /** */ doc-comments on exported functions. Types are the documentation.

TODO comments: Only 3 in the entire codebase:

  • ui/src/pages/AgentDetail.tsx:771 — commented-out skills tab view
  • ui/src/adapters/runtime-json-fields.tsx:5 — disabled UI pending worktree workflow
  • cli/src/commands/client/company.ts:362 — temporary claude_local fallback in import TUI

README: README.md exists at root. CONTRIBUTING.md has clear contribution guidelines including required PR "thinking path" format.

PR template: .github/PULL_REQUEST_TEMPLATE.md enforces thinking path, what changed, verification steps, risks, and checklist including test and doc updates.


Technical Debt Indicators

No linting or formatting tooling:

  • Risk: style drift over time, no automated enforcement
  • Files affected: entire codebase
  • Fix approach: add Biome (preferred for TS monorepos) or ESLint + Prettier with CI gate

No coverage measurement:

  • Risk: regressions in untested paths go undetected
  • Files affected: primarily ui/src/pages/, ui/src/components/, server/src/storage/
  • Fix approach: add @vitest/coverage-v8, set minimum thresholds per workspace

UI components have near-zero unit tests:

  • 83 component files, 2 test files
  • Risk: UI logic regressions caught only by E2E or manually
  • Most UI lib logic (ui/src/lib/) is tested; the gap is components and pages
  • Fix approach: add React Testing Library for component tests

as any in production plugin services (plugin-host-services.ts):

  • 16 occurrences in a single file
  • Indicates dynamic dispatch complexity in plugin host layer
  • Risk: type errors in plugin boundary surface at runtime

Embedded Postgres tests skip silently on many hosts:

  • Pattern: const describeEmbeddedPostgres = supported ? describe : describe.skip
  • This means DB integration tests do not run in many dev environments and potentially some CI hosts
  • CI does run them (ubuntu-latest is a supported host)

server/src/services/company-portability.ts uses as any 12 times:

  • Most complex service file; high import count (30+ types from shared)
  • Deserves a refactor pass once the portability feature stabilizes

Quality audit: 2026-03-30